使用列队做为串口数据帧缓存器的实现

串口作为单片机最基本的外设之一,在单片机中的应用也是非常广泛。

本文讲述如何使用数据结构的顺序队列来做为数据帧的缓存器,可适用于一般的串口通信协议中。

首先我们需要构造一个串口数据帧格式缓存类型:

//数据帧格式缓存类型
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);//处理从列队中读取出来的数据
    }
}

我们如何确定列队的长度呢?

调试过程中,用上位机串口调试软件模拟串口发送数据帧给单片机,我们在入队之前先查询一下列队是否为空并加上打印,如果列队不为空,则说明列队的长度还不过,这时就加大长度,直到不打印为止,在这时的基础上把长度再加上这时长度的一半长。

猜你喜欢

转载自www.cnblogs.com/jank/p/12824029.html