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

第29章_瑞萨MCU零基础入门系列教程之改进型环形缓冲区

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总: https://blog.csdn.net/qq_35181236/article/details/132779862


第29章 改进型环形缓冲区

29.1 基本概念

环形缓冲区是一个先进先出(FIFO)的闭环的存储空间。通俗的理解为,在内存中规划了一块“圆形”的地,将该“圆形”进行N(Ring Buffer的大小)等分,如下图所示:

但是实际上,处理器的内存不可能是这样一个闭环的存储方式,而是一片连续的,有起始有结束的空间:

开发者在程序中只能申请一段有头有尾的内存,通过软件设计将这片内存实现为一个环形的缓冲区。

一般而言,对于环形缓冲区的操作需要了解几个基本单位:

  • 内存起始地址pHead
  • 内存结束地址pEnd
  • 内存总大小Length
  • 可写内存起始地址pwStart
  • 可写内存大小wLength
  • 可读内存起始地址prStart
  • 可读内存大小rLength

可以发现这几个单位中是存在算术关系的:

将②式换算下,以可写内存大小为结果:

将可读的数据称作有效数据valid data,可读的起始内存地址叫有效数据起始地址pValid,可读的数据个数叫有效数据个数pValidLength。而可写的内存,位于有效数据之后,称之为pValidEnd:

基于以上信息,就可以将环形缓冲区的信息抽象为结构体RingBufferInfo:

typedef struct RingBuffInfo{unsigned char *pHead;unsigned char *pEnd;    unsigned char *pValid;    unsigned char *pValidEnd; unsigned int  nBufferLength;unsigned int  nValidLength;   
}RingBuffInfo;

由于可写的数据个数是可以通过缓冲区大小nBufferLength和有效数据个数nValidLength计算得到,因而未将其封装到RingBufferInfo结构体中。

对于环形缓冲区,主要的操作有:申请和释放空间,读写数据、清除数据。将这些操作方法和缓冲区信息一起封装为结构体RingBuffer:

typedef struct RingBuffer{RingBuffInfo info;int         (*Write)(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length);int         (*Read)(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length);int         (*Clear)(struct RingBuffer *ptbuf);int         (*Free)(struct RingBuffer *ptbuf);struct RingBuffer *next;
}RingBuffer;

第07行的链表,用来管理多个环形缓冲区:把它们放在一个链表里。

29.2 申请缓冲区

先申请一个RingBuffer结构体,再申请存储数据的空间,最后初始化。代码如下:

struct RingBuffer *RingBufferNew(unsigned int length)
{struct RingBuffer *ptbuf;if(0 == length)     return NULL;ptbuf = (struct RingBuffer*)malloc(sizeof(struct RingBuffer));if(NULL == ptbuf)   return NULL;if(NULL != ptbuf->info.pHead){free(ptbuf->info.pHead);}ptbuf->info.pHead = (uint8_t*)malloc(length);if(NULL == ptbuf->info.pHead) {printf("Error. Malloc %d bytes failed.\r\n", length);return -EIO;}ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;ptbuf->info.pEnd = ptbuf->info.pHead + length;ptbuf->info.nValidLength = 0;ptbuf->info.nBufferLength = length;ptbuf->Write = RingBufferWrite;ptbuf->Read = RingBufferRead;ptbuf->Clear = RingBufferClear;ptbuf->Free = RingBufferFree;return ptbuf;
}
  • 第06行:使用C库函数malloc申请一个RingBuffer结构体;
  • 第12行:分配存储数据的内存;
  • 第18~21行:初始化缓冲区的信息;
  • 第23~26行:填充操作函数;

29.3 释放缓冲区

先是否数据存储空间,再释放RingBuffer结构体本身。代码如下:

static int RingBufferFree(struct RingBuffer *ptbuf)
{if(ptbuf == NULL)           return -EINVAL;if(ptbuf->info.pHead==NULL) return -EINVAL;free((uint8_t*)ptbuf->info.pHead);ptbuf->info.pHead = NULL;ptbuf->info.pValid = NULL;ptbuf->info.pValidEnd = NULL;ptbuf->info.pEnd = NULL;ptbuf->info.nValidLength = 0;free((struct RingBuffer *)ptbuf);return ESUCCESS;
}

