基于stm32串口环形缓冲队列处理机制

2023-05-16

原文链接:基于stm32串口环形缓冲队列处理机制—入门级(单字节)

串口环形缓冲区实验

1.1 实验简介

最简单的串口数据处理机制是数据接收并原样回发的机制是:成功接收到一个数,触发进入中断,在中断函数中将数据读取出来,然后立即回发。这一种数据处理机制是“非缓冲中断方式”,虽然这种数据处理方式不消耗时间,但是这种数据处理方式严重的缺点是:数据无缓冲区,如果先前接收的的数据如果尚未发送完成(处理完成),然后串口又接收到新的数据,新接收的数据就会把尚未处理的数据覆盖,从而导致“数据丢包”。

对于“数据丢包”,最简单的办法就是使用一个数组来接收数据:每接收一个数据,数组下标偏移。虽然这样的做法能起到一定的“缓冲效果”,但是数组的空间得不到很好的利用,已处理的数据仍然会占据原有的数据空间,直到该数组“满载”(数组的每一个元素都保存了有效的数据),将整个数组的数据处理完成后,重新清零数组,才能开启新一轮的数据接收。

那么,有什么好的数据接收处理机制既能起到“缓冲”效果,又能有效地利用“数组空间”?答案是:有的,那就是“环形缓冲区”。

环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图所示。

在这里插入图片描述

当然,环形缓冲区的“头指针”和“尾指针”可以用“头变量”和“尾变量”来代替,因为切换数组的元素空间,除了可以用“指针偏移法”之外,还可以用“素组下标偏移法”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾变量”加一,以保存下一个数据;应用程序在读取数据时,“头变量”加一,以读取下一个数据。

“环形缓冲区”数据接收处理机制的好处在于:利用了队列的特点,一头进,一头出,互不影响,在数据进去(往里存)的时候,另一边也可以把数据读出来,而读出来的数据,留下的空位,又可以增加多的存储空间,从而避免一边接收数据且一边处理数据会在数据量密集的时候而导致的丢掉数据或者数据产生冲突的问题。

如果仅有一个线程读取环形缓冲区的数据,只有一个串口往环形缓冲区写入数据,则不需要添加互斥保护机制就可以保证数据的正确性。

需要注意的是,如果串口每接收x个字节的数据才处理一次,则环形缓冲区的缓冲数组的大小必须是x的N倍,具体N为多少,需要结合具体的数据接收速率以及处理速率,适当调节。这就好比喻,水壶永远大于水杯,这样子水壶才能存放很多杯水。

下面我们来一起讨论一下环形队列的具体状态以及实现。下文构建的环形队列采用的是“头变量”“尾变量”来控制队列的存储和读取。

环形队列采用 “头指针”和“尾指针”来控制队列的存储和读取可以参考 环形缓冲区控制队列的存储和读取方法之指针

首先,我们会构造一个结构体,并定义一个结构变量。

#define MAX_SIZE  12               //缓冲区大小

typedef struct 
{
  unsigned char head;        //缓冲区头部位置
  unsigned char tail;         //缓冲区尾部位置
  unsigned char ringBuf[MAX_SIZE]; //缓冲区数组
} ringBuffer_t;

ringBuffer_t buffer;                 //定义一个结构体

定义一个结构头体则表示新的消息队列已经创建完成。新建创建的队列,头指针head和尾指针tail都是指向数组的元素0。如下图所示,此时的消息队列是“空队列”。

在这里插入图片描述

当如果有11个数据 abcdefghijk 存入缓冲队列,则如下图所示:

在这里插入图片描述

当如果 l 加入队列,则缓冲队列处于满载状态,如下图所示:如果此时,接收到新的数据并需要保存,则tail需要归零,将接收到的数据存到数组的第一个元素空间,如果尚未读取缓冲数组的一个元素空间的数据,则此数据会被新接收的数据覆盖。同时head需要增加1,修改头节点偏移位置丢弃早期数据。

在这里插入图片描述

读取缓冲队列中的11个数据后,状态如下:

在这里插入图片描述

当消息队列中的所有数据都读取出来后,此时环形队列是空的,状态如下图所示。从图可以总结得知,如果tail和head相等,则表示缓冲队列是空的。

在这里插入图片描述

1.2 软件设计

ringBuffer.c

1. 构造环形缓冲区

