控制/MCU
最近在进行一个项目的开发和调试,使用的是单片机 + 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);
2、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中可以用于阻塞的还有信号量、事件标志组、消息邮箱。
3、消息的发送线程
消息的发送如下:
void Can_SendMsg_Task(void *pvParameters)
{
while (1)
{
xQueueReceive(CanTxQueue, xxxxxx, portMAX_DELAY);
/*
处理部分
*/
}
}
消息的发送中也使用了消息队列,需要发送的消息可以先压入队列,然后由发送线程去发送。同样使用队列的阻塞特性,在队列为空的时候阻塞挂起发送线程,减少CPU调度线程的压力;当发送消息的队列不为空的时候,队列不再阻塞,线程从挂起中恢复,参与调度并将消息发送出去。
4、 其他功能的处理线程
void OtherHandle_Task(void *pvParameters)
{
while (1)
{
/*
处理部分
*/
}
}
其他功能的处理就放在其他任务线程中处理,比如GUI显示、按键扫描、和传感器通信等等的。具体需要几个线程管理需要根据实际的项目情况进行安排。另外各个线程的优先级也要根据情况进行安排,确保重要的功能部分能被及时的执行到!
全部0条评论
快来发表一下你的评论吧 !