29.4 写数据到缓冲区

往缓冲区中写入数据需要考虑三个点:

  • 剩下的空间是否足够?
  • 超过空间的数据是丢还是留?
  • 写入数据时如果越界了,就需要缓冲器的头部继续写

如果从pValidEnd开始写入数据不会超过缓冲区的结束地址,那么直接从pValidEnd处开始写入数据即可:

如果从pValidEnd开始写入数据会超过缓冲区的结束地址,那么就需要考虑很多:

  • 计算从pValidEnd开始到pEnd可以写入多少个数据
  • 还剩多少个数据需要从pHead处开始写
  • 计算从pHead开始到pValid可以写入多少个数据,是否足够写入剩下的数据;不够的话如何处理?

在本书实验例程中,如果出现了剩余空间不足以容纳新数据时,就用新数据覆盖旧数据:

在这个过程中,有效数据的起始地址和结束地址,以及有效数据的个数,需要随着数据的写入跟着变化,这些数据的计算结合示意图可谓一目了然,此处就不再列出计算公式了。

如果缓冲区的剩余空间足够容纳新数据,那么写操作比较简单。代码如下:

static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length)
{......(省略内容)// copy buffer to pValidEndif( (ptbuf->info.pValidEnd + length) > ptbuf->info.pEnd )  // 超过了Buffer范围需要分为两段{len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValidEnd);len2 = length - len1;memcpy((uint8_t*)ptbuf->info.pValidEnd, src, len1);memcpy((uint8_t*)ptbuf->info.pHead, src + len1, len2);ptbuf->info.pValidEnd = ptbuf->info.pHead + len2;   // 更新有效数据区尾地址}else{memcpy((uint8_t*)ptbuf->info.pValidEnd, src, length);ptbuf->info.pValidEnd = ptbuf->info.pValidEnd + length;}......(省略内容)
}

如果缓冲区的剩余空间不足以容纳新数据,在使用新数据覆盖老数据时,涉及的计算比较繁琐,代码如下:

static int RingBufferWrite(struct RingBuffer *ptbuf, const unsigned char *src, unsigned int length)
{......(省略内容)// 重新计算已使用区的起始位置if( (ptbuf->info.nValidLength + length) > ptbuf->info.nBufferLength )     // 要写入的数据超过了缓冲区总长度,分为两段写{move_len = ptbuf->info.nValidLength + length - ptbuf->info.nBufferLength;if( (ptbuf->info.pValid + move_len) > ptbuf->info.pEnd ){len1 = (unsigned)(ptbuf->info.pEnd - ptbuf->info.pValid);len2 = move_len - len1;ptbuf->info.pValid = ptbuf->info.pHead + len2;}else{ptbuf->info.pValid = ptbuf->info.pValid + move_len;}ptbuf->info.nValidLength = ptbuf->info.nBufferLength;}else{ptbuf->info.nValidLength = ptbuf->info.nValidLength + length;}return (int)length;
}

29.5 从缓冲区读数据

相比于写数据,读数据的操作就简单了许多。读数据时,从pValid处开始读,如果越过了pEnd,需要从pHead继续读取剩下的数据:

而如果从pValid处读取的数据个数不会越过pEnd,那么直接读出即可:

环形缓冲区的读函数代码如下:

static int RingBufferRead(struct RingBuffer *ptbuf, unsigned char *dst, unsigned int length)
{unsigned int len1 = 0, len2 = 0;if(ptbuf->info.pHead==NULL)     return -EINVAL;if(ptbuf->info.nValidLength==0) return -ENOMEM;if(length > ptbuf->info.nValidLength){length = ptbuf->info.nValidLength;}if( (ptbuf->info.pValid + length) > ptbuf->info.pEnd ){len1 = (unsigned int)(ptbuf->info.pEnd - ptbuf->info.pValid);len2 = length - len1;memcpy(dst, (uint8_t*)ptbuf->info.pValid, len1);memcpy(dst + len1, (uint8_t*)ptbuf->info.pHead, len2);ptbuf->info.pValid = ptbuf->info.pHead + len2;}else{memcpy(dst, (uint8_t*)ptbuf->info.pValid, length);ptbuf->info.pValid = ptbuf->info.pValid + length;}ptbuf->info.nValidLength -= length;return (int)length;
}

