TencentOS tiny RTOS快速入門
上節(jié),我們介紹了TencentOS tiny,參考官方給出的移植教程親自動手做了一遍,文章如下:
天?。※Z廠都開始做開發(fā)板了?網(wǎng)紅騰訊物聯(lián)網(wǎng)開發(fā)板終極開箱評測,讓我們一睹為快!
趁著最近有時間,這節(jié),我擼了幾個例程作為后面做項目參考的基本框架,當(dāng)然也有一些是直接拿了官方文檔的例程:
一般來說,學(xué)習(xí)任何一個RTOS,本質(zhì)是沒有什么太大的區(qū)別的,通常在最簡版nano上進行開發(fā),關(guān)于TencentOS tiny
,我個人認(rèn)為,掌握以下基礎(chǔ)組件的用法足矣,其它的一些組件,可以等需要使用的時候再參考文檔學(xué)習(xí)應(yīng)用即可。
-
TencentOS tiny多任務(wù) -
TencentOS tiny RTOS軟件定時器 -
TencentOS tiny RTOS任務(wù)間通信(互斥鎖、信號量、事件、隊列)
在使用基本組件之前,我們需要配置tos_config.h
文件:
#ifndef?_TOS_CONFIG_H_
#define??_TOS_CONFIG_H_
//#include?"stm32l0xx.h"?//?目標(biāo)芯片頭文件,用戶需要根據(jù)情況更改
#include?"stm32l4xx_hal.h"
#define?TOS_CFG_TASK_PRIO_MAX???????????10u??//?配置TencentOS?tiny默認(rèn)支持的最大優(yōu)先級數(shù)量
#define?TOS_CFG_ROUND_ROBIN_EN??????????0u??//?配置TencentOS?tiny的內(nèi)核是否開啟時間片輪轉(zhuǎn)
#define?TOS_CFG_OBJECT_VERIFY_EN???????????1u?//?配置TencentOS?tiny是否校驗指針合法
#define?TOS_CFG_TASK_DYNAMIC_CREATE_EN??1u??//?TencentOS?tiny?動態(tài)任務(wù)創(chuàng)建功能宏
#define?TOS_CFG_EVENT_EN????????????????1u??//?TencentOS?tiny?事件模塊功能宏
#define?TOS_CFG_MMBLK_EN????????????????1u??//配置TencentOS?tiny是否開啟內(nèi)存塊管理模塊
#define?TOS_CFG_MMHEAP_EN???????????????1u??//配置TencentOS?tiny是否開啟動態(tài)內(nèi)存模塊
#define?TOS_CFG_MMHEAP_DEFAULT_POOL_EN??1u??//?TencentOS?tiny?默認(rèn)動態(tài)內(nèi)存池功能宏
#define?TOS_CFG_MMHEAP_DEFAULT_POOL_SIZE????????0x100?//?配置TencentOS?tiny默認(rèn)動態(tài)內(nèi)存池大小
#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是否開啟優(yōu)先級消息隊列模塊
#define?TOS_CFG_PRIORITY_MAIL_QUEUE_EN?1u??//?配置TencentOS?tiny是否開啟優(yōu)先級消息郵箱模塊
#define?TOS_CFG_TIMER_EN????????????????1u??//?配置TencentOS?tiny是否開啟軟件定時器模塊
#define?TOS_CFG_PWR_MGR_EN??????????????0u??//?配置TencentOS?tiny是否開啟外設(shè)電源管理模塊
#define?TOS_CFG_TICKLESS_EN?????????????0u??//?配置Tickless?低功耗模塊開關(guān)
#define?TOS_CFG_SEM_EN??????????????????1u??//?配置TencentOS?tiny是否開啟信號量模塊
#define?TOS_CFG_TASK_STACK_DRAUGHT_DEPTH_DETACT_EN??????1u?//?配置TencentOS?tiny是否開啟任務(wù)棧深度檢測
#define?TOS_CFG_FAULT_BACKTRACE_EN??????0u??//?配置TencentOS?tiny是否開啟異常?;厮莨δ?/span>
#define?TOS_CFG_IDLE_TASK_STK_SIZE??????128u?//?配置TencentOS?tiny空閑任務(wù)棧大小
#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配置成函數(shù)模式
#endif
這樣后面我們才能正常使用。
1、TencentOS tiny多任務(wù)
1.1 為什么要采用RTOS多任務(wù)?
對于普通的項目來說,比如密碼鎖類項目,單獨的一個傳感器模塊的開發(fā),某些簡單的儀器儀表等等,對于這類場景單一,業(yè)務(wù)需求也單一的項目來說,使用狀態(tài)機或者事件驅(qū)動的方式就足以完成項目的基本功能了。
但是如果開發(fā)一個巨量代碼的工程項目,項目可能設(shè)計到傳感器數(shù)據(jù)讀取、無線數(shù)據(jù)上傳與接收、數(shù)據(jù)傳輸、UI實時刷新、算法處理等等,功能諸多還需要相互配合的情況下,那么如果還在用裸機的思想去完成,那么開發(fā)者一般會面臨以下兩個問題:
-
設(shè)計思路過于復(fù)雜,光怎么想程序的設(shè)計思路就得想好久了 -
設(shè)計下來的各個功能,要考慮相互配合的問題,實時性可能得不到要求
RTOS的多任務(wù)就可以解決對應(yīng)的問題,它既能讓項目開發(fā)起來思路清晰,方便易維護;同時RTOS也能保證整個產(chǎn)品運行的實時性,典型的程序設(shè)計架構(gòu),就可以按下面的方式來劃分:
1.2 TencentOS tiny RTOS多任務(wù)實踐
關(guān)于怎么創(chuàng)建多個任務(wù),可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,以下工程是我基于上一節(jié)的移植工程,在移植工程的基礎(chǔ)上,由于官方給的OLED驅(qū)動例程是軟件模擬驅(qū)動的,后來我將其改為I2C硬件驅(qū)動,所以,在STM32CubeMX上對OLED的I2C接口進行了配置:
更改后重新生成軟件工程,然后修改oled.c中關(guān)于寫命令和寫數(shù)據(jù)的接口為硬件I2C驅(qū)動:
//?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);
}
接下來,進入多任務(wù)程序編寫,我們主要實現(xiàn)以下兩個功能:
-
task1以1s的頻率循環(huán)打印 Hello TencentOS tiny
-
task2以100ms的頻率循環(huán)翻轉(zhuǎn)。
main.c
定義兩個基本任務(wù):
//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函數(shù)中:
/**
??*?@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);
????//初始化內(nèi)核
????osKernelInitialize();
????//創(chuàng)建并啟動一個任務(wù)用于打印調(diào)試信息
????osThreadCreate(osThread(task1),?NULL);
????//創(chuàng)建并啟動一個任務(wù)用于以100ms的間隔翻轉(zhuǎn)LED
????osThreadCreate(osThread(task2),?NULL);
????//啟動內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
task1以1s的頻率循環(huán)打印Hello TencentOS tiny
,task2以100ms的頻率循環(huán)翻轉(zhuǎn)。
1.3 總結(jié)
概念性總結(jié):
-
多任務(wù)適合業(yè)務(wù)場景更加復(fù)雜的應(yīng)用場景 -
多任務(wù)適合對實時響應(yīng)要求更高的場景
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔
2、TencentOS tiny RTOS軟件定時器
2.1、為什么要采用RTOS軟件定時器?
軟件定時器,顧名思義就是軟件實現(xiàn)的定時器,它是和硬件定時器有本質(zhì)區(qū)別的,軟件定時器使用的是系統(tǒng)調(diào)度所依賴的嘀嗒定時器,也就是Systick來實現(xiàn)的,它主要解決一些不需要特別精準(zhǔn)的定時觸發(fā)場合,目前github倉庫上有開源不少軟件定時器的實例,比如multi_timer,TecentOS tiny也在自己的內(nèi)核中集成了自己的一套軟件定時器,實現(xiàn)原理其實也是差不多的。
2.2、TencentOS tiny RTOS軟件定時器實踐
關(guān)于怎么使用定時器,可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,以下工程基于多任務(wù)例程修改,接下來,進入軟件定時器程序編寫,我們主要實現(xiàn)以下兩個功能:
-
task1以1s的頻率循環(huán)打印 Hello TencentOS tiny
-
軟件定時器以500ms的頻率翻轉(zhuǎn)LED
main.c
/*定義一個定時器句柄*/
k_timer_t?os_tmr_handler;
//創(chuàng)建一個任務(wù)
#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);
????}
}
//定時器回調(diào)函數(shù)
void?os_tmr_handler_callback(void?*arg)
{
???HAL_GPIO_TogglePin(DEBUG_LED_GPIO_Port,?DEBUG_LED_Pin);
}
在main函數(shù)中:
/**
??*?@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);
????//初始化內(nèi)核
????osKernelInitialize();
????//創(chuàng)建一個以500ms周期運行的軟件定時器
????tos_timer_create(&os_tmr_handler,?500,?500,?os_tmr_handler_callback,?K_NULL,?TOS_OPT_TIMER_PERIODIC);
????//創(chuàng)建一個任務(wù)
????osThreadCreate(osThread(task1),?NULL);
????//啟動定時器
????tos_timer_start(&os_tmr_handler);
????//啟動內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
task1以1s的頻率循環(huán)打印Hello TencentOS tiny
,軟件定時器以500ms的頻率執(zhí)行,此時LED會以500ms的速率循環(huán)翻轉(zhuǎn)。
2.3 總結(jié)
概念性總結(jié):
-
軟件定時器就是用"軟件邏輯"實現(xiàn)的定時器 -
軟件定時器適合一些不需要特別精準(zhǔn)的定時觸發(fā)場合.
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔
3、TencentOS tiny RTOS任務(wù)間通信
3.1、TencentOS tiny RTOS互斥鎖
3.1.1 、為什么要采用RTOS互斥鎖?
互斥鎖適用于實現(xiàn)臨界區(qū)資源的互斥性訪問,當(dāng)有多個任務(wù)同時并行對一個數(shù)據(jù)操作時,就會存在不確定性,典型的案例就是全局變量,在不帶操作系統(tǒng)的裸機功能開發(fā)中,我們通常會使用全局變量,讓其在整個工程中通過外部引用的方式全局可見,這樣我們就可以很方便的在任何一個地方對其進行讀寫操作,但如果在操作系統(tǒng)中卻恰恰相反,這種奇怪的現(xiàn)象被稱為不可重入,通常在操作系統(tǒng)里叫臨界區(qū)資源,在字符串操作中,典型的不可重入函數(shù)是strtok,strtok函數(shù)內(nèi)部有一個static變量,這種類型的變量可以被多次重入調(diào)用共同控制,其最終的結(jié)果依賴于它們的執(zhí)行順序,所以,使用互斥鎖可以解決這種不確定性的問題,也就是說在任意時刻,只會有一個任務(wù)對其進行訪問。
3.1.2、TencentOS tiny RTOS互斥鎖實踐
關(guān)于怎么使用互斥鎖,可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,以下工程基于多任務(wù)例程修改,接下來,進入互斥鎖程序編寫,我們主要實現(xiàn)三個任務(wù)同時執(zhí)行一段代碼:
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函數(shù)中:
/**
??*?@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);
????//初始化內(nèi)核
????osKernelInitialize();
????//分別創(chuàng)建任務(wù),用于驗證互斥鎖
????osThreadCreate(osThread(task1),?NULL);
????osThreadCreate(osThread(task2),?NULL);
????osThreadCreate(osThread(task3),?NULL);
????//啟動內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
3.1.3、總結(jié)
概念性總結(jié):
-
互斥鎖在任意時刻,只會有一個任務(wù)對臨界資源進行訪問 -
互斥鎖用于實現(xiàn)臨界區(qū)資源的互斥性訪問
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔
3.2、TencentOS tiny RTOS信號量
3.2.1、為什么要采用RTOS信號量?
信號量,俗話說就是信號的數(shù)量,它是一種任務(wù)間傳遞系統(tǒng)可用資源的機制;舉一個生產(chǎn)者與消費者的問題;也就是說消費者在消費了一個資源之前需要等待資源釋放,生產(chǎn)者生產(chǎn)資源以后要即時去通知其它的消費者,簡單的說就是凡事都要有個先來后到,所以信號量最常用的地方就是實現(xiàn)任務(wù)間同步。
3.2.2、TencentOS tiny RTOS信號量實踐
關(guān)于怎么使用信號量,可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,以下工程基于多任務(wù)例程修改,接下來,進入信號量程序編寫,我們主要實現(xiàn)生產(chǎn)者和消費者的問題,這段程序在參考文檔里可以找到:
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函數(shù)實現(xiàn)如下:
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);
????//初始化內(nèi)核
????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);
????//啟動內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
3.2.3、總結(jié)
概念性總結(jié):
-
信號量可以用于實現(xiàn)任務(wù)間同步 -
信號量最典型的應(yīng)用就是處理生產(chǎn)者與消費者的問題
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔
3.3、TencentOS tiny RTOS事件
3.3.1、為什么要采用RTOS事件?
事件,是RTOS任務(wù)間用來傳遞的一種信號的信息,它可以傳遞多個信息,事件和信號量的區(qū)別就是信號量只能傳遞0和1兩個信息,而事件的類型通常用k_event_flag_t
進行描述,它的本質(zhì)是一個uint32_t
數(shù)據(jù)類型,也就是說事件最多可以定義32個,使用事件可以很方便的實現(xiàn)任務(wù)間同步和信息傳遞,但是要注意的是事件達(dá)到的是一種類似通知的效果,本身是不帶負(fù)載的。
3.3.2、TencentOS tiny RTOS事件實踐
關(guān)于怎么使用事件,可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,以下工程基于多任務(wù)例程修改,我們在多任務(wù)例程的基礎(chǔ)上,移植了multi_button組件,這個組件的移植方法在之前寫小熊派相關(guān)的文章中都有詳細(xì)的方法,這里就不再多說了,參考文章如下:
第1期 | MultiButton,一個小巧簡單易用的事件驅(qū)動型按鍵驅(qū)動模塊
開源按鍵組件MultiButton支持菜單操作(事件驅(qū)動型)
這里的button_ticks()
我把它放在SysTick_Handler
函數(shù)中進行處理,實現(xiàn)如下:
/**
??*?@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循環(huán)調(diào)用一次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?*/
}
接下來進入事件程序的編寫,我們主要實現(xiàn)以下兩個功能:
-
定義任務(wù)task1,用于初始化multi_button,通過按鍵回調(diào)發(fā)送事件 -
定義任務(wù)task2,用于接收task1發(fā)送過來的事件,并進行處理
main.c
定義multi_button結(jié)構(gòu)體變量以及事件相關(guān)的變量
#include?"multi_button.h"
/*創(chuàng)建一個按鍵事件*/
k_event_t?key_event;
//創(chuàng)建幾個按鍵的事件標(biāo)志
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);
//定義描述按鍵的結(jié)構(gòu)體變量
struct?Button?button1,?button2,?button3,?button4;
定義按鍵電平讀取以及按鍵處理的函數(shù):
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函數(shù)中:
/**
??*?@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);
????//初始化內(nèi)核
????osKernelInitialize();
????//創(chuàng)建一個按鍵事件
????tos_event_create(&key_event,?(k_event_flag_t)0u);
????//創(chuàng)建并啟動一個任務(wù)用于通過按鍵發(fā)送事件
????osThreadCreate(osThread(task1),?NULL);
????//創(chuàng)建并啟動一個任務(wù)用于接收按鍵事件并執(zhí)行相應(yīng)的軟件邏輯
????osThreadCreate(osThread(task2),?NULL);
????//啟動TencentOS?tiny內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
當(dāng)分別按下四個按鍵后,task2接收到具體消息后執(zhí)行不同的邏輯
3.3.3 總結(jié)
概念性總結(jié):
-
事件區(qū)別于信號量,信號量是0-1傳遞,而事件可以傳遞多個信息 -
事件是用于RTOS任務(wù)間傳遞的一種沒有信息負(fù)載的信號類信息
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔
3.4、TencentOS tiny RTOS隊列
3.4.1、為什么要采用RTOS隊列?
隊列也是任務(wù)間傳遞信息的一種方式,它和事件最本質(zhì)的區(qū)別就是,事件傳遞沒有負(fù)載,而隊列的傳遞是包含數(shù)據(jù)負(fù)載的,在事件章節(jié)中,當(dāng)我們按下按鍵的時候其中一個任務(wù)發(fā)出事件,另一個任務(wù)則接收事件,而接收的這個事件是非常單一的,除此之外并沒有更多具體的新信息載體,而隊列就是為了解決這個問題而誕生的,比如串口接收數(shù)據(jù),當(dāng)數(shù)據(jù)接收滿了,此時我們就可以使用隊列將接收滿的數(shù)據(jù)通過隊列的信息發(fā)出去,然后任務(wù)里進行接收處理。
3.4.2、TencentOS tiny RTOS隊列實踐
關(guān)于怎么使用隊列,可以參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
文檔,但該文檔的API過老,可能不適合現(xiàn)在的版本,于是找來了一個新版的API,參考網(wǎng)友修改的,以下工程基于多任務(wù)例程修改:
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函數(shù)實現(xiàn):
/**
??*?@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);
????//初始化內(nèi)核
????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);
????//啟動內(nèi)核
????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+開發(fā)板后,運行結(jié)果如下:
3.4.3、總結(jié)
概念性總結(jié):
-
事件傳遞不帶信息負(fù)載,而隊列是帶信息負(fù)載的 -
隊列除了可以告訴我們發(fā)生了什么事,還可以告訴我們發(fā)生這件事情的詳細(xì)過程
使用總結(jié):
詳情請參考騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf文檔
4、總結(jié)
關(guān)于TencentOS tiny
還有非常多的組件可以學(xué)習(xí),這里只是列出了最常用的幾種,最后我們給本文做下簡短的總結(jié):
-
多任務(wù)
解決復(fù)雜需求、實時性問題。
-
互斥鎖
解決不可重入,資源的競爭關(guān)系。
-
信號量
解決任務(wù)間同步問題,典型為生產(chǎn)者-消費者的問一體。
-
事件
解決任務(wù)間同步問題,相比信號量,事件可以傳遞多個,但不帶負(fù)載。
-
隊列
解決任務(wù)間傳遞帶負(fù)載的問題。
案例下載
公眾號后臺回復(fù):TencentOS tiny即可獲取本節(jié)所有程序案例及參考文檔下載鏈接。
全文參考資料
騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)SDK文檔.pdf
騰訊物聯(lián)網(wǎng)終端操作系統(tǒng)開發(fā)指南.pdf
TencentOS tiny技術(shù)講解與開發(fā)實踐PPT.pdf
云加社區(qū)沙龍(騰訊物聯(lián)網(wǎng)操作系統(tǒng)TencentOS tiny架構(gòu)解析與實踐).pdf
公眾號粉絲福利時刻
這里我給大家申請到了福利,本公眾號讀者購買小熊派開發(fā)板可享受9折優(yōu)惠,有需要購買小熊派以及騰訊物聯(lián)網(wǎng)開發(fā)板的朋友,淘寶搜索即可,跟客服說你是公眾號:嵌入式云IOT技術(shù)圈 的粉絲,立享9折優(yōu)惠!
往期精彩
網(wǎng)紅騰訊物聯(lián)網(wǎng)開發(fā)板終極開箱評測,讓我們一睹為快!
STM32硬核DIY機械鍵盤|藍(lán)牙USB雙模|燈控
一個超火超給力的STM32開源疫情監(jiān)控項目
一個超酷的開源uHand2.0機械手掌項目
居民身份證閱讀器產(chǎn)品開發(fā)學(xué)習(xí)心得(再談標(biāo)準(zhǔn)-軟件-協(xié)議)
覺得本次分享的文章對您有幫助,隨手點[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!