嵌入式|串口接收数据的几种处理方式!

嵌入式大杂烩 2023-04-03 21:46

来源:https://blog.csdn.net/qq_56527127/article


说起通信,首先想到的肯定是串口,日常中232和485的使用比比皆是,数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。

空闲中断断帧

一些mcu(如:stm32f103)在出厂时就已经在串口中封装好了一种中断——空闲帧中断,用户可以通过获取该中断标志位来判断数据是否接收完成,终端标志在中断服务函数中获取,使用起来相对简单。

STM32串口空闲中断接收不定长数据(DMA方式)

void UART4_IRQHandler(void)
{
    uint8_t data = 0;
    data = data;
    if(USART_GetITStatus(LoraUSARTx, USART_IT_RXNE) == SET)
    {
        USART_ClearITPendingBit(LoraUSARTx, USART_IT_RXNE);
        if(Lora_RecvData.Rx_over == 0)
            Lora_RecvData.RxBuf[Lora_RecvData.Rx_count++] = LoraUSARTx->DR;
    }
    if(USART_GetITStatus(LoraUSARTx, USART_IT_IDLE) == SET)
    {
        data = LoraUSARTx->SR;
        data = LoraUSARTx->DR;
        
        Lora_RecvData.Rx_over = 1//接收完成
    }
}

例程中,当接收完成标志Lora_RecvData.Rx_over为1时,就可以获取uart4接收到的一帧数据,该数据存放在Lora_RecvData.RxBuf中。

超时断帧

空闲帧中断的使用固然方便,但是并不是每个mcu都有这种中断存在(只有个别高端mcu才有),那么这个时候就可以考虑使用超时断帧了。

Modbus协议中规定一帧数据的结束标志为3.5个字符时长,那么同样的可以把这种断帧方式类比到串口的接收上,这种方法需要搭配定时器使用,其作用原理就是:串口进一次接收中断,就打开定时器超时中断,同时装载值清零(具体的装载值可以自行定义),只要触发了定时器的超时中断,说明在用户规定的时间间隔内串口接收中断里没有新的数据进来,可以认为数据接收完成。

uint16_t Time3_CntValue = 0;//计数器初值
 
/*******************************************************************************
 * TIM3中断服务函数
 ******************************************************************************/

void Tim3_IRQHandler(void)
{
    if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))
    {
        Tim3_M0_Stop();    //关闭定时器3
        Uart0_Rec_Count = 0;//接收计数清零
        Uart0_Rec_Flag = 1//接收完成标志
        Tim3_ClearIntFlag(Tim3UevIrq); //清除定时器中断
    }
}
 
