嵌入式经常用到串口,如何判断串口数据接收完成?

news2025/2/20 9:31:03

说起通信,首先想到的肯定是串口,日常中232和485的使用比比皆是,数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。
空闲中断断帧
一些mcu(如:stm32f103)在出厂时就已经在串口中封装好了一种中断——空闲帧中断,用户可以通过获取该中断标志位来判断数据是否接收完成,中断标志在中断服务函数中获取,使用起来相对简单。
在这里插入图片描述
例程中,当接收完成标志 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_Spacing10)/RS485_BAUDRATEu32PclkValue);//根据帧间隔计算超时时间
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_BUFF_LENGTH)//帧长度
    {
        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;
        }
    }
}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2298518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从图像中提取的每行数字作为一张完整的图片,而不是每个数字单独成为一张图片

具体实现思路&#xff1a; 提取行区域&#xff1a;先通过轮廓或空白区域分割出每行数字。确保每行是一个整体&#xff1a;在提取每行时&#xff0c;确保提取区域的宽度包含该行所有的数字&#xff08;即避免单独分割每个数字&#xff09;。保存每一行作为一张图片&#xff1a;…

文心一言4月起全面免费,6月底开源新模型:AI竞争进入新阶段?

名人说&#xff1a;莫听穿林打叶声&#xff0c;何妨吟啸且徐行。—— 苏轼 Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、文心一言免费化的背后&#xff1a;AI成本与应用的双重驱动1️⃣成本下降&#xff0c;推动文心一言普及2…

基于斜坡单元的机器学习模型预测滑坡易发性,考虑条件因素的异质性

&#xff11;、引用 Chang Z, Catani F, Huang F, et al. Landslide susceptibility prediction using slope unit-based machine learning models considering the heterogeneity of conditioning factors[J]. Journal of Rock Mechanics and Geotechnical Engineering, 2023…

面向对象程序设计-实验七

6-1 计算捐款总量 这里需要设计一个捐款人类Donator及一个相关函数getMaxName( )&#xff0c;Donator类中包含捐款人的姓名及其捐款额 代码清单&#xff1a; #include <iostream> using namespace std; class Donator { private: string name; float money; //单位&…

Java面试宝典:说下Spring Bean的生命周期?

Java面试宝典专栏范围&#xff1a;JAVA基础&#xff0c;面向对象编程&#xff08;OOP&#xff09;&#xff0c;异常处理&#xff0c;集合框架&#xff0c;Java I/O&#xff0c;多线程编程&#xff0c;设计模式&#xff0c;网络编程&#xff0c;框架和工具等全方位面试题详解 每…

early bird inject

基本原理 本质是利用windows系统的apc机制&#xff0c;以及涉及到windows进程启动的流程. 因为线程初始化阶段LdrInitializeThunk函数会调用NtTestAlert函数,这个函数执行后,所有apc队列中的例程都会执行.因此我们在主线程初始化之前向主线程的apc队列中加入恶意代码即可实现…

uvm错误记录4

如下所示&#xff0c;奇怪的是penable莫名其妙的出X。可问题&#xff0c;我发送激励了。 仔细定位发现&#xff0c;39行用的是vif中的penable, 问题是都是赋值&#xff0c;却出现同时赋值多次&#xff0c;这是因为nonblocking和blocking同时触发导致的&#xff0c;因此&#xf…

3dtiles——Cesium ion for Autodesk Revit Add-In插件

一、说明&#xff1a; Cesium已经支持3dtiles的模型格式转换&#xff1b; 可以从Cesium官方Aesset中上传gltf等格式文件转换为3dtiles&#xff1b; 也可以下载插件&#xff08;例如revit-cesium插件&#xff09;转换并自动上传到Cesium官方Aseet中。 Revit转3dtiles插件使用…

QT 异步编程之多线程

一、概述 1、在进行桌面应用程序开发的时候&#xff0c;假设应用程序在某些情况下需要处理比较复制的逻辑&#xff0c;如果只有一个线程去处理&#xff0c;就会导致窗口卡顿&#xff0c;无法处理用户的相关操作。这种情况下就需要使用多线程&#xff0c;其中一个线程处理窗口事…

Proxmox 更新软件包数据库(TASK ERROR: command ‘apt-get update‘ failed: exit code 100)

