上节,我们介绍了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驱动:
代码语言: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
函数中进行处理,实现如下:
/**
* @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
还有非常多的组件可以学习,这里只是列出了最常用的几种,最后我们给本文做下简短的总结:
- 多任务
解决复杂需求、实时性问题。
- 互斥锁
解决不可重入,资源的竞争关系。
- 信号量
解决任务间同步问题,典型为生产者-消费者的问一体。
- 事件
解决任务间同步问题,相比信号量,事件可以传递多个,但不带负载。
- 队列
解决任务间传递带负载的问题。