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

单片机串口环形缓冲区应该怎么写,或解析串口协议

在开发单片机的过程中,我们经常使用到串口用来与其他mcu进行通信的情况,有时候需要处理特殊的串口消息,如固定的帧头,帧尾,还有校验位的数据,通常这种情况可能还是不固定的。但是有个至少的长度,例如6位或者7位,或者更多,这种情况时候用这种环形缓存的问题来解决,通常情况下可以使能串口的空闲中断加上dma方式的接收处理,这样使得串口的接收更加高效。

话不多说,直接上代码:

/** * @file uart1_data_service.c * @brief 串口1数据接收与帧解析处理模块(含环形缓冲区读写操作及初始化) * @version 1.3 * @date 2026-06-29 * * 协议格式(固定帧头 4 字节): * [0] 0x55 * [1] 0xAA * [2] 0x03 * [3] 0x00 * [4] 命令/类型(固定,本代码未使用) * [5] 数据长度(1字节,表示后续数据字节数) * [6] ... 数据(长度由[5]决定) * [最后] 校验和(前面所有字节的累加和,不含校验本身) * * 总帧长 = 7(固定开销:帧头4 + 命令1 + 长度1) + 数据长度 + 1(校验) * = 8 + 数据长度 */ #include <string.h> // 若使用标准 memcpy 可包含 #include <stdint.h> // 标准整数类型 /*--------------------------- 类型定义(与用户代码兼容) ---------------------------*/ typedef unsigned char u8; typedef unsigned short u16; /*--------------------------- 宏定义(需根据实际协议调整) ---------------------------*/ #define HEAD_FIRST 0x55 // 帧头第1字节 #define HEAD_SECOND 0xAA // 帧头第2字节 #define PROTOCOL_VERSION 0x03 // 帧头第3字节(固定版本) #define FRAME_TYPE 0x00 // 帧头第4字节(固定类型) #define LEN_INDEX 5 // 数据长度字段偏移(从帧头开始) #define FIXED_OVERHEAD 7 // 固定开销(不含数据、不含校验): 帧头4 + 命令1 + 长度1 #define CHECKSUM_LEN 1 // 校验和字节数 #define MAX_FRAME_LEN 64 // 最大帧长(根据缓冲区大小设定) /*--------------------------- 硬件环形缓冲区相关变量 ---------------------------*/ volatile u8 Uart_Drv_Buff[64]; // 硬件环形缓冲区 volatile u8 *rx1_buf_in; // 写入指针(由中断更新) volatile u8 *rx1_buf_out; // 读取指针(由取数函数更新) volatile u8 uart1_data_process_buf[64]; // 本地处理缓冲区 /*--------------------------- 外部函数声明(若使用标准库可替换) ---------------------------*/ // 若使用标准 memcpy,可注释掉下面两行并包含 <string.h> extern void my_memcpy(u8 *dst, const u8 *src, u16 len); // 内存搬移(用户需实现) /*--------------------------- 环形缓冲区初始化函数 ---------------------------*/ /** * @brief 初始化串口接收环形缓冲区,将读写指针复位到缓冲区起始 * @note 应在系统启动时调用一次 */ void uart1_rx_buffer_init(void) { rx1_buf_in = (u8 *)Uart_Drv_Buff; rx1_buf_out = (u8 *)Uart_Drv_Buff; } /*--------------------------- 环形缓冲区写入函数(供中断调用) ---------------------------*/ /** * @brief 串口接收中断写入函数(将接收到的字节存入环形缓冲区) * @param value 接收到的字节值 * @note 该函数应在 UART 接收中断服务程序中调用 * 当缓冲区满时,新数据被丢弃(可根据需要添加错误计数) */ void uart1_receive_input(u8 value) { // 判断缓冲区是否已满 if (rx1_buf_out == rx1_buf_in + 1) { // 情况1:写指针紧跟读指针之后(缓冲区满) // 缓冲区已满,丢弃该字节(可根据需求增加溢出计数) // 例如: rx1_overflow_count++; } else if ((rx1_buf_in > rx1_buf_out) && ((rx1_buf_in - rx1_buf_out) >= sizeof(Uart_Drv_Buff))) { // 情况2:写指针在读指针之后且差值达到缓冲区大小(缓冲区满) // 缓冲区已满,丢弃该字节 // 例如: rx1_overflow_count++; } else { // 缓冲区未满,写入数据 if (rx1_buf_in >= (u8 *)(Uart_Drv_Buff + sizeof(Uart_Drv_Buff))) { // 写指针到达缓冲区末尾,回绕到开头 rx1_buf_in = (u8 *)(Uart_Drv_Buff); } *rx1_buf_in++ = value; } } /*--------------------------- 环形缓冲区读取函数(供处理层调用) ---------------------------*/ /** * @brief 判断环形缓冲区是否有未读数据 * @return 1:有数据, 0:无数据 */ u8 GetMcuUartByte(void) { if (rx1_buf_out != rx1_buf_in) return 1; else return 0; } /** * @brief 从环形缓冲区取一个字节(并移动读指针) * @return 读取的字节值(若缓冲区为空则返回 0,但正常调用前应先用 GetMcuUartByte 判断) */ u8 take_byte_rx1buff(void) { u8 value = 0; if (rx1_buf_out != rx1_buf_in) { // 有数据 if (rx1_buf_out >= (u8 *)(Uart_Drv_Buff + sizeof(Uart_Drv_Buff))) { // 数据已经到末尾,回绕到开头 rx1_buf_out = (u8 *)(Uart_Drv_Buff); } value = *rx1_buf_out++; } return value; } /*--------------------------- 辅助函数 ---------------------------*/ /** * @brief 计算校验和(累加和,取低8位) * @param data 数据起始指针 * @param len 需要校验的字节数(不包含校验和字节本身) * @return 校验和值 */ static u8 get_check_sum(const u8 *data, u16 len) { u8 sum = 0; for (u16 i = 0; i < len; i++) { sum += data[i]; } return sum; } /*--------------------------- 核心处理函数 ---------------------------*/ /** * @brief 串口1数据接收与帧解析服务函数 * @note 需周期性调用(例如在主循环中) */ void uart1_data_service(void) { static u16 rx1_in = 0; // 本地缓冲区中有效数据字节数 u16 offset = 0; // 当前已处理的偏移 u16 fr_len = 0; // 当前帧总长度 u8 check_num = 0; // 计算出的校验和 // 1. 从硬件环形缓冲区取数据到本地处理缓冲区(保留1字节空间防止溢出) while ((rx1_in < sizeof(uart1_data_process_buf) - 1) && GetMcuUartByte() > 0) { uart1_data_process_buf[rx1_in++] = take_byte_rx1buff(); } // 至少需要4字节帧头才能开始解析 if (rx1_in < 4) { return; } // 2. 帧解析循环 while ((rx1_in - offset) >= 4) { // 2.1 检查固定帧头(不匹配则跳过当前字节) if (uart1_data_process_buf[offset + 0] != HEAD_FIRST || uart1_data_process_buf[offset + 1] != HEAD_SECOND || uart1_data_process_buf[offset + 2] != PROTOCOL_VERSION || uart1_data_process_buf[offset + 3] != FRAME_TYPE) { offset++; // 不匹配,跳过1字节,继续寻找 continue; } // 2.2 读取数据长度字段(索引 LEN_INDEX) fr_len = uart1_data_process_buf[offset + LEN_INDEX]; fr_len += FIXED_OVERHEAD + CHECKSUM_LEN; // 总帧长 = 数据长度 + 固定开销 + 校验 // 2.3 验证帧长合理性及数据是否完整 if (fr_len > MAX_FRAME_LEN || (rx1_in - offset) < fr_len) { // 数据不完整或帧过长,退出循环等待更多数据(不丢弃已有数据) break; } // 2.4 校验和验证(累加和,不含校验字节) check_num = get_check_sum((const u8 *)(uart1_data_process_buf + offset), fr_len - 1); if (check_num != uart1_data_process_buf[offset + fr_len - 1]) { // 校验失败,跳过当前帧头尝试重新同步 offset += 1; continue; } // 2.5 校验通过,处理有效帧(此处可根据业务需求扩展) // 例如复制帧数据到上层队列或执行命令 // process_frame(&uart1_data_process_buf[offset], fr_len); // 调试打印(示例,注意加上 offset) // PR_DEBUG("Frame: 0x%02X 0x%02X 0x%02X ...\r\n", // uart1_data_process_buf[offset+0], // uart1_data_process_buf[offset+1], // uart1_data_process_buf[offset+2]); // 跳过整个已处理帧 offset += fr_len; } // 3. 移除已处理数据,将剩余未处理数据搬移到缓冲区开头 if (offset > 0) { rx1_in -= offset; if (rx1_in > 0) { // 将 uart1_data_process_buf[offset] 开始的数据搬移到开头 // 若使用标准 memcpy,替换为:memcpy((void*)uart1_data_process_buf, (void*)(uart1_data_process_buf + offset), rx1_in); my_memcpy((u8 *)uart1_data_process_buf, (const u8 *)(uart1_data_process_buf + offset), rx1_in); } // 若 rx1_in == 0,则无需搬移,缓冲区可全部重用 } }

