#创作者激励#【4.操作系统】多线程、定时器、中断开发例程 原创 精华

Hello_Kun
发布于 2023-4-3 19:55
浏览
5收藏

【文正在参加2023年第一期优质创作者激励计划】
OpenHarmony轻量和小型系统开发例程2023
【0.开发环境搭建】
【1.GPIO基本操作】
【2.硬件通信uart\i2c\spi\i2s】
【3.无线通信 wifi mqtt】
【4.操作系统 多线程、定时器、中断】

@toc

4.操作系统

4.1 多线程

4.1.1 多线程相关函数GPI

OpenHarmony是一个可裁剪的操作系统,hi3861运行着裁剪后的轻量化系统,支持多线程。下面先看多线程相关的函数:

1.新建线程
定义: osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, 
const osThreadAttr_t *attr);
功能 创建一个线程并将其添加到活动线程
参数
func:线程函数
argument:参数指针,作为开始参数传递给线程函数
attr:线程属性;NULL:默认值
返回值 创建成功:返回线程ID 创建失败:返回NULL
依赖 //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

2. 其他函数
// 接口名称 		           函数说明 
osThreadGetName 获取指定线程的名字
osThreadGetId 获取当前运行线程的线程ID
osThreadGetState 获取当前线程的状态
osThreadSetPriority 设置指定线程的优先级
osThreadGetPriority 获取当前线程的优先级
osThreadYield 将运行控制转交给下一个处于READY状态的线程
osThreadSuspend 挂起指定线程的运行
osThreadResume 恢复指定线程的运行
osThreadDetach 分离指定的线程(当线程终止运行时,线程存储可以被回收)
osThreadJoin 等待指定线程终止运行
osThreadExit 终止当前线程的运行
osThreadTerminate 终止指定线程的运行
osThreadGetStackSize 获取指定线程的栈空间大小
osThreadGetStackSpace 获取指定线程的未使用的栈空间大小
osThreadGetCount 获取活跃线程数
osThreadEnumerate 获取线程组中的活跃线程数
依赖://third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

4.1.2 多线程点灯实例

下面编写一个多线程实例,一个线程检测按键,当按键按下时,另一个线程实现点亮LED。

  • 第一步,在iothardware目录下,新建osthread_demo.c文件,首先定义线程相关的宏,引入头头文件,并定义一个全局变量value,用于指示按键状态,便于控制LED,在osthread_demo.c中添加如下代码:
// 1. 分别定义按键、LED线程相关宏, 引入头文件
#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "hi_io.h"

#define KEY_TASK_STACK_SIZE 512
#define KEY_TASK_PRIO 25
#define LED_TASK_STACK_SIZE 512
#define LED_TASK_PRIO 24

#define KEY_TEST_GPIO 5 // hispark_pegasus 连接GPIO5 按下user键是 低电平-0
#define LED_GPIO_9 9    // LED 一端通过电阻R6上拉接到V3.3  故按下user键时灯就亮

//==定义存贮电平的全局变量value 枚举类型有value0-value1
IotGpioValue value = IOT_GPIO_VALUE1;  
  • 第二步,编写按键检测的线程。流程和第一篇实验雷同,初始化GPIO,编写业务逻辑(检测按键)、注册线程。https://ost.51cto.com/posts/22450#_LED_19, 不详细赘述。下面给出代码,在osthread_demo.c中继续追加如下代码:
// 2.编写按键key业务逻辑
// 获取输入电平 IoTGpioGetInputVal(KEY_TEST_GPIO,&value);
static void *KeyTask(const char *arg)
{
    (void)arg;
    while (1)
    {
        IoTGpioGetInputVal(KEY_TEST_GPIO, &value); //==获取GPIO user 按键引脚电平
    }
    return NULL;
}

// 3.注册线程任务
static void KeyEntry(void)
{
    osThreadAttr_t attr;
    attr.name = "KeyTask";                //==指定线程运行的任务
    attr.attr_bits = 0U;                   //==
    attr.cb_mem = NULL;                    //==
    attr.cb_size = 0U;                     //==
    attr.stack_mem = NULL;                 //==
    attr.stack_size = KEY_TASK_STACK_SIZE; //==
    attr.priority = KEY_TASK_PRIO;         //==优先权限

    // 初始化GPIO5为输入 上拉
    IoTGpioInit(KEY_TEST_GPIO);
    hi_io_set_func(KEY_TEST_GPIO, HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO, IOT_GPIO_DIR_IN);
    hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP);

    if (osThreadNew(KeyTask, NULL, &attr) == NULL)
    {
        printf("[KeyEntry] create GpioTask failed!\n");
    }
}
APP_FEATURE_INIT(KeyEntry);
  • 第三步,雷同第二步,编写LED任务线程,初始化LED的GPIO,编写逻辑,由value的值确定led亮灭,最后注册另一个线程。在osthread_demo.c中继续追加如下代码:
// 4.编写LED业务逻辑
// 根据按键状态 设置LED亮灭
static void *LedTask(const char *arg)
{
    (void)arg;
    while (1)
    {
        IoTGpioSetOutputVal(LED_GPIO_9, value);    //==设置GPIO9引脚的状态
    }
    return NULL;
}

在osthread_demo.c中第二步代码基础上添加注册led线程任务

// 4.注册线程任务
static void KeyEntry(void)
{
    osThreadAttr_t attr;
    attr.name = "KeyTask";                //==指定线程运行的任务
    attr.attr_bits = 0U;                   //==
    attr.cb_mem = NULL;                    //==
    attr.cb_size = 0U;                     //==
    attr.stack_mem = NULL;                 //==
    attr.stack_size = KEY_TASK_STACK_SIZE; //==
    attr.priority = KEY_TASK_PRIO;         //==优先权限

    // 初始化GPIO5为输入 上拉
    IoTGpioInit(KEY_TEST_GPIO);
    hi_io_set_func(KEY_TEST_GPIO, HI_IO_FUNC_GPIO_5_GPIO);
    IoTGpioSetDir(KEY_TEST_GPIO, IOT_GPIO_DIR_IN);
    hi_io_set_pull(KEY_TEST_GPIO, HI_IO_PULL_UP);
    if (osThreadNew(KeyTask, NULL, &attr) == NULL)
    {
        printf("[KeyEntry] running\n");
    }

    // 设置LED GPIO9为输出
    IoTGpioInit(LED_GPIO_9);
    hi_io_set_func(LED_GPIO_9, HI_IO_FUNC_GPIO_9_GPIO);
    IoTGpioSetDir(LED_GPIO_9, IOT_GPIO_DIR_OUT);
    if (osThreadNew(LedTask, NULL, &attr) == NULL)
    {
        printf("[LedEntry] create GpioTask failed!\n");
    }
}
SYS_RUN(KeyEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行
// APP_FEATURE_INIT(KeyEntry);
static_library("led_example") {
    sources = [
        "osthread_demo.c",
    ]
    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/kal/cmsis",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include", 
    ]
}

修改src\applications\sample\wifi-iot\app\BUILD.gn

import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
    features = [      "iothardware:led_example",   ]
}

编译无误烧录运行后,可以看到和gpio_input_output实验一样的效果,按键按下,led亮。
可以注意到,有两个函数可以添加线程到活动任务中去:

SYS_RUN(KeyEntry); //==ohos_init.h中定义的宏 让一个函数在系统启动时自动执行
APP_FEATURE_INIT(KeyEntry);

4.2 定时器

4.2.1 定时器相关函数

定时器功能可以完成计数、定时任务等。轻量化系统也支持定时器,相关函数如下:

1. 开启定时器
定义: osTimerId_t osTimerNew (osTimerFunc_t func, osTimerType_t type, 
void *argument, const osTimerAttr_t *attr);
功能: 创建并初始化计时器
参数:
func:指向回调函数的指针
type:osTimerOnce表示一次触发, osTimerPeriodic表示周期性行为
argument:计数器回调函数的参数 attr :定时器属性;NULL:默认值
返回值 创建成功:返回定时器ID 创建失败:返回NULL
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
2. 获取定时器名称
定义: const char *osTimerGetName (osTimerId_t timer_id);
功能: 获取定时器的名称
参数: timer_id:定时器ID
返回值: 创建成功:将名称返回以null结尾的字符串 创建失败:返回NULL
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
3.开始定时
定义: osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks);
功能: 启动或重新启动定时器
参数: timer_id:定时器ID 
ticks:CMSIS_RTOS_TimeOutValue定时器的“时间刻度”值。
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
4.停止定时
定义: osStatus_t osTimerStop (osTimerId_t timer_id);
功能: 停止定时器
参数: timer_id:定时器ID
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
5.检测定时器运行
定义: uint32_t osTimerIsRunning (osTimerId_t timer_id);
功能: 检查定时器是否正在运行
参数: timer_id:定时器ID
返回值: 返回1:正在运行;返回0,:未在运行。
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h
6.删除定时
定义: osStatus_t osTimerDelete (osTimerId_t timer_id);
功能: 删除定时器
参数: timer_id:定时器ID
返回值: 返回指示函数执行状态的状态码
依赖: //third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

