串口作为单片机最基本的外设之一,在单片机中的应用也是非常广泛。
本文讲述如何使用数据结构的顺序队列来做为数据帧的缓存器,可适用于一般的串口通信协议中。
首先我们需要构造一个串口数据帧格式缓存类型:
//数据帧格式缓存类型 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);//处理从列队中读取出来的数据 } }
我们如何确定列队的长度呢?
调试过程中,用上位机串口调试软件模拟串口发送数据帧给单片机,我们在入队之前先查询一下列队是否为空并加上打印,如果列队不为空,则说明列队的长度还不过,这时就加大长度,直到不打印为止,在这时的基础上把长度再加上这时长度的一半长。
原文:https://www.cnblogs.com/jank/p/12824029.html