以下是基于 STM32 HAL 库实现 串口 DMA + 状态机接收 的代码框架,支持帧头 (0xA5 0x5A
) 和帧尾 (0x0D 0x0A
) 的数据解析:
1. 硬件配置(STM32CubeMX)
- 启用 USART(如 USART1),配置波特率(如 115200)。
- 启用 DMA:
- 添加 DMA 通道(如
USART1_RX
),模式为 Circular(循环模式)。 - 内存地址自增,数据宽度为
Byte
。
- 添加 DMA 通道(如
- 生成代码。
2. 代码实现
2.1 定义数据结构和缓冲区
// 定义状态机状态
typedef enum {
RX_STATE_WAIT_HEADER1, // 等待帧头第一个字节 0xA5
RX_STATE_WAIT_HEADER2, // 等待帧头第二个字节 0x5A
RX_STATE_RECEIVE_DATA, // 接收数据直到帧尾
} UART_RxState;
// 定义接收控制块
typedef struct {
UART_HandleTypeDef *huart; // 串口句柄
UART_RxState state; // 当前状态
uint8_t *rx_buffer; // DMA 接收缓冲区(需足够大)
uint16_t data_index; // 数据索引
uint16_t frame_length; // 完整帧长度(含头尾)
uint8_t frame_ready; // 帧接收完成标志
} UART_DMA_RxCB;
// 全局变量
UART_DMA_RxCB uart_rx;
uint8_t dma_rx_buffer[256]; // DMA 接收缓冲区
2.2 初始化接收控制块
void UART_DMA_Init(UART_HandleTypeDef *huart) {
uart_rx.huart = huart;
uart_rx.rx_buffer = dma_rx_buffer;
uart_rx.state = RX_STATE_WAIT_HEADER1;
uart_rx.data_index = 0;
uart_rx.frame_ready = 0;
// 启动 DMA 接收(循环模式)
HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_rx.rx_buffer, sizeof(dma_rx_buffer));
}
2.3 状态机解析数据
void UART_ProcessData(void) {
for (uint16_t i = 0; i < sizeof(dma_rx_buffer); i++) {
uint8_t byte = uart_rx.rx_buffer[i];
switch (uart_rx.state) {
case RX_STATE_WAIT_HEADER1:
if (byte == 0xA5) {
uart_rx.state = RX_STATE_WAIT_HEADER2;
uart_rx.data_index = 0;
}
break;
case RX_STATE_WAIT_HEADER2:
if (byte == 0x5A) {
uart_rx.state = RX_STATE_RECEIVE_DATA;
} else {
uart_rx.state = RX_STATE_WAIT_HEADER1; // 帧头错误,重置
}
break;
case RX_STATE_RECEIVE_DATA:
// 检查帧尾:0x0D 0x0A
if (uart_rx.data_index > 0 &&
uart_rx.rx_buffer[uart_rx.data_index - 1] == 0x0D &&
byte == 0x0A)
{
uart_rx.frame_length = uart_rx.data_index + 1;
uart_rx.frame_ready = 1; // 标记帧接收完成
uart_rx.state = RX_STATE_WAIT_HEADER1;
} else {
uart_rx.data_index++;
if (uart_rx.data_index >= sizeof(dma_rx_buffer)) {
uart_rx.state = RX_STATE_WAIT_HEADER1; // 缓冲区溢出,重置
}
}
break;
}
}
}
2.4 中断回调处理
// 在 stm32xxxx_it.c 中实现 DMA 或 UART 空闲中断回调
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart == uart_rx.huart) {
UART_ProcessData(); // 处理接收到的数据
}
}
2.5 主循环处理完整帧
// 在 main.c 的 while(1) 循环中检查帧完成标志
while (1) {
if (uart_rx.frame_ready) {
// 处理完整帧数据(帧内容在 dma_rx_buffer[0] 到 dma_rx_buffer[frame_length-1])
// 例如:解析数据、校验等
Process_Frame(dma_rx_buffer, uart_rx.frame_length);
// 重置标志
uart_rx.frame_ready = 0;
}
}
3. 关键点说明
-
DMA 模式:
- 使用
HAL_UARTEx_ReceiveToIdle_DMA
实现 循环 DMA 接收,配合空闲中断 (IDLE
) 触发数据处理。 - 缓冲区需足够大(如 256 字节),避免数据覆盖。
- 使用
-
状态机设计:
- 状态1:等待帧头第一个字节
0xA5
。 - 状态2:验证帧头第二个字节
0x5A
。 - 状态3:持续接收数据,直到检测到帧尾
0x0D 0x0A
。
- 状态1:等待帧头第一个字节
-
帧处理:
- 检测到帧尾后,标记
frame_ready
,主循环处理数据。 - 需实现
Process_Frame()
函数解析有效数据(如校验、数据提取等)。
- 检测到帧尾后,标记
4. 优化建议
- 数据校验:在帧尾前添加 CRC 校验字节,并在
Process_Frame()
中验证。 - 双缓冲:使用双 DMA 缓冲区切换,避免处理数据时覆盖。
- 超时机制:若长时间未收到帧尾,强制重置状态机。
通过此方案,可高效实现串口数据的 DMA 接收 + 状态机解析,确保数据完整性和低 CPU 占用率。