前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在编写RTOS代码时,如何设计一个简单、优雅、可拓展的任务初始化结构?

在编写RTOS代码时,如何设计一个简单、优雅、可拓展的任务初始化结构?

作者头像
杨源鑫
发布2022-03-24 09:07:50
7330
发布2022-03-24 09:07:50
举报
文章被收录于专栏:嵌入式开发圈嵌入式开发圈

随着写代码功力的提升,个人对于代码的整洁、优雅、可维护、易拓展等就有了一定的要求,虽然自己曾经就属于那种全局变量满天飞,想到哪里写到哪里的嵌入式软件工程师;但是这一切在现在来说必须要结束了!要想做一个好的项目,我们时刻都要去想它的框架如何设计,如何去兼容未来的拓展,以便我们构建一个优雅、整洁、易维护、易拓展的程序,少出问题,少加班,拿高薪;因此,我们必须在代码的设计上利用编程语言的特性来下一些功夫。

在之前,我就经常发现很多工程师在写RTOS代码的时候存在如下问题:

  • 随意定义任务的位置,随意初始化任务代码。
  • 由于任务函数初始化参数过多,当同时创建多个任务时,任务初始化函数写得非常长,非常难看。

例如我之前写的这个RT-Thread的项目:

码云仓库:

代码语言:javascript
复制
git clone https://gitee.com/morixinguan/personal-open-source-project.git

部分代码如下:

代码语言:javascript
复制
/***************按键处理任务*************/
#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);
}

其实这个看起来还算舒服一点,至少它的位置是比较统一的,而且任务并不算很多;但是如果任务更多,这个代码看起来就会很长,比如我找来的下面这个代码,具体就不说是哪位小伙伴写的了:

代码语言:javascript
复制
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为例:

代码语言:javascript
复制
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步所定义的结构体以后,我们就可以创建一个配置表了,这个配置表就包含了所有的任务以及初始化这些任务的所需的参数,例如:

代码语言:javascript
复制
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里就可以了。例如,我们可以使用以下循环初始化任务:

代码语言:javascript
复制
#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一起使用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式应用研究院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、创建任务初始化结构
  • 2、创建任务配置表
  • 3、创建初始化循环
  • 4、结论
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档