前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TencentOS tiny RTOS快速入门

TencentOS tiny RTOS快速入门

作者头像
杨源鑫
发布2020-09-23 11:43:31
2.7K0
发布2020-09-23 11:43:31
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈

上节,我们介绍了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 tiny\n");
       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 tiny\n");
       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=%d\n",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=%d\n",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=%d\n",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("%d\t", 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 one\n");
            tos_msg_q_post(&msg_q, msg_to_one_receiver);
        }

        if (i == 3)
        {
            printf("sender: send a message to all recevier\n");
            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 one\n");
            tos_msg_q_post(&msg_q, msg_to_one_receiver);
        }

        if (i == 5)
        {
            printf("sender: send a message to all recevier\n");
            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还有非常多的组件可以学习,这里只是列出了最常用的几种,最后我们给本文做下简短的总结:

  • 多任务

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

  • 互斥锁

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

  • 信号量

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

  • 事件

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

  • 队列

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式云IOT技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、TencentOS tiny多任务
    • 1.1 为什么要采用RTOS多任务?
      • 1.2 TencentOS tiny RTOS多任务实践
        • 1.3 总结
        • 2、TencentOS tiny RTOS软件定时器
          • 2.1、为什么要采用RTOS软件定时器?
            • 2.2、TencentOS tiny RTOS软件定时器实践
              • 2.3 总结
              • 3、TencentOS tiny RTOS任务间通信
                • 3.1、TencentOS tiny RTOS互斥锁
                  • 3.1.1 、为什么要采用RTOS互斥锁?
                  • 3.1.2、TencentOS tiny RTOS互斥锁实践
                  • 3.1.3、总结
                • 3.2、TencentOS tiny RTOS信号量
                  • 3.2.1、为什么要采用RTOS信号量?
                  • 3.2.2、TencentOS tiny RTOS信号量实践
                  • 3.2.3、总结
                • 3.3、TencentOS tiny RTOS事件
                  • 3.3.1、为什么要采用RTOS事件?
                  • 3.3.2、TencentOS tiny RTOS事件实践
                  • 3.3.3 总结
                • 3.4、TencentOS tiny RTOS队列
                  • 3.4.1、为什么要采用RTOS队列?
                  • 3.4.2、TencentOS tiny RTOS队列实践
                  • 3.4.3、总结
              • 4、总结
              相关产品与服务
              腾讯物联网终端操作系统
              腾讯物联网终端操作系统(TencentOS tiny)是腾讯面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率。TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU 及模组芯片上。而且,基于RTOS内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入腾讯云物联网平台。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档