/**********************************************************************************************
描述   :      环形缓冲读写
作者   :      Jahol Fan
版本   :      V1.0
修改   :      
完成日期: 
Notice    :本程序只供学习使用,未经作者许可,不得用于其它任何用途。版权所有,盗版必究
***********************************************************************************************/
#include "ringbuffer.h"

#define BUFFER_MAX  36               //缓冲区大小

typedef struct 
{
  unsigned char headPosition;        //缓冲区头部位置
  unsigned char tailPositon;         //缓冲区尾部位置
  unsigned char ringBuf[BUFFER_MAX]; //缓冲区数组
} ringBuffer_t;

ringBuffer_t buffer; //定义一个结构体

首先,需要构建一个结构体ringBuffer_t,如果定义一个结构体变量buffer,则意味着创建一个环形缓冲区。

2. 往环形缓冲区存数据

/**
* @brief 写一个字节到环形缓冲区
* @param data:待写入的数据
* @return none
*/
void RingBuf_Write(unsigned char data)
{
  buffer.ringBuf[buffer.tailPositon]=data;     //从尾部追加
  if(++buffer.tailPositon>=BUFFER_MAX)         //尾节点偏移
    buffer.tailPositon=0;                      //大于数组最大长度 归零 形成环形队列
  if(buffer.tailPositon == buffer.headPosition)//如果尾部节点追到头部节点,则修改头节点偏移位置丢弃早期数据
    if(++buffer.headPosition>=BUFFER_MAX)
      buffer.headPosition=0;
}

8行:将数据存放到tailPosition所指向的元素空间。

9行:tailPosition变量自增1,并且判断,如果大于最大缓冲,则将tailPosition归零。

11行:如果tailPositon与headPosition相等,则表示,数据存入速度大于数据取出速度,从而导致“追尾”。此时headPosition需要自增1,以丢弃早期数据,这也就是数据“覆盖现象”,这种现象是不允许存在的,解决这种现象的办法是将缓冲队列的空间再开大点。

13行:如果headPosition也大于最大数组,则需要将headPosition清零。

3. 读取环形缓冲区的数据

/**
* @brief 读取环形缓冲区的一个字节的数据
* @param *pData:指针,用于保存读取到的数据
* @return 1表示缓冲区是空的,0表示读取数据成功
*/
u8 RingBuf_Read(unsigned char* pData)
{
  if(buffer.headPosition == buffer.tailPositon)    //如果头尾接触表示缓冲区为空
   {
      return 1;   //返回1,环形缓冲区是空的
   }
  else
  {
    *pData=buffer.ringBuf[buffer.headPosition];    //如果缓冲区非空则取头节点值并偏移头节点
    if(++buffer.headPosition>=BUFFER_MAX)
      buffer.headPosition=0;
    return 0;     //返回0,表示读取数据成功
  }
}

8行:首先判断headPosition是否等于tailPositon,如果相等,则表明,此时缓冲区是空的。

10行:缓冲区为空,则直接返回,不执行后面的程序

12行:如果缓冲区不为空,则条件成立并执行

14行:读取headPosition所指向的环形缓冲队列的元素空间的数据。

15行:headPosition自增1以读取下一个数据。如果headPosition大于最大值BUFFER_MAX,则将headPosition归零。

17行:返回0,表示读取数据成功。

Hal_uart.c

/**
* @brief 串口1中断函数
* @param none
* @return none
*/
void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //判断接收标志位是否为1
  {         
        USART_ClearITPendingBit(USART1,USART_IT_RXNE);       //清楚标志位
        RingBuf_Write(USART_ReceiveData(USART1));
        //阻塞等待直到传输完成
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    }
}

11行:数据接收成功,则将数据存入环形缓冲队列。

Main.c

/**
************************************************************
* @file         main.c
* @brief        MCU entry file
* @author       JaholFan
* @date         2017-11-20
* @version      V010100
* @note         
***********************************************************/
#include "Hal_Led/Hal_Led.h"
#include "Hal_delay/delay.h"
#include "Hal_Key/Hal_Key.h"
#include "Hal_Relay/Hal_Relay.h"
#include "Hal_Usart/hal_uart.h"
#include "ringbuffer.h"
/**
* @brief 程序入口
* @param none
* @return none
*/
int main(void)
{
    u8 data = 0;
    SystemInit();  //系统时钟初始化
    delayInit(72); //滴答定时器初始化
    uartxInit();
    while(1)
    {
        if(0 == RingBuf_Read(&data))            //从环形缓冲区中读取数据
        {
            USART_SendData(USART1,data);          //读取接收到的数据并回发数据
        }
        delayMs(1);  //延时1ms:使得处理数据的速度小于接收数据的速度,用于验证接收缓冲区的“缓冲”特性
        
    }
}

