目录
前言
有深入理解RTOS原理,或阅读过RTOS源码的同学应该知道:RTOS实现任务间通信通常是由一系列指针进行操作实现的。任务间通信的“有效数据”,其实也是由指针指向一个“变量”或“数组”实现的。
信号量
信号量,本质是传递一个“事件”。比如:任务A完成发送数据,通过信号量通知任务B。
队列
队列和信号量原理类似有点类似,只是这里是“变量”。比如:串口接收完成一帧数据,通过队列发送给任务B。
消息邮箱
本质上邮箱是信号量的拓展,相当于把一个指针定义的变量从一个任务传递到另一个或多个任务中去,这个指针是先发到邮箱,然后等待任务从邮箱里提取指针,这也就传递了指针指向的具体变量值。
全局变量有什么问题?
抢占问题
两个或多个任务,都要去“使用”同一个全局变量,如果不添加任何“互斥”措施,必定会存在抢占的问题。
代码规范问题
整个项目只有少数几个全局变量没什么问题,如果是整个项目有几十个,甚至几百个全局变量,你觉得这样的代码,后面好维护吗?经过多次迭代,代码只会越来越难理解,越来越难阅读。
耦合性问题
全局变量会导致分层不合理与模块化编程相违背,你的全局变量没有归属,既不是任务A,也不是模块A,最终可能“任人宰割”导致“夭折”。
安全性问题
有一句话怎么说的呢,全局变量是项目的“罪魁祸首”,项目做大之后,一旦有小修改,可能就会引发大Bug。
全局变量还有很多弊端,这里就不一一描述了,总之一点:慎用全局变量。
为什么推荐在裸机开发中,也尽量避免过多的使用全局变量?------考虑到程序的可读性和可移植性。假如你的项目需要从裸机移植到RTOS,这时就需要考虑原来工程中的全局变量是不是存在抢占问题,原来的逻辑可能要做相应的修改。如果一开始就避免了全局变量的使用,那么在后续的移植过程中只需要把现有的接口切换为RTOS的接口即可,其它逻辑不需要改变。
解决方案
上边已经对消息邮箱做了简单的说明,那么如何实现一个自己的邮箱呢?自定义消息邮箱实现需要配合队列使用(请参考这里: 队列驱动)。
/******************my_mq.c**************/
#define __MQ_C
#include "mq.h"
void mq_init(msgqueue *mq, void *msg_pool, uint16_t msg_size, uint16_t max_msgs)
{
mq->msg_pool = msg_pool;
mq->msg_size = msg_size;
mq->max_msgs = max_msgs;
BufferInit(&mq->msg_rb, mq->msg_size, mq->msg_pool, mq->max_msgs);
}
int mq_send(msgqueue *mq, void *in, uint32_t size)
{
if(size > mq->msg_size)
{
return -1;
}
BufferWrite(&mq->msg_rb, in, size);
return 0;
}
int8_t mq_recv(msgqueue *mq, void *out, uint32_t size)
{
if(BufferNumToRead(&mq->msg_rb))
{
BufferRead(&mq->msg_rb, out, size > mq->msg_size ? mq->msg_size : size);
return 0;
}
return -1;
}
/******************my_mq.c**************/
#ifndef __MQ_H
#define __MQ_H
#include "CircleBuffer.h"
#ifdef __MQ_C
#define MQ_EXT
#else
#define MQ_EXT extern
#endif
typedef struct
{
uint16_t type;
uint16_t value;
}sys_msg_t;
typedef struct
{
void *msg_pool;
uint32_t msg_size;
uint32_t max_msgs;
tCircleBuffer msg_rb;
}msgqueue;
MQ_EXT msgqueue mq_msg;
#define MSG_SIZE sizeof(sys_msg_t)
#define MAX_MSGS 20
MQ_EXT uint8_t msg_buf[MSG_SIZE * MAX_MSGS];
typedef enum
{
MSG_NULL = 0x00, /*have no message*/
MSG_LPUART1_RX, //USART1
MSG_USART1_RX, //USART1
MSG_USART2_RX, //USART2
}MSG_TYPE;
void mq_init(msgqueue *mq, void *msg_pool, uint16_t msg_size, uint16_t max_msgs);
int mq_send(msgqueue *mq, void *in, uint32_t size);
int8_t mq_recv(msgqueue *mq, void *out, uint32_t size);
#endif
例程(以串口为例)
/**********************usart.c****************/
//低功耗串口1中断函数
void LPUART1_IRQHandler(void)
{
sys_msg_t send_msg;
if(LL_USART_IsActiveFlag_IDLE(LPUART1)) //接收空闲中断
{
LL_USART_ClearFlag_IDLE(LPUART1);
send_msg.value = USARTx_Recvice_Data(LPUART1, &lpuart1_ringbuf.rx);
if(send_msg.value > 0)
{
send_msg.type = MSG_LPUART1_RX;
mq_send(&mq_msg, &send_msg, sizeof(send_msg));
}
}
}
//串口1中断函数
void USART1_IRQHandler(void)
{
sys_msg_t send_msg;
if(LL_USART_IsActiveFlag_IDLE(USART1)) //接收空闲中断
{
LL_USART_ClearFlag_IDLE(USART1);
send_msg.value = USARTx_Recvice_Data(USART1, &usart1_ringbuf.rx);
if(send_msg.value > 0)
{
send_msg.type = MSG_USART1_RX;
mq_send(&mq_msg, &send_msg, sizeof(send_msg));
}
}
}
//串口2中断函数
void USART2_IRQHandler(void)
{
sys_msg_t send_msg;
if(LL_USART_IsActiveFlag_IDLE(USART2)) //接收空闲中断
{
LL_USART_ClearFlag_IDLE(USART2);
send_msg.value = USARTx_Recvice_Data(USART2, &usart2_ringbuf.rx);
if(send_msg.value > 0)
{
send_msg.type = MSG_USART2_RX;
mq_send(&mq_msg, &send_msg, sizeof(send_msg));
}
}
}
/*****************main.c**************/
void main(void)
{
mq_init(&mq_msg, msg_buf, MSG_SIZE, MAX_MSGS);
sys_msg_t recv_msg;
while(1)
{
if(mq_recv(&mq_msg, &recv_msg, sizeof(recv_msg)) == 0)
{
switch(recv_msg.type)
{
case MSG_LPUART1_RX:
lpusart1_pro();
break;
case MSG_USART1_RX:
usart1_pro();
break;
case MSG_USART2_RX:
usart2_pro();
break;
default:
break;
}
}
}
}
当然,这里只是以串口接收处理为例,事实上,该邮箱机制可以在任意地方使用,具体使用方法各位可以自行摸索,这里就不再赘述了。