一种简洁、可拓展的RTOS任务初始化设计

随着写代码功力的提升,个人对于代码的整洁、优雅、可维护、易拓展等就有了一定的要求,虽然自己曾经就属于那种全局变量满天飞,想到哪里写到哪里的嵌入式软件工程师;但是这一切在现在来说必须要结束了!要想做一个好的项目,我们时刻都要去想它的框架如何设计,如何去兼容未来的拓展,以便我们构建一个优雅、整洁、易维护、易拓展的程序,少出问题,少加班,拿高薪;因此,我们必须在代码的设计上利用编程语言的特性来下一些功夫。
在之前,我就经常发现很多工程师在写rtos代码的时候存在如下问题:
随意定义任务的位置,随意初始化任务代码。
由于任务函数初始化参数过多,当同时创建多个任务时,任务初始化函数写得非常长,非常难看。
例如我之前写的这个rt-thread的项目:
码云仓库:
git clone https://gitee.com/morixinguan/personal-open-source-project.git  
部分代码如下:
/***************按键处理任务*************/#define key_task_priority      3#define key_task_size         2000static rt_thread_t key_task_thread = rt_null;static void start_key_task(void *parameter);/***************按键处理任务*************//***************传感器任务处理*************/#define sensor_priority           4#define sensor_task_size            2048rt_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           2048static 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 < nr(task_parameters_conf); count++){    (void)xtaskcreate(task_parameters_conf[taskcount].taskptr,                      task_parameters_conf[taskcount].taskname,                      task_parameters_conf[taskcount].stackdepth,                      task_parameters_conf[taskcount].parametersptr,                      task_parameters_conf[taskcount].taskpriority,                       task_parameters_conf[taskcount].taskhandle);}  
这里要注意的是,我们将(void)放在xtaskcreate前面,其实这样是表示我们在创建任务的时候忽略了xtaskcreate这个函数的的返回值。正常情况下,我们当前希望检查函数的返回值,这样可以增加整个程序的健壮性,但在这种情况下,我们将在初始化期间创建所有任务,并且不会出现任何内存问题。但是,我们可以依靠freertos malloc失败的钩子函数来捕获开发过程中的任何动态内存分配问题。或者,我们可以检查返回值,然后创建一个函数,这个函数在出现问题时进行检查和恢复。
4、结论
这种简单的rtos初始化的设计模式是可扩展的,可重用的,并且能够很容易进行修改。这是嵌入式软件工程师如何利用设计模式的一个很好的例子。这种设计模式可以与任何rtos一起使用。


怎样选择教学投影幕?
软通动力亮相华为伙伴暨开发者大会
目前数字隔离器领域的大趋势
什么样的Verilog代码风格是好的风格?
高德红外与国网湖北检修公司推动主网数字化转型”战略合作协议签署仪式
一种简洁、可拓展的RTOS任务初始化设计
iphone13运行内存会更新吗
骨传导耳机是什么?科普骨传导耳机究竟是智商税还是运动佳品
汽车芯片新闻 芯片短缺问题
ABB PLC-AC500控制系统及其在污水处理中的应用
商业的本质是企业要创造独特的价值
地平线余凯亚布力谈AI:人工智能时代,让计算从云端走向终端
亚马逊CPU销售排行榜曝光,AMD竞争激烈,TOP5中除了第一的Core i7-8700K其他都是AMD的
需要了解linux设备驱动中的阻塞与非阻塞等问题
怎么解决HMI-Board在调试4bitSDHI挂载文件系统失败的问题呢?
采用LM3409P沟道MosFET设计的调光控制LED驱动器电路
松下PTZ一体化摄像机的创新应用
齿轮减速电机的单相和双相之间,有什么区别?
33600A安捷伦波形发生器自检失败,报错,CH1电平幅度超差,过载维修
三星2023年战略:AI与性能质量并重