? 隨著寫代碼功力的提升,個(gè)人對于代碼的整潔、優(yōu)雅、可維護(hù)、易拓展等就有了一定的要求,雖然自己曾經(jīng)就屬于那種全局變量滿天飛,想到哪里寫到哪里的嵌入式軟件工程師;但是這一切在現(xiàn)在來說必須要結(jié)束了!要想做一個(gè)好的項(xiàng)目,我們時(shí)刻都要去想它的框架如何設(shè)計(jì),如何去兼容未來的拓展,以便我們構(gòu)建一個(gè)優(yōu)雅、整潔、易維護(hù)、易拓展的程序,少出問題,少加班,拿高薪;因此,我們必須在代碼的設(shè)計(jì)上利用編程語言的特性來下一些功夫。
在之前,我就經(jīng)常發(fā)現(xiàn)很多工程師在寫RTOS代碼的時(shí)候存在如下問題:
隨意定義任務(wù)的位置,隨意初始化任務(wù)代碼。
由于任務(wù)函數(shù)初始化參數(shù)過多,當(dāng)同時(shí)創(chuàng)建多個(gè)任務(wù)時(shí),任務(wù)初始化函數(shù)寫得非常長,非常難看。
例如我之前寫的這個(gè)RT-Thread的項(xiàng)目:
碼云倉庫:
?
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è)看起來還算舒服一點(diǎn),至少它的位置是比較統(tǒng)一的,而且任務(wù)并不算很多;但是如果任務(wù)更多,這個(gè)代碼看起來就會(huì)很長,比如我找來的下面這個(gè)代碼,具體就不說是哪位小伙伴寫的了:
?
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è)問題可以使用一種簡單的、可擴(kuò)展的RTOS初始化設(shè)計(jì)模式,這個(gè)設(shè)計(jì)模式的原則就是創(chuàng)建一個(gè)通用的初始化函數(shù),然后這個(gè)函數(shù)可以遍歷RTOS初始化配置表來初始化所有的任務(wù),讓我們來看看如何創(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)先級可能都不一樣,這里用一個(gè)宏,例如TASK_1_PRIORITY來進(jìn)行表示。
3、創(chuàng)建初始化循環(huán)
創(chuàng)建任務(wù)配置表以后,初始化任務(wù)只用一個(gè)for循環(huán)就好了,然后將結(jié)構(gòu)體數(shù)組里的各個(gè)參數(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前面,其實(shí)這樣是表示我們在創(chuàng)建任務(wù)的時(shí)候忽略了xTaskCreate這個(gè)函數(shù)的的返回值。正常情況下,我們當(dāng)前希望檢查函數(shù)的返回值,這樣可以增加整個(gè)程序的健壯性,但在這種情況下,我們將在初始化期間創(chuàng)建所有任務(wù),并且不會(huì)出現(xiàn)任何內(nèi)存問題。但是,我們可以依靠freerTOS malloc失敗的鉤子函數(shù)來捕獲開發(fā)過程中的任何動(dòng)態(tài)內(nèi)存分配問題?;蛘?,我們可以檢查返回值,然后創(chuàng)建一個(gè)函數(shù),這個(gè)函數(shù)在出現(xiàn)問題時(shí)進(jìn)行檢查和恢復(fù)。
4、結(jié)論
這種簡單的RTOS初始化的設(shè)計(jì)模式是可擴(kuò)展的,可重用的,并且能夠很容易進(jìn)行修改。這是嵌入式軟件工程師如何利用設(shè)計(jì)模式的一個(gè)很好的例子。這種設(shè)計(jì)模式可以與任何RTOS一起使用。
審核編輯:湯梓紅
評論
查看更多