系统定时器原理(来自:物联网技术及应用实验指导手册):

  • 软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,LiteOS会根据当前系统Tick时间及用户设置的定时时间间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。
  • 当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超过的定时器记录下来。
  • Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下
    来的定时器的超时回调函数。

4.2.2 定时开关灯实例

本实验使用定时器功能,每1s切换一次LED状态。

  • 第一步,在iohardware目录下新建timer_led.c文件,定义LED闪烁次数、线程等宏,编写切换LED状态的函数,用于软件定时器的回调。具体代码如下:
 /* 
 * 软件定时器实现LED闪烁5次
 * 
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_watchdog.h"
#include "iot_gpio.h"

#define STACK_SIZE          1024
#define COUNT_STOP          5
#define TIMER_COUNT_NUM     100
#define LED_DELAY          10
#define LED_GPIO_9 9

static int count = 0;   // 定义一个count变量,用于闪烁次数
IotGpioValue value = IOT_GPIO_VALUE1;    // led状态                   

void BlinkLed(void)
{
    count++;
    printf("[Timer_demo] Blink count is %d \r\n", count);
    value = !value;
    IoTGpioSetOutputVal(LED_GPIO_9,value); 
}
  • 第二步,创建周期性的定时器,回调函数是上述BlinkLed函数;然后开启定时,每100个时钟周期执行依次回调函数(一个周期是10ms)。在文件中,追加如下代码:
void TimerThread(const char *arg)
{
    (void)arg;
    osTimerId_t id;
    osStatus_t status;

    // 创建一个周期性的定时器,回调函数是BlinkLed,用于切换LED状态
    id = osTimerNew((osTimerFunc_t)BlinkLed, osTimerPeriodic, NULL, NULL);
    if (id == NULL) {
        printf("[Timer_demo] osTimerNew failed.\r\n");
    } else {
        printf("[Timer_demo] osTimerNew success.\r\n");
    }

    // 开始计时100个时钟周期,一个时钟周期是10ms,100个时钟周期就是1s
    status = osTimerStart(id, TIMER_COUNT_NUM);
    if (status != osOK) {
        printf("[Timer_demo] osTimerStart failed.\r\n");
    } else {
        printf("[Timer_demo] osTimerStart success.\r\n");
    }
}
  • 第三步,当达到指定定时次数后,停止定时,删除定时器。在void TimerThread(const char *arg)函数中继续添加如下代码:
 // 切换5次后,停止闪烁
    while (count < COUNT_STOP) {
        osDelay(LED_DELAY);
    }

    // 停止定时器
    status = osTimerStop(id);
    printf("[Timer_demo] Timer Stop, status :%d. \r\n", status);

    // 删除定时器
    status = osTimerDelete(id);
    printf("[Timer_demo] Timer Delete, status :%d. \r\n", status);
  • 第四步,开启线程,运行任务。
void TimerExampleEntry(void)
{
    osThreadAttr_t attr;
    IoTWatchDogDisable();

    IoTGpioInit(LED_GPIO_9);
    IoTGpioSetDir(LED_GPIO_9,IOT_GPIO_DIR_OUT);         
    attr.name = "TimerThread";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = STACK_SIZE;
    attr.priority = osPriorityNormal;

    // 创建一个线程,并注册一个回调函数 TimerThread,控制LED灯每隔1秒钟闪烁一次
    if (osThreadNew((osThreadFunc_t)TimerThread, NULL, &attr) == NULL) {
        printf("[Timer_demo] osThreadNew Falied to create TimerThread!\n");
    }
}

APP_FEATURE_INIT(TimerExampleEntry);
  • 第五步,编译烧录运行。修改iothardware下的BUILD文件,将timer_led.c添加入sources = [ ]字段中。运行后打开Monitor可以看到count从1-5变化,LED在闪烁,5s后停止闪烁。
     #创作者激励#【4.操作系统】多线程、定时器、中断开发例程-鸿蒙开发者社区

4.3 中断

4.2.1 中断相关函数

1.清中断
 hi_irq_free(irq_idx);   // 释放中断
参数:中断号ID
依赖:src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_isr.h
2.注册中断函数
hi_u32 hi_irq_request(hi_u32 vector, hi_u32 flags, irq_routine routine, hi_u32 param);
  * @param  vector [IN] type #hi_u32,Interrupt ID.CNcomment:中断号。CNend
* @param  flag [IN]   type #hi_u32, attributes like priority,etc.CNcomment:中断优先级等属性。CNend
* @param  routine  [IN] type #irq_routine,Interrupt callback function.CNcomment:中断回调函数。CNend
* @param  param    [IN] type #hi_u32,Parameter transferred to the callback function.
依赖:src\device\hisilicon\hispark_pegasus\sdk_liteos\include\hi_isr.h
3. 使能中断
参数:中断号
hi_void hi_irq_enable(hi_u32 vector);
4.失能中断
hi_void hi_irq_disable(hi_u32 vector);
5.关中断
hi_u32 hi_int_lock(hi_void);
6.恢复中断
hi_void hi_int_restore(hi_u32 int_value);

中断原理(来自:物联网技术及应用实验指导手册):中断是指CPU暂停执行当前程序,转而执行新程序的过程。中断相关的硬件可以划
分为3类:

  • 设备:发起中断的源,当设备需要请求CPU时,产生一个中断信号,该信号连接至中断控制器。
  • 中断控制器:接收中断输入并上报给CPU。可以设置中断源的优先级、触发方式、打开和关闭等
    操作。
  • CPU软中断: 由CPU软件发起的中断,一般用于操作系统的模式和线程切换。
    使用场景:
    当有中断请求产生时,CPU暂停当前的任务,转而去响应外设请求。用户通过中断申请,注册中断
    处理程序,可以指定CPU响应中断请求时所执行的具体操作。

4.2.2 中断实例

hi3861_hdu源码中有给定定时器实验案例。下面简要说明如何运行。

  • 第一步 hi3861_hdu_iot_application/src/vendor/hisilicon/hispark_pegasus/demo/interrupt_demo文件夹复制到hi3861_hdu_iot_application/src/applications/sample/wifi-iot/app/目录下。
  • 第二步:修改applications/sample/wifi-iot/app/目录下的BUILD.gn,在features字段中添加interrupt_demo:interrupt_demo。
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [ "interrupt_demo:interrupt_demo", ]
}

打开文件可以看到,demo设定了定时器、获取时钟频率,开启了中断。实现了每一秒中断1次执行中断处理函数,输出一段into the func timer2_irq_handle,这个中断是在3s函数hi_sleep()执行时切入进去的。

// 注册中断函数
    // Register interrupt function
    unsigned int ret = hi_irq_request(TIMER_2_IRQ, HI_IRQ_FLAG_PRI1, timer2_irq_handle, 0);
    if (ret != HI_ERR_SUCCESS) {
        printf("request example irq fail:%x\n", ret);
        return;
    }
    if (g_timer_cnt_cb != 0) {
        dprintf("\n [hi_int_lock] failed\n");
    }
    dprintf("\n [hi_int_lock] success\n\n -Restore the state before shutting down the interrupt-\n");
    hi_int_restore(uvIntSave);  // 中断前完成相关处理
                                // Complete relevant processing before interruption
    if (g_timer_cnt_cb != 0) {
        dprintf("\n [hi_int_restore] failed, timer cnt cb = %d\n", g_timer_cnt_cb);
    }

    tmp = g_timer_cnt_cb;
    ret = hi_irq_enable(irq_idx);   // 使能中断
                                    // Enable interrupt
    if (ret != HI_ERR_SUCCESS) {
        dprintf("failed to hi_irq_enable func ,ret = 0x%x\r\n", ret);
    }
    hi_sleep(TIMER_INTERVAL);       // 进入3s的中断函数
                                    // Interrupt function entering 3s
// 时间中断函数(中断处理函数)
// Time interrupt function
void timer2_irq_handle(unsigned int irqv)
{
    (void)irqv;
    g_timer_cnt_cb++;
    dprintf("\n into the func timer2_irq_handle\n");
    timer_clear(TIMER_ID);  // 清中断
                            // close the interrupt
}

第三步,编译运行。现象是在Monitor看到串口输出两行into the func timer2_irq_handle。
 #创作者激励#【4.操作系统】多线程、定时器、中断开发例程-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-4-3 19:55:13修改
7
收藏 5
回复
举报
4条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

近期大佬是真的高产

回复
2023-4-4 10:25:24
hmyxd
hmyxd

实操的讲解很详细

回复
2023-4-6 11:16:53
青舟321
青舟321

学物联网的看这些名词很亲切

回复
2023-4-6 18:30:41
FlashinMiami
FlashinMiami

正在学习参考一下

回复
2023-4-7 15:21:56
回复
    相关推荐