使用列队做为串口数据帧缓存器的实现
串口作为单片机最基本的外设之一,在单片机中的应用也是非常广泛。
本文讲述如何使用数据结构的顺序队列来做为数据帧的缓存器,可适用于一般的串口通信协议中。
首先我们需要构造一个串口数据帧格式缓存类型:
//数据帧格式缓存类型
typedef struct _UART_RecData
{
unsigned char UART_RecBuff[REC_MAX_LEN]; //接收缓存
unsigned int UART_Count; //接收计数器
unsigned char UART_StartFlag; //开始接收标志
unsigned char UART_EndFlag; //结束标志
} UART_RecData, *pUART_RecData;
并定义一个缓存变量:
UART_RecData UartRecData;//串口接收的数据帧
考虑到有时候不能及时处理接收到的数据帧,这时又有一帧新的数据发送过来而破环了前一帧未处理完的数据。所以我们需要用一个缓存数据帧的结构,我们可以使用环形列队来作为数据帧的缓存器。我们构造一个数据帧的缓存列队数据类型:这个列队的数据元素类型就是上面定义的接收数据帧类型,还包含一个列队头尾指针(这里是数组下标):
//串口接收缓存列队
typedef struct _UARTx_BufQueue
{
UART_RecData UART_RecBuf[UART_BUF_QUEUE_MAX];
unsigned char front;
unsigned char rear;
}UARTx_BufQueue,*pUARTx_BufQueue;
其中的UART_BUF_QUEUE_MAX为列队的长度,列队的长度根据实际情况而定,我们可以做一个小小的测试来确定列队的长度,下面会讲到。
然后定义缓存队列:
UARTx_BufQueue RecQueue;//定义一个列队,用来做串口数据的缓存
顺序列队相关函数:
/************************************************************************************************************
* C语言现实顺序列队数据结构
* start
***********************************************************************************************************/
/************************************************************************************************************
*函数功能说明:初始化队列操作
*函数参数说明:
* @pQ:指向队列的指针
*函数返回值:无
*其他说明:无
***********************************************************************************************************/
static unsigned char QueueInit(pUARTx_BufQueue pQ)
{
pQ->front = 0;
pQ->rear = 0;
return TRUE;
}
/************************************************************************************************************
*函数功能说明:入队操作
*函数参数说明:
* @pQ:指向队列的指针
* @e:需要入队的元素
*函数返回值:列队已满返回FALSE,否则返回TRUE
*其他说明:无
***********************************************************************************************************/
static unsigned char QueueEn(pUARTx_BufQueue pQ, const UART_RecData e)
{
unsigned char temp = pQ->rear;
// 判断队列是否已满
if (LOOP_FRONT_COUNTER(temp,UART_BUF_QUEUE_MAX) == pQ->front)
return FALSE;
pQ->UART_RecBuf[pQ->rear] = e; // 将元素e赋值给队尾
pQ->rear = LOOP_FRONT_COUNTER(pQ->rear,UART_BUF_QUEUE_MAX); // rear指针向后移一位置,若到最后则转到数组头部
return TRUE;
}
/************************************************************************************************************
*函数功能说明:出队操作
*函数参数说明:
* @pQ:指向队列的指针
* @e:需要出队的元素
*函数返回值:列队为空返回FALSE,否则返回TRUE
*其他说明:无
***********************************************************************************************************/
static unsigned char QueueDe(pUARTx_BufQueue pQ, pUART_RecData pE)
{
// 判断是否为空队
if (pQ->front == pQ->rear)
return FALSE;
*pE = pQ->UART_RecBuf[pQ->front]; // 将队头元素赋值给pE
pQ->front = LOOP_FRONT_COUNTER(pQ->front,UART_BUF_QUEUE_MAX); // front指针向后移一位置,若到最后则转到数组头部
return TRUE;
}
/************************************************************************************************************
*函数功能说明:判断是否为空队列
*函数参数说明:
* @pQ:指向队列的指针
*函数返回值:列队为空返回TRUE,否则返回FALSE
*其他说明:无
***********************************************************************************************************/
static unsigned char QueueIsEmpty(pUARTx_BufQueue pQ)
{
return pQ->front == pQ->rear ? TRUE : FALSE;
}
/************************************************************************************************************
* C语言现实顺序列队数据结构
* end
***********************************************************************************************************/
其中的两个宏,用于控制循环自加:
#define LOOP_FRONT_COUNTER(src,max_num) (++(src) % (max_num))
#define LOOP_REAR_COUNTER(src,max_num) ((src)++ % (max_num))
在串口接收处理中我们还需要一个计时器,用于超时处理,于是我们构造一个计时类型:
typedef struct _TimeOut
{
unsigned long InitTime;
unsigned char StarFlag;
}TimeOut;
当我们接收到一帧完整的数据后,就将其放入缓存列队中,具体接收及入队代码如下:
void UART_RecHander(char RecData)
{
if (TRUE == UartRecData.UART_StartFlag)//开始接收
{
if ((msAPI_Timer_DiffTimeFromNow(UartRecCount.InitTime) >= SECOND(1))||
(UartRecData.UART_Count >= REC_MAX_LEN))
{
UartRecData.UART_StartFlag = FALSE;
UartRecData.UART_Count = 0;
return;
}
UartRecData.UART_RecBuff[UartRecData.UART_Count++] = RecData;
if (TRUE == UartRecData.UART_EndFlag)
{
if (RecData == XorSum(UartRecData.UART_RecBuff,UartRecData.UART_Count-1))//检验校验和
{
QueueEn(&RecQueue, UartRecData);//数据入队
}
UartRecData.UART_StartFlag = FALSE;
UartRecData.UART_Count = 0;
return;
}
if (END_2 == RecData)
{
if (END_1 == UartRecData.UART_RecBuff[UartRecData.UART_Count-2])
{
UartRecData.UART_EndFlag = TRUE;
}
}
}
else
{
if (HEAD_1 == RecData)//判断头码1
{
UartRecData.UART_Count = 0;
UartRecData.UART_RecBuff[UartRecData.UART_Count++] = RecData;
}
else if (HEAD_2 == RecData)//判断头码2
{
if ((1 == UartRecData.UART_Count) &&
(HEAD_1 == UartRecData.UART_RecBuff[0]))
{
UartRecData.UART_RecBuff[UartRecData.UART_Count++] = RecData;
UartRecData.UART_StartFlag = TRUE;
UartRecData.UART_EndFlag = FALSE;
UartRecCount.InitTime = msAPI_Timer_GetTime0();
}
}
else
{
UartRecData.UART_Count = 0;
}
}
}
此函数应该放在串口接收的中断服务程序或中断服务回调函数中。
接收完一帧数据并存入列队后,我们需要对数据帧进行解析,所以在系统轮询中我们会一直查询列队是否为空:
void UART_InquireQueue(void)
{
UART_RecData RecData;
if (QueueIsEmpty(&RecQueue) != TRUE)//判断列队是否为空
{
QueueDe(&RecQueue, &RecData);//出队
UART_DataAnalysis(RecData);//处理从列队中读取出来的数据
}
}
我们如何确定列队的长度呢?
调试过程中,用上位机串口调试软件模拟串口发送数据帧给单片机,我们在入队之前先查询一下列队是否为空并加上打印,如果列队不为空,则说明列队的长度还不过,这时就加大长度,直到不打印为止,在这时的基础上把长度再加上这时长度的一半长。