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

uart_rx.h

/**

******************************************************************************

* @file uart_rx.h

* @brief 串口中断接收、环形缓冲区和简单二进制帧接收。

*

* 本文件是团队自己写的代码,放在 Core/Inc;uart_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\nPWM=50\r\n12\r\n

*

* 程序会依次得到三个独立字符串:"LEDON"、"PWM=50"、"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] 是等号后的第一个字符,例如 "PWM=50" 中的 "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 字节,由题目决定。

* - LENGTH:DATA 区的字节数,不包括帧头、长度字节和校验字节。

* - CHECKSUM:SUM8/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 __cplusplus

extern "C" {

#endif

#include "usart.h"

typedef 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:已经收到 \n,line 内是一条完整字符串。

* - 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]='1',line[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; // 电脑发 12,number 变成 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:字符串格式错误或参数为 NULL,value 保持原值。

*

* 支持:首尾空格、可选的 + / - 号、整数部分和小数部分,例如 "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 */

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

相关文章:

  • JavaScript字符串操作全解析:从基础概念到性能优化实践
  • U-Boot编译全解析:从.config生成到镜像构建的底层原理
  • 2026年7月沧州异型法兰定制/沧州碳钢异型法兰厂家推荐盘点_沧州誉守管件制造有限公司 - 行业平台推荐
  • 嵌入式Linux忘记root密码的三种破解路径与实战操作
  • 多智能体评审工作流:用角色分工提升代码质量
  • 桌面待办事项工具支持置顶提醒喝水「智护日程・健康饮水管家」
  • 2026年7月西安昆仑液压油/西安抗磨液压油公司哪家口碑好_陕西金好易石化有限公司 - 行业平台推荐
  • 2026 年更新:定兴优秀的封头制造厂家哪个好,别再傻傻花高价定制这玩意儿了!这3种封头选法帮你省至少30%成本-江东管道 - 行业推荐官【官方】
  • Python Scrapy实战:抓取腾讯招聘数据,掌握反爬与数据存储
  • AI技术-监督微调SFT
  • 2026年7月深圳crm系统/医药crm系统服务公司哪家权威_深圳市咨微信息科技有限公司 - 行业平台推荐
  • Sim2Real技术解析:从仿真训练到现实部署的完整指南
  • 从零搭建TensorRT C++开发环境:g++、CMake与VS Code实战指南
  • 自动化巡检工具的设计:让规范自己检查自己
  • Java后端程序员收藏:大模型应用开发,你的“第二条曲线”已上线!
  • 医院病房订餐系统架构设计与一床一码技术实现详解
  • 2026年7月食堂智能炒菜机/后厨数智化厂家实力推荐_珠海优特智厨(陕西分部) - 品牌宣传支持者
  • 2026年7月西安工业用油/西安抗磨液压油公司靠谱推荐_陕西金好易石化有限公司 - 品牌宣传支持者
  • 2026年7月弯头厂家/沧州无缝弯头公司推荐测评_沧州誉守管件制造有限公司 - 品牌宣传支持者
  • Hutool 库中使用 Pair类返回键值对
  • 2026年7月海南建材灰沙砖/海口加气砖和灰沙砖厂家推荐排行_海南重力建材有限公司 - 品牌宣传支持者
  • 大模型上下文长度:原理、挑战与RAG等主流扩展方案详解
  • png转jpg最简单方法:素材库格式混乱时的一条时间线 - 办公小帮手
  • 【单片机毕业设计推荐】基于 STM32 的超声波测距与智能声光报警监测系统设计,基于 STM32 的带温度补偿超声波测距 WiFi 监测 APP 系统设计(014204)
  • Cursor上安装agent skills --- MAC版本
  • 2026年7月佛山医疗自助终端机/佛山自助终端机机柜行业实力厂家_佛山市锐铠智能科技有限公司 - 行业平台推荐
  • 2026年7月_9.6米冷链车/昆明厢式冷链车厂家怎么选_云南鲜丰达冷链设备有限公司 - 品牌宣传支持者
  • 塔能两相液冷:单CDU支持30+机柜稳定并联,打破两相液冷规模化部署瓶颈**
  • Unity WebGL全屏自适应解决方案:从画布到UI的完整实践
  • 放大器非线性失真研究装置:从原理到实践的硬件设计与算法实现