TencentOS tiny RTOS快速入门

2020-09-23 11:43:31 浏览数 (3)

上节,我们介绍了TencentOS tiny,参考官方给出的移植教程亲自动手做了一遍,文章如下:

天啊!鹅厂都开始做开发板了?网红腾讯物联网开发板终极开箱评测,让我们一睹为快!

趁着最近有时间,这节,我撸了几个例程作为后面做项目参考的基本框架,当然也有一些是直接拿了官方文档的例程:

一般来说,学习任何一个RTOS,本质是没有什么太大的区别的,通常在最简版nano上进行开发,关于TencentOS tiny,我个人认为,掌握以下基础组件的用法足矣,其它的一些组件,可以等需要使用的时候再参考文档学习应用即可。

  • TencentOS tiny多任务
  • TencentOS tiny RTOS软件定时器
  • TencentOS tiny RTOS任务间通信(互斥锁、信号量、事件、队列)

在使用基本组件之前,我们需要配置tos_config.h文件:

代码语言:javascript复制
#ifndef _TOS_CONFIG_H_
#define  _TOS_CONFIG_H_

//#include "stm32l0xx.h" // 目标芯片头文件,用户需要根据情况更改
#include "stm32l4xx_hal.h"

#define TOS_CFG_TASK_PRIO_MAX           10u  // 配置TencentOS tiny默认支持的最大优先级数量

#define TOS_CFG_ROUND_ROBIN_EN          0u  // 配置TencentOS tiny的内核是否开启时间片轮转

#define TOS_CFG_OBJECT_VERIFY_EN           1u // 配置TencentOS tiny是否校验指针合法

#define TOS_CFG_TASK_DYNAMIC_CREATE_EN  1u  // TencentOS tiny 动态任务创建功能宏

#define TOS_CFG_EVENT_EN                1u  // TencentOS tiny 事件模块功能宏

#define TOS_CFG_MMBLK_EN                1u  //配置TencentOS tiny是否开启内存块管理模块

#define TOS_CFG_MMHEAP_EN               1u  //配置TencentOS tiny是否开启动态内存模块

#define TOS_CFG_MMHEAP_DEFAULT_POOL_EN  1u  // TencentOS tiny 默认动态内存池功能宏

#define TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE        0x100 // 配置TencentOS tiny默认动态内存池大小

#define TOS_CFG_MUTEX_EN                1u  // 配置TencentOS tiny是否开启互斥锁模块

#define TOS_CFG_MESSAGE_QUEUE_EN        1u  // 配置TencentOS tiny是否开启消息队列模块

#define TOS_CFG_MAIL_QUEUE_EN           1u  // 配置TencentOS tiny是否开启消息邮箱模块

#define TOS_CFG_PRIORITY_MESSAGE_QUEUE_EN 1u // 配置TencentOS tiny是否开启优先级消息队列模块

#define TOS_CFG_PRIORITY_MAIL_QUEUE_EN 1u  // 配置TencentOS tiny是否开启优先级消息邮箱模块

#define TOS_CFG_TIMER_EN                1u  // 配置TencentOS tiny是否开启软件定时器模块

#define TOS_CFG_PWR_MGR_EN              0u  // 配置TencentOS tiny是否开启外设电源管理模块

#define TOS_CFG_TICKLESS_EN             0u  // 配置Tickless 低功耗模块开关

#define TOS_CFG_SEM_EN                  1u  // 配置TencentOS tiny是否开启信号量模块

#define TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN      1u // 配置TencentOS tiny是否开启任务栈深度检测

#define TOS_CFG_FAULT_BACKTRACE_EN      0u  // 配置TencentOS tiny是否开启异常栈回溯功能

#define TOS_CFG_IDLE_TASK_STK_SIZE      128u // 配置TencentOS tiny空闲任务栈大小

#define TOS_CFG_CPU_TICK_PER_SECOND     1000u // 配置TencentOS tiny的tick频率

#define TOS_CFG_CPU_CLOCK               (SystemCoreClock) // 配置TencentOS tiny CPU频率

