STM32 学习 —— 个人学习笔记9-2(USART串口数据包 串口收发 HEX 及 文本 数据包)

STM32 学习 —— 个人学习笔记9-2(USART串口数据包  串口收发 HEX 及 文本 数据包) 声明文中内容为观看 BiliBili 视频【STM32入门教程-2023版 细致讲解 中文字幕】后学习并扩展总结。本文章为个人学习使用版面观感若有不适请谅解文中知识仅代表个人观点若出现错误欢迎各位批评指正。一、数据模式及数据包1.1 数据模式在数字通信系统中发送方与接收方通过编码 - 译码的双向映射实现字符与二进制数据的转换这一过程构成了信息可靠传输的核心基础。以字符A为例发送方将其编码为十六进制值0x41对应二进制01000001并以原始比特流形式传输至接收方接收方则通过译码操作将0x41还原为字符A完成信息的完整闭环传递。在此过程中数据的显示模式直接影响对传输内容的解读维度十六进制模式HEX 模式/ 二进制模式以原始数据形式呈现直观展示通信链路中传输的比特序列或其十六进制等价形式可精准定位传输过程中的比特错误与数据完整性文本模式字符模式则以编码后的字符形式呈现更贴近人类可读的信息语义便于理解传输内容的实际含义。二者分别从物理层数据传输与应用层语义表达两个层面共同构成了数字通信系统中数据观测与分析的完整视角为协议调试、故障排查及性能优化提供了互补的技术手段。1.2 HEX 数据包 及 数据包接收固定包长 HEX 数据包格式固定包长 HEX 数据包采用帧头 - 数据 - 帧尾的定长封装结构以特定字节作为帧边界标识帧头固定为0xFF帧尾固定为0xFE中间承载 4 字节定长有效载荷。该格式通过帧头0xFF与帧尾0xFE的字节序列实现帧同步与边界划分有效载荷长度严格固定为 4 字节无需额外长度字段即可完成帧解析适用于对传输时延确定性要求较高的场景。典型数据包序列为0xFF 0x01 0x02 0x03 0x04 0xFE其中0xFF为帧起始标识0x01~0x04为有效数据0xFE为帧结束标识。可变包长 HEX 数据包格式可变包长 HEX 数据包同样采用帧头 - 数据 - 帧尾的封装范式帧头0xFF与帧尾0xFE的边界标识规则与固定包长格式保持一致但有效载荷长度可动态变化。该格式通过帧头0xFF标记帧起始帧尾0xFE标记帧结束有效载荷长度由应用层逻辑或协议约定动态调整无需在帧结构中显式携带长度字段仅依赖帧尾0xFE完成帧边界判定。典型数据包序列为0xFF 0x01 0x02 0x03 0x04 0xFE4 字节载荷或0xFF 0x05 0x06 0x07 0xFE3 字节载荷适用于数据长度可变的异步通信场景。HEX 数据包接收状态机流程HEX 数据包接收采用有限状态机FSM实现帧同步与解析核心状态定义如下状态 S0等待包头系统初始状态持续监听接收字节流仅当检测到帧头字节0xFF时状态迁移至 S1接收数据若收到其他字节则保持 S0 状态。状态 S1接收数据进入数据接收阶段累计接收有效载荷字节。对于固定包长格式需累计接收 4 字节有效数据对于可变包长格式持续接收直至触发帧尾检测。若未收满约定长度的有效数据或收到非帧尾字节则保持 S1 状态当收满约定长度数据时状态迁移至 S2等待包尾。状态 S2等待包尾等待帧尾字节0xFE的到来若检测到0xFE则判定当前帧接收完成状态重置为 S0等待包头并向上层提交完整数据包若收到其他字节则保持 S2 状态直至检测到0xFE或超时丢弃当前帧。该状态机通过字节级别的状态跃迁实现了对固定 / 可变包长 HEX 数据包的可靠同步与解析有效避免了帧黏连与帧丢失问题保障了串行通信的帧完整性。1.3 文本数据包 及 数据包接收固定包长文本数据包格式固定包长文本数据包采用帧头 - 数据 - 帧尾的定长封装范式以字符作为帧起始标识帧头以\r\n回车换行符作为帧结束标识帧尾中间承载 3 字节固定长度的有效文本载荷。该格式通过与\r\n的字符序列实现帧同步与边界划分有效载荷长度严格限定为 3 字节无需额外长度字段即可完成帧解析适用于对传输时延确定性、数据完整性要求较高的文本通信场景。典型数据包序列为ABC\r\n其中为帧起始标识A/B/C为有效文本数据\r\n为帧结束标识。可变包长文本数据包格式可变包长文本数据包同样遵循帧头 - 数据 - 帧尾的封装结构帧头与帧尾\r\n的边界标识规则与固定包长格式保持一致但有效载荷长度可动态变化。该格式以标记帧起始以\r\n标记帧结束有效载荷长度由应用层业务逻辑动态调整无需在帧结构中显式携带长度字段仅依赖\r\n序列完成帧边界判定。典型数据包序列为ABC\r\n3 字节载荷或DE\r\n2 字节载荷适用于数据长度可变的异步文本通信场景如串口调试、传感器文本数据上报等。文本数据包接收状态机流程文本数据包接收采用有限状态机FSM实现帧同步与解析核心状态定义如下状态 S0等待包头系统初始状态持续监听接收字符流仅当检测到帧头字符时状态迁移至 S1接收数据等待包尾若收到其他字符则保持 S0 状态。状态 S1接收数据等待包尾进入数据接收阶段累计接收有效文本载荷。对于固定包长格式需累计接收 3 字节有效数据对于可变包长格式持续接收直至检测到\r字符。若收到其他字符则保持 S1 状态当检测到\r字符时状态迁移至 S2等待包尾。状态 S2等待包尾等待帧尾第二个字符\n的到来若检测到\n则判定当前帧接收完成状态重置为 S0等待包头并向上层提交完整文本数据包若收到其他字符则保持 S2 状态直至检测到\n或超时丢弃当前帧。该状态机通过字符级别的状态跃迁实现了对固定 / 可变包长文本数据包的可靠同步与解析有效避免了帧黏连与帧丢失问题保障了串行文本通信的帧完整性。二、串口收发 HEX 及 文本 数据包2.1 串口收发 HEX 数据包的实现首先按下图接线方式搭建面包板电路连接 OLED 显示屏并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上为使 OLED 显示屏的 VCC 和 GND 正确连接正负极请先连接对应正负极跳线或直接使用 GPIO 口进行供电。直接复制先前演示的已有文件目录重命名并双击后缀名为 .uvprojx 的文件打开工程文件并对 main.c 进行修改工程中所使用的全部头文件其详细内容已放于文末。#include stm32f10x.h // Device header #include Serial_HEX.h #include OLED.h #include Key.h uint8_t KeyNum; int main(void) { OLED_Init(); Key_Init(); Serial_Init(); OLED_ShowString(1, 1, TxPacket); OLED_ShowString(3, 1, RxPacket); Serial_TxPacket[0] 0x01; Serial_TxPacket[1] 0x02; Serial_TxPacket[2] 0x03; Serial_TxPacket[3] 0x04; while (1) { KeyNum Key_GetNum(); if (KeyNum 1){ Serial_TxPacket[0] ; Serial_TxPacket[1] ; Serial_TxPacket[2] ; Serial_TxPacket[3] ; Serial_SendPacket(); OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2); OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2); OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2); OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2); } if (Serial_GetRxFlag() 1){ OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2); OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2); OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2); OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2); } } }打开串口助手串口助手软件 请在【江协科技】视频下方下载按照预设的数据包格式在串口助手的发送区域编辑数据 “FF 11 22 33 44 FE” 并点击发送后即可在 RxPacket 区域下方查看到所发送的数据包内容。2.2 串口收发文本数据包的实现首先按下图接线方式搭建面包板电路连接 OLED 显示屏并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上为使 OLED 显示屏的 VCC 和 GND 正确连接正负极请先连接对应正负极跳线或直接使用 GPIO 口进行供电。直接复制先前演示的已有文件目录重命名并双击后缀名为 .uvprojx 的文件打开工程文件并对 main.c 进行修改工程中所使用的全部头文件其详细内容已放于文末。注文末 LED 代码中保留了void LED_Turn(void);功能的实现感兴趣的可以尝试将 LED 灯状态转换添加进来并通过设定 LED 初始状态以及 FLAG 标记位返回当前 LED 灯的状态信息即当前 LED 是开还是关。#include stm32f10x.h // Device header #include Serial_TEXT.h #include OLED.h #include LED.h #include string.h int main(void) { OLED_Init(); LED_Init(); Serial_Init(); OLED_ShowString(1, 1, TxPacket); OLED_ShowString(3, 1, RxPacket); while (1) { if (Serial_RxFlag 1){ OLED_ShowString(4, 1, ); OLED_ShowString(4, 1, Serial_RxPacket); if (strcmp(Serial_RxPacket, LED_ON) 0){ LED_ON(); Serial_SendString(LED_ON_OK\r\n); OLED_ShowString(2, 1, ); OLED_ShowString(2, 1, LED_ON_OK); } else if (strcmp(Serial_RxPacket, LED_OFF) 0){ LED_OFF(); Serial_SendString(LED_OFF_OK\r\n); OLED_ShowString(2, 1, ); OLED_ShowString(2, 1, LED_OFF_OK); } else { Serial_SendString(ERROR_COMMAND\r\n); OLED_ShowString(2, 1, ); OLED_ShowString(2, 1, ERROR_COMMAND); } Serial_RxFlag 0; } } }打开串口助手串口助手软件 请在【江协科技】视频下方下载按照预设的数据包格式在串口助手的发送区域编辑数据 “LED_ON 换行” 并点击发送后即可观察到 LED 灯亮。注文末LED.h中保留了void LED_Turn(void);功能的实现可尝试完善 LED_TURN 部分内容输入 LED_TURN 转换当前 LED 状态输入 GET_STATE 获取当前 LED 开关状态。。三、演示代码关联的头文件与源文件说明OLED 相关头文件请从 STM32 学习 —— 个人学习笔记4OLED 显示屏及调试工具 文末查看此处不重复展示。Delay 相关头文件请从 STM32 学习 —— 个人学习笔记3-1GPIO 输出 文末查看此处不重复展示。Key.c#include stm32f10x.h // Device header #include Delay.h void Key_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); } uint8_t Key_GetNum(void) { uint8_t KeyNum 0; if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0) { Delay_ms(20); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0); Delay_ms(20); KeyNum 1; } return KeyNum; }Key.h#ifndef __KEY_H #define __KEY_H void Key_Init(void); uint8_t Key_GetNum(void); #endifLED.c#include stm32f10x.h // Device header void LED_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_6); } void LED_ON(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_6); } void LED_OFF(void) { GPIO_SetBits(GPIOA, GPIO_Pin_6); } void LED_Turn(void) { if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6) 0) { GPIO_SetBits(GPIOA, GPIO_Pin_6); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_6); } }LED.h#ifndef __LED_H #define __LED_H void LED_Init(void); void LED_ON(void); void LED_OFF(void); void LED_Turn(void); #endifSerial_HEX.c#include stm32f10x.h // Device header #include stdio.h #include stdarg.h uint8_t Serial_TxPacket[4]; uint8_t Serial_RxPacket[4]; uint8_t Serial_RxFlag; void Serial_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_Init(USART2, USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_Init(NVIC_InitStructure); USART_Cmd(USART2, ENABLE); } void Serial_SendByte(uint8_t Byte){ USART_SendData(USART2, Byte); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); } void Serial_SendArray(uint8_t *Array, uint16_t Length){ uint16_t i; for(i0; iLength; i){ Serial_SendByte(Array[i]); } } void Serial_SendString(char *String){ uint8_t i; for(i0; String[i] ! \0; i){ Serial_SendByte(String[i]); } } uint32_t Serial_Pow(uint32_t X, uint32_t Y){ uint32_t Result 1; while(Y--){ Result *X; } return Result; } void Serial_SendNumber(uint32_t Number, uint8_t Length){ uint8_t i; for(i0; iLength; i){ Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 0x30); } } int fputc(int ch, FILE *f){ Serial_SendByte(ch); return ch; } void Serial_printf(char *format, ...){ char String[100]; va_list arg; va_start(arg, format); vsprintf(String, format, arg); va_end(arg); Serial_SendString(String); } void Serial_SendPacket(void){ Serial_SendByte(0xFF); Serial_SendArray(Serial_TxPacket, 4); Serial_SendByte(0xFE); } uint8_t Serial_GetRxFlag(void){ if(Serial_RxFlag1){ Serial_RxFlag 0; return 1; } return 0; } void USART2_IRQHandler(void){ static uint8_t RxState 0; static uint8_t pRxState 0; if(USART_GetITStatus(USART2, USART_IT_RXNE) SET){ uint8_t RxData USART_ReceiveData(USART2); if (RxState 0){ if (RxData 0xFF){ RxState 1; pRxState 0; } }else if (RxState 1){ Serial_RxPacket[pRxState] RxData; pRxState ; if (pRxState 4){ RxState 2; } }else if (RxState 2){ if (RxData 0xFE){ RxState 0; Serial_RxFlag 1; } } USART_ClearITPendingBit(USART2, USART_IT_RXNE); } }Serial_HEX.h#ifndef __SERIAL_HEX_H #define __SERIAL_HEX_H #include stdio.h extern uint8_t Serial_TxPacket[]; extern uint8_t Serial_RxPacket[]; void Serial_Init(void); void Serial_SendByte(uint8_t Byte); void Serial_SendArray(uint8_t *Array, uint16_t Length); void Serial_SendString(char *String); void Serial_SendNumber(uint32_t Number, uint8_t Length); int fputc(int ch, FILE *f); void Serial_printf(char *format, ...); void Serial_SendPacket(void); uint8_t Serial_GetRxFlag(void); #endifSerial_TEXT.c#include stm32f10x.h // Device header #include stdio.h #include stdarg.h char Serial_RxPacket[100]; uint8_t Serial_RxFlag; void Serial_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_Init(USART2, USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_Init(NVIC_InitStructure); USART_Cmd(USART2, ENABLE); } void Serial_SendByte(uint8_t Byte){ USART_SendData(USART2, Byte); while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); } void Serial_SendArray(uint8_t *Array, uint16_t Length){ uint16_t i; for(i0; iLength; i){ Serial_SendByte(Array[i]); } } void Serial_SendString(char *String){ uint8_t i; for(i0; String[i] ! \0; i){ Serial_SendByte(String[i]); } } uint32_t Serial_Pow(uint32_t X, uint32_t Y){ uint32_t Result 1; while(Y--){ Result *X; } return Result; } void Serial_SendNumber(uint32_t Number, uint8_t Length){ uint8_t i; for(i0; iLength; i){ Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 0x30); } } int fputc(int ch, FILE *f){ Serial_SendByte(ch); return ch; } void Serial_printf(char *format, ...){ char String[100]; va_list arg; va_start(arg, format); vsprintf(String, format, arg); va_end(arg); Serial_SendString(String); } void USART2_IRQHandler(void){ static uint8_t RxState 0; static uint8_t pRxState 0; if(USART_GetITStatus(USART2, USART_IT_RXNE) SET){ uint8_t RxData USART_ReceiveData(USART2); if (RxState 0){ if (RxData Serial_RxFlag 0){ RxState 1; pRxState 0; } }else if (RxState 1){ if (RxData \r){ RxState 2; } else { Serial_RxPacket[pRxState] RxData; pRxState ; } }else if (RxState 2){ if (RxData \n){ RxState 0; Serial_RxPacket[pRxState] \0; Serial_RxFlag 1; } } USART_ClearITPendingBit(USART2, USART_IT_RXNE); } }Serial_TEXT.h#ifndef __SERIAL_TEXT_H #define __SERIAL_TEXT_H #include stdio.h extern char Serial_RxPacket[]; extern uint8_t Serial_RxFlag; void Serial_Init(void); void Serial_SendByte(uint8_t Byte); void Serial_SendArray(uint8_t *Array, uint16_t Length); void Serial_SendString(char *String); void Serial_SendNumber(uint32_t Number, uint8_t Length); int fputc(int ch, FILE *f); void Serial_printf(char *format, ...); #endif文中部分知识参考B 站 —— 江协科技百度百科