1、连接自己报错的物理机Shell&#xff0c;编辑文件 vi /etc/apt/sources.list.d/pve-enterprise.list 2、注释文件的第一行在开头加上# 按I进入编辑模式后 开头添加# 然后shift&#xff1a; 输入wq或者wq&#xff01;进行保存 3、注释后执行两个命令apt-get update 和 apt…

JVM——垃圾回收算法

目录 垃圾回收算法 评价标准&#xff1a; 标记-清除算法&#xff1a; 复制算法&#xff1a; 标记-整理算法&#xff1a; 分代GC&#xff1a; arthas查看分代之后的内存情况&#xff1a; 垃圾回收算法 java是如何实现垃圾回收的呢&#xff1f;简单来说&#xff0c;垃圾回…

服务器安全——日志分析和扫描

如何通过访问日志查询被攻击 扫描攻击 攻击日志 GET /index?sindex/%5Cthink%5CModule/Action/Param/$%7Bphpinfo()%7D HTTP/1.1", host: "主机", referrer: "主机sindex/\think\Module/Action/Param/${phpinfo()}" 攻击日志文件 .error.log sql注…

ubuntu 22.04 安装vsftpd服务

先决条件&#xff0c;确保你已经配置好了存储库。 安装vsftpd 为了方便实验&#xff0c;我已经切换到了root用户。 rootlocal:~# apt-get install vsftpd修改配置 配置文件在 /etc/vsftpd.conf rootlocal:~# grep -vE ^#|^$ /etc/vsftpd.conf listenNO listen_ipv6YES anonymou…

STM32F407通过FSMC扩展外部SRAM和NAND FLASH

1 扩展外部SRAM 1.1 地址情况 FSMC控制器的存储区分为4个区(Bank)&#xff0c;每个区256MB。其中&#xff0c;Bank1可以用于连接SRAM、NOR FLASH、PSRAM&#xff0c;还可以连接TFT LCD。Bank1的地址范围是0x60000000&#xff5e;0x6FFFFFFF。Bank1又分为4个子区&#xff0c;每…

AndroidStudio查看Sqlite和SharedPreference

1.查看Sqlite 使用App Inspection&#xff0c;这是个好东西 打开方式&#xff1a;View → Tool Windows → App Inspection 界面如图&#xff1a; App inspection不但可以看Sqlite还可以抓包network和background task连抓包工具都省了。 非常好使 2.查看sharedPreference 使…

Elasticsearch:15 年来致力于索引一切,找到重要内容

作者&#xff1a;来自 Elastic Shay Banon 及 Philipp Krenn Elasticsearch 刚刚 15 岁了&#xff01;回顾过去 15 年的索引和搜索&#xff0c;并展望未来 15 年的相关内容。 Elasticsearch 刚刚成立 15 周年。一切始于 2010 年 2 月的一篇公告博客文章&#xff08;带有标志性的…

信呼OA办公系统sql注入漏洞分析

漏洞描述 信呼OA办公系统uploadAction存在SQL注入漏洞&#xff0c;攻击者可利用该漏洞获取数据库敏感信息。 环境搭建 源码下载地址&#xff1a;https://github.com/rainrocka/xinhu 下载后解压到本地网站根目录下&#xff0c;配置好数据库&#xff0c;然后安装即可 默认密…

机器学习算法 - 随机森林之决策树初探(1)

随机森林是基于集体智慧的一个机器学习算法&#xff0c;也是目前最好的机器学习算法之一。 随机森林实际是一堆决策树的组合&#xff08;正如其名&#xff0c;树多了就是森林了&#xff09;。在用于分类一个新变量时&#xff0c;相关的检测数据提交给构建好的每个分类树。每个…

原生Three.js 和 Cesium.js 案例 。 智慧城市 数字孪生常用功能列表

对于大多数的开发者来言&#xff0c;看了很多文档可能遇见不到什么有用的&#xff0c;就算有用从文档上看&#xff0c;把代码复制到自己的本地大多数也是不能用的&#xff0c;非常浪费时间和学习成本&#xff0c; 尤其是three.js &#xff0c; cesium.js 这种难度较高&#xff…

在 PyCharm 中接入deepseek的API的各种方法

在 PyCharm 中接入 DeepSeek 的 API&#xff0c;通常需要以下步骤&#xff1a; 1. 获取 DeepSeek API 密钥 首先&#xff0c;确保你已经在 DeepSeek 平台上注册并获取了 API 密钥&#xff08;API Key&#xff09;。如果没有&#xff0c;请访问 DeepSeek 的官方网站注册并申请 …