29.6 清除缓冲区

清除缓冲区时,让RingBuffer的各个成员恢复初始值即可:

static int RingBufferClear(struct RingBuffer *ptbuf)
{if(ptbuf == NULL)           return -EINVAL;if(ptbuf->info.pHead==NULL) return -EINVAL;if(ptbuf->info.pHead != NULL){memset(ptbuf->info.pHead, 0, ptbuf->info.nBufferLength);}ptbuf->info.pValid = ptbuf->info.pValidEnd = ptbuf->info.pHead;ptbuf->info.nValidLength = 0;return ESUCCESS;
}

本章完

相关文章:

第29章_瑞萨MCU零基础入门系列教程之改进型环形缓冲区

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id728461040949 配套资料获取:https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总: ht…...

如何搭建一个react项目(详细介绍)

要搭建一个基本的 React 项目,你需要执行以下步骤。在开始之前,请确保你已经安装了 Node.js 和 npm(Node 包管理器)。 搭建一个React项目 1,创建项目目录2,初始化项目3,安装 React 和 ReactDOM4…...

ActiveMQ用法

ActiveMQ 和 JMS的关系? ActiveMQ是流行的开源消息中间件,JMS是Java平台定义的一种消息传递的标准。ActiveMQ实现了JMS规范,因此可以使用JMS API来与ActiveMQ进行交互。 JMS定义了一种标准的API。API包括了一些接口和类,用于创建…...

TouchGFX之缓存位图