#define TOS_CFG_TIMER_AS_PROC           1u  // 配置是否将TIMER配置成函数模式

#endif

这样后面我们才能正常使用。

1、TencentOS tiny多任务

1.1 为什么要采用RTOS多任务?

对于普通的项目来说,比如密码锁类项目,单独的一个传感器模块的开发,某些简单的仪器仪表等等,对于这类场景单一,业务需求也单一的项目来说,使用状态机或者事件驱动的方式就足以完成项目的基本功能了。

但是如果开发一个巨量代码的工程项目,项目可能设计到传感器数据读取、无线数据上传与接收、数据传输、UI实时刷新、算法处理等等,功能诸多还需要相互配合的情况下,那么如果还在用裸机的思想去完成,那么开发者一般会面临以下两个问题:

  • 设计思路过于复杂,光怎么想程序的设计思路就得想好久了
  • 设计下来的各个功能,要考虑相互配合的问题,实时性可能得不到要求

RTOS的多任务就可以解决对应的问题,它既能让项目开发起来思路清晰,方便易维护;同时RTOS也能保证整个产品运行的实时性,典型的程序设计架构,就可以按下面的方式来划分:

1.2 TencentOS tiny RTOS多任务实践

关于怎么创建多个任务,可以参考腾讯物联网终端操作系统开发指南.pdf文档,以下工程是我基于上一节的移植工程,在移植工程的基础上,由于官方给的OLED驱动例程是软件模拟驱动的,后来我将其改为I2C硬件驱动,所以,在STM32CubeMX上对OLED的I2C接口进行了配置:

更改后重新生成软件工程,然后修改oled.c中关于写命令和写数据的接口为硬件I2C驱动:

代码语言:javascript复制
// IIC Write Command
void Write_IIC_Command(unsigned char IIC_Command)
{
    uint8_t buf[2] = {0};
    buf[0] = 0x00 ;
    buf[1] = IIC_Command ;
    HAL_I2C_Master_Transmit(&hi2c3, 0x78, buf, 2, HAL_TICK_FREQ_100HZ);
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
    uint8_t buf[2] = {0};
    buf[0] = 0x40 ;
    buf[1] = IIC_Data ;
    HAL_I2C_Master_Transmit(&hi2c3, 0x78, buf, 2, HAL_TICK_FREQ_100HZ);
}

接下来,进入多任务程序编写,我们主要实现以下两个功能:

  • task1以1s的频率循环打印Hello TencentOS tiny
  • task2以100ms的频率循环翻转。

main.c

定义两个基本任务:

代码语言:javascript复制
//task1
#define TASK1_STK_SIZE  256
void task1(void *pdata);
osThreadDef(task1, osPriorityNormal, 1, TASK1_STK_SIZE);

void task1(void *pdata)
{
    while(1)
    {
       printf("Hello TencentOS tinyn");
       osDelay(1000);
    }
}

//task2
#define TASK2_STK_SIZE  256
void task2(void *pdata);
osThreadDef(task2, osPriorityNormal, 1, TASK2_STK_SIZE);

void task2(void *pdata)
{
    while(1)
    {
       HAL_GPIO_TogglePin(DEBUG_LED_GPIO_Port, DEBUG_LED_Pin);
       osDelay(100);
    }
}

在main函数中:

代码语言:javascript复制
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 0, (uint8_t*)"TencentOS tiny", 16);
    OLED_ShowString(0, 2, (uint8_t*)"Bruce.yang", 16);
    //初始化内核
    osKernelInitialize();
    //创建并启动一个任务用于打印调试信息
    osThreadCreate(osThread(task1), NULL);
    //创建并启动一个任务用于以100ms的间隔翻转LED
    osThreadCreate(osThread(task2), NULL);
    //启动内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

task1以1s的频率循环打印Hello TencentOS tiny,task2以100ms的频率循环翻转。

1.3 总结

概念性总结:

  • 多任务适合业务场景更加复杂的应用场景
  • 多任务适合对实时响应要求更高的场景

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

