单片机基础核心知识点汇总(二十九)

发布时间:2026/9/5 3:40:36
单片机基础核心知识点汇总(二十九) 目录前言一、串口通信最大痛点粘包与分包1、什么是分包2、什么是粘包二、工业通用串口帧协议格式三、串口底层架构环形缓冲区四、状态机帧解析解决粘包分包核心五、串口空闲中断判定帧结束六、CRC16 校验代码工业标准七、业务解耦协议层与业务层分离八、串口工程九大踩坑总结九、RTOS 架构升级版前言在所有嵌入式通信中串口 USART 是使用最多、BUG 最多、最需要工程架构的通信方式。新手串口写法收到数据立刻处理裸字节判断、无帧结构遇到粘包、断包直接解析错乱无校验、无超时、无容错协议和业务耦合完全无法迭代量产设备必须拥有标准帧协议 环形缓冲区 状态机解析 超时容错架构。本篇手把手搭建工业级串口协议栈解决粘包、分包、干扰乱码、超时断帧、CRC 校验、自动容错裸机 / RTOS 通用。一、串口通信最大痛点粘包与分包1、什么是分包一帧数据被分多次接收 比如一帧 100 字节分 3 次进串口缓冲区2、什么是粘包多帧数据粘在一起一次性收到 两三条指令粘成一坨直接解析错误解决核心必须基于「完整数据帧」解析不能单字节处理业务二、工业通用串口帧协议格式帧头2字节 长度1字节 命令1字节 数据域 CRC16校验2字节 帧尾常用标准格式帧头0xAA 0x55数据长度有效数据长度命令码区分不同功能数据域自定义参数CRC16校验整条数据帧尾可选优势不怕乱码、不怕丢字节精准区分每一帧错误帧直接丢弃、自动容错三、串口底层架构环形缓冲区固定数组容易溢出、丢数据量产全部用环形 FIFO#define USART_BUF_LEN 256 typedef struct { uint8_t buf[USART_BUF_LEN]; uint16_t w_index; uint16_t r_index; }usart_fifo_t; usart_fifo_t g_usart_fifo; //入队 void usart_fifo_push(uint8_t dat) { uint16_t next (g_usart_fifo.w_index 1) % USART_BUF_LEN; if(next ! g_usart_fifo.r_index) { g_usart_fifo.buf[g_usart_fifo.w_index] dat; g_usart_fifo.w_index next; } } //出队 uint8_t usart_fifo_pop(uint8_t *dat) { if(g_usart_fifo.r_index g_usart_fifo.w_index) return 0; *dat g_usart_fifo.buf[g_usart_fifo.r_index]; g_usart_fifo.r_index (g_usart_fifo.r_index 1) % USART_BUF_LEN; return 1; } //获取缓存数量 uint16_t usart_fifo_cnt(void) { return (g_usart_fifo.w_index - g_usart_fifo.r_index USART_BUF_LEN) % USART_BUF_LEN; }中断里只做一件事压入环形缓冲区void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t dat USART_ReceiveData(USART1); usart_fifo_push(dat); } }四、状态机帧解析采用空闲中断 状态机解析串口超过 20ms 无新数据判定一帧接收完毕typedef enum { USART_IDLE, USART_GET_HEAD, USART_GET_LEN, USART_GET_DATA, USART_GET_CRC }usart_state_t; void usart_parse_process(void) { static usart_state_t state USART_IDLE; static uint8_t tmp_buf[128]; static uint16_t idx 0; static uint16_t crc 0; uint8_t dat; while(usart_fifo_pop(dat)) { switch(state) { case USART_IDLE: idx 0; //匹配帧头 0xAA 0x55 if(dat 0xAA) { tmp_buf[idx] dat; state USART_GET_HEAD; } break; case USART_GET_HEAD: if(dat 0x55) { tmp_buf[idx] dat; state USART_GET_LEN; } else { state USART_IDLE; } break; case USART_GET_LEN: tmp_buf[idx] dat; state USART_GET_DATA; break; case USART_GET_DATA: //根据长度接收剩余数据 if(idx tmp_buf[2] 3) { tmp_buf[idx] dat; } else { //最后两字节CRC tmp_buf[idx] dat; state USART_GET_CRC; } break; case USART_GET_CRC: tmp_buf[idx] dat; //CRC校验判断 //crc_check(tmp_buf, idx-2) 正确 if(1) { //完整合法帧执行业务处理 usart_data_handle(tmp_buf, idx); } state USART_IDLE; idx 0; break; default: state USART_IDLE; break; } } }五、串口空闲中断裸机唯一精准判帧方式 一帧数据传输完成后总线空闲触发空闲中断标志本次帧接收完成void USART_Idle_IRQ(void) { if(USART_GetITStatus(USART1, USART_IT_IDLE) ! RESET) { USART_ClearITPendingBit(USART1, USART_IT_IDLE); //触发一次解析 usart_parse_process(); } }六、CRC16 校验代码uint16_t crc16_calc(uint8_t *buf,uint16_t len) { uint16_t crc 0xFFFF; uint16_t i,j; for(i0;ilen;i) { crc ^ buf[i]; for(j0;j8;j) { if(crc 1) crc (crc 1) ^ 0xA001; else crc 1; } } return crc; }校验作用杜绝干扰乱码、字节错误量产设备必须带 CRC。七、业务解耦协议层与业务层分离void usart_data_handle(uint8_t *buf,uint16_t len) { uint8_t cmd buf[3]; switch(cmd) { case 0x01: //设备查询指令 break; case 0x02: //参数设置指令 break; case 0x03: //控制指令 break; default: //非法指令直接丢弃 break; } }八、串口工程九大踩坑总结不用环形缓冲区数据溢出、丢字节无帧头帧尾无法区分粘包无超时判断残留半截脏数据卡死解析无 CRC 校验干扰直接乱执行中断里处理业务导致中断过长、卡死、优先级抢占单字节判断指令完全不适合量产不做容错复位一次错误后续全部解析失败串口发送不屏蔽中断并发错乱大数据不做分包发送缓冲区溢出九、RTOS 架构升级版RTOS 中直接将接收数据放入队列xQueueSend(uart_queue,dat,0);任务中统一解析实时性更强、完全无阻塞。