30行:读取环形缓冲的数据,如果环形缓冲队列有数据,则条件成立

32行:将数据原样回发

34行:延时1ms的目的是:使得处理数据的速度小于接收数据的速度,用于验证接收缓冲区的“缓冲”特性。实际上,在项目工程中,项目代码的执行是消耗一定的CPU时间的,本例程序用延时1毫秒来等效替代项目代码执行小号的CPU时间。

1.3 下载验证

当如果将缓冲队列最大缓冲空间设为12

#define BUFFER_MAX 12 //缓冲区大小

串口助手毫秒发送11个字节的数据(115200的波特率每毫秒最多发送115200bit/s÷10bit÷1000 = 111.52Byte/ms),11个字节的数据是:1234567890a。经过测试,发送6167个字节的数据,实际上接收到的数据只有5890个字节,丢包很严重!如下图所示:

在这里插入图片描述

当如果将缓冲队列最大缓冲空间设为100

#define BUFFER_MAX 100 //缓冲区大小

串口助手毫秒发送11个字节的数据,11个字节的数据是:1234567890a。经过测试,发送10857个字节的数据,接收到的数据也是10857个字节,没有丢包!现象如下图所示:

在这里插入图片描述

版权归原作者所有,如有侵权,请联系删除。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于stm32串口环形缓冲队列处理机制 的相关文章

  • 处理器指令周期执行时间

    我的猜测是 no operation 内在 ARM 指令应花费 1 168 MHz 来执行 前提是每个NOP在一个时钟周期内执行 我想通过文档验证这一点 有关处理器指令周期执行时间的信息是否有标准位置 我试图确定 STM32f407IGh6
  • 初始化 ST-Link 设备时出错 - 无法连接到设备

    我目前正在使用 ST Link 调试器对我的 STM32F3 Discovery 板进行编程 我使用的IDE是Atollic TrueStudio 5 5 2 现在我面临一个非常奇怪的问题 那就是我不断收到消息 初始化 ST Link 设备
  • 优化 ARM Cortex M3 代码

    我有一个 C 函数 它尝试将帧缓冲区复制到 FSMC RAM 这些函数将游戏循环的帧速率降低至 10FPS 我想知道如何分析反汇编的函数 我应该计算每个指令周期吗 我想知道CPU把时间花在哪里 在哪个部分 我确信该算法也是一个问题 因为它的
  • 134-基于stm32单片机矿井瓦斯天然气浓度温湿度检测自动通风系统Proteus仿真+源程序...

    资料编号 134 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 ds1302时钟 DHT11温湿度 电机 蜂鸣器 制作一个基于stm32单片机矿井瓦斯天然气浓度温湿度检测自动通风系统Proteus仿真 2 通过DH
  • 140-基于stm32单片机智能晾衣杆控制系统Proteus仿真+源程序

    资料编号 140 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 ds1302时钟 光敏传感器 蜂鸣器 LED灯 制作一个基于stm32单片机智能晾衣杆控制系统Proteus仿真 2 通过光敏传感器
  • STM32F4 通过软复位跳转到引导加载程序,无需 BOOT0 和 BOOT1 引脚

    我问这个问题是因为可以在这里找到类似问题的答案 通过应用程序跳转到 STM32 中的引导加载程序 即从用户闪存在引导模式下使用引导 0 和引导 1 引脚 用户 JF002 JF002回答 当我想跳转到引导加载程序时 我在其中一个备份寄存器中
  • HAL库学习

    CMSIS简介 CMSIS Cortex Microcontroller Software Interface Standard 微控制器软件接口标准 由ARM和其合作的芯片厂商 ST NXP 软件工具厂商 KEIL IAR 共同制定的标准
  • 毕业设计 江科大STM32的智能温室控制蓝牙声光报警APP系统设计

    基于STM32的智能温室控制蓝牙声光报警APP系统设计 1 项目简介 1 1 系统构成 1 2 系统功能 2 部分电路设计 2 1 stm32f103c8t6单片机最小系统电路设计 2 2 LCD1602液晶显示电路设计 2 2 风
  • SHT10温湿度传感器——STM32驱动

    实验效果 硬件外观 接线 3 3V供电 IIC通讯 代码获取 查看下方 END
  • [MM32硬件]搭建灵动微MM32G0001A6T的简易开发环境

    作为学习单片机的经典 自然是通过GPIO点亮LED 或者是响应按钮的外部中断例程 这我们看看SOP8封装的芯片MM32G0001A6T得引脚 除了VDD和GND固定外 我们可以使用PA14 PA1 PA13 PA15 PA2 PA3这六个G
  • 解决KEIL编译慢问题

    两种方案 使用v6版本的ARM Compiler 如果v6版本编译不过 必须使用v5版本的 则可以勾选掉Browse Information选项 提升很明显 1分多钟能优化到几秒 看代码量 但是这个有个弊端 在KEIL中会影响函数跳转 建议
  • 跟着野火学FreeRTOS:第一段(任务定义,切换以及临界段)

    在裸机系统中 系统的主体就是 C P U CPU CP U 按照预先设定的程序逻辑在 m a i n
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • 无法使用 OpenOCD 找到脚本文件

    我正在尝试按照本教程将 OpenOCD 与我的 ST 发现板一起使用 https japaric github io discovery README html https japaric github io discovery READM
  • STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

    文章目录 一 前言 二 问题1 数值超过4096 三 问题1的排错过程 四 问题2 右对齐模式设置失败 五 问题2的解决方法 5 1 将ADC ExternalTrigConv设置为0 5 2 使用ADC StructInit 函数 一 前
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • 通过JTAG恢复STM32 MCU磨掉的标记

    我有一块可能带有 STM32 MCU 的板 我想为该板制作定制固件 因为库存板有很多问题 不幸的是 电路板制造商很友善地磨掉了所有标记 有没有办法通过 jtag 获取设备 系列 ID 并将其交叉引用到型号 我能找到的一切都是关于获取芯片的唯
  • STM32 上的位置无关代码 - 指针

    我已成功在 STM32 上构建并运行位置无关的代码 向量表和 GOT 已修补 一切正常 但我对这样的代码有问题 double myAdd double x return x 0 1 double ptrmyAdd double myAdd
  • STM32内部时钟

    我对 STM32F7 设备 意法半导体的 Cortex M7 微控制器 上的时钟系统感到困惑 参考手册没有充分阐明这些时钟之间的差异 SYSCLK HCLK FCLK 参考手册中阅读章节 gt RCC 为 Cortex 系统定时器 SysT
  • stm32l0: 执行MI命令失败。使用 vFlashErase 数据包擦除闪存时出错

    我正在使用 Nucleo STM32L031 和 AC6 STM32 工作台 eclipse 我编写应用程序并进入调试模式 一切正常 直到我在应用程序中添加另一个功能 我注意到当我删除 评论 新函数 软件可以再次进入调试模式 但是当我添加

