STM32单片机采用环形缓冲区实现串口中断数据接收管理

一、前言

在嵌入式系统开发中,与上位机进行串口通信是非常常见的场景。上位机可以通过串口发送指令或者数据给嵌入式设备,而嵌入式设备需要可靠地接收并解析这些数据,以执行相应的操作。然而,在串口通信过程中,上位机发送数据的速率往往与嵌入式设备接收和处理数据的速率不一致,这就可能导致数据的丢失或者误解析。

为了解决这个问题,决定设计并实现一个环形缓冲区来进行数据接收管理。环形缓冲区是一种高效的数据结构,适用于数据产生速率快于消费速率的场景。它具有固定大小的缓冲区,并且可以循环利用空间,保证数据的连续存储和有效利用。

在本项目中,选择使用STM32微控制器来实现串口数据接收功能。STM32具有丰富的外设资源和强大的性能,非常适合用于串口通信和数据处理。通过在STM32上实现环形缓冲区,可以实现以下目标:

(1)数据稳定接收:通过使用环形缓冲区,确保即使在接收数据速率慢于发送速率的情况下,数据也能够得到稳定的接收,避免数据丢失。

(2)数据缓存和管理:环形缓冲区可以作为一个数据缓存区,将接收到的数据暂时存储起来,以便后续处理。这样可以降低数据处理的延迟和复杂性。

(3)数据解析和应用:通过从环形缓冲区中读取数据,并进行解析和处理,嵌入式设备可以根据接收到的数据执行相应的操作,如控制外部设备或响应上位机指令。

通过使用环形缓冲区管理串口接收的数据,可以实现可靠的数据接收和处理,并提高系统的稳定性和可靠性。同时,该方案也适用于其他嵌入式系统和通信场景。

image-20230902144017003

image-20230902144051473

二、实现思路

(1)定义环形缓冲区的结构体:首先,需要定义一个表示环形缓冲区的结构体,其中包含以下成员变量:

  • 缓冲区的大小(capacity):表示环形缓冲区的容量,即可以存储的最大元素数量。
  • 写指针(write_ptr):表示当前可写入数据的位置。
  • 读指针(read_ptr):表示当前可读取数据的位置。
  • 数据数组(buffer):用于存储实际的数据。

(2)初始化环形缓冲区:在使用环形缓冲区之前,需要进行初始化。初始化时,将缓冲区的大小、写指针和读指针都设置为初始位置,通常都是0。

(3)写入数据:当有新的数据要写入缓冲区时,需要执行以下操作:

  • 检查缓冲区是否已满,如果已满则无法写入新的数据。
  • 将数据写入当前写指针所指向的位置。
  • 更新写指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。

(4)读取数据:当需要从缓冲区中读取数据时,需要执行以下操作:

  • 检查缓冲区是否为空,如果为空则无数据可读取。
  • 读取当前读指针所指向的数据。
  • 更新读指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。

(5)判断缓冲区状态:为了方便使用和管理缓冲区,可以实现一些用于判断缓冲区状态的函数,例如:

  • is_full():判断缓冲区是否已满。
  • is_empty():判断缓冲区是否为空。

实现环形缓冲区时,需要注意:

  • 写指针和读指针的位置计算要考虑到环形特性,即超过缓冲区容量时需要进行取模运算。
  • 缓冲区大小要合理选择,根据实际需求确定,以充分利用内存资源并避免数据丢失。
  • 多线程或中断环境下的并发访问要考虑数据同步和互斥操作,以避免竞争条件和数据不一致的问题。

通过以上思路,可以在C语言中实现一个简单高效的环形缓冲区,用于存储和管理数据,在数据收发过程中提高系统的稳定性和可靠性。

三、 C语言实现验证思路

