uart_rx.h

📅 2026/7/30 4:23:13
uart_rx.h
/********************************************************************************* file uart_rx.h* brief 串口中断接收、环形缓冲区和简单二进制帧接收。** 本文件是团队自己写的代码放在 Core/Incuart_rx.c 放在 Core/Src。* CubeMX 重新生成代码不会覆盖这两个文件。** 使用前确认 CubeMX 已配置USARTx、USARTx global interrupt且中断服务函数中* 有 HAL_UART_IRQHandler(huartx)。本模块自己实现 HAL_UART_RxCpltCallback* 工程中不要再定义第二个同名回调函数。** --------------------------------------------------------------------------* 【串口助手发送多条可读命令回车换行结尾】** UART_Rx_ReadLine() 用于接收人能看懂的 ASCII 命令。发送端每一条命令都应以* 换行 \n 结束串口助手一般选择“发送新行”或“CRLF”即发送 \r\n。* 本模块会忽略 \r并把 \n 识别为一条命令结束。因此连续发送** LEDON\r\nPWM50\r\n12\r\n** 程序会依次得到三个独立字符串LEDON、PWM50、12。不能只使用 if* 取一次因为电脑可能一次就发送多条命令应在 while 内把已完整接收的行全部处理。** main.c 命令识别完整示例需要在 Includes 区加入 #include string.h* code* char line[32];** // 放在 while(1) 内。每轮会处理接收缓冲区中所有已收完整的命令。* while (UART_Rx_ReadLine(uart1_rx, line, sizeof(line)) UART_RX_STATUS_OK)* {* if (strcmp(line, LEDON) 0)* {* // strcmp 返回 0 表示两个字符串完全相同。* HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);* UART_Tx_SendLine(huart1, LED is ON, 100U);* }* else if (strcmp(line, LEDOFF) 0)* {* HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);* UART_Tx_SendLine(huart1, LED is OFF, 100U);* }* else if (strncmp(line, PWM, 4U) 0)* {* // strncmp(..., 4U)0 表示前四个字符正好是 P、W、M、。* // line[4] 是等号后的第一个字符例如 PWM50 中的 50。* uint32_t duty;* if ((UART_Rx_ParseU32(line[4], duty) ! 0U) (duty 100U))* {* // 这里按你的 PWM 驱动实际函数替换例如设置为 50% 占空比。* // PWM_Driver_SetDutyPercent(boost_pwm, (float)duty);* UART_Tx_SendLine(huart1, PWM updated, 100U);* }* else* {* UART_Tx_SendLine(huart1, PWM must be 0~100, 100U);* }* }* else* {* UART_Tx_SendLine(huart1, Unknown command, 100U);* }* }* endcode** 比赛时题目会规定命令字和参数格式只需要把 LEDON、PWM 等字符串及其* 对应处理动作替换成题目要求即可。若题目规定的是 AA 55 等二进制帧不使用本段* 的 ReadLine/strcmp 方法应使用后面的 ReadByte Protocol_Parser。* --------------------------------------------------------------------------** 二进制帧固定格式* HEADER[0] ... HEADER[N-1] LENGTH DATA[0] ... DATA[LENGTH-1] CHECKSUM** - HEADER帧头数组长度 N 可为 1~16 字节由题目决定。* - LENGTHDATA 区的字节数不包括帧头、长度字节和校验字节。* - CHECKSUMSUM8/XOR8 为 1 字节CRC16-Modbus 为 2 字节低字节在前。* - 校验范围默认是 LENGTH DATA不包含帧头和校验字节。** main.c 完整使用示例* code* // USER CODE BEGIN Includes* #include uart_rx.h** // USER CODE BEGIN PV* static UART_Rx_t uart1_rx;* static uint8_t uart1_rx_buffer[128]; // 中断收到的字节先暂存在这里* static Protocol_Parser_t protocol;* static Protocol_Frame_t received_frame;** // USER CODE BEGIN 2必须放在 MX_USART1_UART_Init() 之后* UART_Rx_Init(uart1_rx, huart1, uart1_rx_buffer, sizeof(uart1_rx_buffer));* UART_Rx_Start(uart1_rx); // 开始单字节中断接收** // 题目若定义帧AA 55 LEN DATA... SUM8则这样写* static const uint8_t frame_header[] {0xAAU, 0x55U};* Protocol_Parser_Init(protocol, frame_header, sizeof(frame_header),* PROTOCOL_CHECKSUM_SUM8);** // USER CODE BEGIN 3放进 while(1)* uint8_t one_byte;* while (UART_Rx_ReadByte(uart1_rx, one_byte) UART_RX_STATUS_OK)* {* if (Protocol_Parser_FeedByte(protocol, one_byte) PROTOCOL_STATUS_FRAME_READY)* {* Protocol_Parser_TakeFrame(protocol, received_frame);** // 例如收到AA 55 03 11 22 33 69* // received_frame.length 7U;* // received_frame.data[0..6] {0xAA,0x55,0x03,0x11,0x22,0x33,0x69};* // 之后直接按数组下标使用不需要关心命令、载荷等额外结构。* // 第一个数据的下标 帧头长度 1 个 LENGTH 字节。* uint8_t first_data received_frame.data[sizeof(frame_header) 1U]; // 0x11* (void)first_data;* }* }* endcode** 比赛时只改帧头数组内容、帧头数组长度和校验方式* code* static const uint8_t frame_header[] {0x55U, 0xAAU, 0x12U, 0x34U};* Protocol_Parser_Init(protocol, frame_header, sizeof(frame_header),* PROTOCOL_CHECKSUM_XOR8);* // 可选校验方式PROTOCOL_CHECKSUM_SUM8、PROTOCOL_CHECKSUM_XOR8、* // PROTOCOL_CHECKSUM_CRC16_MODBUS、PROTOCOL_CHECKSUM_NONE。* endcode*******************************************************************************/#ifndef UART_RX_H#define UART_RX_H#ifdef __cplusplusextern C {#endif#include usart.htypedef enum{UART_RX_STATUS_OK 0,UART_RX_STATUS_EMPTY,UART_RX_STATUS_INCOMPLETE,UART_RX_STATUS_INVALID_ARG,UART_RX_STATUS_NOT_INITIALIZED,UART_RX_STATUS_BUSY,UART_RX_STATUS_HAL_ERROR,UART_RX_STATUS_BUFFER_SMALL} UART_Rx_Status_t;/* UART 中断接收对象。应用层只需定义变量并传给 Init不要手动修改成员。 */typedef struct{UART_HandleTypeDef *huart;uint8_t *buffer;uint16_t capacity;volatile uint16_t read_index;volatile uint16_t write_index;uint8_t rx_byte;volatile uint32_t overflow_count;volatile uint32_t error_count;uint8_t initialized;uint8_t running;} UART_Rx_t;UART_Rx_Status_t UART_Rx_Init(UART_Rx_t *device, UART_HandleTypeDef *huart,uint8_t *buffer, uint16_t capacity);UART_Rx_Status_t UART_Rx_Start(UART_Rx_t *device);UART_Rx_Status_t UART_Rx_Stop(UART_Rx_t *device);uint16_t UART_Rx_Available(const UART_Rx_t *device);UART_Rx_Status_t UART_Rx_ReadByte(UART_Rx_t *device, uint8_t *byte);/*** brief 从串口环形缓冲区取出一整行 ASCII 文本并转换成 C 字符串。* param device 已 Init 且 Start 的 UART 接收对象例如 uart1_rx。* param line 用户提供的 char 数组用于保存文本例如 char line[32]。* param line_capacity line 数组总容量必须写 sizeof(line)不要手写数字。* return UART_RX_STATUS_OK已得到一行其他常见返回值见下方。** 本函数适用于“串口助手文本发送”不是十六进制二进制帧解析器。* 例如电脑发送 hello 并按回车串口收到的原始字节是* 0x68 0x65 0x6C 0x6C 0x6F 0x0D 0x0A* 本函数返回后 line 内容是 hello末尾的 \r 和 \n 被自动丢弃且自动补 \0。** 返回值含义* - UART_RX_STATUS_OK已经收到 \nline 内是一条完整字符串。* - UART_RX_STATUS_EMPTY目前没有收到任何字节。* - UART_RX_STATUS_INCOMPLETE收到了一部分文本但尚未收到 \n下次 while 循环再调用。* - UART_RX_STATUS_BUFFER_SMALL完整一行长度超过 line 数组容量该行被丢弃。** main.c 使用例子* code* char line[32];* if (UART_Rx_ReadLine(uart1_rx, line, sizeof(line)) UART_RX_STATUS_OK)* {* // 电脑文本发送 hello 回车line 为 hello。* // 电脑文本发送 12 回车line[0]1line[1]2不是数值 12。* UART_Tx_SendLine(huart1, line, 100U);* }* endcode** 若题目给的是 AA 55 LEN DATA CHECKSUM 这类二进制报文请使用* UART_Rx_ReadByte() Protocol_Parser_FeedByte()不要使用本函数。*/UART_Rx_Status_t UART_Rx_ReadLine(UART_Rx_t *device, char *line, uint16_t line_capacity);/*** brief 将一行纯十进制 ASCII 文本转换为可计算的 uint32_t 无符号整数。* param text 以 \0 结尾的字符串通常来自 UART_Rx_ReadLine()例如 12。* param value 转换成功后写入数值例如 12 转为 12U。* return 1转换成功0字符串为空、含非数字字符或数值超过 uint32_t 范围。** 此函数只接受十进制非负整数允许首尾空格和开头的 号* 12、 12 、12 均成功12.5、-12、hello 均失败。** main.c 使用例子* code* char line[32];* uint32_t number;* if (UART_Rx_ReadLine(uart1_rx, line, sizeof(line)) UART_RX_STATUS_OK)* {* if (UART_Rx_ParseU32(line, number) ! 0U)* {* number number 1U; // 电脑发 12number 变成 13* UART_Tx_SendU32(huart1, number, 100U);* }* }* endcode*/uint8_t UART_Rx_ParseU32(const char *text, uint32_t *value);/*** brief 将一行 ASCII 小数文本转换为可计算的 float 浮点数。* param text 以 \0 结尾的字符串通常来自 UART_Rx_ReadLine()例如 3.287。* param value 转换成功后写入浮点结果的变量地址例如 dac_pin_voltage。* return 1U转换成功0U字符串格式错误或参数为 NULLvalue 保持原值。** 支持首尾空格、可选的 / - 号、整数部分和小数部分例如 12、3.287、* -0.5、.25。不支持科学计数法例如 1e-3、逗号小数点3,2* 和单位3.3V。发送端每条数据仍须以 \r\n 结尾。** main.c 使用案例电脑发送 3.287 并勾选“发送新行CRLF”后变量保存为 3.287f。* code* char line[32];* float dac_pin_voltage;** if (UART_Rx_ReadLine(uart1_rx, line, sizeof(line)) UART_RX_STATUS_OK)* {* if (UART_Rx_ParseFloat(line, dac_pin_voltage) ! 0U)* {* // 例如3.287 1.0 4.287* dac_pin_voltage dac_pin_voltage 1.0f;** // 参数依次为串口、浮点值、小数位数、超时 ms。* UART_Tx_SendFloat(huart1, dac_pin_voltage, 3U, 100U);* UART_Tx_SendLine(huart1, , 100U);* }* else* {* UART_Tx_SendLine(huart1, Float format error, 100U);* }* }* endcode*/uint8_t UART_Rx_ParseFloat(const char *text, float *value);UART_Rx_Status_t UART_Rx_Clear(UART_Rx_t *device);uint32_t UART_Rx_GetOverflowCount(const UART_Rx_t *device);uint32_t UART_Rx_GetErrorCount(const UART_Rx_t *device);/* 单帧 DATA 最大长度。完整数组还需容纳帧头、长度和最多两个校验字节。 */#define PROTOCOL_MAX_HEADER_LENGTH 16U /* 题目帧头超过 16 字节时只改这个数值。 */#define PROTOCOL_MAX_DATA_LENGTH 128U#define PROTOCOL_MAX_FRAME_LENGTH (PROTOCOL_MAX_HEADER_LENGTH 1U PROTOCOL_MAX_DATA_LENGTH 2U)typedef enum{PROTOCOL_CHECKSUM_NONE 0, /* 无校验帧尾没有校验字节。 */PROTOCOL_CHECKSUM_SUM8, /* LENGTH 和 DATA 累加保留低 8 位。 */PROTOCOL_CHECKSUM_XOR8, /* LENGTH 和 DATA 逐字节异或。 */PROTOCOL_CHECKSUM_CRC16_MODBUS/* CRC 初值 FFFF低字节先到。 */} Protocol_Checksum_t;typedef enum{PROTOCOL_STATUS_OK 0,PROTOCOL_STATUS_FRAME_READY, /* 一帧已校验完成可调用 TakeFrame。 */PROTOCOL_STATUS_INVALID_ARGUMENT,PROTOCOL_STATUS_NOT_READY,PROTOCOL_STATUS_LENGTH_ERROR,PROTOCOL_STATUS_CHECKSUM_ERROR,PROTOCOL_STATUS_BUSY} Protocol_Status_t;/* 这就是给业务程序用的完整帧数组。data[0] 永远是帧头的第一个字节。 */typedef struct{uint16_t length; /* data[] 实际有效字节数。 */uint8_t data[PROTOCOL_MAX_FRAME_LENGTH];/* 完整帧帧头、长度、数据、校验。 */} Protocol_Frame_t;/* 解析器内部状态。只需定义变量和调用 API不需直接修改成员。 */typedef struct{uint8_t header[PROTOCOL_MAX_HEADER_LENGTH];uint8_t header_length;uint8_t header_index;Protocol_Checksum_t checksum_type;uint8_t state;uint8_t data_length;uint8_t received_data_count;uint16_t checksum;uint8_t received_crc_low;Protocol_Frame_t completed_frame;uint8_t frame_ready;uint8_t initialized;uint32_t frame_count;uint32_t length_error_count;uint32_t checksum_error_count;} Protocol_Parser_t;/* 设置帧头数组、帧头长度和校验方式格式始终为 HEADER LENGTH DATA CHECKSUM。 */Protocol_Status_t Protocol_Parser_Init(Protocol_Parser_t *parser,const uint8_t *header, uint8_t header_length,Protocol_Checksum_t checksum_type);/* 每从 UART 环形缓冲区读到一个字节就调用一次。 */Protocol_Status_t Protocol_Parser_FeedByte(Protocol_Parser_t *parser, uint8_t byte);uint8_t Protocol_Parser_IsFrameReady(const Protocol_Parser_t *parser);/* 把已完成的一帧复制到 frame-data[]并允许接收下一帧。 */Protocol_Status_t Protocol_Parser_TakeFrame(Protocol_Parser_t *parser,Protocol_Frame_t *frame);#ifdef __cplusplus}#endif#endif /* UART_RX_H */