美文网首页
单片机串口数据处理框架

单片机串口数据处理框架

作者: 查无此人的查无此人 | 来源:发表于2021-01-25 15:50 被阅读0次

    1、背景

        串口通信具有广泛的应用,一方面串口配置简单,仅需3根线(tx、rx、gnd)即可实现通信,另一方面串口具备全双工通信的能力。因此串口开发是单片机开发中一个重要的能力。
        串口通信的难点在于,每条通信命令的长度可能不一致,何时判断数据包是否接收完整,每包数据如何校验,在单片机开发中均占用很大的工作量。

    2、串口数据接收

        由于单片机往往同时对接多个串口通信,可以将所有的通信统一处理,收到一包数据后再通知相应的线程进行处理。

    typedef struct {
        TaskHandle_t *handle;
        uint16_t *writeptr;
        uint16_t *readptr;
        uint8_t ctrl;
        uint8_t discart;
        uint8_t timeout;
    }UART_CTRL;
    

        writeptrreadptr分别记录串口缓冲区内数据写入和读取的指针(标号)。一般将缓冲区构建为环形缓冲,*writeptr==*readptr认为缓冲区空,*writeptr==*readptr + 1认为缓冲区满。ctrl字段用来控制是否开始计时数据接收超时,在超时时间内没接收到一个字节的数据,重新累计数据包超时时间,timeout字段则是具体的超时时间。discart字段用来丢弃不完整的数据包,如果数据包在规定的时间内均没有收到完整数据,则将该数据包丢弃。
    系统初始化时,对每个字段进行赋值:

    UartCtrl[UART_VIDEO].timeout = 0;
    UartCtrl[UART_VIDEO].handle = &VideoRecvTaskHandle;
    UartCtrl[UART_VIDEO].writeptr = (uint16_t *)&VideoRxWritePtr;
    UartCtrl[UART_VIDEO].readptr = (uint16_t *)&VideoRxReadPtr;
    UartCtrl[UART_VIDEO].ctrl = 0;
    UartCtrl[UART_VIDEO].discart = 0xff;
    

        串口数据的接收既可以采用中断的方式,也可以采用DMA的方式。尽管中断方式会影响CPU使用效率,但从实际使用效果上来看,一般不是时间要求非常高的应用,中断的影响非常小。

    void UART0_2_IRQHandler(void)
    {
        if(Uart_GetStatus(M0P_UART2, UartRC)) {
            Uart_ClrStatus(M0P_UART2, UartRC);
            VideoRxBuf[*(UartCtrl[UART_VIDEO].writeptr)] = Uart_ReceiveData(M0P_UART2);
            *(UartCtrl[UART_VIDEO].writeptr)= (*(UartCtrl[UART_VIDEO].writeptr)+ 1) % sizeof(VideoRxBuf)
            UartCtrl[UART_VIDEO].timeout = 0;
            UartCtrl[UART_VIDEO].ctrl = 1;
        }
    }
    

        每收到一包数据,将timeout置零,并将ctrl置一,开始计算数据包是否超时。由于缓冲区是环形的,UartCtrl[UART_VIDEO].writeptr字段增加时需要注意对边界的处理。
        通过定时器中不断检测相应的字段,来决定是否通知线程处理收到的数据包,或者丢弃不完整的数据包。

    for(i = 0;i < (sizeof(UartCtrl) / sizeof(UartCtrl[0]));i++) {
        if(UartCtrl[i].ctrl) {
            UartCtrl[i].timeout++;
            if(UartCtrl[i].timeout > 20) {
                UartCtrl[i].ctrl = 0;
                UartCtrl[i].timeout = 0;
                vTaskNotifyGiveFromISR(*(UartCtrl[i].handle), &xHigherPriorityTaskWoken);
                portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
            }
        }
        if(UartCtrl[i].discart != 0xff) {
            UartCtrl[i].discart++;
            if(UartCtrl[i].discart > 200) {
                UartCtrl[i].discart = 0xff;
                *(UartCtrl[i].readptr) = *(UartCtrl[i].writeptr);
            }
        }
    }
    

    3、串口数据处理

        串口数据包往往具有比较简单数据结构,如包头、包尾、长度、校验等字段,通过对每包数据相应字段的检验,判断数据包是否完整及合法。不同的传感器的通信格式一般都比较类似,因此可以采用以下的流程进行检验。最后既可以将合格的数据包复制到另外的缓存进行处理,也可以在该函数中直接处理以节省空间。

    int8_t processVideoRxData(uint8_t *buf)
    {
        uint8_t WritePtrtemp;
        uint8_t usedlen;
        uint8_t framelen;
        uint8_t i = 0;
        uint16_t framesum = 0;
        
        WritePtrtemp = VideoRxWritePtr;
        usedlen = getUsedLen(VideoRxReadPtr, WritePtrtemp, sizeof(VideoRxBuf));
    
        for(i = 0;i < usedlen;i++) {
            if(VideoRxBuf[VideoRxReadPtr] == 0x7b)
                break;
            VideoRxReadPtr++;
            if(VideoRxReadPtr == sizeof(VideoRxBuf))
                VideoRxReadPtr = 0;
        }
        if(i == usedlen) {
            UartCtrl[UART_VIDEO].discart = 0;
            return -1;
        }
        usedlen = getUsedLen(VideoRxReadPtr, WritePtrtemp, sizeof(VideoRxBuf));
        if(usedlen < 4) {
            printf("video packet small\r\n");
            UartCtrl[UART_VIDEO].discart = 0;
            return -1;
        }
        framelen = getPosDataBig(VideoRxBuf, sizeof(VideoRxBuf), VideoRxReadPtr + 1, GET_BYTE);
        if(framelen > usedlen) {
            printf("video not complete\r\n");
            UartCtrl[UART_VIDEO].discart = 0;
            return -1;
        }
        framesum = getPosDataBig(VideoRxBuf, sizeof(VideoRxBuf), (VideoRxReadPtr + framelen - 1) % sizeof(VideoRxBuf), GET_BYTE);
        if(framesum != videoCheck(VideoRxBuf, VideoRxReadPtr, framelen - 1, sizeof(VideoRxBuf))) {
            VideoRxReadPtr = (VideoRxReadPtr + framelen) % sizeof(VideoRxBuf);
            printf("video sum error\r\n");
            return -1;
        }
    
        getBulkData(buf, VideoRxBuf, VideoRxReadPtr, framelen, sizeof(VideoRxBuf));
        VideoRxReadPtr = (VideoRxReadPtr + framelen) % sizeof(VideoRxBuf);
        usedlen = getUsedLen(VideoRxReadPtr, WritePtrtemp, sizeof(VideoRxBuf));
        if(usedlen > 0) {
            xTaskNotifyGive(VideoRecvTaskHandle);
        }
        return 0;
    }
    

        处理完一包数据以后,若发现剩余长度大于0,认为环形缓存中还有待处理的数据包,重新进入该函数进行处理。

    4、总结

        串口数据的处理在单片机开发中占有很大比重的工作量,通过上述数据结构和相应处理函数,可以将不同的传感器的数据用同样的方式处理,有效提高开发效率。其他总线的数据处理,也可以采用类似的方式进行。

    相关文章

      网友评论

          本文标题:单片机串口数据处理框架

          本文链接:https://www.haomeiwen.com/subject/vczbzktx.html