2、TencentOS tiny RTOS软件定时器

2.1、为什么要采用RTOS软件定时器?

软件定时器,顾名思义就是软件实现的定时器,它是和硬件定时器有本质区别的,软件定时器使用的是系统调度所依赖的嘀嗒定时器,也就是Systick来实现的,它主要解决一些不需要特别精准的定时触发场合,目前github仓库上有开源不少软件定时器的实例,比如multi_timer,TecentOS tiny也在自己的内核中集成了自己的一套软件定时器,实现原理其实也是差不多的。

2.2、TencentOS tiny RTOS软件定时器实践

关于怎么使用定时器,可以参考腾讯物联网终端操作系统开发指南.pdf文档,以下工程基于多任务例程修改,接下来,进入软件定时器程序编写,我们主要实现以下两个功能:

  • task1以1s的频率循环打印Hello TencentOS tiny
  • 软件定时器以500ms的频率翻转LED

main.c

代码语言:javascript复制
/*定义一个定时器句柄*/
k_timer_t os_tmr_handler;
//创建一个任务
#define TASK1_STK_SIZE  256
void task1(void *pdata);
osThreadDef(task1, osPriorityNormal, 1, TASK1_STK_SIZE);

void task1(void *pdata)
{
    while(1)
    {
       printf("Hello TencentOS tinyn");
       osDelay(1000);
    }
}

//定时器回调函数
void os_tmr_handler_callback(void *arg)
{
   HAL_GPIO_TogglePin(DEBUG_LED_GPIO_Port, DEBUG_LED_Pin);
}

在main函数中:

代码语言:javascript复制
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 0, (uint8_t*)"TencentOS tiny", 16);
    OLED_ShowString(0, 2, (uint8_t*)"Bruce.yang", 16);
    //初始化内核
    osKernelInitialize();
    //创建一个以500ms周期运行的软件定时器
    tos_timer_create(&os_tmr_handler, 500, 500, os_tmr_handler_callback, K_NULL, TOS_OPT_TIMER_PERIODIC);
    //创建一个任务
    osThreadCreate(osThread(task1), NULL);
    //启动定时器
    tos_timer_start(&os_tmr_handler);
    //启动内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

task1以1s的频率循环打印Hello TencentOS tiny,软件定时器以500ms的频率执行,此时LED会以500ms的速率循环翻转。

2.3 总结

概念性总结:

  • 软件定时器就是用"软件逻辑"实现的定时器
  • 软件定时器适合一些不需要特别精准的定时触发场合.

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

3、TencentOS tiny RTOS任务间通信

3.1、TencentOS tiny RTOS互斥锁

3.1.1 、为什么要采用RTOS互斥锁?

互斥锁适用于实现临界区资源的互斥性访问,当有多个任务同时并行对一个数据操作时,就会存在不确定性,典型的案例就是全局变量,在不带操作系统的裸机功能开发中,我们通常会使用全局变量,让其在整个工程中通过外部引用的方式全局可见,这样我们就可以很方便的在任何一个地方对其进行读写操作,但如果在操作系统中却恰恰相反,这种奇怪的现象被称为不可重入,通常在操作系统里叫临界区资源,在字符串操作中,典型的不可重入函数是strtok,strtok函数内部有一个static变量,这种类型的变量可以被多次重入调用共同控制,其最终的结果依赖于它们的执行顺序,所以,使用互斥锁可以解决这种不确定性的问题,也就是说在任意时刻,只会有一个任务对其进行访问。

3.1.2、TencentOS tiny RTOS互斥锁实践

关于怎么使用互斥锁,可以参考腾讯物联网终端操作系统开发指南.pdf文档,以下工程基于多任务例程修改,接下来,进入互斥锁程序编写,我们主要实现三个任务同时执行一段代码:

main.c

代码语言:javascript复制
k_mutex_t mutex;
int number = 0 ;

//task1
#define TASK1_STK_SIZE  256
void task1(void *pdata);
osThreadDef(task1, osPriorityNormal, 1, TASK1_STK_SIZE);

