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

STM32 串口接收数据包(自定义帧头帧尾)

一、基本概述

本实验基于 STM32C8T6 单片机 开发,串口作为嵌入式系统中基础且核心的外设,广泛应用于设备间数据通信。本文重点讲解 自定义帧头帧尾的串口数据包接收与发送逻辑,核心是理解 “如何通过帧结构区分有效数据、避免数据混淆”,并掌握中断驱动的串口数据处理思路。

核心设计思路

通过自定义数据包格式(帧头 + 定长数据 + 帧尾),解决串口通信中 “数据边界模糊” 的问题:

  • 帧头(Header):固定为 0xFE,用于标记数据包的开始。
  • 数据段(Data):定长 4 字节,存储实际业务数据(可根据需求调整长度)。
  • 帧尾(Tail):固定为 0xFF,用于标记数据包的结束。

二、关键变量定义与逻辑解析

2.1 核心变量

// 接收相关变量(usart.c中定义,需通过usart.h extern导出供其他文件使用)
uint8_t rxd_buf[4];    // 接收缓冲区,存储4字节定长数据段
uint8_t rxd_flag = 0;  // 接收完成标志:0=未完成,1=完成(用于主函数判断是否处理数据)
uint8_t rxd_index = 0; // 接收索引:记录当前接收数据在rxd_buf中的位置// 发送相关变量(示例用,可根据需求修改)
uint8_t txd_buf[4] = {1,2,3,4}; // 默认发送数据包(仅示例)

2.2 核心逻辑:串口中断服务函数

串口接收采用 中断驱动方式(仅当有数据接收时触发中断,降低 CPU 占用),通过 状态机(switch-case) 解析数据包,流程如下:

状态机设计
状态(recv_state)功能描述
0(等待帧头)检测是否接收到帧头 0xFE,若收到则切换到状态 1,同时重置接收索引;否则保持状态 0。
1(接收数据段)将接收到的字节依次存入 rxd_buf,每存 1 字节rxd_index自增 1;当接收满 4 字节(rxd_index>=4),切换到状态 2。
2(等待帧尾)检测是否接收到帧尾 0xFF,若收到则置位 rxd_flag=1(标记接收完成),同时重置状态为 0;否则丢弃该包(不置位标志)。
中断服务函数代码
/*** @brief  USART1中断服务程序(核心:数据包解析)* @param  无* @retval 无*/
void USART1_IRQHandler(void)                 
{u8 recv_dat; // 临时变量,存储单次接收到的字节static uint8_t recv_state = 0; // 静态状态变量,默认从状态0开始(中断退出后不丢失值)// 1. 判断是否为“接收数据寄存器非空”中断(有新数据接收)if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  {// 2. 读取接收到的1字节数据(从USART1的DR寄存器读取)recv_dat = USART_ReceiveData(USART1); // 3. 状态机解析数据包switch (recv_state){ case 0: // 状态0:等待帧头 0xFEif (recv_dat == 0XFE) // 检测到帧头{recv_state = 1;   // 切换到“接收数据段”状态rxd_index = 0;    // 重置接收索引(从buf[0]开始存数据)}else // 未检测到帧头,保持状态0{recv_state = 0;}break;case 1: // 状态1:接收4字节数据段rxd_buf[rxd_index] = recv_dat; // 存入缓冲区rxd_index++;                   // 索引自增,指向下一个存储位置if (rxd_index >= 4)            // 判断是否接收满4字节数据{recv_state = 2;            // 切换到“等待帧尾”状态}break;case 2: // 状态2:等待帧尾 0xFFif (recv_dat == 0XFF) // 检测到帧尾{rxd_flag = 1;     // 置位接收完成标志(主函数可检测该标志处理数据)recv_state = 0;   // 重置状态为0,准备接收下一包数据}break;}// 4. 清除中断标志位(必须操作,否则会重复触发中断)USART_ClearITPendingBit(USART1, USART_IT_RXNE);	 		} 
}

三、串口工具测试验证

使用 XCOM V2.6 串口助手 发送自定义格式数据包,验证接收与回显功能,测试配置与结果如下:

3.1 串口配置

配置项参数值
串口端口COM3(根据实际硬件选择)
波特率115200
数据位8 位
停止位1 位
校验位None(无校验)
发送格式16 进制发送

3.2 测试数据与结果

  • 发送数据包(16 进制):FE 00 00 00 01 FF(帧头 + 4 字节数据 + 帧尾)
  • 接收回显结果(串口助手显示):FE 00 00 00 01 FF(STM32 接收完成后回显相同数据包)
串口助手日志示例

plaintext

