当前位置: 首页 > article >正文

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

说起通信,首先想到的肯定是串口,日常中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;} elsereturn 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;} elsereturn 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;}}
}

}

相关文章:

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

说起通信&#xff0c;首先想到的肯定是串口&#xff0c;日常中232和485的使用比比皆是&#xff0c;数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。 空闲中断断帧 一些mcu&#xff08;如&#xff1a;stm32f103&#xff09;在出厂时就已经在…...

面试真题 | B站C++渲染引擎

一、基础与语法 自我介绍 请简要介绍自己的背景、专业技能和工作经验。实习介绍 详细描述你在实习期间参与的项目、职责和成果。二、智能指针相关问题回答 unique_ptr 是如何实现的?它有哪些特点和优势? unique_ptr 是C++11引入的一种智能指针,用于管理动态分配的内存资源…...

系统不是基于UEFI的win11,硬盘格式MBR,我如何更改为GPT模式添加UEFI启动?

我的系统不是基于UEFI的win11&#xff0c;硬盘格式MBR&#xff0c;我如何更改为GPT模式添加UEFI启动&#xff1f; 相当于你的Windows 11系统从MBR转换为GPT&#xff0c;并添加UEFI启动支持&#xff0c;你需要执行以下步骤&#xff1a; 备份数据 首先&#xff0c;强烈建议你备份…...

Vue2/Vue3分别如何使用computed

computed 是 Vue 中用于定义计算属性的功能&#xff0c;它会根据依赖的数据动态计算并缓存结果。Vue 2 和 Vue 3 中的 computed 使用方式有所不同&#xff0c;以下是详细说明&#xff1a; Vue2中的computed 在 Vue 2 中&#xff0c;computed 是通过选项式 API 实现的&#xff…...

操作系统知识速记:实现线程同步的方式

操作系统知识速记&#xff1a;实现线程同步的方式 在当今的多核和多线程世界里&#xff0c;线程同步是确保数据一致性和提高系统性能的关键。 互斥锁&#xff08;Mutex&#xff09; 互斥锁是实现线程安全的基础。它通过确保同一时间只有一个线程能访问共享资源来防止数据竞争。…...

用vue3写一个好看的wiki前端页面

以下是一个使用 Vue 3 Element Plus 实现的 Wiki 风格前端页面示例&#xff0c;包含现代设计、响应式布局和常用功能&#xff1a; <template><div class"wiki-container"><!-- 头部导航 --><el-header class"wiki-header"><d…...

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

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

【Elasticsearch】通过运行时字段在查询阶段动态覆盖索引字段

在 Elasticsearch 中&#xff0c;Override field values at query time是指通过运行时字段&#xff08;runtime fields&#xff09;在查询阶段动态覆盖索引字段的值&#xff0c;而无需修改原始索引数据。这种功能特别适用于以下场景&#xff1a; 1. 动态修改字段值&#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; //单位&…...

如何学习Elasticsearch(ES):从入门到精通的完整指南

如何学习Elasticsearch&#xff08;ES&#xff09;&#xff1a;从入门到精通的完整指南 嘿&#xff0c;小伙伴们&#xff01;如果你对大数据搜索和分析感兴趣&#xff0c;并且想要掌握Elasticsearch这一强大的分布式搜索引擎&#xff0c;那么你来对地方了&#xff01;本文将为…...

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队列中加入恶意代码即可实现…...

Spring Boot 的约定优于配置:简化开发的艺术

# Spring Boot 的约定优于配置&#xff1a;简化开发的艺术 在现代软件开发中&#xff0c;Spring Boot 凭借其“约定优于配置”&#xff08;Convention Over Configuration&#xff0c;简称 CoC&#xff09;的理念&#xff0c;极大地简化了 Spring 应用的开发流程。本文将深入探…...

WSL Ubuntu 安装 CUDA 教程