void count_sample_code(void)
{
    tos_mutex_pend(&mutex);
    number=0;
    number =1;
    number =2;
    number =3;
    tos_mutex_post(&mutex);
}

void task1(void *pdata)
{
    while(1)
    {
        number = 0 ;
        osDelay(300);
        count_sample_code();
        printf("task1 number=%dn",number);
        osDelay(100);
    }
}

//task2
#define TASK2_STK_SIZE  256
void task2(void *pdata);
osThreadDef(task2, osPriorityNormal, 1, TASK2_STK_SIZE);
void task2(void *pdata)
{
    while(1)
    {
        osDelay(300);
        count_sample_code();
        printf("task2 number=%dn",number);
        osDelay(200);
    }
}

//task3
#define TASK3_STK_SIZE  256
void task3(void *pdata);
osThreadDef(task3, osPriorityNormal, 1, TASK3_STK_SIZE);
void task3(void *pdata)
{
    while(1)
    {
        osDelay(300);
        count_sample_code();
        printf("task3 number=%dn",number);
        osDelay(300);
    }
}

在main函数中:

代码语言:javascript复制
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 2, (uint8_t*)"TencentOS tiny", 16);
    tos_mutex_create(&mutex);
    //初始化内核
    osKernelInitialize();
    //分别创建任务,用于验证互斥锁
    osThreadCreate(osThread(task1), NULL);
    osThreadCreate(osThread(task2), NULL);
    osThreadCreate(osThread(task3), NULL);
    //启动内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

3.1.3、总结

概念性总结:

  • 互斥锁在任意时刻,只会有一个任务对临界资源进行访问
  • 互斥锁用于实现临界区资源的互斥性访问

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

3.2、TencentOS tiny RTOS信号量

3.2.1、为什么要采用RTOS信号量?

信号量,俗话说就是信号的数量,它是一种任务间传递系统可用资源的机制;举一个生产者与消费者的问题;也就是说消费者在消费了一个资源之前需要等待资源释放,生产者生产资源以后要即时去通知其它的消费者,简单的说就是凡事都要有个先来后到,所以信号量最常用的地方就是实现任务间同步。

3.2.2、TencentOS tiny RTOS信号量实践

关于怎么使用信号量,可以参考腾讯物联网终端操作系统开发指南.pdf文档,以下工程基于多任务例程修改,接下来,进入信号量程序编写,我们主要实现生产者和消费者的问题,这段程序在参考文档里可以找到:

main.c

代码语言:javascript复制
#define STK_SIZE_TASK_PRODUCER 512
#define STK_SIZE_TASK_CONSUMER 512
k_stack_t stack_task_producer[STK_SIZE_TASK_PRODUCER];

k_stack_t stack_task_consumer[STK_SIZE_TASK_CONSUMER];
k_task_t task_producer;
k_task_t task_consumer;
extern void entry_task_producer(void *arg);
extern void entry_task_consumer(void *arg);
k_mutex_t buffer_locker;
k_sem_t full;
k_sem_t empty;
#define RESOURCE_COUNT_MAX 3
struct resource_st
{
    int cursor;
    uint32_t buffer[RESOURCE_COUNT_MAX];
} resource = { 0, {0} };

static void produce_item(int salt)
{
    printf("produce item:n");
    printf("%d", salt);
    resource.buffer[resource.cursor  ] = salt;
    printf("n");
}
void entry_task_producer(void *arg)
{
    size_t salt = 0;
    k_err_t err;

    while (K_TRUE)
    {
        err = tos_sem_pend(&empty, TOS_TIME_FOREVER);

        if (err != K_ERR_NONE)
        {
            continue;
        }

        err = tos_mutex_pend(&buffer_locker);

        if (err != K_ERR_NONE)
        {
            continue;
        }

        produce_item(salt);
        tos_mutex_post(&buffer_locker);
        tos_sem_post(&full);
        tos_task_delay(1000);
          salt;
    }
}
static void consume_item(void)
{
    printf("cosume item:n");
    printf("%dt", resource.buffer[--resource.cursor]);
    printf("n");
}
void entry_task_consumer(void *arg)
{
    k_err_t err;

    while (K_TRUE)
    {
        err = tos_sem_pend(&full, TOS_TIME_FOREVER);

        if (err != K_ERR_NONE)
        {
            continue;
        }

        tos_mutex_pend(&buffer_locker);

        if (err != K_ERR_NONE)
        {
            continue;
        }

        consume_item();
        tos_mutex_post(&buffer_locker);
        tos_sem_post(&empty);
        tos_task_delay(2000);
    }
}

