? 隨著寫代碼功力的提升,個人對于代碼的整潔、優(yōu)雅、可維護、易拓展等就有了一定的要求,雖然自己曾經(jīng)就屬于那種全局變量滿天飛,想到哪里寫到哪里的嵌入式軟件工程師;但是這一切在現(xiàn)在來說必須要結(jié)束了!要想做一個好的項目,我們時刻都要去想它的框架如何設(shè)計,如何去兼容未來的拓展,以便我們構(gòu)建一個優(yōu)雅、整潔、易維護、易拓展的程序,少出問題,少加班,拿高薪;因此,我們必須在代碼的設(shè)計上利用編程語言的特性來下一些功夫。

在之前,我就經(jīng)常發(fā)現(xiàn)很多工程師在寫RTOS代碼的時候存在如下問題:
隨意定義任務(wù)的位置,隨意初始化任務(wù)代碼。
由于任務(wù)函數(shù)初始化參數(shù)過多,當(dāng)同時創(chuàng)建多個任務(wù)時,任務(wù)初始化函數(shù)寫得非常長,非常難看。
例如我之前寫的這個RT-Thread的項目:
碼云倉庫:
?
git?clone?https://gitee.com/morixinguan/personal-open-source-project.git
?
部分代碼如下:
?
/***************按鍵處理任務(wù)*************/ #define?KEY_TASK_PRIORITY??????3 #define?KEY_TASK_SIZE?????????2000 static?rt_thread_t?key_task_thread?=?RT_NULL; static?void?Start_Key_Task(void?*parameter); /***************按鍵處理任務(wù)*************/ /***************傳感器任務(wù)處理*************/ #define?SENSOR_PRIORITY???????????4 #define?SENSOR_TASK_SIZE????????????2048 rt_sem_t?sensor_data_sem?=?RT_NULL; static?rt_thread_t?sensor_task_thread?=?RT_NULL; /*狀態(tài)欄更新線程入口函數(shù)?*/ static?void?StartSensor_Task(void?*parameter); /***************傳感器任務(wù)處理*************/ /***************控制任務(wù)處理*************/ #define?CONTROL_PRIORITY???????????5 #define?CONTROL_TASK_SIZE???????????2048 static?rt_thread_t?control_task_thread?=?RT_NULL; /*控制任務(wù)更新線程入口函數(shù)?*/ static?void?StartControl_Task(void?*parameter); /***************控制任務(wù)處理*************/ ......省略..... /*啟動其它任務(wù)*/ void?start_other_rt_thread(void) { ????/*1、創(chuàng)建按鍵線程*/ ????key_task_thread?=?rt_thread_create("key_th", ???????????????????????????????????????Start_Key_Task,?RT_NULL, ???????????????????????????????????????KEY_TASK_SIZE, ???????????????????????????????????????KEY_TASK_PRIORITY,?TASK_TIMESLICE); ????/*?如果獲得線程控制塊,啟動這個線程?*/ ????if?(key_task_thread?!=?RT_NULL) ????????rt_thread_startup(key_task_thread); ????/*2、創(chuàng)建控制線程*/ ????control_task_thread?=?rt_thread_create("con_th", ???????????????????????????????????????????StartControl_Task,?RT_NULL, ???????????????????????????????????????????CONTROL_TASK_SIZE, ???????????????????????????????????????????CONTROL_PRIORITY,?TASK_TIMESLICE); ????/*?如果獲得線程控制塊,啟動這個線程?*/ ????if?(control_task_thread?!=?RT_NULL) ????????rt_thread_startup(control_task_thread); ????Menu_Init(); ????//關(guān)指示燈 ????HAL_GPIO_WritePin(BOARD_LED_GPIO_Port,?BOARD_LED_Pin,?GPIO_PIN_RESET); }
?
其實這個看起來還算舒服一點,至少它的位置是比較統(tǒng)一的,而且任務(wù)并不算很多;但是如果任務(wù)更多,這個代碼看起來就會很長,比如我找來的下面這個代碼,具體就不說是哪位小伙伴寫的了:
?
static??void??AppTaskStart?(void?*p_arg)
{
????OS_ERR???????err;
?CPU_SR??????cpu_sr?=?0;
?uint8_t?test[10];
???(void)p_arg;
????BSP_Init();?
????CPU_Init();??????????????????
?delay_init(168);
?uart_init(9600);???
?TxDMAConfig();
?RxDMAConfig((uint32_t)g_usart1RxBuf0,(uint32_t)g_usart1RxBuf1,USART1BUFSIZE);
?USART1_RxCallback?=?USART1_DMARxCallback;
?__HAL_DMA_ENABLE(&UART1RxDMA_Handler);?
?RTC_Init();
?PRINTER_Init();
?W25QXX_Init();
?LCD_BSP_Init();
?LcdInit();
?ADC_BSP_Init();
?NixieTube_BSPInit();
?MenuSystemInit();
?offplay();
?SRAM_Init();
?CH456IF_Init();
?ch456_test();
?my_mem_init(SRAMEX);?/*?初始化外部SRAM?*/
?Data_Init();?????????/*?初始化數(shù)據(jù)存儲模塊?*/
#if?OS_CFG_STAT_TASK_EN?>?0u
????OSStatTaskCPUUsageInit(&err);
#endif
#ifdef?CPU_CFG_INT_DIS_MEAS_EN
????CPU_IntDisMeasMaxCurReset();
#endif
#if?OS_CFG_SCHED_ROUND_ROBIN_EN??//時間片輪度算法??
?OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);??
#endif?
?OS_CRITICAL_ENTER();
?
?/*mutex?create?zone:begin*/
?OSMutexCreate((OS_MUTEX*?)&TEST_MUTEX,
??????(CPU_CHAR*?)"TEST_MUTEX",
??????????????????(OS_ERR*??)&err);
?
?OSMutexCreate((OS_MUTEX*?)&FLASH_MUTEX,
??????(CPU_CHAR*?)"FLASH?READ?MUTEX",
??????????????????(OS_ERR*??)&err);
?/*mutex?create?zone:end*/
?
?/*USER?TASK?CREATE?ZONE:BEGIN*/
?OSTaskCreate(&USBProcessTaskTCB,
?????"USB?Process?Task",
?????USBProcessTask,
?????0u,
?????USB_CFG_PROCESS_TASK_PRIO,
?????USBProcessTaskStk,
?????USBProcessTaskStk[USB_CFG_PROCESS_TASK_STK_SIZE?/?10u],
?????USB_CFG_PROCESS_TASK_STK_SIZE,
?????0u,???//message?amount
?????0u,
?????0u,
????(OS_OPT_TASK_STK_CHK?|?OS_OPT_TASK_STK_CLR),
????&err);
?
?OSTaskCreate(&teskTaskTCB,??????????????????????????????/*?Create?the?start?task????????????????????????????????*/
?????"test?Process?Task",
?????testProcessTask,
?????0u,
?????TEST_CFG_PROCESS_TASK_PRIO,
?????TESTProcessTaskStk,
?????TESTProcessTaskStk[TEST_CFG_PROCESS_TASK_STK_SIZE?/?10u],
?????TEST_CFG_PROCESS_TASK_STK_SIZE,
?????0u,???//message?amount
?????0u,
?????0u,
????(OS_OPT_TASK_STK_CHK?|?OS_OPT_TASK_STK_CLR),
????&err);
?OS_CRITICAL_EXIT();?
????while?(DEF_TRUE)?{???
????????udp_flag?|=?LWIP_SEND_DATA;??
????????OSTimeDlyHMSM(0u,?0u,?0u,?100u,
??????????????????????OS_OPT_TIME_HMSM_STRICT,
??????????????????????&err);
????}
}
?
難受嗎?至少我是覺得很難受的!解決這個問題可以使用一種簡單的、可擴展的RTOS初始化設(shè)計模式,這個設(shè)計模式的原則就是創(chuàng)建一個通用的初始化函數(shù),然后這個函數(shù)可以遍歷RTOS初始化配置表來初始化所有的任務(wù),讓我們來看看如何創(chuàng)建這樣的設(shè)計模式。
1、創(chuàng)建任務(wù)初始化結(jié)構(gòu)
第一步是檢查 RTOS 的任務(wù)創(chuàng)建函數(shù),并查看初始化任務(wù)所需的參數(shù)。任務(wù)初始化結(jié)構(gòu)只是一個包含初始化任務(wù)所需的所有參數(shù)的結(jié)構(gòu)。但是不同的RTOS之間可能不同,以freertos為例:
?
typedef?struct
{
????TaskFunction_t?const?taskptr;???????????
????const?char?*???const?taskname;????????????????
????const?configSTACK_DEPTH_TYPE?stackdepth;????
????void?*?const???parametersptr;?????????????????
????UBaseType_t????taskpriority;???????????????????
????TaskHandle_t?*?const?taskhandle;????????????
}FreertosTaskParams_t;
?
2、創(chuàng)建任務(wù)配置表
有了第1步所定義的結(jié)構(gòu)體以后,我們就可以創(chuàng)建一個配置表了,這個配置表就包含了所有的任務(wù)以及初始化這些任務(wù)的所需的參數(shù),例如:
?
FreertosTaskParams_t?Task_Parameters_conf[]?=?
{
????{(Function_t)Task_1,?"Task_1",TASK_1_STACK_DEPTH,?&Telemetry,?TASK_1_PRIORITY,?NULL},?
????{(Function_t)Task_2,?"Task_2",TASK_2_STACK_DEPTH,?NULL??????,?TASK_2_PRIORITY,?NULL},?
????{(Function_t)Task_3,?"Task_3",TASK_3_STACK_DEPTH,?&Telemetry,?TASK_3_PRIORITY,?NULL},?
????{(Function_t)Task_4,?"Task_4",TASK_4_STACK_DEPTH,?&Telemetry,?TASK_4_PRIORITY,?NULL},?
????{(Function_t)Task_5,?"Task_5",TASK_5_STACK_DEPTH,?&Telemetry,?TASK_5_PRIORITY,?NULL},?
????{(Function_t)Task_6,?"Task_6",TASK_6_STACK_DEPTH,?&Telemetry,?TASK_6_PRIORITY,?NULL},?
};
?
這個表里有很多參數(shù)我們還沒有進行宏定義。這些都是我們將在應(yīng)用程序中定義的用于初始化任務(wù)的參數(shù)。例如,每個任務(wù)的優(yōu)先級可能都不一樣,這里用一個宏,例如TASK_1_PRIORITY來進行表示。
3、創(chuàng)建初始化循環(huán)
創(chuàng)建任務(wù)配置表以后,初始化任務(wù)只用一個for循環(huán)就好了,然后將結(jié)構(gòu)體數(shù)組里的各個參數(shù)分別對應(yīng)到RTOS創(chuàng)建任務(wù)的API里就可以了。例如,我們可以使用以下循環(huán)初始化任務(wù):
?
#define?NR(x)?(sizeof(x)/sizeof(x[0])) for(uint8_t?count?=?0;?count??
這里要注意的是,我們將(void)放在xTaskCreate前面,其實這樣是表示我們在創(chuàng)建任務(wù)的時候忽略了xTaskCreate這個函數(shù)的的返回值。正常情況下,我們當(dāng)前希望檢查函數(shù)的返回值,這樣可以增加整個程序的健壯性,但在這種情況下,我們將在初始化期間創(chuàng)建所有任務(wù),并且不會出現(xiàn)任何內(nèi)存問題。但是,我們可以依靠freerTOS malloc失敗的鉤子函數(shù)來捕獲開發(fā)過程中的任何動態(tài)內(nèi)存分配問題。或者,我們可以檢查返回值,然后創(chuàng)建一個函數(shù),這個函數(shù)在出現(xiàn)問題時進行檢查和恢復(fù)。
4、結(jié)論
這種簡單的RTOS初始化的設(shè)計模式是可擴展的,可重用的,并且能夠很容易進行修改。這是嵌入式軟件工程師如何利用設(shè)計模式的一個很好的例子。這種設(shè)計模式可以與任何RTOS一起使用。
審核編輯:湯梓紅
電子發(fā)燒友App












評論