上述代码。的 帧头。信息可以自定义,或者更改自己合适的值,再者是固定长度根据自己合适的方式去更改。

最后,介绍使用方法:

1.周期性调用service 函数,或者在主循环里调用

2.receive_input函数为数据输入,此函数为单个字符逐步输入模式,可自行修改,比如封起来,字符数组调用的,通过循环逐步加入。

3.check_sum是用来校验一帧数据是否有错误或者漏掉的情况,当然你有更好的也可以替换,合理即可。

4.buffer_init函数在串口初始化完毕后调用即可,因为是初始化指针。另外Uart_drv_buffer的大小可根据自己情况适度更改,或者128.或者200都行。

http://www.jsqmd.com/news/1104158/

相关文章:

  • 信息化项目的分类
  • java-基于ssm的一款房屋租赁管理系统
  • 基于交流潮流的电力系统多元件N-k故障模型研究(Matlab代码实现)【电力系统故障】
  • ESim电工制图图文介绍
  • 将“Git Bash Here“添加到windows11的新式右键菜单
  • Linux 系统编程 04:进程基础
  • 终极解决方案:一键捕获完整网页的Chrome扩展神器
  • PostgREST防SQL注入实战:从原理到纵深防御体系构建
  • STM32与LENA-R8构建低功耗高精度定位系统
  • 3分钟免费解锁全皮肤:R3nzSkin国服换肤终极指南
  • 贾扬清从英伟达离职,7 亿美元收购一年告终,AI Infra 赛道面临挑战
  • 统信系统升级后的兼容性问题
  • 深度解析SDINBDA6-128G-ZA1:闪迪128GB车规级eMMC 5.1存储芯片
  • 嵌入式交流群
  • 终极网盘直链下载助手:免费获取九大网盘真实下载链接的完整解决方案
  • 大宅门中式建筑,已按人物标准升高修改
  • 设计模式-策略模式精讲
  • 学了 GPT-5.5 新特性,我重构了去年写的聊天应用
  • 产业园区两轮车乱象难治理?观芯AI摄像头专项实测方案
  • 沃尔玛拥抱 AI 转型:Sparky 承载期待,弗纳面临员工安置与竞争挑战
  • 扫码apk下载
  • AI账单乱象丛生:审计揪出170万多收费用,模型厂商退钱却不认账
  • 2026德宏黄金回收白银回收铂金回收旧料回收怎么选?五家高实价铂金白银线下门店测评清单 + 联系方式
  • 《打通全链路数据 智慧云通构建沥青供应链协同新生态》
  • 拒绝“幻觉”代码:那些 Gemini 3.5 擅长而其他模型容易出错的边界场景
  • AI掘金头条新闻系统 (Toutiao News)-安装Redis客户端
  • Python常见问题解决方法
  • 冬青先令到场复查,重点看哪些到货细节
  • 案例分析:100GigE高速相机的出现助力创新生物医学诊断
  • Python测量音视频相对音量