main函数实现如下:

代码语言:javascript复制
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 0, (uint8_t*)"TencentOS tiny", 16);
    OLED_ShowString(0, 2, (uint8_t*)"Bruce.yang", 16);
    //初始化内核
    osKernelInitialize();
    tos_mutex_create(&buffer_locker);
    tos_sem_create(&full, 0);
    tos_sem_create(&empty, RESOURCE_COUNT_MAX);
    (void)tos_task_create(&task_producer, "producer", entry_task_producer, NULL,
                          4, stack_task_producer, STK_SIZE_TASK_PRODUCER, 0);

    (void)tos_task_create(&task_consumer, "consumer", entry_task_consumer, NULL,
                          4, stack_task_consumer, STK_SIZE_TASK_CONSUMER, 0);
    //启动内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

3.2.3、总结

概念性总结:

  • 信号量可以用于实现任务间同步
  • 信号量最典型的应用就是处理生产者与消费者的问题

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

3.3、TencentOS tiny RTOS事件

3.3.1、为什么要采用RTOS事件?

事件,是RTOS任务间用来传递的一种信号的信息,它可以传递多个信息,事件和信号量的区别就是信号量只能传递0和1两个信息,而事件的类型通常用k_event_flag_t进行描述,它的本质是一个uint32_t数据类型,也就是说事件最多可以定义32个,使用事件可以很方便的实现任务间同步和信息传递,但是要注意的是事件达到的是一种类似通知的效果,本身是不带负载的。

3.3.2、TencentOS tiny RTOS事件实践

关于怎么使用事件,可以参考腾讯物联网终端操作系统开发指南.pdf文档,以下工程基于多任务例程修改,我们在多任务例程的基础上,移植了multi_button组件,这个组件的移植方法在之前写小熊派相关的文章中都有详细的方法,这里就不再多说了,参考文章如下:

第1期 | MultiButton,一个小巧简单易用的事件驱动型按键驱动模块

开源按键组件MultiButton支持菜单操作(事件驱动型)

这里的button_ticks()我把它放在SysTick_Handler函数中进行处理,实现如下:

代码语言:javascript复制
/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
    /* USER CODE BEGIN SysTick_IRQn 0 */
    static uint8_t timer_ticks = 0 ;
      timer_ticks ;
     //5ms循环调用一次button_ticks();
    if(5 == timer_ticks)
    {
        timer_ticks = 0 ;
        button_ticks();
    }

    /* USER CODE END SysTick_IRQn 0 */
    HAL_IncTick();

    /* USER CODE BEGIN SysTick_IRQn 1 */
    if(tos_knl_is_running())
    {
        tos_knl_irq_enter();
        tos_tick_handler();
        tos_knl_irq_leave();
    }

    /* USER CODE END SysTick_IRQn 1 */
}

接下来进入事件程序的编写,我们主要实现以下两个功能:

  • 定义任务task1,用于初始化multi_button,通过按键回调发送事件
  • 定义任务task2,用于接收task1发送过来的事件,并进行处理

main.c

定义multi_button结构体变量以及事件相关的变量

代码语言:javascript复制
#include "multi_button.h"
/*创建一个按键事件*/
k_event_t key_event;
//创建几个按键的事件标志
const k_event_flag_t event_key1 = (k_event_flag_t)(1 << 0);
const k_event_flag_t event_key2 = (k_event_flag_t)(1 << 1);
const k_event_flag_t event_key3 = (k_event_flag_t)(1 << 2);
const k_event_flag_t event_key4 = (k_event_flag_t)(1 << 3);
const k_event_flag_t event_key5 = (k_event_flag_t)(1 << 4);
//定义描述按键的结构体变量
struct Button button1, button2, button3, button4;