WSL Ubuntu 安装 CUDA 教程 1. 概述2. 准备工作3. 删除旧的 GPG 密钥4. 安装 CUDA Toolkit4.1 使用 WSL-Ubuntu 包安装&#xff08;推荐&#xff09; 5. 设置环境变量6. 注意事项7. 参考链接8. 总结 1. 概述 随着 WSL 2 的推出&#xff0c;Windows 用户现在可以在 Windows 子系…...

docker运行perplexica

序 本文主要研究一下如何用docker运行perplexica 步骤 git clone git clone https://github.com/ItzCrazyKns/Perplexica.gitapp.dockerfile FROM docker.1ms.run/node:20.18.0-alpineARG NEXT_PUBLIC_WS_URLws://127.0.0.1:3001 ARG NEXT_PUBLIC_API_URLhttp://127.0.0.1…...

15、Python面试题解析:列表推导式-条件推导与嵌套推导

1. 列表推导式简介 列表推导式&#xff08;List Comprehension&#xff09;是 Python 中一种简洁的创建列表的方式。它允许我们通过一行代码生成列表&#xff0c;通常比传统的 for 循环更简洁、更易读。 基本语法 [表达式 for 元素 in 可迭代对象]表达式&#xff1a;对元素的…...

uvm错误记录4

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

正则表达式(Regular expresssion)

正则表达式 匹配单次 . &#xff1a;匹配任意一个字符 [ ] &#xff1a;匹配[ ]里举例的任意一个字符 /d &#xff1a;匹配数字0-9 /D &#xff1a;匹配非数字 /s &#xff1a;匹配空白或tab建 /S &#xff1a;匹配非空白 /w &#xff1a;…...

React 中级教程

1. useState 与 setState 深入理解 import React, { useState } from react;const Counter = () => {const [count, setCount] = useState(0);const increment = () => {setCount(count + 1); // setState 会异步更新};return (<div><p>Count: {count}</…...

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插件使用…...

高级 Conda 使用:环境导出、共享与优化

1. 引言 在 Conda 的基础包管理功能中&#xff0c;我们了解了如何安装、更新和卸载包。但对于开发者来说&#xff0c;如何更好地管理环境、导出环境配置、共享环境&#xff0c;以及如何优化 Conda 的使用效率&#xff0c;才是提高工作效率的关键。本篇博客将进一步深入 Conda …...

函数perror 和全局变量errno

#include <stdio.h> #include <errno.h> #include <fcntl.h>int main() {int fd open("nonexistent_file.txt", O_RDONLY);if (fd -1) {perror("Failed to open file");}return 0; }控制台有如下输出 Failed to open file: No such f…...

微信小程序的制作

制作微信小程序的过程大致可以分为几个步骤&#xff1a;从环境搭建、项目创建&#xff0c;到开发、调试和发布。下面我会为你简要介绍每个步骤。 1. 准备工作 在开始开发微信小程序之前&#xff0c;你需要确保你已经完成了以下几个步骤&#xff1a; 注册微信小程序账号&…...

QT 异步编程之多线程

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

人工智能之数学基础:线性子空间

本文重点 在前面的课程中,我们学习了线性空间,本文我们我们在此基础上学习线性子空间。在应用中,线性子空间的概念被广泛应用于信号处理、机器学习、图像处理等领域。 子空间的性质 子空间是线性空间的一部分,它需要满足下面的性质: 设V是数域F上的线性空间,W是V的一个…...

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…...

Python--常见库与函数

二、Python常见库与函数 2.1 OS库 常用功能&#xff1a;文件/目录操作、路径管理、环境变量。 import os # 路径操作 abs_path os.path.abspath("data.txt") exists os.path.exists(abs_path) # 目录操作 os.mkdir("logs") # 创建目录&#xff08;需处…...

算法——数学建模的十大常用算法

数学建模的十大常用算法在数学建模竞赛和实际问题解决中起着至关重要的作用。以下是这些算法的具体信息、应用场景以及部分算法的C语言代码示例&#xff08;由于篇幅限制&#xff0c;这里只给出部分算法的简要代码或思路&#xff0c;实际应用中可能需要根据具体问题进行调整和扩…...