#include <stdio.h>
#include <stdlib.h>#define BUFFER_SIZE 10typedef struct {int* buffer;      // 缓冲区数组指针int size;         // 缓冲区大小int head;         // 头部索引int tail;         // 尾部索引
} CircularBuffer;// 创建环形缓冲区
CircularBuffer* createCircularBuffer(int size) {CircularBuffer* cb = (CircularBuffer*)malloc(sizeof(CircularBuffer));  // 分配内存空间cb->buffer = (int*)malloc(sizeof(int) * size);  // 分配缓冲区数据的内存空间cb->size = size;   // 设置缓冲区大小cb->head = 0;      // 初始化头部索引为0cb->tail = 0;      // 初始化尾部索引为0return cb;
}// 销毁环形缓冲区
void destroyCircularBuffer(CircularBuffer* cb) {free(cb->buffer);  // 释放缓冲区数据的内存空间free(cb);          // 释放缓冲区结构体的内存空间
}// 判断环形缓冲区是否已满
int isCircularBufferFull(CircularBuffer* cb) {return ((cb->tail + 1) % cb->size == cb->head);
}// 判断环形缓冲区是否为空
int isCircularBufferEmpty(CircularBuffer* cb) {return (cb->head == cb->tail);
}// 写入数据到环形缓冲区
void writeData(CircularBuffer* cb, int data) {if (isCircularBufferFull(cb)) {  // 如果缓冲区已满,则无法写入数据printf("Circular buffer is full. Data cannot be written.\n");return;}cb->buffer[cb->tail] = data;  // 将数据写入缓冲区的尾部cb->tail = (cb->tail + 1) % cb->size;  // 更新尾部索引,循环利用缓冲区空间
}// 从环形缓冲区读取数据
int readData(CircularBuffer* cb) {if (isCircularBufferEmpty(cb)) {  // 如果缓冲区为空,则无数据可读取printf("Circular buffer is empty. No data to read.\n");return -1;  // 返回一个默认值表示读取失败}int data = cb->buffer[cb->head];  // 从缓冲区的头部读取数据cb->head = (cb->head + 1) % cb->size;  // 更新头部索引,循环利用缓冲区空间return data;
}int main() {CircularBuffer* cb = createCircularBuffer(BUFFER_SIZE);  // 创建大小为BUFFER_SIZE的环形缓冲区writeData(cb, 1);  // 写入数据1writeData(cb, 2);  // 写入数据2writeData(cb, 3);  // 写入数据3printf("Read data: %d\n", readData(cb));  // 读取数据并打印printf("Read data: %d\n", readData(cb));writeData(cb, 4);writeData(cb, 5);printf("Read data: %d\n", readData(cb));printf("Read data: %d\n", readData(cb));printf("Read data: %d\n", readData(cb));destroyCircularBuffer(cb);  // 销毁环形缓冲区return 0;
}

四、STM32串口接收

#define BUFFER_SIZE 256typedef struct {uint8_t buffer[BUFFER_SIZE];uint16_t head;uint16_t tail;
} CircularBuffer;// 初始化环形缓冲区
void CircularBuffer_Init(CircularBuffer* cb) {cb->head = 0;cb->tail = 0;
}// 判断环形缓冲区是否已满
bool CircularBuffer_IsFull(const CircularBuffer* cb) {return (cb->head + 1) % BUFFER_SIZE == cb->tail;
}// 判断环形缓冲区是否为空
bool CircularBuffer_IsEmpty(const CircularBuffer* cb) {return cb->head == cb->tail;
}// 向环形缓冲区写入数据
bool CircularBuffer_Write(CircularBuffer* cb, uint8_t data) {if (CircularBuffer_IsFull(cb)) {  // 缓冲区已满,无法写入return false;}cb->buffer[cb->head] = data;cb->head = (cb->head + 1) % BUFFER_SIZE;return true;
}// 从环形缓冲区读取数据
bool CircularBuffer_Read(CircularBuffer* cb, uint8_t* data) {if (CircularBuffer_IsEmpty(cb)) {  // 缓冲区为空,无数据可读取return false;}*data = cb->buffer[cb->tail];cb->tail = (cb->tail + 1) % BUFFER_SIZE;return true;
}// 获取环形缓冲区剩余大小
uint16_t CircularBuffer_GetRemainingSize(const CircularBuffer* cb) {if (cb->head >= cb->tail) {return BUFFER_SIZE - (cb->head - cb->tail);} else {return cb->tail - cb->head - 1;}
}// 获取环形缓冲区已写入大小
uint16_t CircularBuffer_GetWrittenSize(const CircularBuffer* cb) {if (cb->head >= cb->tail) {return cb->head - cb->tail;} else {return BUFFER_SIZE - (cb->tail - cb->head - 1);}
}// 从环形缓冲区读取指定长度的数据
bool CircularBuffer_ReadData(CircularBuffer* cb, uint8_t* data, uint16_t length) {if (CircularBuffer_GetWrittenSize(cb) < length) {return false;  // 缓冲区中的数据不足}for (uint16_t i = 0; i < length; ++i) {if (!CircularBuffer_Read(cb, &data[i])) {return false;  // 读取数据出错}}return true;
}// 向环形缓冲区写入指定长度的数据
bool CircularBuffer_WriteData(CircularBuffer* cb, const uint8_t* data, uint16_t length) {if (CircularBuffer_GetRemainingSize(cb) < length) {return false;  // 缓冲区剩余空间不足}for (uint16_t i = 0; i < length; ++i) {if (!CircularBuffer_Write(cb, data[i])) {return false;  // 写入数据出错}}return true;
}// 示例:STM32串口接收中断处理函数
void USART_Receive_IRQHandler(void) {uint8_t data = USART_ReceiveData(USART1);  // 获取接收到的数据if (!CircularBuffer_Write(&rxBuffer, data)) {// 缓冲区已满,处理错误}
}

在代码中,定义了一个名为CircularBuffer的结构体来表示环形缓冲区。包含了一个具有固定大小的数组buffer用于存储数据,以及头部指针head和尾部指针tail用于管理数据的读写位置。

接下来,实现了一些函数来对环形缓冲区进行操作。CircularBuffer_Init函数用于初始化环形缓冲区;CircularBuffer_IsFullCircularBuffer_IsEmpty函数分别判断缓冲区是否已满和是否为空;CircularBuffer_Write函数用于向缓冲区写入数据;CircularBuffer_Read函数用于从缓冲区读取数据。

CircularBuffer_GetRemainingSize函数用于获取环形缓冲区的剩余大小,即还能写入多少个字节的数据;CircularBuffer_GetWrittenSize函数用于获取已经写入到缓冲区的字节数;CircularBuffer_ReadData函数用于从环形缓冲区读取指定长度的数据,将其存储到提供的数据数组中;CircularBuffer_WriteData函数用于向环形缓冲区写入指定长度的数据,从提供的数据数组中复制相应的字节。

使用这些方便函数,可以更方便地管理环形缓冲区,实现数据的读取和写入。

最后,给出了一个示例,展示在STM32串口接收中断处理函数中将接收到的数据写入环形缓冲区。在中断处理函数中,通过USART_ReceiveData函数获取接收到的数据,调用CircularBuffer_Write函数将数据写入缓冲区。

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

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

相关文章

Error: error:0308010C:digital envelope routines::unsupported

这个错误通常发生在使用 Node.js 中的 TLS/SSL 模块时。它表示在尝试建立 TLS 连接时&#xff0c;无法识别或不支持远程服务器所使用的加密协议。 可能的原因包括以下几点&#xff1a; 远程服务器使用了不安全的加密算法或协议&#xff0c;而最新版本的 OpenSSL 已经将其移除…

如何实现前端路由保护?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

OpenCV实现图像傅里叶变换

傅里叶变换 dftcv.dft(img_float32,flagscv.DFT_COMPLEX_OUTPUT): flags:标志位&#xff0c;指定变换类型&#xff0c;cv.DFT_COMPLEX_OUTPUT会返回复数结果。 傅立叶变换&#xff0c;将输入的图像从空间域转换到频率域。 返回结果: 此函数返回一个复杂数值数组&#xff0c…

初学者必看,前端 Debugger 调试学习

1.文章简介&#xff1a; 报错和Bug&#xff0c;是贯穿程序员整个编程生涯中&#xff0c;无法回避的问题。而调试&#xff0c;就是帮助程序员定位问题、解决问题的重要手段&#xff0c;因此调试是每个程序员必备技能。 调试本身可分为两个过程: 定位问题 和 解决问题&#xff0…

4Spring及Spring系列-基本

8、Spring/Spring MVC Spring常见面试题55道&#xff08;附答案2023最新版&#xff09;_spring面试题-CSDN博客Spring是一个开源的Java企业级开发框架&#xff0c;由Rod Johnson于2003年创建。它提供了一套全面的编程和配置模型&#xff0c;用于构建现代化的基于Java的企业应用…

基于 SpringBoot+Hikvision SDK 远程查看配置海康网络摄像头配置

写在前面 工作中遇到&#xff0c;简单整理理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&#xff0c;永不停息。所有其它的路都是不完整的&#xff0c;是人的逃避方式&#xff0c;是对大…

多输入多输出 | MATLAB实现CNN-BiGRU-Attention卷积神经网络-双向门控循环单元结合SE注意力机制的多输入多输出预测

多输入多输出 | MATLAB实现CNN-BiGRU-Attention卷积神经网络-双向门控循环单元结合SE注意力机制的多输入多输出预测 目录 多输入多输出 | MATLAB实现CNN-BiGRU-Attention卷积神经网络-双向门控循环单元结合SE注意力机制的多输入多输出预测预测效果基本介绍程序设计往期精彩参考…

HarmonyOS学习 -- ArkTS开发语言入门

文章目录 一、编程语言介绍二、TypeScript基础类型1. 布尔值2. 数字3. 字符串4. 数组5. 元组6. 枚举7. unknown8. void9. null 和 undefined10. 联合类型 三、TypeScript基础知识条件语句if语句switch语句 函数定义有名函数和匿名函数可选参数剩余参数箭头函数 类1. 类的定义2.…

低压配电系统中浪涌保护器的作用,安装位置和接线方法

低压配电系统是指在变压器低压侧或用户侧的电气装置&#xff0c;主要用于向用户提供安全、可靠和经济的电能。低压配电系统中常见的电气设备有低压配电柜、分支箱、开关箱、插座、照明等。这些设备都需要防止因外部或内部原因产生的过电压对其造成损坏或影响其正常工作。过电压…

竞赛选题 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数&#xff1a;2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 4 MobileNetV2网络5 损失函数softmax 交叉熵5.1 softmax函数5.2 交叉熵损失函数 6 优化器SGD7 学…

virtuoso如何导出def?

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 数模混合的项目中需要把PAD位置通过def的形式读入pr设计中,以此让power plan规避PAD的区域,避免DRC问题。 使用virtuoso导出def的流程如下: 1)新建一个空cell,在Library Manager-File-New-Cell…

【从0开发】百度BML全功能AI开发平台【实操:以部署情感分析模型为例】

目录 一、全功能AI开发平台介绍二、AI项目落地应用流程&#xff08;以文本分类为例&#xff09;2-0、项目开始2-1、项目背景2-2、数据准备介绍2-3、项目数据2-4、建模调参介绍2-5、项目的建模调参2-6、开发部署2-7、项目在公有云的部署 附录&#xff1a;调用api代码总结 一、全…

PromptScript:轻量级 DSL 脚本,加速多样化的 LLM 测试与验证

TL&#xff1b;DR 版本 PromptScript 是一个轻量级的 Prompt 调试用的 DSL &#xff08;Yaml&#xff09;脚本&#xff0c;以用于快速使用、构建 Prompt。 PromptScript 文档&#xff1a;https://framework.unitmesh.cc/prompt-script Why PromptScript &#xff1f; 几个月前&…

CSS特殊学习网址

css基本教程内填充padding——前端编程新手必学_哔哩哔哩_bilibilicss3基本语法CSS flex布局&#xff08;弹性布局/弹性盒子&#xff09;Agence digitale crative Paris et Montpellier | Sweet PunkCSS布局CSS Layout — Phuoc NguyenCSS中文特效Coco ’s blog特效网址Coco ’…

linux中安装RocketMQ以及dashboard

前提&#xff1a; 需要安装jdk8 上传下面的文件到服务器中 新建目录 mkdir rocketmq 将下载后的压缩包上传到阿里云服务器或者虚拟机中去&#xff0c;并解压 unzip rocketmq-all-4.9.2-bin-release.zip 配置环境变量 vim /etc/profile 配置内容&#xff1a; export NAM…

使用 Apache Camel 和 Quarkus 的微服务(五)

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 在本系列的第三部分中&#xff0c;我们了解了如何在 Minikube 中部署基于 Quarkus/Camel 的微服务&#xff0c;这是最常用的 Kubernetes 本地实现之一。虽然这样的本地…

力扣:130. 被围绕的区域(Python3)

题目&#xff1a; 给你一个 m x n 的矩阵 board &#xff0c;由若干字符 X 和 O &#xff0c;找到所有被 X 围绕的区域&#xff0c;并将这些区域里所有的 O 用 X 填充。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#…

SpringBoot的配置文件——.yml和.properties

目录 1. Spring Boot 配置文件的使用场景 2. 配置文件的两种格式 2.0 特殊说明&#xff1a; 2.1 .properties 2.1.1 格式 2.2.2 缺陷 2.2.3 解决中文乱码的问题 2.2 .yml 2.2.3 格式 配置数据库连接 注意转义字符 ​编辑 ​编辑 配置null 配置对象 从.yml读取文件举例 Stud…

设计模式 - 行为型模式考点篇:迭代器模式(概述 | 案例实现 | 优缺点 | 使用场景)

目录 一、行为型模式 一句话概括行为型模式 1.1、迭代器模式 1.1.1、概述 1.1.2、案例实现 1.1.3、优缺点 1.1.4、使用场景 一、行为型模式 一句话概括行为型模式 行为型模式&#xff1a;类或对象间如何交互、如何划分职责&#xff0c;从而更好的完成任务. 1.1、迭代器…

Vuex使用方式及异步问题处理

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 目录 1.Vuex简介&#xff1a; 2.vuex获取值 2.1安装 2.2.菜单栏 2.3.模块 2.4使用 3.改…