定义按键电平读取以及按键处理的函数:

代码语言:javascript复制
uint8_t key1_read_pin(void);
uint8_t key2_read_pin(void);
uint8_t key3_read_pin(void);
uint8_t key4_read_pin(void);
void key_handler(void* btn);

uint8_t key1_read_pin(void)
{
    return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
}
uint8_t key2_read_pin(void)
{
    return HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin);
}
uint8_t key3_read_pin(void)
{
    return HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin);
}
uint8_t key4_read_pin(void)
{
    return HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin);
}
//按键处理
void key_handler(void* btn)
{
    struct Button *button = btn ;
    if(btn == &button1)
    {
      if(button->event == PRESS_DOWN)
        tos_event_post(&key_event, event_key1);
      else if(button->event == PRESS_UP)
        tos_event_post(&key_event, event_key5);
    }
    else if(btn == &button2)
    {
      if(button->event == PRESS_DOWN)
        tos_event_post(&key_event, event_key2);
      else if(button->event == PRESS_UP)
        tos_event_post(&key_event, event_key5);
    }
    else if(btn == &button3)
    {
      if(button->event == PRESS_DOWN)
        tos_event_post(&key_event, event_key3);
      else if(button->event == PRESS_UP)
        tos_event_post(&key_event, event_key5);
    }
    else if(btn == &button4)
    {
      if(button->event == PRESS_DOWN)
        tos_event_post(&key_event, event_key4);
      else if(button->event == PRESS_UP)
        tos_event_post(&key_event, event_key5);
    }
}

在main函数中:

代码语言:javascript复制
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 0, (uint8_t*)"TencentOS tiny", 16);
    OLED_ShowString(0, 2, (uint8_t*)"Bruce.yang", 16);
    //初始化内核
    osKernelInitialize();
    //创建一个按键事件
    tos_event_create(&key_event, (k_event_flag_t)0u);
    //创建并启动一个任务用于通过按键发送事件
    osThreadCreate(osThread(task1), NULL);
    //创建并启动一个任务用于接收按键事件并执行相应的软件逻辑
    osThreadCreate(osThread(task2), NULL);
    //启动TencentOS tiny内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

当分别按下四个按键后,task2接收到具体消息后执行不同的逻辑

3.3.3 总结

概念性总结:

  • 事件区别于信号量,信号量是0-1传递,而事件可以传递多个信息
  • 事件是用于RTOS任务间传递的一种没有信息负载的信号类信息

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

3.4、TencentOS tiny RTOS队列

3.4.1、为什么要采用RTOS队列?

队列也是任务间传递信息的一种方式,它和事件最本质的区别就是,事件传递没有负载,而队列的传递是包含数据负载的,在事件章节中,当我们按下按键的时候其中一个任务发出事件,另一个任务则接收事件,而接收的这个事件是非常单一的,除此之外并没有更多具体的新信息载体,而队列就是为了解决这个问题而诞生的,比如串口接收数据,当数据接收满了,此时我们就可以使用队列将接收满的数据通过队列的信息发出去,然后任务里进行接收处理。

3.4.2、TencentOS tiny RTOS队列实践

关于怎么使用队列,可以参考腾讯物联网终端操作系统开发指南.pdf文档,但该文档的API过老,可能不适合现在的版本,于是找来了一个新版的API,参考网友修改的,以下工程基于多任务例程修改:

main.c

代码语言:javascript复制
#define STK_SIZE_TASK_RECEIVER      512
#define STK_SIZE_TASK_SENDER        512

#define PRIO_TASK_RECEIVER_HIGHER_PRIO      4
#define PRIO_TASK_RECEIVER_LOWER_PRIO       (PRIO_TASK_RECEIVER_HIGHER_PRIO   1)

#define MESSAGE_MAX     10