void Time3_Init(uint16_t Frame_Spacing)
{
    uint16_t u16ArrValue;//自动重载值
    uint32_t u32PclkValue;//PCLK频率
    
    stc_tim3_mode0_cfg_t     stcTim3BaseCfg;
    
        //结构体初始化清零
    DDL_ZERO_STRUCT(stcTim3BaseCfg);
    
    Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外设时钟使能
    
    stcTim3BaseCfg.enWorkMode = Tim3WorkMode0;              //定时器模式
    stcTim3BaseCfg.enCT       = Tim3Timer;                  //定时器功能,计数时钟为内部PCLK
    stcTim3BaseCfg.enPRS      = Tim3PCLKDiv1;               //不分频
    stcTim3BaseCfg.enCntMode  = Tim316bitArrMode;           //自动重载16位计数器/定时器
    stcTim3BaseCfg.bEnTog     = FALSE;
    stcTim3BaseCfg.bEnGate    = FALSE;
    stcTim3BaseCfg.enGateP    = Tim3GatePositive;
    
    Tim3_Mode0_Init(&stcTim3BaseCfg);                       //TIM3 的模式0功能初始化
        
    u32PclkValue = Sysctrl_GetPClkFreq();          //获取Pclk的值
   //u16ArrValue = 65535-(u32PclkValue/1000);        //1ms测试
    u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing*10)/RS485_BAUDRATE*u32PclkValue);//根据帧间隔计算超时时间
    Time3_CntValue = u16ArrValue;              //计数初值
    Tim3_M0_ARRSet(u16ArrValue);                            //设置重载值
    Tim3_M0_Cnt16Set(u16ArrValue);                         //设置计数初值
    
    Tim3_ClearIntFlag(Tim3UevIrq);                          //清中断标志
    Tim3_Mode0_EnableIrq();                                 //使能TIM3中断(模式0时只有一个中断)
    EnableNvic(TIM3_IRQn, IrqLevel3, TRUE);                 //TIM3 开中断  
}
 
 
/**************************此处省略串口初始化部分************************/
//串口0中断服务函数
void Uart0_IRQHandler(void)
{
    uint8_t rec_data=0;
    
    if(Uart_GetStatus(M0P_UART0, UartRC))         
    {
        Uart_ClrStatus(M0P_UART0, UartRC);        
        rec_data = Uart_ReceiveData(M0P_UART0);     
        if(Uart0_Rec_Count//帧长度
        {
            Uart0_Rec_Buffer[Uart0_Rec_Count++] = rec_data;        
        }
        Tim3_M0_Cnt16Set(Time3_CntValue);//设置计数初值 
        Tim3_M0_Run();   //开启定时器3 超时即认为一帧接收完成
    }
}

例程所用的是华大的hc32l130系列mcu,其它类型的mcu也可以参考这种写法。其中超时时间的计算尤其要注意数据类型的问题,u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing * 10)/RS485_BAUDRATE * u32PclkValue);其中Frame_Spacing为用户设置的字符个数,uart模式为一个“1+8+1”共10bits。

状态机断帧

状态机,状态机,又是状态机,没办法!谁让它使用起来方便呢?其实这种方法我用的也不多,但是状态机的思想还是要有的,很多逻辑用状态机梳理起来会更加的清晰。

相对于超时断帧,状态机断帧的方法节约了一个定时器资源,一般的mcu外设资源是足够的,但是做一些资源冗余也未尝不是一件好事,万一呢?对吧。

嵌入式软件架构设计-状态机

//状态机断帧
void UART_IRQHandler(void)  //作为485的接收中断
{
    uint8_t count = 0;
    unsigned char lRecDat = 0
 
    if(/*触发接收中断标志*/)  
    {
        //清中断状态位
        rec_timeout = 5;
        if((count == 0)) //接收数据头,长度可以自定义
        {
            RUart0485_DataC[count++] = /*串口接收到的数据*/;
            gRecStartFlag = 1;
            return;
        }
        if(gRecStartFlag == 1)
        {
            RUart0485_DataC[count++] = /*串口接收到的数据*/;
        
            if(count > MAXLEN) //一帧数据接收完成
            {
                count=0;
                gRecStartFlag = 0;
                
                if(RUart0485_DataC[MAXLEN]==CRC16(RUart0485_DataC,MAXLEN))
                {
                    memcpy(&gRecFinshData,RUart0485_DataC,13);
                    gRcvFlag = 1//接收完成标志位
                    
                }
            }   
        }
        return
    }
    return ;
}

这种做法适合用在一直有数据接收的场合,每次接收完一帧有效数据后就把数据放到缓冲区中去解析,同时还不影响下一帧数据的接收。整个接收状态分为两个状态——接收数据头和接收数据块,如果一帧数据存在多个部分的话还可以在此基础上再增加几种状态,这样不仅可以提高数据接收的实时性,还能够随时看到数据接收到哪一部分,还是比较实用的。

"状态机+FIFO"断帧

记得刚毕业面试的时候,面试官还问过我一个问题:如果串口有大量数据要接收,同时又没有空闲帧中断你会怎么做?

