上节,我们介绍了TencentOS tiny,参考官方给出的移植教程亲自动手做了一遍,文章如下:
天啊!鹅厂都开始做开发板了?网红腾讯物联网开发板终极开箱评测,让我们一睹为快!
趁着最近有时间,这节,我撸了几个例程作为后面做项目参考的基本框架,当然也有一些是直接拿了官方文档的例程:
一般来说,学习任何一个RTOS,本质是没有什么太大的区别的,通常在最简版nano上进行开发,关于TencentOS tiny
,我个人认为,掌握以下基础组件的用法足矣,其它的一些组件,可以等需要使用的时候再参考文档学习应用即可。
- TencentOS tiny多任务
- TencentOS tiny RTOS软件定时器
- TencentOS tiny RTOS任务间通信(互斥锁、信号量、事件、队列)
在使用基本组件之前,我们需要配置tos_config.h
文件:
#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驱动:
// 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
定义两个基本任务:
//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函数中:
/** * @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
/*定义一个定时器句柄*/ 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函数中:
/** * @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
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函数中:
/** * @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
#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函数实现如下:
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
函数中进行处理,实现如下:
/** * @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结构体变量以及事件相关的变量
#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;
定义按键电平读取以及按键处理的函数:
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函数中:
/** * @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 */ }
当分别按下四个按键后,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
#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函数实现:
/** * @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
还有非常多的组件可以学习,这里只是列出了最常用的几种,最后我们给本文做下简短的总结:
- 多任务
解决复杂需求、实时性问题。
- 互斥锁
解决不可重入,资源的竞争关系。
- 信号量
解决任务间同步问题,典型为生产者-消费者的问一体。
- 事件
解决任务间同步问题,相比信号量,事件可以传递多个,但不带负载。
- 队列
解决任务间传递带负载的问题。
案例下载
公众号后台回复:TencentOS tiny即可获取本节所有程序案例及参考文档下载链接。
全文参考资料
腾讯物联网终端操作系统SDK文档.pdf
腾讯物联网终端操作系统开发指南.pdf
TencentOS tiny技术讲解与开发实践PPT.pdf
云加社区沙龙(腾讯物联网操作系统TencentOS tiny架构解析与实践).pdf