k_stack_t stack_task_receiver_higher_prio[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_receiver_lower_prio[STK_SIZE_TASK_RECEIVER];
k_stack_t stack_task_sender[STK_SIZE_TASK_SENDER];

uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)];

k_task_t task_receiver_higher_prio;
k_task_t task_receiver_lower_prio;
k_task_t task_sender;

k_msg_q_t msg_q;

void entry_task_receiver_higher_prio(void *arg)
{
    k_err_t err;
    void *msg_received;

    while (K_TRUE)
    {
        err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER);

        if (err == K_ERR_NONE)
        {
            printf("higher: msg incoming[%s]n", (char *)msg_received);
        }
    }
}

void entry_task_receiver_lower_prio(void *arg)
{
    k_err_t err;
    void *msg_received;

    while (K_TRUE)
    {
        err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER);

        if (err == K_ERR_NONE)
        {
            printf("lower: msg incoming[%s]n", (char *)msg_received);
        }
    }
}

void entry_task_sender(void *arg)
{
    int i = 1;
    char *msg_to_one_receiver = "message for one receiver(with highest priority)";
    char *msg_to_all_receiver = "message for all receivers";

    while (K_TRUE)
    {
        if (i == 2)
        {
            printf("sender: send a message to one receiver, and shoud be the highest priority onen");
            tos_msg_q_post(&msg_q, msg_to_one_receiver);
        }

        if (i == 3)
        {
            printf("sender: send a message to all receviern");
            tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
        }

        if (i == 4)
        {
            printf("sender: send a message to one receiver, and shoud be the highest priority onen");
            tos_msg_q_post(&msg_q, msg_to_one_receiver);
        }

        if (i == 5)
        {
            printf("sender: send a message to all receviern");
            tos_msg_q_post_all(&msg_q, msg_to_all_receiver);
        }

        tos_task_delay(1000);
          i;
    }
}

main函数实现:

代码语言:javascript复制
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_I2C3_Init();
    /* USER CODE BEGIN 2 */
    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0, 0, (uint8_t*)"TencentOS tiny", 16);
    OLED_ShowString(0, 2, (uint8_t*)"Bruce.yang", 16);
    //初始化内核
    osKernelInitialize();
    tos_msg_q_create(&msg_q, msg_pool, MESSAGE_MAX);
    (void)tos_task_create(&task_receiver_higher_prio, "receiver_higher_prio",
                          entry_task_receiver_higher_prio, NULL, PRIO_TASK_RECEIVER_HIGHER_PRIO,
                          stack_task_receiver_higher_prio, STK_SIZE_TASK_RECEIVER, 0);
    (void)tos_task_create(&task_receiver_lower_prio, "receiver_lower_prio",
                          entry_task_receiver_lower_prio, NULL, PRIO_TASK_RECEIVER_LOWER_PRIO,
                          stack_task_receiver_lower_prio, STK_SIZE_TASK_RECEIVER, 0);
    (void)tos_task_create(&task_sender, "sender", entry_task_sender, NULL,
                          4, stack_task_sender, STK_SIZE_TASK_SENDER, 0);
    //启动内核
    osKernelStart();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

    /* USER CODE END 3 */
}

编译后下载到EVB_MX 开发板后,运行结果如下:

3.4.3、总结

概念性总结:

  • 事件传递不带信息负载,而队列是带信息负载的
  • 队列除了可以告诉我们发生了什么事,还可以告诉我们发生这件事情的详细过程

使用总结:

详情请参考腾讯物联网终端操作系统开发指南.pdf文档

4、总结

关于TencentOS tiny还有非常多的组件可以学习,这里只是列出了最常用的几种,最后我们给本文做下简短的总结:

  • 多任务

解决复杂需求、实时性问题。

  • 互斥锁

解决不可重入,资源的竞争关系。

  • 信号量

解决任务间同步问题,典型为生产者-消费者的问一体。

  • 事件

解决任务间同步问题,相比信号量,事件可以传递多个,但不带负载。

  • 队列

解决任务间传递带负载的问题。

0 人点赞