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

在之前,我就經(jīng)常發(fā)現(xiàn)很多工程師在寫RTOS代碼的時(shí)候存在如下問題:
隨意定義任務(wù)的位置,隨意初始化任務(wù)代碼。
由于任務(wù)函數(shù)初始化參數(shù)過多,當(dāng)同時(shí)創(chuàng)建多個(gè)任務(wù)時(shí),任務(wù)初始化函數(shù)寫得非常長(zhǎng),非常難看。
例如我之前寫的這個(gè)RT-Thread的項(xiàng)目:
碼云倉(cāng)庫(kù):
?
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ù)處理*************/ ......省略..... /*啟動(dòng)其它任務(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); ????/*?如果獲得線程控制塊,啟動(dòng)這個(gè)線程?*/ ????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); ????/*?如果獲得線程控制塊,啟動(dòng)這個(gè)線程?*/ ????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); }
?
其實(shí)這個(gè)看起來(lái)還算舒服一點(diǎn),至少它的位置是比較統(tǒng)一的,而且任務(wù)并不算很多;但是如果任務(wù)更多,這個(gè)代碼看起來(lái)就會(huì)很長(zhǎng),比如我找來(lái)的下面這個(gè)代碼,具體就不說(shuō)是哪位小伙伴寫的了:
?
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ù)存儲(chǔ)模塊?*/
#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??//時(shí)間片輪度算法??
?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);
????}
}
?
難受嗎?至少我是覺得很難受的!解決這個(gè)問題可以使用一種簡(jiǎn)單的、可擴(kuò)展的RTOS初始化設(shè)計(jì)模式,這個(gè)設(shè)計(jì)模式的原則就是創(chuàng)建一個(gè)通用的初始化函數(shù),然后這個(gè)函數(shù)可以遍歷RTOS初始化配置表來(lái)初始化所有的任務(wù),讓我們來(lái)看看如何創(chuàng)建這樣的設(shè)計(jì)模式。
1、創(chuàng)建任務(wù)初始化結(jié)構(gòu)
第一步是檢查 RTOS 的任務(wù)創(chuàng)建函數(shù),并查看初始化任務(wù)所需的參數(shù)。任務(wù)初始化結(jié)構(gòu)只是一個(gè)包含初始化任務(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)建一個(gè)配置表了,這個(gè)配置表就包含了所有的任務(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},?
};
?
這個(gè)表里有很多參數(shù)我們還沒有進(jìn)行宏定義。這些都是我們將在應(yīng)用程序中定義的用于初始化任務(wù)的參數(shù)。例如,每個(gè)任務(wù)的優(yōu)先級(jí)可能都不一樣,這里用一個(gè)宏,例如TASK_1_PRIORITY來(lái)進(jìn)行表示。
3、創(chuàng)建初始化循環(huán)
創(chuàng)建任務(wù)配置表以后,初始化任務(wù)只用一個(gè)for循環(huán)就好了,然后將結(jié)構(gòu)體數(shù)組里的各個(gè)參數(shù)分別對(duì)應(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前面,其實(shí)這樣是表示我們?cè)趧?chuàng)建任務(wù)的時(shí)候忽略了xTaskCreate這個(gè)函數(shù)的的返回值。正常情況下,我們當(dāng)前希望檢查函數(shù)的返回值,這樣可以增加整個(gè)程序的健壯性,但在這種情況下,我們將在初始化期間創(chuàng)建所有任務(wù),并且不會(huì)出現(xiàn)任何內(nèi)存問題。但是,我們可以依靠freerTOS malloc失敗的鉤子函數(shù)來(lái)捕獲開發(fā)過程中的任何動(dòng)態(tài)內(nèi)存分配問題。或者,我們可以檢查返回值,然后創(chuàng)建一個(gè)函數(shù),這個(gè)函數(shù)在出現(xiàn)問題時(shí)進(jìn)行檢查和恢復(fù)。
4、結(jié)論
這種簡(jiǎn)單的RTOS初始化的設(shè)計(jì)模式是可擴(kuò)展的,可重用的,并且能夠很容易進(jìn)行修改。這是嵌入式軟件工程師如何利用設(shè)計(jì)模式的一個(gè)很好的例子。這種設(shè)計(jì)模式可以與任何RTOS一起使用。
審核編輯:湯梓紅
電子發(fā)燒友App












評(píng)論