没错,就是FIFO(当时并没有回答上来,因为没用过),说白了就是开辟一个缓冲区,每次接收到的数据都放到这个缓冲区里,同时记录数据在缓冲区中的位置,当数据到达要求的长度的时候再把数据取出来,然后放到状态机中去解析。当然FIFO的使用场合有很多,很多数据处理都可以用FIFO去做,有兴趣的可以多去了解一下。

/********************串口初始化省略,华大mcu hc32l130******************/
void Uart1_IRQHandler(void)
{
    uint8_t data;
    if(Uart_GetStatus(M0P_UART1, UartRC))         //UART0数据接收
    {
        Uart_ClrStatus(M0P_UART1, UartRC);        //清中断状态位
        data = Uart_ReceiveData(M0P_UART1);   //接收数据字节
        comFIFO(&data,1);
    }
 
}
 
/******************************FIFO*******************************/
volatile uint8_t     fifodata[FIFOLEN],fifoempty,fifofull;
volatile uint8_t     uart_datatemp=0;
 
uint8_t comFIFO(uint8_t *data,uint8_t cmd)
{
    static uint8_t rpos=0;  //当前写的位置 position 0--99 
    static uint8_t wpos=0//当前读的位置
 
    if(cmd==0//写数据
    {
        if(fifoempty!=0)       //1 表示有数据 不为空,0表示空
        {
            *data=fifodata[rpos];
            fifofull=0;
            rpos++;
            if(rpos==FIFOLEN) 
                rpos=0;
            if(rpos==wpos) 
                fifoempty=0;
            return 0x01;
        } 
        else
            return 0x00;
 
    } 
    else if(cmd==1//读数据
    {
        if(fifofull==0)
        {
            fifodata[wpos]=*data;
            fifoempty=1;
            wpos++;
            if(wpos==FIFOLEN) 
                wpos=0;
            if(wpos==rpos) 
                fifofull=1;
            return 0x01;
        } else
            return 0x00;
    }
    return 0x02;
}
 
/********************************状态机处理*******************************/
void LoopFor485ReadCom(void)
{
    uint8_t data;
 
    while(comFIFO(&data,0)==0x01)
    {
        if(rEadFlag==SAVE_HEADER_STATUS) //读取头
        {
            if(data==Header_H)
            {
                buffread[0]=data;
                continue;
            }
            if(data==Header_L)
            {
                buffread[1]=data;
                if(buffread[0]==Header_H)
                {
                    rEadFlag=SAVE_DATA_STATUS;
                }
            } 
            else
            {
                memset(buffread,0,Length_Data);
            }
        } 
        else if(rEadFlag==SAVE_DATA_STATUS)  //读取数据
        {
            buffread[i485+2]=data;
            i485++;
            if(i485==(Length_Data-2)) //数据帧除去头
            {
                unsigned short crc16=CRC16_MODBUS(buffread,Length_Data-2);
                if((buffread[Length_Data-2]==(crc16>>8))&&(buffread[Length_Data-1]==(crc16&0xff)))
                {
                    rEadFlag=SAVE_OVER_STATUS;
                    memcpy(&cmddata,buffread,Length_Data);  //拷贝Length_Struct个字节,完整的结构体
                } 
                else
                {
                    rEadFlag=SAVE_HEADER_STATUS;
                }
        
                memset(buffread,0,Length_Data);
                i485=0;
                break;
            }
        }
    }
}

好了,就这些吧,如果有没有注意到的地方,欢迎各位看官给出宝贵意见,相互学习,共同进步!

本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。


注意

由于微信公众号近期改变了推送规则,为了防止找不到,可以星标置顶,这样每次推送的文章才会出现在您的订阅列表里。

猜你喜欢:

分享一种日志滚动覆盖的方法

bug解决不了?使用日志法

关于嵌入式系统日志打印的一点建议

分享一份嵌入式软件工具清单!

易懂 | 手把手教你编写你的第一个上位机

实用 | 10分钟教你搭建一个嵌入式web服务器

嵌入式常用通信传输协议动图,收藏!

适用于嵌入式的差分升级通用库!

分享一种灵活性很高的协议格式(附代码例子)

分享几个实用的代码片段(第二弹)


在公众号聊天界面回复1024,可获取嵌入式资源;回复 ,可查看文章汇总

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论 (0)
  • 故障现象一辆2016款奔驰C200L车,搭载274 920发动机,累计行驶里程约为13万km。该车组合仪表上的防侧滑故障灯、转向助力故障灯、安全气囊故障灯等偶尔异常点亮,且此时将挡位置于R挡,中控显示屏提示“后视摄像头不可用”,无法显示倒车影像。 故障诊断用故障检测仪检测,发现多个控制单元中均存储有通信类故障代码(图1),其中故障代码“U015587 与仪表盘的通信存在故障。信息缺失”出现的频次较高。 图1 存储的故障代码1而组合仪表中存储有故障代码“U006488 与用户界
    虹科Pico汽车示波器 2025-04-23 11:22 101浏览
  •   航空兵训练与战术对抗仿真平台系统解析   北京华盛恒辉航空兵训练与战术对抗仿真平台系统是现代军事训练的关键工具,借助计算机技术构建虚拟战场,支持多兵种协同作战模拟,为军事决策、训练及装备研发提供科学依据。   应用案例   目前,已有多个航空兵训练与战术对抗仿真平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润航空兵训练与战术对抗仿真平台。这些成功案例为航空兵训练与战术对抗仿真平台的推广和应用提供了有力支持。   一、系统架构与核心功能   系统由模拟器、计算机兵力生
    华盛恒辉l58ll334744 2025-04-24 16:34 87浏览
  •   高海拔区域勤务与装备保障调度系统平台解析   北京华盛恒辉高海拔区域勤务与装备保障调度系统平台专为高海拔特殊地理环境打造,致力于攻克装备适应、人员健康保障、物资运输及应急响应等难题。以下从核心功能、技术特点、应用场景及发展趋势展开全面解读。   应用案例   目前,已有多个高海拔区域勤务与装备保障调度系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润高海拔区域勤务与装备保障调度系统。这些成功案例为高海拔区域勤务与装备保障调度系统的推广和应用提供了有力支持。   一、核心
    华盛恒辉l58ll334744 2025-04-24 10:13 88浏览
  •   电磁频谱数据综合管理平台系统解析   一、系统定义与目标   北京华盛恒辉电磁频谱数据综合管理平台融合无线传感器、软件定义电台等前沿技术,是实现无线电频谱资源全流程管理的复杂系统。其核心目标包括:优化频谱资源配置,满足多元通信需求;运用动态管理与频谱共享技术,提升资源利用效率;强化频谱安全监管,杜绝非法占用与干扰;为电子战提供频谱监测分析支持,辅助作战决策。   应用案例   目前,已有多个电磁频谱数据综合管理平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁频谱数
    华盛恒辉l58ll334744 2025-04-23 16:27 190浏览
  •   无人机结构仿真与部件拆解分析系统平台解析   北京华盛恒辉无人机结构仿真与部件拆解分析系统无人机技术快速发展的当下,结构仿真与部件拆解分析系统平台成为无人机研发测试的核心工具,在优化设计、提升性能、降低成本等方面发挥关键作用。以下从功能、架构、应用、优势及趋势展开解析。   应用案例   目前,已有多个无人机结构仿真与部件拆解分析系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润无人机结构仿真与部件拆解分析系统。这些成功案例为无人机结构仿真与部件拆解分析系统的推广和应用提
    华盛恒辉l58ll334744 2025-04-23 15:00 201浏览
  •   后勤实验仿真系统平台深度解析   北京华盛恒辉后勤实验仿真系统平台依托计算机仿真技术,是对后勤保障全流程进行模拟、分析与优化的综合性工具。通过搭建虚拟场景,模拟资源调配、物资运输等环节,为后勤决策提供数据支撑,广泛应用于军事、应急管理等领域。   应用案例   目前,已有多个后勤实验仿真系统平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润后勤实验仿真系统平台。这些成功案例为后勤实验仿真系统平台的推广和应用提供了有力支持。   一、核心功能   (一)后勤资源模拟
    华盛恒辉l58ll334744 2025-04-23 15:39 165浏览
  •   有效样本分析决策系统平台全面解析   一、引言   北京华盛恒辉有效样本分析决策系统在当今数据驱动的时代,企业、科研机构等面临着海量数据的处理与分析挑战。有效样本分析决策系统平台应运而生,它通过对样本数据的精准分析,为决策提供有力支持,成为提升决策质量和效率的关键工具。   应用案例   目前,已有多个有效样本分析决策系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润有效样本分析决策系统。这些成功案例为有效样本分析决策系统的推广和应用提供了有力支持。   二、平台概述
    华盛恒辉l58ll334744 2025-04-24 11:13 89浏览
  •   陆地装备体系论证与评估综合平台系统解析   北京华盛恒辉陆地装备体系论证与评估综合平台系统是契合现代军事需求而生的专业系统,借助科学化、智能化手段,实现对陆地装备体系的全方位论证与评估,为军事决策和装备发展提供关键支撑。以下从功能、技术、应用及展望展开分析。   应用案例   目前,已有多个陆地装备体系论证与评估综合平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润陆地装备体系论证与评估综合平台。这些成功案例为陆地装备体系论证与评估综合平台的推广和应用提供了有力支持。
    华盛恒辉l58ll334744 2025-04-24 10:53 100浏览
  • 前言本文主要演示基于TL3576-MiniEVM评估板HDMI OUT、DP 1.4和MIPI的多屏同显、异显方案,适用开发环境如下。Windows开发环境:Windows 7 64bit、Windows 10 64bitLinux开发环境:VMware16.2.5、Ubuntu22.04.5 64bitU-Boot:U-Boot-2017.09Kernel:Linux-6.1.115LinuxSDK:LinuxSDK-[版本号](基于rk3576_linux6.1_release_v
    Tronlong 2025-04-23 13:59 134浏览
  •   陆地边防事件紧急处置系统平台解析   北京华盛恒辉陆地边防事件紧急处置系统平台是整合监测、预警、指挥等功能的智能化综合系统,致力于增强边防安全管控能力,快速响应各类突发事件。以下从系统架构、核心功能、技术支撑、应用场景及发展趋势展开全面解读。   应用案例   目前,已有多个陆地边防事件紧急处置系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润陆地边防事件紧急处置系统。这些成功案例为陆地边防事件紧急处置系统的推广和应用提供了有力支持。   一、系统架构   感知层:部
    华盛恒辉l58ll334744 2025-04-23 11:22 126浏览
  •   海上训练与保障调度指挥平台系统解析   北京华盛恒辉海上训练与保障调度指挥平台系统是现代海上作战训练的核心枢纽,融合信息技术、GIS、大数据及 AI 等前沿技术,旨在实现海上训练高效组织、作战保障科学决策。以下从架构功能、应用场景、系统优势及发展挑战展开解读。   应用案例   目前,已有多个海上训练与保障调度指挥平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润海上训练与保障调度指挥平台。这些成功案例为海上训练与保障调度指挥平台的推广和应用提供了有力支持。   一
    华盛恒辉l58ll334744 2025-04-24 15:26 76浏览
  •   通用装备论证与评估系统平台解析   北京华盛恒辉通用装备论证与评估系统平台是服务军事装备全生命周期管理的综合性信息化平台,通过科学化、系统化手段,实现装备需求论证、效能分析等核心功能,提升装备建设效益。   应用案例   目前,已有多个通用装备论证与评估系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润通用装备论证与评估系统。这些成功案例为通用装备论证与评估系统的推广和应用提供了有力支持。   一、系统分层架构   (一)数据层   整合装备性能、作战、试验等多源异
    华盛恒辉l58ll334744 2025-04-24 16:14 79浏览
我要评论
0
4
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