? 隨著寫代碼功力的提升,個人對于代碼的整潔、優雅、可維護、易拓展等就有了一定的要求,雖然自己曾經就屬于那種全局變量滿天飛,想到哪里寫到哪里的嵌入式軟件工程師;但是這一切在現在來說必須要結束了!要想做一個好的項目,我們時刻都要去想它的框架如何設計,如何去兼容未來的拓展,以便我們構建一個優雅、整潔、易維護、易拓展的程序,少出問題,少加班,拿高薪;因此,我們必須在代碼的設計上利用編程語言的特性來下一些功夫。
在之前,我就經常發現很多工程師在寫RTOS代碼的時候存在如下問題:
隨意定義任務的位置,隨意初始化任務代碼。
由于任務函數初始化參數過多,當同時創建多個任務時,任務初始化函數寫得非常長,非常難看。
例如我之前寫的這個RT-Thread的項目:
碼云倉庫:
?
git?clone?https://gitee.com/morixinguan/personal-open-source-project.git
?
部分代碼如下:
?
/***************按鍵處理任務*************/ #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); /***************按鍵處理任務*************/ /***************傳感器任務處理*************/ #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; /*狀態欄更新線程入口函數?*/ static?void?StartSensor_Task(void?*parameter); /***************傳感器任務處理*************/ /***************控制任務處理*************/ #define?CONTROL_PRIORITY???????????5 #define?CONTROL_TASK_SIZE???????????2048 static?rt_thread_t?control_task_thread?=?RT_NULL; /*控制任務更新線程入口函數?*/ static?void?StartControl_Task(void?*parameter); /***************控制任務處理*************/ ......省略..... /*啟動其它任務*/ void?start_other_rt_thread(void) { ????/*1、創建按鍵線程*/ ????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、創建控制線程*/ ????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(); ????//關指示燈 ????HAL_GPIO_WritePin(BOARD_LED_GPIO_Port,?BOARD_LED_Pin,?GPIO_PIN_RESET); }
?
其實這個看起來還算舒服一點,至少它的位置是比較統一的,而且任務并不算很多;但是如果任務更多,這個代碼看起來就會很長,比如我找來的下面這個代碼,具體就不說是哪位小伙伴寫的了:
?
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();?????????/*?初始化數據存儲模塊?*/ #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初始化設計模式,這個設計模式的原則就是創建一個通用的初始化函數,然后這個函數可以遍歷RTOS初始化配置表來初始化所有的任務,讓我們來看看如何創建這樣的設計模式。
1、創建任務初始化結構
第一步是檢查 RTOS 的任務創建函數,并查看初始化任務所需的參數。任務初始化結構只是一個包含初始化任務所需的所有參數的結構。但是不同的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、創建任務配置表
有了第1步所定義的結構體以后,我們就可以創建一個配置表了,這個配置表就包含了所有的任務以及初始化這些任務的所需的參數,例如:
?
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},? };
?
這個表里有很多參數我們還沒有進行宏定義。這些都是我們將在應用程序中定義的用于初始化任務的參數。例如,每個任務的優先級可能都不一樣,這里用一個宏,例如TASK_1_PRIORITY來進行表示。
3、創建初始化循環
創建任務配置表以后,初始化任務只用一個for循環就好了,然后將結構體數組里的各個參數分別對應到RTOS創建任務的API里就可以了。例如,我們可以使用以下循環初始化任務:
?
#define?NR(x)?(sizeof(x)/sizeof(x[0])) for(uint8_t?count?=?0;?count??
這里要注意的是,我們將(void)放在xTaskCreate前面,其實這樣是表示我們在創建任務的時候忽略了xTaskCreate這個函數的的返回值。正常情況下,我們當前希望檢查函數的返回值,這樣可以增加整個程序的健壯性,但在這種情況下,我們將在初始化期間創建所有任務,并且不會出現任何內存問題。但是,我們可以依靠freerTOS malloc失敗的鉤子函數來捕獲開發過程中的任何動態內存分配問題。或者,我們可以檢查返回值,然后創建一個函數,這個函數在出現問題時進行檢查和恢復。
4、結論
這種簡單的RTOS初始化的設計模式是可擴展的,可重用的,并且能夠很容易進行修改。這是嵌入式軟件工程師如何利用設計模式的一個很好的例子。這種設計模式可以與任何RTOS一起使用。
審核編輯:湯梓紅
評論
查看更多