串口的缓存发送以及缓存接收机制

#创作灵感#

在我们实际使用MCU进行多串口任务分配的时候,我们会碰到这样一种情况,即串口需要短间隔周期性发送数据,且相邻两帧之间需要间隔一段时间,防止连帧。我们常常需要在软件层面对串口的发送和接受做一个缓存的处理方式。

针对发送,我们使用以下策略:

即1、上层应用在发送数据的时候其实是先将数据送入我们自己设定的发送缓存内(一个数组);

2、真正的发送是主循环在判断出缓存内有数据的时候,自行启动的发送;

所以我们的发送函数其实有两个,一个用于上层应用将数据写入缓存,一个用于串口自启动发送过程。串口自启动发送连续字节的时候  我给大家提供了有三种发送方式:堵塞型发送、轮询式发送、中断式发送

对于MCU主频不高的情况,通常跑完一整个while(1)循环 需要的时间相对而言比较长,而串口连续数据的发送是常常要满足一定超时时间范围的,比如你的串口连的是某个可以通信的电机,一帧的完整性需要得到保证,超出这个超时时间,电机可能会识别不出来这一帧指令。当然,现在大部分的MCU主控选用高速晶振的时候 都是可以满足要求的。

堵塞型发送

通常一帧会包含很多字节数据,堵塞型发送其实就是程序在原地等待发送缓冲区空的标志位 或者发送完成标志位 ,一般发送完成肯定是比发送缓冲区空的时间晚。

 u8 i = 0;if (USARTx == USART1) 
{for (; i < len; i++) { if ((USART1->STATR & USART_STATR_TXE)) {  USART1->DATAR = Data[i];}while (!(USART1->STATR & USART_STATR_TXE));  // 检查发送缓冲区空标志}}

我们会使用while循环在这里等待发送缓冲区空 ,当然也会容易导致系统在此堵塞,从而对于其他控制响应很慢,如果你的系统并没有短时间的连续发送帧的要求,响应也没有要求很高,使用这种方式也是可以的。

轮询型发送

轮询的核心思想其实就是利用最外层的while(1)循环,每次都只用if来判断条件是否成立,这样就不会导致系统的堵塞,但是这对你的整个系统大循环一次的时间有要求,现在基本高主频都是可以满足要求的,也不需占用中断资源,是个不错的方法。在使用状态机理论的时候,其实也是在轮询,这样的思想在很多产品中都会出现。

具体的实现代码见上述堵塞型发送,只不过不再有while循环原地等待,同时要记录下当前发送字节的位置,这样下次循环进来可以根据位置直接发掉下个对应的字节数据。

中断型发送

核心思想其实是先发一个字节触发发送完成中断,而后在中断里将下一个需要发送的数据塞入串口的数据寄存器内,等待下一次触发中断,直到缓存内的帧数据全部被发送完,期间如果有多帧,那么需要延后一段时间Ts来避免连帧。

对此我常常会定义这么两个结构体

typedef struct
{u8  length;        //帧长  写入数据的时候需要更新u8  DataBuff[20];   //帧的内容  这里的最大帧长就是20u8  ProcessLoc;    //当前帧处理的字节位置 如果不是一次性发完所有字节 建议都保留使用
}Frame;
struct
{UartFrame FBuff[20];  //帧的内容u8 UsartFrameNum;     //帧的数量 每次写入这个数据就会加一 到20末尾的话回环到0u8 UsartFinishLoc;    //当前处理帧的位置 每次发送完一帧 就加一 同样也需要回环到0u8 UsartBusyFlag : 1;  //帧处理间隔 就设置这个标志位 表示繁忙 20ms以后自动解封u16 UsartTimeNap;     //放在定时器中断内用来累加
}UsartSendStorage;

第一个是帧的结构体,用来保存与单帧有关系的各项数据;

第二个是串口的发送存储,如果有很多串口需要使用,可以单独设置;

下面是关于串口的初始化设置,每种MCU可能都不太一样,但是建议大家使用宏定义来选择当前的工作方式,比较方便。

void UsartInit (void) {USART_InitTypeDef USART_InitStructure = {0};NVIC_InitTypeDef NVIC_InitStructure = {0};USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_Init (USART2, &USART_InitStructure);USART_Init (USART3, &USART_InitStructure);USART_Init (USART1, &USART_InitStructure);#define USART_Interrupt_Mode  // 中断发送模式#ifdef USART_Interrupt_ModeUSART_ITConfig (USART2, USART_IT_RXNE, ENABLE);  // 接收寄存器非空中断USART_ITConfig (USART2, USART_IT_TC, ENABLE);    // 发送寄存空中断USART_ITConfig (USART3, USART_IT_RXNE, ENABLE);  // 接收寄存器非空中断USART_ITConfig (USART3, USART_IT_TC, ENABLE);    // 发送寄存空中断NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;       //响应优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init (&NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init (&NVIC_InitStructure);
#endifUSART_Cmd (USART3, ENABLE);USART_Cmd (USART2, ENABLE);USART_Cmd (USART1, ENABLE);
}

抢占优先级要设置一样,这样两个串口不会互相打断,响应优先级可以设置不一样。

下面是几个函数用于添加帧到发送缓存以及自启动发送过程。 

void UsartSetFrame(USART_TypeDef *USARTx, unsigned char *Data, unsigned char len)
{if(USARTx== USART1){memcpy(UsartSendStoorage.FBuff[UsartSendStoorage.UsartFrameNum].DataBuff,Data,len)UsartSendStoorage.FBuff[UsartSendStoorage.UsartFrameNum].length = len;UsartSendStoorage.UsartFrameNum++;if(UsartSendStoorage.UsartFrameNum >=10)UsartSendStoorage.UsartFrameNum = 0;}
}

注意我们这里能缓存的帧最长只有10帧,超出这个范围以后,num的值就回头覆盖掉第一项了,你可以认为它就是一个会回环的写指针,用来指示当前可以存储帧的第一个位置。

 这里是把我们要发送的帧送到发送缓存里,

void UsartSendFrameTask(USART_TypeDef *USARTx)
{if (USARTx == USART1)                                                                                   {if(UsartSendStorage.UsartFrameNum != UsartSendStorage.UsartFinishLoc && !UsartSendStorage.UsartBusyFlag ) //有数据需要发 且不繁忙#ifdef USART_Interrupt_ModeUSART1->DATAR =  UsartSendStorage.FBuff[UsartSendStorage.UsartFinishLoc];#elseUsartSendData(UsartSendStorage.FBuff[UsartSendStorage.UsartFinishLoc]);UsartSendStorage.UsartFinishLoc++;UsartSendStorage.UsartFrameNum--;  //可选if(UsartSendStorage.UsartFinishLoc>=20)UsartSendStorage.UsartFinishLoc = 0;#endif}    
}

每次发送完一帧,记得需要把finishLoc加一,这样只要加入帧的位置和最后处理完帧的位置是一致的,说明此时就没有帧要发送,相反,如果此时有帧需要发送,num的值和finishLoc的值是不一致的。当然,这个num我的本意是将它作为写入帧的位置,如果你要将它理解为帧的数量,当你处理完一帧的时候,可以将它减一,只用于记录帧的个数,是个标量。这样的话你判断是否有帧的标准就是num的值是不是为0,就不是两者是否相等。

void USART_IRQHandler (void)  
{u8 i;if (USART_GetITStatus (USART2, USART_IT_TC))  //  发送完成标志  表示上一次数据已经发送{if (USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc < USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameLength) {// 当前帧处理字节的位置还没到末尾 继续发送i = USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc;  // 当前需要处理数据的位置USART1->DATAR = USARTST.USART1SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameBuff[i];USARTST.USART2SendBUFF[USARTST.USART1SendFrameFinishLoc].FrameProcessLoc++;}     //else  // 帧处理完毕了  可以调用busyflag 来产生间隔    {USARTST.USART1BusyFlag = 1;USARTST.USART1SendFrameFinishLoc++;          // 这一帧发送完毕if (USARTST.USART1SendFrameFinishLoc >= 10)  // 回环处理USARTST.USART1SendFrameFinishLoc = 0;}}
}

大家可能对结构体内的TimeNap感到疑惑,其实它就是用来进行累加,这个累加函数会被调用在滴答定时器中断内,当busyflag  = 1 的时候可以将TimeNap清0,而后在SendFrameTask()函数内部给一个if判断,当TimeNap >= 200的时候,自动将busyflag = 0,就完成了帧间隔的设置,因为发送帧的时候是需要判断帧是否繁忙的,不繁忙才会启动帧的发送。

以上仅供大家参考使用,如有错误之处,还请多多体谅。

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

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

相关文章

时间交织(TIADC)的失配误差校正处理(以4片1GSPS采样率的12bitADC交织为例讲解)

待写…有空再写&#xff0c;有需要的留言。 存在失配误差的4GSPS交织 校正完成后的4GSPS交织

Linux进程间通信(二)之管道1【匿名管道】

文章目录 管道什么是管道匿名管道用fork来共享管道原理站在文件描述符角度-深度理解管道站在内核角度-管道本质 接口实例代码管道特点管道的4种情况管道读写规则应用场景 管道 什么是管道 管道是Unix中最古老的进程间通信的形式。 我们把从一个进程连接到另一个进程的一个数…

Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析

注&#xff1a;本文为 “Xilinx FPGA | 管脚约束 / 时序约束 / 问题解析” 相关文章合辑。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 Xilinx FPGA 管脚 XDC 约束之&#xff1a;物理约束 FPGA技术实战 于 2020-02-04 17:14:53 发布 说明&#x…

家用服务器 Ubuntu 服务器配置与 Cloudflare Tunnel 部署指南

Ubuntu 服务器配置与 Cloudflare Tunnel 部署指南 本文档总结了我们讨论的所有内容&#xff0c;包括 Ubuntu 服务器配置、硬盘扩容、静态 IP 设置以及 Cloudflare Tunnel 的部署步骤。 目录 硬盘分区与扩容设置静态 IPCloudflare Tunnel 部署SSH 通过 Cloudflare Tunnel常见…

分享5款开源、美观的 WinForm UI 控件库

前言 今天大姚给大家分享5款开源、美观的 WinForm UI 控件库&#xff0c;助力让我们的 WinForm 应用更好看。 WinForm WinForm是一个传统的桌面应用程序框架&#xff0c;它基于 Windows 操作系统的原生控件和窗体。通过简单易用的 API&#xff0c;开发者可以快速构建基于窗体…

PHP盲盒商城系统源码从零搭建部署:专业级开发与优化实践

【导语&#xff1a;技术驱动商业创新】 在2025年社交电商全面升级的浪潮下&#xff0c;基于PHP的盲盒系统凭借其高开发效率与低成本优势&#xff0c;成为中小企业的首选方案。本文将深度拆解盲盒源码从开发到部署的全流程技术细节&#xff0c;涵盖架构设计、性能优化与安全防护…

(33)VTK C++开发示例 ---图片转3D

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 这是 VTK 测试 clipArt.tcl 的改编版本。 提供带有 2D 剪贴画的 jpg 文件&#xff0c;该示例将创建 3D 多边形数据模…

2025东三省B题深圳杯B题数学建模挑战赛数模思路代码文章教学

完整内容请看文章最下面的推广群 已经完成全部问题的代码和建模 一、问题一的模型构建与优化&#xff08;RGB颜色空间转换模型&#xff09; 基础模型&#xff08;线性映射模型&#xff09;/高斯过程回归模型&#xff08;GPR&#xff09;&#xff1a; 针对高清视频源&#xff0…

linux netlink实现用户态和内核态数据交互

1&#xff0c;内核态代码 #include <linux/module.h> #include <linux/netlink.h> #include <net/sock.h> #define NETLINK_TEST 31 struct sock *nl_sk NULL; static void nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; …

LeetCode:DP-多状态问题

简单 面试题 17.16. 按摩师 一个有名的按摩师会收到源源不断的预约请求&#xff0c;每个预约都可以选择接或不接。在每次预约服务之间要有休息时间&#xff0c;因此她不能接受相邻的预约。给定一个预约请求序列&#xff0c;替按摩师找到最优的预约集合&#xff08;总预约时间最…

Spring AOP---面向切面编程由认识到使用

1. AOP AOP(Aspect-Oriented Programming), 是一种思想, 面向切面编程。 在前文统一异常处理&#xff0c;统一结果返回就是使用了这一思想&#xff08;都是在集中处理某一类事情, 但又不影响原有代码的正常运行&#xff09;&#xff0c;但他们不是AOP&#xff0c;只是应用了这…

专题二十四:虚拟专用网络

一、VPN简介 VPN&#xff08;Virtual Personal Network&#xff09;即虚拟专用网&#xff0c;泛指通过VPN技术在公用网络上构建的虚拟专用网络。VPN用户在此虚拟网络中传输私网流量&#xff0c;在不改变网络现状的情况下实现安全、可靠的连接。其主要功能是在公用网络上建立专…

Milvus(12):分析器

1 分析器概述 在文本处理中&#xff0c;分析器是将原始文本转换为结构化可搜索格式的关键组件。每个分析器通常由两个核心部件组成&#xff1a;标记器和过滤器。它们共同将输入文本转换为标记&#xff0c;完善这些标记&#xff0c;并为高效索引和检索做好准备。 在 Milvus 中&a…

Power Query精通指南1:查询结构设计、数据类型、数据导入与迁移(平面文件、Excel、Web)

文章目录 零、Power Query简介0.1 Power Query 主要功能0.2 Power Query 的优势0.3 Power Query 组件 一、Power Query数据处理基本流程1.1 前期准备1.2 提取1.3 转换1.3.1 Power Query 编辑器界面1.3.2 默认转换1.3.3 自定义转换 1.4 加载1.4.1 自动检测数据类型1.4.2 重命名查…

WebRTC 服务器之Janus概述和环境搭建

1 概述 Janus 是由 Meetecho 开发的通用 WebRTC 服务器&#xff0c;它为构建 WebRTC 应用程序提供了一个模块化框架。服务器目标&#xff1a;Janus WebRTC 网关被设计为轻量级、通用的 WebRTC 服务器&#xff0c;除了实现以下方法外&#xff0c;它本身不提供任何功能&#xff1…

19:常见的Halcon数据格式

遍历文件夹与文件选择 1&#xff09;遍历文件夹&#xff1a; list_files( : : Directory, Options : Files) Directory&#xff1a;目录&#xff08;文件夹路径&#xff09; Options&#xff1a;选项 files 指定搜索的格式为文件 directories 指定搜索的格式为文件夹 re…

QML图像提供器 (Image Provider)

QML 中的图像提供器是一种自定义图像加载机制&#xff0c;允许你从非文件源&#xff08;如数据库、网络或程序生成的内容&#xff09;提供图像数据。 主要类型 QQuickImageProvider - 基础图像提供器 QPixmapImageProvider - 提供 QPixmap 图像 QImageImageProvider - 提供 …

计算机视觉与深度学习 | 双目立体匹配算法理论+Opencv实践+matlab实践

双目立体匹配 一、双目立体匹配算法理论与OpenCV、matlab实践一、双目立体匹配理论二、OpenCV实践三、优化建议四、算法对比与适用场景二、双目立体匹配算法理论及Matlab实践指南一、双目立体匹配理论二、Matlab实践步骤三、算法对比与优化建议四、完整流程示例五、常见问题与解…

AI国学智慧语录视频,条条视频10W+播放量

家人们&#xff01;图书类带货玩法真的非常多&#xff0c;之前也分享过蛮多&#xff0c;例如情感语录、育儿教育、爆款图书金句类、AI历史人物解说类等等。 本期继续来分享一个对于普通人来说&#xff0c;上手相当简单&#xff0c;容易起号&#xff0c;可作为长线深耕的AI带货…

echart图表使用

2、接口编写 该部分代码定义了UserController控制器类&#xff0c;用于处理与用户相关的请求。包含一个用于跳转页面的方法和一个返回用户详细数据&#xff08;以 JSON 格式呈现&#xff09;的接口。前者负责将用户导航至指定页面&#xff0c;后者通过构建ChartVO对象并填充数…