在哪里创建网站应用市场下载app
2026/4/18 4:23:51 网站建设 项目流程
在哪里创建网站,应用市场下载app,男的直接做的视频网站,濮阳网站注册嵌入式开发居然有这神操作#xff1f;环形缓冲区让数据读写快到飞起#xff01; 你是不是也遇到过这样的崩溃时刻#xff1a;嵌入式开发里#xff0c;串口数据刚收到就丢失、传感器数据缓存半天读不出来#xff0c;或者普通数组当缓冲区时#xff0c;数据搬来搬去搞得程…嵌入式开发居然有这神操作环形缓冲区让数据读写快到飞起你是不是也遇到过这样的崩溃时刻嵌入式开发里串口数据刚收到就丢失、传感器数据缓存半天读不出来或者普通数组当缓冲区时数据搬来搬去搞得程序卡顿不已明明是简单的数据存储需求却硬生生变成了让人头秃的难题别急今天要给大家安利一个嵌入式开发的“宝藏工具”——环形缓冲区也叫Ring Buffer。它就像一个会自动循环的“数据仓库”不用手动搬运数据读写速度快到离谱堪称串口通信、传感器数据缓存、嵌入式系统数据处理的“救星”看完这篇你再也不用为数据缓存问题熬夜秃头啦一、环形缓冲区嵌入式开发的“循环仓库”先给大家通俗解释下环形缓冲区就是一个固定大小的“先进先出FIFO”数据结构你可以把它想象成一个圆形的传送带数据就像传送带上的包裹先放上去的先被取走。它最神奇的地方在于“循环复用”——当数据写到缓冲区末尾时不用把前面的数据往前挪而是直接绕回开头继续写读取数据也是一样读到末尾就自动回到起点。这种设计直接省去了普通数组缓冲区的“数据搬移”麻烦读写效率直接拉满和普通数组缓冲区比起来环形缓冲区的优势简直碾压无数据搬移开销不用浪费CPU资源挪数据省下来的性能能做更多正事读写效率超高不管缓冲区里有多少数据读写操作都是O(1)时间复杂度简单说就是“一秒完成”内存利用率高固定大小的内存能循环使用不会造成内存浪费。不管是单片机裸机开发、串口数据接收还是传感器实时数据缓存环形缓冲区都能轻松hold住说是嵌入式开发的“必备神器”一点不为过二、核心实现原理3个要素搞定“循环魔法”很多人觉得环形缓冲区听起来高深其实核心原理特别简单就靠“2个指针1个固定数组”所有逻辑都围绕这三者展开咱们一步步拆解开讲1. 基础组成3个核心要素存储数组这是数据的“物理仓库”就是一块固定长度的连续内存数据就实实在在存在这里读指针rd_idx相当于仓库的“取货员”永远指向下一个要读取的数据位置取完货就自动往前走写指针wr_idx相当于仓库的“送货员”永远指向下一个要存放数据的位置送完货也自动往前走。这俩指针就像仓库里配合默契的搭档一个只管存一个只管取分工明确还不打架2. 关键难题怎么区分“空”和“满”环形缓冲区有个特别有意思的问题当读指针和写指针指向同一个位置时到底是缓冲区空了所有数据都读完了还是满了所有位置都存满了这就像仓库里“送货员”和“取货员”站在同一个 spot你没法直接判断是货送完了还是仓库满了。业界最通用、最容易实现的解决方案来了——预留1个字节的存储空间不用就像仓库特意留了一个“空位”用这个小小的牺牲换来了绝对无歧义的判断规则性价比超高缓冲区为空读指针 写指针rd_idx wr_idx→ 相当于“送货员”和“取货员”碰面了仓库里没货了缓冲区为满(wr_idx 1) % 缓冲区总大小 rd_idx → 相当于“送货员”往前一步就追上“取货员”了仓库里再也塞不下新货有效存储容量缓冲区总大小 - 1 → 比如总大小是8实际能存7个数据少一个位置换“不纠结”太值了这里的“%”取模运算是实现“环形”的核心魔法它能让指针走到数组末尾时自动“瞬移”回起点。比如缓冲区大小是8当写指针到7最后一个位置时(71)%80直接回到开头完美实现循环3. 核心操作读写数据原来这么简单搞懂了指针和判断规则读写操作就像“按流程办事”一步都不复杂写数据先看看缓冲区满没满用上面的满判断规则如果没满就把数据放到写指针当前的位置然后写指针往前挪一步记得用取模运算实现循环读数据先看看缓冲区空没空用上面的空判断规则如果没空就从读指针当前的位置取数据然后读指针往前挪一步同样用取模运算。整个过程没有多余的步骤不用搬数据不用等时间效率直接拉满三、完整可运行代码拿来就用附带测试用例说了这么多理论不如直接上代码下面是完整的环形缓冲区实现代码支持字节型数据缓存包含初始化、判空、判满、写数据、读数据、获取有效数据量全套核心接口还附带测试用例无任何依赖直接编译就能运行#includestdio.h#includestdint.h#includestdbool.h// 配置区可按需修改像搭积木一样简单#defineBUF_SIZE8// 缓冲区总大小实际可用 BUF_SIZE-17 个位置预留1个防歧义typedefuint8_tbuf_data_t;// 缓冲区存储的数据类型支持uint8/uint16/int等后续教你修改// 环形缓冲区结构体定义相当于给“仓库”画设计图typedefstruct{buf_data_tbuffer[BUF_SIZE];// 数据存储数组仓库的“货架”uint16_trd_idx;// 读指针取货员的位置uint16_twr_idx;// 写指针送货员的位置}RingBuffer_t;// 核心接口声明告诉编译器有这些“工具函数”voidring_buf_init(RingBuffer_t*rb);// 初始化缓冲区给仓库开业做准备boolring_buf_is_empty(RingBuffer_t*rb);// 判断缓冲区是否为空仓库没货了吗boolring_buf_is_full(RingBuffer_t*rb);// 判断缓冲区是否为满仓库放不下了吗boolring_buf_write(RingBuffer_t*rb,buf_data_tdata);// 写入1个数据送货员放包裹boolring_buf_read(RingBuffer_t*rb,buf_data_t*data);// 读取1个数据取货员拿包裹uint16_tring_buf_get_len(RingBuffer_t*rb);// 获取有效数据长度仓库里现在有多少货// 核心接口实现工具函数的具体用法/** * brief 初始化环形缓冲区 * 作用给读指针和写指针“分配初始位置”让仓库准备好接收数据 */voidring_buf_init(RingBuffer_t*rb){if(rbNULL)return;// 防止传入空指针避免程序崩溃安全第一rb-rd_idx0;// 读写指针都从0位置开始送货员和取货员都站在起点rb-wr_idx0;}/** * brief 判断缓冲区是否为空 * 返回值true空false非空 */boolring_buf_is_empty(RingBuffer_t*rb){if(rbNULL)returntrue;// 指针为空就默认是空缓冲区return(rb-rd_idxrb-wr_idx);// 读写指针碰面没货了}/** * brief 判断缓冲区是否为满 * 返回值true满false未满 */boolring_buf_is_full(RingBuffer_t*rb){if(rbNULL)returntrue;// 指针为空就默认是满缓冲区避免出错// 核心判断写指针下一个位置 读指针 → 仓库满了return((rb-wr_idx1)%BUF_SIZE)rb-rd_idx;}/** * brief 写入单个数据到缓冲区 * param data要写入的数据要送的包裹 * return true写入成功false缓冲区满写入失败 */boolring_buf_write(RingBuffer_t*rb,buf_data_tdata){// 先判断指针为空或者仓库满了就不写了if(rbNULL||ring_buf_is_full(rb)){returnfalse;}rb-buffer[rb-wr_idx]data;// 把数据放到写指针当前位置放包裹rb-wr_idx(rb-wr_idx1)%BUF_SIZE;// 写指针后移循环处理送货员往前走returntrue;}/** * brief 从缓冲区读取单个数据 * param data输出参数存储读取到的数据拿包裹的容器 * return true读取成功false缓冲区空读取失败 */boolring_buf_read(RingBuffer_t*rb,buf_data_t*data){// 先判断指针为空、没给容器或者仓库没货就不读了if(rbNULL||dataNULL||ring_buf_is_empty(rb)){returnfalse;}*datarb-buffer[rb-rd_idx];// 从读指针位置取数据拿包裹rb-rd_idx(rb-rd_idx1)%BUF_SIZE;// 读指针后移循环处理取货员往前走returntrue;}/** * brief 获取缓冲区中有效数据的长度 * 返回值有效数据个数仓库里现有包裹数量 */uint16_tring_buf_get_len(RingBuffer_t*rb){if(rbNULL)return0;// 指针为空就返回0个数据// 计算方式(写指针 - 读指针 缓冲区大小) % 缓冲区大小避免负数return(rb-wr_idx-rb-rd_idxBUF_SIZE)%BUF_SIZE;}// 测试用例验证功能好不好用放心抄intmain(void){RingBuffer_t rb;// 定义一个环形缓冲区对象新建一个仓库buf_data_twrite_data0,read_data;// 要写入的数据和读取到的数据uint8_ti;// 1. 初始化缓冲区仓库开业ring_buf_init(rb);printf( 环形缓冲区测试总大小%d可用大小%d\n,BUF_SIZE,BUF_SIZE-1);// 2. 写入7个数据仓库最大可用容量printf(\n1. 写入7个数据);for(i0;i7;i){write_data;// 数据从1开始递增if(ring_buf_write(rb,write_data)){printf(%d ,write_data);// 打印成功写入的数据}}printf(\n当前有效数据量%d\n,ring_buf_get_len(rb));printf(缓冲区是否为满%s\n,ring_buf_is_full(rb)?是:否);// 3. 尝试写入第8个数据应该失败因为仓库满了if(!ring_buf_write(rb,write_data)){printf(2. 写入第8个数据失败缓冲区已满\n);}// 4. 读取所有数据把仓库里的货全拿出来printf(3. 读取所有数据);while(!ring_buf_is_empty(rb)){if(ring_buf_read(rb,read_data)){printf(%d ,read_data);// 打印读取到的数据}}printf(\n当前有效数据量%d\n,ring_buf_get_len(rb));printf(缓冲区是否为空%s\n,ring_buf_is_empty(rb)?是:否);// 5. 空缓冲区读取应该失败因为没货了if(!ring_buf_read(rb,read_data)){printf(4. 空缓冲区读取失败符合预期\n);}return0;}四、编译运行结果一目了然功能拉满写完代码怎么验证好不好用用GCC编译就行编译命令超简单gcc ring_buf.c -o ring_buf ./ring_buf运行后会输出这样的结果和预期完全一致说明功能没问题 环形缓冲区测试总大小8可用大小7 1. 写入7个数据1 2 3 4 5 6 7 当前有效数据量7 缓冲区是否为满是 2. 写入第8个数据失败缓冲区已满 3. 读取所有数据1 2 3 4 5 6 7 当前有效数据量0 缓冲区是否为空是 4. 空缓冲区读取失败符合预期从输出能看到7个数据顺利写入第8个因为缓冲区满失败读取时能完整读出所有数据空缓冲区读取也会失败完全符合我们的设计逻辑五、接口使用说明4步上手小白也会用很多小伙伴拿到代码会犯愁“这么多函数我该怎么用啊” 其实超简单跟着这4步走分分钟搞定基础使用步骤4步走定义缓冲区对象就像新建一个“仓库”一行代码搞定RingBuffer_t rb;初始化缓冲区给“仓库”做好开业准备ring_buf_init(rb);写数据往“仓库”里放数据比如放一个0x12ring_buf_write(rb, 0x12);读数据从“仓库”里拿数据把拿到的数据存到data里buf_data_t data; ring_buf_read(rb, data);常用接口速查表收藏起来随用随查函数名功能返回值 / 说明ring_buf_init初始化缓冲区无调用就行ring_buf_is_empty判断缓冲区是否为空true空false非空ring_buf_is_full判断缓冲区是否为满true满false未满ring_buf_write写入单个数据true成功false失败缓冲区满ring_buf_read读取单个数据true成功false失败缓冲区空ring_buf_get_len获取有效数据量无符号整数比如返回7就是有7个有效数据就像查字典一样需要什么功能就调用对应的函数完全不用记复杂逻辑六、进阶扩展按需修改适配各种场景基础版代码已经能满足大部分需求但如果你的场景比较特殊比如要存整数、要更大的缓冲区或者想批量读写数据不用重写代码简单修改就行超灵活1. 修改存储数据类型想存啥就存啥默认代码里存的是uint8_t字节型数据但实际开发中可能需要存整数、浮点型甚至自定义的结构体。没关系只需要修改1行代码// 原配置字节型默认typedefuint8_tbuf_data_t;// 想存整型改这里typedefintbuf_data_t;// 想存浮点型改这里typedeffloatbuf_data_t;// 想存自定义结构体比如存ID和数值改这里typedefstruct{intid;floatval;}buf_data_t;修改后整个缓冲区的读写都会自动适配新的数据类型不用改其他代码是不是超方便2. 修改缓冲区大小想要多大就多大如果觉得默认的8个总大小不够用想搞个更大的“仓库”只需要修改宏定义BUF_SIZE就行支持任意正整数建议设为2的幂比如64、128取模运算效率更高#defineBUF_SIZE64// 总大小64可用63个位置预留1个防歧义改完重新编译缓冲区就变成你想要的大小了完美适配不同的存储需求3. 批量读写扩展一次搞定多个数据有时候需要一次性写很多数据或者一次性读很多数据一个个调用ring_buf_write和ring_buf_read太麻烦可以新增批量接口比如新增批量写入接口一次写入n个数据写满就停止还会返回实际写入的个数// 批量写入n个数据uint16_tring_buf_write_batch(RingBuffer_t*rb,buf_data_t*data,uint16_tn){if(rbNULL||dataNULL||n0)return0;// 参数无效就返回0uint16_twrite_cnt0;// 记录实际写入的个数for(uint16_ti0;in;i){if(ring_buf_write(rb,data[i]))write_cnt;// 写入成功就计数elsebreak;// 缓冲区满了就停止写入}returnwrite_cnt;// 返回实际写入个数方便判断是否写全}批量读取接口可以照着这个逻辑写原理一样一次性搞定多个数据效率更高七、线程安全说明这些坑千万别踩重要提醒重要提醒重要提醒说三遍咱们这个基础版环形缓冲区在单线程环境下用着完全没问题但如果涉及多线程或者中断可不能直接用不然可能会出现数据错乱、程序崩溃的情况先搞懂哪些场景是安全的单线程使用比如裸机单片机的主循环里或者中断服务函数内部只有一个“写者”和一个“读者”还在同一个线程里 → 完全安全放心用多线程/中断主循环比如线程1写数据、线程2读数据或者中断里写数据、主循环里读数据 → 不安全必须加保护不然指针操作会冲突。线程安全改造方案2种实用方法方案1裸机/嵌入式系统中断主循环→ 关闭/开启中断如果是单片机裸机开发经常会遇到“中断写、主循环读”的场景这时候可以通过关闭全局中断来避免冲突修改写数据接口就行// 写数据中断中写主循环读boolring_buf_write_isr(RingBuffer_t*rb,buf_data_tdata){__disable_irq();// 关闭全局中断不同MCU接口可能不同比如关总中断bool retring_buf_write(rb,data);// 执行写入操作__enable_irq();// 开启全局中断写完必须开不然其他中断用不了returnret;}原理很简单关闭中断后主循环里的读操作会暂停等写入完成再开启中断避免读写指针“打架”。方案2操作系统Linux/RTOS→ 加互斥锁/信号量如果是在Linux或者RTOS比如FreeRTOS、UCOS环境下多线程读写的话就需要用互斥锁来保护比如Linux下的pthread_mutexpthread_mutex_trb_mutex;// 定义一个互斥锁相当于“仓库大门钥匙”// 线程安全的写数据接口boolring_buf_write_safe(RingBuffer_t*rb,buf_data_tdata){pthread_mutex_lock(rb_mutex);// 加锁拿钥匙开门其他人不能进bool retring_buf_write(rb,data);// 执行写入操作pthread_mutex_unlock(rb_mutex);// 解锁还钥匙其他人可以用了returnret;}互斥锁的作用就是“同一时间只有一个线程能操作缓冲区”完美解决多线程冲突问题。八、总结环形缓冲区嵌入式开发的“效率神器”看到这里相信你已经彻底搞懂环形缓冲区了总结一下重点方便大家记忆核心优势固定大小FIFO无数据搬移读写效率O(1)嵌入式场景必备实现关键靠“读写指针取模运算”实现循环预留1字节解决“空满判断歧义”适用场景单线程直接用多线程/中断场景加互斥保护就行灵活适配修改BUF_SIZE改大小修改buf_data_t改数据类型批量读写可扩展接口齐全初始化、判空、判满、读写、获取长度全套接口直接复用不用重复造轮子。其实环形缓冲区一点都不复杂核心逻辑就那几点掌握后能解决嵌入式开发中很多数据缓存的难题。赶紧把代码抄过去试试体验一下“数据读写快到飞起”的快乐吧如果有其他扩展需求也可以根据上面的进阶方法自己修改超级灵活

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询