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

2023-09-13 23:04:29

本教程基于韦东山百问网出的 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 pValidEnd
    if( (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;
}

本章完
更多推荐

学习Node js:raw-body模块源码解析

raw-body是什么raw-body的主要功能是处理HTTP请求体的原始数据。它提供了以下核心功能:解析请求体:可以从HTTP请求中提取原始数据,包括文本和二进制数据。配置选项:通过配置项,可以设置请求体的大小限制、编码方式等参数。异常处理:模块能够处理异常情况,如请求体超出限制。编码转换:支持将原始数据解码为指定编

Android 网络请求方式

前言最近需要将Android项目接入物联网公司提供的接口,所以顺便给大家分享一下Android中我们常用的网络请求吧!提醒大家一下,我们遇到接口需求,一定要先在Postman上测试接口是否正确,然后再去项目上写程序来请求接口;否则,请求出问题,你都不确定是你程序写的有问题还是接口本身提供的有问题。Android网络请求

第八章 网络基本配置与应用

第八章网络基本配置与应用​Linux操作系统时随着计算机网络技术的发展而产生并发展的,因此其网络功能也十分强大。Ubuntu系统作为Linux的一种具体实现,同样集成了Linux强大的网络功能,并且也只有在网络环境下才能充分发挥Ubuntu系统的全部功能。1.网络的基本配置1.1.网络基础知识​计算机网络是指将地理位置

9.20金融科技(比特币)

​比特币的起源和发展2008年爆发全球金融危机,同年11月1日,一个自称中本聪(SatoshiNakamoto)的人在P2Pfoundation网站上发布了比特币白皮书《比特币:一种点对点的电子现金系,陈述了他对电子货币的新设想——比特币就此面世。2009年1月3日,比特币创世区块诞生。2014年9月9日,美国电商巨头

看完这篇 教你玩转渗透测试靶机Vulnhub——Grotesque:3.0.1

Vulnhub靶机Grotesque:3.0.1渗透测试详解Vulnhub靶机介绍:Vulnhub靶机下载:Vulnhub靶机安装:①:信息收集:②:漏洞发现:③:LFI漏洞利用(本地文件包含漏洞):④:SSH登入:⑤:提权:⑥:获取FLAG:Vulnhub靶机渗透总结:Vulnhub靶机介绍:vulnhub是个提供各

比特币的蒙提霍尔问题

把钱放在嘴边我们在比特币上建立了蒙提霍尔问题模拟。如果您知道概率谜题的正确答案,不仅炫耀您的数学技能,还会获得金钱奖励。它完全无需信任地在链上运行。蒙提霍尔问题蒙提霍尔问题(三门问题)是一个以蒙提霍尔命名的概率谜题,蒙提霍尔是电视节目《让我们做个交易》的原主持人。这是一个著名的反直觉统计难题,其解决方案非常荒谬,即使被

Python爬虫基础(三):使用Selenium动态加载网页

文章目录系列文章索引一、Selenium简介1、什么是selenium?2、为什么使用selenium3、安装selenium(1)谷歌浏览器驱动下载安装(2)安装selenium二、Selenium使用1、简单使用2、元素定位3、获取元素信息4、交互三、Phantomjs使用(停更)1、什么是Phantomjs2、下

BD就业复习第一天

hive1.分区分桶在Hive中,分区(Partition)和分桶(Bucketing)都是用于数据管理和查询性能优化的技术。它们有不同的用途和特点。分区(Partition):定义:分区是将数据按照某一列或多列的值划分为不同的子目录,使数据可以按照分区列的值进行组织。例如,可以根据日期将数据分为不同的分区,每个分区对

Node18.x基础使用总结(一)

Node18.x基础使用总结1、Node安装2、Buffer2.1、概念2.2、特点2.3、使用2.3.1、创建Buffer2.3.2、Buffer与字符串的转化2.3.3、Buffer的读写3、fs模块3.1、文件写入3.1.1、writeFile异步写入3.1.2、writeFileSync同步写入3.1.3、ap

相对论的应用:GPS导航

“但是数学享有盛誉还有另一个原因:正是数学为精确的自然科学提供了一定程度的安全保障,而没有数学,它们就无法实现这一点。”“就现实而言,数学定律是不确定的;就其确定而言,它们并不涉及现实。”—阿尔伯特·爱因斯坦爱因斯坦的相对论彻底改变了我们对宇宙的理解,从根本上改变了我们感知时间和空间的方式。该理论目前是当代物理学的孪生

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(二)

目录前言总体设计系统整体结构图系统流程图运行环境模块实现1.数据预处理2.数据增强3.模型构建1)定义模型结构2)优化损失函数相关其它博客工程源代码下载其它资料下载前言本项目依赖于Keras深度学习模型,旨在对手语进行分类和实时识别。为了实现这一目标,项目结合了OpenCV库的相关算法,用于捕捉手部的位置,从而能够对视

热文推荐