环形缓冲区

什么是环形缓冲区

环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。

环形缓冲区的用途

  1. 串口通信
    在嵌入式设备中,串口是常用的通信接口。环形缓冲区可用于缓存收发数据,平衡通信速率差异。
  2. 音视频数据处理
    音视频数据往往是连续的数据流。使用环形缓冲区可以平滑数据的生成和消耗,避免数据丢失或延迟。
  3. 传感器数据采集
    传感器数据通常以固定频率采样。环形缓冲区可作为数据采集和处理之间的缓冲,降低实时性要求。
  4. 多线程数据传递
    在多线程编程中,环形缓冲区是一种简单高效的线程间通信方式,无需复杂的同步操作。
  5. 数据打包与解析
    一些通信协议使用特定的数据帧格式。环形缓冲区可用于数据的打包和解析,保证数据的完整性。

环形缓冲区的实现示例

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

// 环形缓冲区大小
#define BUFFER_SIZE 256

// 定义解析器状态
typedef enum {
    STATE_WAIT_START,    // 等待消息开始
    STATE_READ_LENGTH,   // 读取消息长度
    STATE_READ_DATA      // 读取消息数据
} ParserState;

typedef struct {
    uint8_t buffer[BUFFER_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} RingBuffer;


void RingBuffer_Init(RingBuffer *rb) {
    rb->head = 0;
    rb->tail = 0;
}


bool RingBuffer_Write(RingBuffer *rb, uint8_t data) {
    uint16_t next = (rb->head + 1) % BUFFER_SIZE;
    if (next == rb->tail) {
        // 缓冲区满
        return false;
    }
    rb->buffer[rb->head] = data;
    rb->head = next;
    return true;
}


bool RingBuffer_Read(RingBuffer *rb, uint8_t *data) {
    if (rb->head == rb->tail) {
        // 判满
        return false;
    }
    *data = rb->buffer[rb->tail];
    rb->tail = (rb->tail + 1) % BUFFER_SIZE;
    return true;
}

// 处理完整消息的函数
void process_message(const uint8_t *msg, uint8_t length) {


    for (uint8_t i = 0; i < length; i++) {
     
         printf("%c", msg[i]);
    }
     printf("\n");
}

// 有限状态机解析器
void parse_messages(RingBuffer *rb) {
    static ParserState state = STATE_WAIT_START;
    static uint8_t msg_length = 0;
    static uint8_t msg_index = 0;
    static uint8_t message[128]; //

    uint8_t byte;
    while (RingBuffer_Read(rb, &byte)) {
        switch (state) {
            case STATE_WAIT_START:  // 等待消息起始
                if (byte == 0xAA) { // 0xAA是消息起始标志
                    state = STATE_READ_LENGTH;
                }
                break;

            case STATE_READ_LENGTH:  // 读取消息长度
                msg_length = byte;
                if (msg_length > 0 && msg_length < sizeof(message)) {
                    msg_index = 0;
                    state = STATE_READ_DATA;
                } else {
                    // 无效长度,重置状态
                    state = STATE_WAIT_START;
                }
                break;

            case STATE_READ_DATA:  // 读取消息数据
                message[msg_index++] = byte;
                if (msg_index >= msg_length) {
                    process_message(message, msg_length);
                    state = STATE_WAIT_START;
                }
                break;

            default:
                state = STATE_WAIT_START;
                break;
        }
    }
}

// 发送函数
void threadA_send(RingBuffer *rb, const uint8_t *msg, uint8_t length) {
    RingBuffer_Write(rb, 0xAA); // 起始标志
    RingBuffer_Write(rb, length); // 长度字段
    for (uint8_t i = 0; i < length; i++) {
        RingBuffer_Write(rb, msg[i]);
    }
}

// 接收函数
void threadB_receive(RingBuffer *rb) {
    parse_messages(rb);
}


void test_ring_buffer(void) {
    // 1. 初始化环形缓冲区
    RingBuffer rb;
    RingBuffer_Init(&rb);
    
    printf("=== 环形缓冲区测试开始 ===\n\n");

    // 2. 测试基本消息发送和接收
    printf("测试1: 基本消息收发\n");
    const uint8_t test_msg1[] = "Hello World";
    threadA_send(&rb, test_msg1, sizeof(test_msg1) - 1);
    threadB_receive(&rb);
    
    // 3. 测试空缓冲区
    printf("\n测试2: 空缓冲区读取\n");
    uint8_t temp;
    if (!RingBuffer_Read(&rb, &temp)) {
        printf("空缓冲区测试通过: 无法从空缓冲区读取数据\n");
    }
    
    // 4. 测试缓冲区满状态
    printf("\n测试3: 缓冲区满状态\n");
    uint8_t large_msg[BUFFER_SIZE];
    for (int i = 0; i < BUFFER_SIZE; i++) {
        large_msg[i] = 'A' + (i % 26);  // 填充A-Z循环
    }
    
    bool write_result = true;
    int write_count = 0;
    while (write_result && write_count < BUFFER_SIZE + 10) {
        write_result = RingBuffer_Write(&rb, large_msg[write_count % BUFFER_SIZE]);
        write_count++;
    }
    printf("写入计数: %d (应小于缓冲区大小)\n", write_count - 1);
    
    // 5. 测试长消息分段发送
    printf("\n测试4: 长消息分段发送\n");
    RingBuffer_Init(&rb);  // 重新初始化
    const uint8_t long_msg[] = "This is a long message to test multiple segments";
    const int SEGMENT_SIZE = 10;
    
    for (size_t i = 0; i < (size_t)(sizeof(long_msg) - 1); i += (size_t)SEGMENT_SIZE) {
        size_t current_length = ((sizeof(long_msg) - 1 - i) < (size_t)SEGMENT_SIZE) ? 
                               (sizeof(long_msg) - 1 - i) : (size_t)SEGMENT_SIZE;
        threadA_send(&rb, &long_msg[i], current_length);
        threadB_receive(&rb);
    }
    
    // 6. 测试无效消息
    printf("\n测试5: 无效消息处理\n");
    uint8_t invalid_msg[] = {0xAA, 0xFF, 0x01, 0x02};  // 无效长度
    for (size_t i = 0; i < sizeof(invalid_msg); i++) {
        RingBuffer_Write(&rb, invalid_msg[i]);
    }
    threadB_receive(&rb);
    
    // 7. 测试快速读写切换
    printf("\n测试6: 快速读写切换\n");
    const uint8_t test_msg2[] = "Test";
    for (int i = 0; i < 5; i++) {
        threadA_send(&rb, test_msg2, sizeof(test_msg2) - 1);
        threadB_receive(&rb);
    }
    
    printf("\n=== 环形缓冲区测试完成 ===\n");
}


int main() {
    test_ring_buffer();
    return 0;
}

总结

与传统的数组或链表相比,环形缓冲区有以下优点:
1.无需频繁移动数据。环形缓冲区的读写指针移动不会导致数据搬移,效率更高。
2.自动处理缓冲区"满"和"空"的状态。通过读写指针的关系可以判断缓冲区状态,无需额外的计数器。
3. 适用于生产者-消费者模型。一个线程写入数据,另一个线程读取数据,天然支持异步处理。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据库的联合查询

数据库的联合查询 简介为什么要使⽤联合查询多表联合查询时MYSQL内部是如何进⾏计算的构造练习案例数据案例&#xff1a;⼀个完整的联合查询的过程 内连接语法⽰例 外连接语法 ⽰例⾃连接应⽤场景示例表连接练习 ⼦查询语法单⾏⼦查询多⾏⼦查询多列⼦查询在from⼦句中使⽤⼦查…

Oracle 23ai 对应windows版本安装配置PLSQL导入pde文件navicat连接Oracle

因为有一个pde文件需要查看里面的数据&#xff0c;所以这次需要配置本地oracle数据库&#xff0c;并且导入数据&#xff0c;因为还有navicat&#xff0c;所以就想用navicat去连接查看。 1、找到官网。 Get Started with Oracle Database 23ai | Oracle 2、下载windows版本。…

Juc01_多线程概述、四种实现方式、常用方法API、生命周期、买票案例、synchronized锁

目录 本章讲述内容&#xff1a;多线程概述、四种实现方式、常用方法API、生命周期、买票案例、synchronized锁 ①. 多线程的概述 ②. 多线程的实现方式 ①. 继承Thread ②. 实现Runnable接口 ③. Callable接口(创建线程) ④. 线程池 ③. 设置和获取线程名称 ④. 线程…

一个高度可扩展的 Golang ORM 库【GORM】

GORM 是一个功能强大的 Golang 对象关系映射&#xff08;ORM&#xff09;库&#xff0c;它提供了简洁的接口和全面的功能&#xff0c;帮助开发者更方便地操作数据库。 1. 完整的 ORM 功能 • 支持常见的关系模型&#xff1a; • Has One&#xff08;一对一&#xff09; • …

ubuntu24挂载硬盘记录

1、显示硬盘及所属分区情况。在终端窗口中输入如下命令&#xff1a; sudo fdisk -l 找到自己硬盘的分区 我的地址/dev/sda 2、显示硬盘及所属分区情况。在终端窗口中输入如下命令&#xff0c;格式化自己硬盘&#xff1a; sudo mkfs -t ext4 /dev/sda 3、在终端窗口中输入如下…

Flink四大基石之Window

为什么要用WIndow 在流处理应用中&#xff0c;数据是连续不断的&#xff0c;有时我们需要做一些聚合类的处理&#xff0c;例如&#xff1a;在过去的1分钟内有多少用户点击了我们的网页。 在这种情况下&#xff0c;我们必须定义一个窗口(window)&#xff0c;用来收集最近1分钟内…

使用ENSP实现默认路由

一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable 进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为2.2.2.1/24 ip address 2.2.2.1 24进入g0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置为1.…

《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试

一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器&#xff0c;旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…

vue 预览pdf 【@sunsetglow/vue-pdf-viewer】开箱即用,无需开发

sunsetglow/vue-pdf-viewer 开箱即用的pdf插件sunsetglow/vue-pdf-viewer, vue3 版本 无需多余开发&#xff0c;操作简单&#xff0c;支持大文件 pdf 滚动加载&#xff0c;缩放&#xff0c;左侧导航&#xff0c;下载&#xff0c;页码&#xff0c;打印&#xff0c;文本复制&…

1-golang_org_x_crypto_bcrypt测试 --go开源库测试

1.实例测试 package mainimport ("fmt""golang.org/x/crypto/bcrypt" )func main() {password : []byte("mysecretpassword")hashedPassword, err : bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)if err ! nil {fmt.Println(err)…

嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点

目录 一、static 1、static 修饰局部变量 2、 static 修饰全局变量 3、static 修饰函数 4、static 修饰类成员 5、小结 二、const 1、const 修饰普通变量 2、const 修饰指针 3、const 修饰函数参数 4. const 修饰函数返回值 5. const 修饰类成员 6. const 与 #defi…

超高流量多级缓存架构设计!

文章内容已经收录在《面试进阶之路》&#xff0c;从原理出发&#xff0c;直击面试难点&#xff0c;实现更高维度的降维打击&#xff01; 文章目录 电商-多级缓存架构设计多级缓存架构介绍多级缓存请求流程负载均衡算法的选择轮询负载均衡一致性哈希负载均衡算法选择 应用层 Ngi…

【C++ 算法进阶】算法提升二十三

目录 左右数组相减绝对值最大值 &#xff08;题意代换&#xff09;题目题目分析 可整合数组 &#xff08;题意代换&#xff09;题目题目分析代码 水王问题题目题目分析代码水王问题变形思路讲解 合并石头的最低成本 &#xff08;动态规划&#xff09;题目题目分析代码 左右数组…

solr 远程命令执行 (CVE-2019-17558)

漏洞描述 Apache Velocity是一个基于Java的模板引擎&#xff0c;它提供了一个模板语言去引用由Java代码定义的对象。Velocity是Apache基金会旗下的一个开源软件项目&#xff0c;旨在确保Web应用程序在表示层和业务逻辑层之间的隔离&#xff08;即MVC设计模式&#xff09;。 Apa…

idea怎么打开两个窗口,运行两个项目

今天在开发项目的时候&#xff0c;前端希望运行一下以前的项目&#xff0c;于是就需要开两个 idea 窗口&#xff0c;运行两个项目 这里记录一下如何设置&#xff1a;首先依次点击&#xff1a; File -> Settings -> Appearance & Behavior ->System Settings 看到如…

PPT分享 | IBM集团业务流程架构顶层规划-订单到交付-销售到回款方案

PPT下载链接见文末~ IBM业务流程规划方法是一套结构化、体系化的流程设计理论&#xff0c;其企业流程框架&#xff08;EPF&#xff09;是一种用于企业业务流程架构设计梳理的方法论。 一、IBM业务流程规划方法的核心 IBM的BPM&#xff08;业务流程管理&#xff09;流程管理体…

MySQL闪回恢复:轻松应对数据误删,数据安全有保障

在数据库管理中&#xff0c;数据误删是一个常见且棘手的问题。传统的数据恢复方法可能涉及复杂的操作&#xff0c;如全量备份和增量备份的恢复。MySQL的闪回恢复功能提供了一种更为简便、高效的数据恢复手段。本文将详细介绍MySQL闪回恢复的原理、配置和使用方法&#xff0c;帮…

加菲工具 - 好用免费的在线工具集合

加菲工具 https://orcc.online AI 工具 加菲工具 集合了目前主流的&#xff0c;免费可用的ai工具 文档处理 加菲工具 pdf转word、office与pdf互转等等工具都有链接 图片图标 加菲工具 统计了好用免费的在线工具 编码解码 加菲工具 base64编码解码、url编码解码、md5计算…

uniapp跨域问题解决方案

uniapp跨域问题解决方案 引言 在使用 uni-app 本地开发 H5> 平台时&#xff0c;需要使用浏览器进行调试&#xff0c;而浏览器会有跨域的问题。比如直接通过本地IP地址去访问开发中的页面&#xff0c;同时这个页面会调一些现有的接口时&#xff0c;就面临着跨域的问题。 解决…

ensp静态路由实验

一、实验目的 1、熟练掌握交换机的基本配置命令 2、熟练掌握静态路由的使用方法 3. 熟练掌握交换机端口模式 二、实验内容 需求&#xff1a; 根据要求利用现有实验设备组建小型局域网 实验设备&#xff1a; 交换机S37002台&#xff1b;PC机2台&#xff1b;路由器2台。 …