随机推荐

  • 基于采样的RRT/RRT*/RRT_connect算法笔记及C++实现

    本文记录常见的基于采样的RRT算法及相关改进算法 xff08 RRT xff0c RRT connect xff09 的原理和代码实现效果 与上一章介绍A 算法的文章不同 xff0c 本文会先给出几种算法之间的效果对比 xff0c 先有个直
  • STM32F103ZE驱动PMW3901光流模块

    本文将会简单的介绍如何使用STM32F103ZE驱动PMW3901光流模块 xff0c 使用标准库 所用材料如下 一块 STM32F103最小系统板以及一个 PMW3901光流模块 通过查阅PMW3901的数据手册可以得知 xff0c 该芯
  • 计算两圆相交面积

    转自 xff1a 模板 求两圆相交面积 xff08 模板 xff09 两圆相交分如下集中情况 xff1a 相离 相切 相交 包含 设两圆圆心分别是O1和O2 xff0c 半径分别是r1和r2 xff0c 设d为两圆心距离 又因为两圆有大有小
  • 深蓝学院-移动机器人运动规划重点笔记

    移动机器人运动规划笔记 xff0c 转载自https blog csdn net wqwqqwqw1231 article details 107310965 感谢原作者的总结 xff01
  • TFmini Plus在Arduino上的开发例程(二)

    本例程以Arduino Uno板为例 xff0c 通过Arduino实现TFmini Plus相关指令的写入 xff0c 上行数据的读取 判断和测量数据的获取打印 xff0c 主要帮助客户快速熟悉我公司雷达 xff0c 减少产品的研发周期
  • Linux命令发送Http GET/POST请求

    Linux命令发送Http GET POST请求 Get请求 1 使用curl命令 xff1a curl span class token string 34 http www baidu com 34 span 如果这里的URL指向的是一
  • VSCode 常用设置项

    代码编辑工具VSCode 常用设置项 span class token punctuation span span class token comment VScode主题配置 span span class token string 34
  • 机器人运动控制-上位机通讯

    机器人 xff0c 无论是工业机器人还是服务机器人等多种类机器人 xff0c 都有自己的控制器 在他们的控制面板上 xff0c 我们可以通过简单的操作和程序指令 xff0c 让机器人自行运动 为了让机器人更加智能 xff0c 我们需要在机器
  • Imu误差模型、零偏、零偏稳定性

    原文链接 零偏 xff0c 零偏稳定性和零偏重复性 xff0c IMU误差模型 什么是零偏 xff08 Bias xff09 在陀螺静止时 xff0c 陀螺仪仍会 xff0c 以规定时间内测得的输出量平均值相应的等效输入角速率表示 xff0
  • 海思3516a实现OSD叠加水印

    文章目录 前言一 三个文件的编译二 海思SDK使用步骤1 创建叠加字符2 添加叠加区域到视频通道 总结 前言 两天的努力终于实现了 xff0c 激动 xff01 在网上查阅了各种资料 xff0c 只是有零散的信息 xff0c 海思3516a
  • 结合下图,说明UART的工作原理

    结合下图 xff0c 说明UART的工作原理 UART提供三个独立的异步串行I O口 xff0c 他们可以运行于中断模式或者DMA模式 xff0c 也就是说UART可以产生中断请求或者DMA请求 xff0c 以便在CPU和UART之间传输数
  • 深入理解计算机系统 -- 大端与小端字节序

    一 大端字节序 vs 小端字节序 字节序指一个多字节对象在内存中存储的方式 xff0c 小端字节序机器在存储多字节对象时采用低地址存低有效字节的策略 xff0c 大端则恰恰相反 字节序由CPU架构决定 xff0c 与操作系统无直接关系 像常
  • TCP连接建立

    TCP 一种面向来连接的 可靠的 基于字节流的传输层通信协议 面向连接 xff1a 数据在发送之前必须在两端建立连接 xff0c 方法就是我们熟知的三次握手连接 可靠传输 xff1a 通过多种机制来保证数据的正确传输 xff0c 比如序列号
  • UDP接收端收不到广播的消息问题排查

    网络调试助手可以互相发送 xff0c 而你的UDP广播代码却不行 你是广播 是不会被路由器转发的 但是在同一个交换机下 是可以收到广播的 还有就是 电脑的虚拟网卡会拦截广播操作 xff0c 因为你没有指定一个地址 xff0c 所以代码正确的
  • STM32带FIFO的DMA传输应用示例

    STM32系列芯片都内置DMA外设 xff0c 其中很多系列的DMA配备了FIFO 这里以STM32F429芯片及开发板为例 xff0c 演示一下带FIFO的DMA传输实现过程 大致情况是这样的 xff0c 我用TIMER1通道1的比较事件
  • 两种方式判断内存的大小端存储方式

    1 目的 xff1a 判断ubuntu操作系统的内存属于大端还是小端存储 2 源代码 两种方法判断大小端 xff08 处理器取值时的字节序 xff09 xff1a 1 字符指针 2 联合体 法1 if 1 include lt stdio
  • 字符串:求str1在str2中首次出现的位置。

    span class token macro property span class token directive keyword include span span class token string lt stdio h gt sp
  • 基于ROS利用客户端和服务端实现C++节点和python节点间传送图像

    基于ROS利用客户端和服务端实现C 43 43 节点和python节点间传送图像 配置ROS下和python3通信以及配置python3可用的cv bridge 环境安装和使用 参考 xff1a https blog csdn net qq
  • iMaxB6充电介绍

    iMaxB6是一款多用途充电器 xff0c 能够为Li ion Li Poly Li Fe Ni Cd Ni MH和Pb类型电池充电 xff0c 支持6串以内的平衡充电 简要步骤 xff1a 1 连接正负电源 xff1b 2 连接平衡线 x
  • 基于stm32串口环形缓冲队列处理机制

    原文链接 xff1a 基于stm32串口环形缓冲队列处理机制 入门级 xff08 单字节 xff09 串口环形缓冲区实验 1 1 实验简介 最简单的串口数据处理机制是数据接收并原样回发的机制是 xff1a 成功接收到一个数 xff0c 触发