位图缓存是专用RAM缓冲区,应用可将位图保存(或缓存)在其中。 如果缓存了位图,在绘制位图时,TouchGFX将自动使用RAM缓存作为像素来源。位图缓存在许多情况下十分有用。 从RAM读取数据通常比从闪存读取要快(特…...

线性代数的本质(十)——矩阵分解

文章目录 矩阵分解LU分解QR分解特征值分解奇异值分解奇异值分解矩阵的基本子空间奇异值分解的性质矩阵的外积展开式 矩阵分解 矩阵的因式分解是把矩阵表示为多个矩阵的乘积,这种结构更便于理解和计算。 LU分解 设 A A A 是 m n m\times n mn 矩阵,…...

vue实现鼠标拖拽div左右移动的功能

直接代码&#xff1a; <template><div class"demo"><div class"third-part" id"发展历程"><div class"title">发展历程</div><div class"content" id"nav" v-if"dataList…...

基于Python和mysql开发的商城购物管理系统分为前后端(源码+数据库+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python和mysql开发的商城购物管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过…...

MySQL内外连接、索引特性

目录 内连接 外连接 索引特性 理解索引 删除索引 MySQL内外连接是一种用于联接两个或多个表的操作。内连接只返回满足连接条件的行&#xff0c;外连接返回满足条件和不满足条件的行。 内连接 SQL如下&#xff1a; SELECT ... FROM t1 INNER JOIN t2 ON 连接条件 [INNER …...

滚动条设置

不同浏览器滚动条样式及滚动定位 是否可以滚动 overflow&#xff1a;scroll overflow&#xff1a;autooverflow:scroll – 只有超出了盒子才会有滚动条 overflow:auto – 一直有滚动的盒子&#xff0c;只是超出了盒子才会出现滚动条滑块&#xff0c;可以滚动 谷歌浏览器滚动…...

【AI】机器学习——感知机

文章目录 4.1 感知机基本概念4.2 策略4.2.1 数据集的线性可分性4.2.2 学习策略目标损失函数的构造关于距离的解释 4.3 算法4.3.1 原始形式损失函数的梯度下降法 4.3.2 PLA例题4.3.3 算法收敛性 4.4 PLA对偶形式4.4.1 原始PLA分析4.4.2 PLA对偶形式4.4.3 优点 4.1 感知机基本概念…...

蓝牙遥控器在T2-U上的应用

文章目录 简介优势使用流程示例代码遥控器命令表遥控器代码实现开启遥控器配对功能运行 简介 Tuya beacon 协议是基于 BLE 广播通信技术&#xff0c;完善配对解绑、组包拆包、群组管理、加密解密、安全策略&#xff0c;形成的一种轻量、安全的可接入涂鸦云的蓝牙协议。 蓝牙 …...

数据驱动的数字营销与消费者运营

引言&#xff1a;基于海洋馆文旅企业在推广宣传中&#xff0c;如何通过指标体系量化分析广告收益对业务带来的收益价值的思考&#xff1f; 第一部分:前链路引流投放的策略与实战 1.1 动态广告的实现: 偶然与必然 动态广告是一种基于实时数据和用户行为的广告形式&#xff0c;它…...

Qt点亮I.MX6U开发板的一个LED

本篇开始将会介绍与开发版相关的Qt项目&#xff0c;首先从点亮一个LED开始。I.MX6U和STM32MP157的相关信息都会用到&#xff0c;但是后期还是将I.MX6U的学习作为重点。当然其他开发版的开发也可以参考本博文。 文章目录 1. Qt是如何操控开发板上的一个LED2. 出厂内核设备树中注…...

网络摄像头-流媒体服务器-视频流客户端

取电脑的视频流 当涉及交通事件检测算法和摄像头视频数据处理时&#xff0c;涉及的代码案例可能会非常复杂&#xff0c;因为这涉及到多个组件和技术。以下是一个简单的Python代码示例&#xff0c;演示如何使用OpenCV库捕获摄像头视频流并进行实时车辆检测&#xff0c;这是一个…...

Django05_反向解析

Django05_反向解析 5.1 反向解析概述 随着功能的不断扩展&#xff0c;路由层的 url 发生变化&#xff0c;就需要去更改对应的视图层和模板层的 url&#xff0c;非常麻烦&#xff0c;不便维护。这个时候我们可以通过反向解析&#xff0c;将 url解析成对应的 试图函数 通过 path…...

基于HTML、CSS和JavaScript制作一个中秋节倒计时网页

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 这个项目使用HTML、CSS和…...

富斯I6刷10通道固件

使用USB转串口模块刷写10通道固件 一、下载固件 1. 十通道英文固件 下载地址: https://github.com/benb0jangles/FlySky-i6-Mod-/tree/master 选择 FlySky-i6-Mod–master \ 10ch Mod i6 Updater \ 10ch_MOD_i6_Programmer_V1 路径下的文件,亲测可用。 2. 原版六通道中…...

vector的模拟实现 总结

vector的模拟实现 总结 vector.hTest.cpp vector.h 1、迭代器的实现 #pragma oncenamespace JPC {template<class T>class vector{public://对于存储空间是连续的结构而言&#xff0c;可以用原身指针来 模拟实现 迭代器。typedef T* iterator;typedef const T* const_i…...

k8s中的有状态,无状态,pv、pvc等

数据库是一个典型的有状态服务&#xff0c;他的部署和无状态服务是不一样的。 PostgresSQL----基于Kubernetes部署PostgresSQL-CSDN博客 一、创建SC、PV和PVC存储对象 二、部署PostgresSQL Volume Kubernetes 中文指南——云原生应用架构实战手册 有状态应用&#xff1a; …...

springboot+jxls复杂excel模板导出

JXLS 是基于 Jakarta POI API 的 Excel 报表生成工具&#xff0c;可以生成精美的 Excel 格式报表。它采用标签的方式&#xff0c;类似 JSP 标签&#xff0c;写一个 Excel 模板&#xff0c;然后生成报表&#xff0c;非常灵活&#xff0c;简单&#xff01; Java 有一些用于创建 …...

GEMM内核与MHA中的寄存器分配优化策略

1. GEMM内核与寄存器分配基础解析通用矩阵乘法&#xff08;GEMM&#xff09;作为深度学习计算的核心算子&#xff0c;其性能表现直接决定了神经网络训练和推理的效率。在硬件层面&#xff0c;寄存器分配的优劣往往能带来数倍的性能差异。我们以典型的GEMM运算C αAB βC为例&…...

古戏台构件声学特性的时域有限差分方法【附模型】

✨ 长期致力于时域有限差分法、窑洞、戏台、八字墙、共形技术研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;1&#xff09;曲面共形网格快速生成算法&#xff1a; …...

告别虚拟机卡顿:在Windows 11的WSL2里搞定Lichee Nano交叉编译环境

告别虚拟机卡顿&#xff1a;在Windows 11的WSL2里搞定Lichee Nano交叉编译环境 对于嵌入式开发者来说&#xff0c;配置开发环境往往是个令人头疼的问题。传统虚拟机方案虽然能提供完整的Linux体验&#xff0c;但资源占用高、启动慢、与宿主系统交互不便等问题一直困扰着开发者。…...

DIY复刻经典:Texar Audio Prism动态处理器克隆套件全攻略

1. 项目概述&#xff1a;Texar Audio Prism 克隆套件如果你在专业音频圈子里混过一段时间&#xff0c;尤其是对上世纪八九十年代那些经典的、带点“魔法”色彩的外置动态处理器感兴趣&#xff0c;那么“Texar Audio Prism”这个名字你大概率不会陌生。它不是最常见的1176或者LA…...

雪球网md5__1038参数逆向解析与Node.js复现

1. 这不是“破解”&#xff0c;而是对前端加密逻辑的常规逆向还原你打开雪球网任意一只股票详情页&#xff0c;F12 打开开发者工具&#xff0c;切到 Network 面板&#xff0c;刷新页面——很快就能在 XHR 请求里捕获到类似这样的接口&#xff1a;https://xueqiu.com/stock/cube…...

孤舟笔记 互联网常用框架篇三 Dubbo是如何动态感知服务下线的?注册中心和服务端双保险

文章目录先说结论机制一&#xff1a;注册中心通知机制二&#xff1a;心跳检测机制三&#xff1a;连接事件感知机制四&#xff1a;定时拉取四种机制的协作回答技巧与点评加分回答面试官点评个人网站微服务环境下&#xff0c;服务实例随时可能上下线——重启、扩容、宕机……调用…...

Keil µVision链接器错误204解决方案

1. 问题现象与背景解析最近在使用Keil Vision进行嵌入式开发时&#xff0c;不少工程师遇到了一个令人头疼的链接器错误。具体表现为编译时出现"FATAL ERROR 204: INVALID KEYWORD"的致命错误&#xff0c;错误位置指向链接器控制文件中的特定行。这个问题在C166和C51两…...

FairyGUI Unity鼠标悬停与点击对象获取原理与实战

1. 这不是“加个OnMouseEnter就能用”的事&#xff1a;FairyGUI在Unity中处理鼠标交互的真实困境很多人第一次在Unity里集成FairyGUI&#xff0c;想实现“鼠标悬停显示提示”或“点击高亮当前按钮”&#xff0c;下意识就去翻Unity的MonoBehaviour文档&#xff0c;找OnMouseEnte…...

通过curl命令快速测试Taotoken大模型API的连通性与返回格式

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 通过curl命令快速测试Taotoken大模型API的连通性与返回格式 在集成大模型能力到应用时&#xff0c;开发者通常需要一种快速、轻量的…...

OpenIPC开源固件:5分钟解锁网络摄像头的终极控制权

OpenIPC开源固件&#xff1a;5分钟解锁网络摄像头的终极控制权 【免费下载链接】firmware Alternative IP Camera firmware from an open community 项目地址: https://gitcode.com/gh_mirrors/fir/firmware 还在为网络摄像头的封闭系统而烦恼吗&#xff1f;想要完全掌控…...