[2023-12-06 22:29:55,476] TX: FE 00 00 00 01 FF 0D 0A  // 上位机发送
[2023-12-06 22:29:55.689] RX: FE 00 00 00 01 FF        // STM32回显
[2023-12-06 22:29:56.790] TX: FE 00 00 00 01 FF 0D 0A  // 再次发送
[2023-12-06 22:29:57.672] RX: FE 00 00 00 01 FF        // 再次回显

四、完整程序代码

4.1 usart.c(串口驱动与数据包处理)

c

运行

#include "usart.h"	
#include "led.h"	  // 全局变量定义(接收相关)
uint8_t rxd_buf[4];    // 接收缓冲区(4字节定长)
uint8_t rxd_flag = 0;  // 接收完成标志
uint8_t rxd_index = 0; // 接收索引
uint8_t txd_buf[4] = {1,2,3,4}; // 默认发送缓冲区(示例)/*** @brief  USART1初始化函数* @param  bound:波特率(如115200、9600等)* @retval 无*/
void USART1_Init(u32 bound)
{// 1. 定义初始化结构体GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 2. 使能时钟(GPIOA和USART1都挂载在APB2总线上)RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); 	// 3. 配置GPIO(TX: PA9,复用推挽输出;RX: PA10,浮空输入)// 配置TX引脚(PA9)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出(串口发送需要复用功能)GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置RX引脚(PA10)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入(避免外部干扰)GPIO_Init(GPIOA, &GPIO_InitStructure);// 4. 配置USART1参数USART_InitStructure.USART_BaudRate = bound; // 波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据位USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 同时使能接收和发送USART_Init(USART1, &USART_InitStructure); // 初始化USART1// 5. 使能USART1USART_Cmd(USART1, ENABLE);  USART_ClearFlag(USART1, USART_FLAG_TC); // 清除发送完成标志(避免初始状态异常)// 6. 配置串口接收中断(使能“接收数据寄存器非空”中断)USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 7. 配置NVIC(中断优先级)NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 串口1中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 响应优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道NVIC_Init(&NVIC_InitStructure); // 初始化NVIC
}/*** @brief  重定义printf函数(支持通过USART1打印调试信息)* @param  ch:要打印的字符,FILE*:标准库文件指针(无需手动传参)* @retval 打印的字符(符合printf函数返回值要求)*/
int fputc(int ch, FILE *p)  
{USART_SendData(USART1, (u8)ch); // 发送1字节数据while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成return ch;
}/*** @brief  发送1字节数据* @param  byte:要发送的字节* @retval 无*/
void send_byte(uint8_t byte)
{USART_SendData(USART1, byte);while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
}/*** @brief  发送字符串(以'\0'为结束符)* @param  str:指向字符串的指针* @retval 无*/
void send_string(uint8_t *str)
{while (*str != '\0') // 遍历字符串,直到遇到结束符{send_byte(*str++); // 发送当前字符,指针自增指向下一个字符}
}/*** @brief  发送指定长度的字节数组* @param  buf:指向数组的指针,len:要发送的字节数* @retval 无*/
void send_buf(uint8_t *buf, uint16_t len)
{uint16_t i;for (i = 0; i < len; i++){send_byte(buf[i]); // 逐个发送数组元素}
}/*** @brief  发送自定义格式数据包(帧头+数据段+帧尾)* @param  无(使用rxd_buf作为数据段,可根据需求修改)* @retval 无*/
void send_pack(void)
{send_byte(0xFE);       // 发送帧头send_buf(rxd_buf, 4);  // 发送4字节数据段(接收缓冲区的数据)send_byte(0xFF);       // 发送帧尾
}/*** @brief  USART1中断服务程序(核心:数据包解析)* @param  无* @retval 无*/
void USART1_IRQHandler(void)                 
{u8 recv_dat;static uint8_t recv_state = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  {recv_dat = USART_ReceiveData(USART1); switch (recv_state){ case 0: if (recv_dat == 0XFE){recv_state = 1;rxd_index = 0;}else{recv_state = 0;}break;case 1: rxd_buf[rxd_index] = recv_dat;rxd_index++;if (rxd_index >= 4){recv_state = 2;}break;case 2: if (recv_dat == 0XFF){rxd_flag = 1;recv_state = 0;}break;}USART_ClearITPendingBit(USART1, USART_IT_RXNE);	 		} 
}

4.2 usart.h(头文件,声明函数与全局变量)

c

运行

#ifndef _usart_H
#define _usart_H #include "system.h" 
#include "stdio.h"  // 声明全局变量(供其他文件使用)
extern uint8_t rxd_flag; // 接收完成标志// 声明函数(供其他文件调用)
void USART1_Init(u32 bound);
void send_byte(uint8_t byte);
void send_string(uint8_t *str);
void send_buf(uint8_t *buf, uint16_t len);
void send_pack(void);#endif  

4.3 main.c(主函数,业务逻辑处理)

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "pwm.h"
#include "usart.h"
#include "key.h"
#include "oled.h"int main(void)
{// 1. 初始化外设SysTick_Init(72); // 初始化SysTick定时器(72MHz时钟,用于延时)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断优先级分组:2组(2位抢占+2位响应)USART1_Init(115200); // 初始化USART1,波特率115200USART2_Init(115200); // 初始化USART2(按需使用,本示例未用到)OLED_Init(); // 初始化OLED显示屏(按需使用)KEY_Init();  // 初始化按键(按需使用)LED_Init();  // 初始化LED(按需使用)// 2. 发送初始化提示信息send_string("hello stm32\r\n"); // 发送字符串(\r\n为换行符,使串口助手显示换行)// 3. 主循环(处理接收完成的数据包)while (1){// 检测到数据包接收完成(rxd_flag=1)if (rxd_flag == 1){rxd_flag = 0; // 重置标志位(避免重复处理)send_pack();  // 回显接收到的数据包(将接收的内容原样发送回去)}}
}

文章转载自:

http://c0qmVqpP.jxLnr.cn
http://f4MZrved.jxLnr.cn
http://bF8fAybd.jxLnr.cn
http://vT4aZsTq.jxLnr.cn
http://V9wGSGfr.jxLnr.cn
http://y0FO1AUt.jxLnr.cn
http://yWA0pCZp.jxLnr.cn
http://i0lPsdIU.jxLnr.cn
http://V2YCHx9w.jxLnr.cn
http://95PcaHIV.jxLnr.cn
http://zfUGsQGv.jxLnr.cn
http://SoABc5AO.jxLnr.cn
http://rSzhSo6t.jxLnr.cn
http://YJTDYro4.jxLnr.cn
http://vxluptto.jxLnr.cn
http://vGDdC9mA.jxLnr.cn
http://VG0fPhDp.jxLnr.cn
http://Kd3Ulpbv.jxLnr.cn
http://9DmNianO.jxLnr.cn
http://U7CxX7hp.jxLnr.cn
http://R6IbDWgt.jxLnr.cn
http://FHZsekCB.jxLnr.cn
http://ZJGrXE58.jxLnr.cn
http://7qiT2PZr.jxLnr.cn
http://0gyd7aRB.jxLnr.cn
http://38gZae2J.jxLnr.cn
http://qF2Jfjmh.jxLnr.cn
http://vtMygNaW.jxLnr.cn
http://Q4ehbMqT.jxLnr.cn
http://UMeZmW1c.jxLnr.cn
http://www.dtcms.com/a/374734.html

相关文章:

  • 正向代理,反向代理,负载均衡还有nginx
  • 用户态与内核态的深度解析:安全、效率与优化之道
  • 搭建本地gitea服务器
  • ArcGIS JSAPI 高级教程 - 倾斜摄影数据开启透明(修改源码)
  • 输电线路分布式故障监测装置技术解析
  • 概率论第四讲—随机变量的数字特征
  • 学习stm32 蓝牙
  • 数据库学习MySQL系列2、Windows11系统安装MySQL方法一.msi安装详细教程
  • STM32物联网项目---ESP8266微信小程序结合OneNET平台MQTT实现STM32单片机远程智能控制---代码篇(四)
  • 北京鲁成伟业 | 三屏加固笔记本电脑C156F3
  • 从0~1搭建技术团队的思路
  • 如何在 Unity3D 中实现圆角效果?
  • LeetCode 面试经典 150 题:多数元素(摩尔投票法详解 + 多解法对比)
  • CStringArray 和 CStringList
  • 银行业安全用电系统建设与智能化管理探析
  • 20250909_排查10.1.1.190档案库房综合管理系统20250908备份缺失问题+优化scp脚本(把失败原因记录进日志)并测试脚本执行情况
  • 硬件开发_基于STM32单片机的海鲜冷藏车检测系统
  • AI一周事件(2025年9月3日-9月8日)
  • Unity3D发布的文件打包成Windows安装程序
  • 已知两个平面点的坐标、切线方向、曲率,使用牛顿迭代法构造三阶 Bézier 曲线的方法
  • 全球工业互联网大会 | 蓝卓supOS以数据底座,筑牢工业AI基石
  • k8s交互桥梁:走进Client-Go
  • K8S-Node
  • 嵌入式 - ARM(4) 硬件介绍与开发环境搭建
  • 网络上那些在线 PDF 转换工具安全吗?转换 PDF 需要注意什么
  • OneMark 插件试用
  • 专题:2025人形机器人、工业机器人、智能焊接机器人、扫地机器人产业洞察报告 | 附158+份报告PDF、数据仪表盘汇总下载
  • 微服务核心组件实战:Nacos 与 Ribbon 的应用
  • PDF处理控件Aspose.PDF教程:使用 Python 将 PDF 转换为 Base64
  • arm启动代码总结