在单片机中使用的RTOS代码框架

描述

最近在进行一个项目的开发和调试,使用的是单片机 + freeRTOS进行开发,通过一段时间的码代码和调试,各个方面都已经调通,功能也顺利的实现,也在挂机测试了。

在这次开发中,也是遇到了很多的问题,主要的感想是关于代码的框架。在单片机开发中,特别是使用了RTOS的时候,一个良好的代码框架真的是相当的必要的。

如果一开始没有仔细的考虑好该怎么搭载一个代码框架,写代码时想写什么就写什么,有什么功能要加找个地方就随便插入进入,当代码量大的时候就会看起来很乱。甚至将来接手代码的人,估计内心一万个CNM从心中飞过,时刻游走在崩溃的边缘,即使是想改点什么功能也不知道从哪里开始着手,估计会烦躁到喜提地中海!!!

本文就想分享一个我个人使用的单片机+freeRTOS的代码框架,框架涉及到消息接收、消息处理、消息发送、其他动作的处理。下面一步步说明代码框架的搭建过程。

  1. 创建任务

当开始一个项目代码的编写之前,都要考虑这份代码要实现一些什么样的功能,并将要实现的功能进行分类,根据功能的各自属性可以归纳出几个Module,然后想想在代码中哪些功能要放在一块,哪些功能要区分开等等的细节问题。

并且还需要考虑代码的耦合性,好的代码是要能够做到高内聚低耦合的,各个功能模块之间能够独立区分开,需要产生联系的功能代码,要通过某些通信手段实现(共享内存、信号量、消息队列等等),不要互相拉扯,像你中有我,我中有你这种情况要尽可能的避免。

比如,我手上的项目通过功能归类划分,就可以分为接收消息、处理消息、发送消息、其他功能处理,由此便可以考虑划分出4个线程去处理。

但是,考虑到项目中使用的是CAN通信的方式,接收消息就可以考虑使用CAN接收中断的方式,能够做到及时的响应接收消息,所以这个时候只需要3个线程即可。并且消息的接收使用队列的方式接收,方便管理消息和进行线程之间的同步。消息的发送也采用先压入队列再发送的方式。

freeRTOS中创建3个线程如下:

#define OTHER_HANDLE_TASK_PRIO         2
#define OTHER_HANDLE_STK_SIZE          256 
TaskHandle_t OtherHandleTask_Handler;


#define CAN_HANDLE_MSG_TASK_PRIO     3 
#define CAN_HANDLE_MSG_STK_SIZE      256  
TaskHandle_t Can_HandleMsgTask_Handler;


#define CAN_SEND_MSG_TASK_PRIO         2
#define CAN_SEND_MSG_STK_SIZE          256  
TaskHandle_t Can_SendMsgTask_Handler;




    // 其他功能的管理线程
    xTaskCreate((TaskFunction_t )OtherHandle_Task,
                (const char *   )"OtherHandle_Task",
                (uint16_t       )OTHER_HANDLE_STK_SIZE,
                (void *         )NULL,
                (UBaseType_t    )OTHER_HANDLE_TASK_PRIO,
                (TaskHandle_t * )&OtherHandleTask_Handler);




    // 接收消息的处理线程
   xTaskCreate((TaskFunction_t )Can_HandleMsg_Task,
               (const char *   )"Can_HandleMsg_Task",
               (uint16_t       )CAN_HANDLE_MSG_STK_SIZE,
               (void *         )NULL,
               (UBaseType_t    )CAN_HANDLE_MSG_TASK_PRIO,
               (TaskHandle_t * )&Can_HandleMsgTask_Handler);

    // 发送消息的处理线程
   xTaskCreate((TaskFunction_t )Can_SendMsg_Task,
               (const char *   )"Can_SendMsg_Task",
               (uint16_t       )CAN_SEND_MSG_STK_SIZE,
               (void *         )NULL,
               (UBaseType_t    )CAN_SEND_MSG_TASK_PRIO,
               (TaskHandle_t * )&Can_SendMsgTask_Handler);

消息接收队列、消息发送队列的创建,如下:

// 消息接收队列
QueueHandle_t CanRxQueue;
CanRxQueue = xQueueCreate(xxxxxx, xxxxxx);




// 消息发送队列
QueueHandle_t CanTxQueue;
CanTxQueue = xQueueCreate(xxxxxx, xxxxxx);
  1. CAN中断接收消息 & 消息处理线程

2.1、CAN中断接收消息如下:

void CAN1_RX0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken;

    /* 其他代码 */

    xResult = xQueueSendFromISR(CanRxQueue, &ptwCanRxMsg, &xHigherPriorityTaskWoken);




    portYIELD_FROM_ISR (xHigherPriorityTaskWoken);
}

注意:接收很多时候不一定要使用中断的方式,用查询的方式也是可以的,只是在RTOS中,查询接收的话,要考虑消息接收是否及时,接收消息的线程优先级要比较高,否则容易造成处理的动作的延迟。

2.2、消息的处理线程

消息处理的线程任务函数如下:

void Can_HandleMsg_Task(void *pvParameters)
{
    while (1)
    {
        xQueueReceive(CanRxQueue, xxxxxx, portMAX_DELAY);
        /*
            处理部分
        */
     }
}

消息处理中使用了消息队列的阻塞的特性,在队列为空的时候阻塞挂起线程,可以减少CPU调度线程的压力;当消息队列不为空的时候,队列不再阻塞,线程从挂起中恢复,参与调度并处理任务。

注意:在freeRTOS中可以用于阻塞的还有信号量、事件标志组、消息邮箱。

  1. 消息的发送线程

消息的发送如下:

void Can_SendMsg_Task(void *pvParameters)
{
    while (1)
    {
        xQueueReceive(CanTxQueue, xxxxxx, portMAX_DELAY);
        /*
            处理部分
        */
     }
}

消息的发送中也使用了消息队列,需要发送的消息可以先压入队列,然后由发送线程去发送。同样使用队列的阻塞特性,在队列为空的时候阻塞挂起发送线程,减少CPU调度线程的压力;当发送消息的队列不为空的时候,队列不再阻塞,线程从挂起中恢复,参与调度并将消息发送出去。

  1. 其他功能的处理线程
void OtherHandle_Task(void *pvParameters)
{
    while (1)
    {
          /*
            处理部分
          */
     }
}

其他功能的处理就放在其他任务线程中处理,比如GUI显示、按键扫描、和传感器通信等等的。具体需要几个线程管理需要根据实际的项目情况进行安排。另外各个线程的优先级也要根据情况进行安排,确保重要的功能部分能被及时的执行到!

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分