STM32串口空闲中断+DMA接收不定长数据开发详解:避坑指南与实战代码

摘要:本文基于STM32H750平台,详细讲解如何通过串口空闲中断配合DMA实现不定长数据接收,解析"ON/OFF"指令控制LED。内容涵盖CubeMX配置、代码实现、6大经典错误分析与解决方案,并提供可移植的工程模板。


一、功能需求与硬件环境

1.1 功能目标

  • 指令格式:串口接收ASCII指令"ON"开灯,"OFF"关灯
  • 数据回显:将接收到的数据通过DMA回传至发送端
  • 性能要求:支持115200bps波特率,数据长度≤256字节

1.2 硬件配置

模块 参数
主控芯片 STM32H750VBT6
串口 USART2(PA2/PA3)
LED PC13(推挽输出)
开发环境 STM32CubeIDE v1.13.1

二、CubeMX关键配置

2.1 串口与DMA配置

  1. USART2参数

    • Mode: Asynchronous
    • Baud Rate: 115200
    • Word Length: 8bit
    • Parity: None
  2. DMA设置

    • RX DMA: Circular模式(避免重复初始化)
    • RX Request: DMA1 Stream5(根据芯片型号选择)
    • 优先级: Medium

2.2 NVIC配置

  • USART2全局中断:Enable
  • DMA中断:关闭HT(Half Transfer)中断,仅使用TC(传输完成)

三、核心代码实现

3.1 接收缓冲区定义

#define RX_BUF_SIZE 256
uint8_t rx_buf[RX_BUF_SIZE]; // 全局接收缓冲区

3.2 空闲中断回调函数

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  if(huart->Instance == USART2 && Size > 0)
  {
    // 1. 数据回显(DMA发送)
    HAL_UART_Transmit_DMA(&huart2, rx_buf, Size);
    
    // 2. 指令解析(支持任意位置包含指令)
    if(memmem(rx_buf, Size, "ON", 2) != NULL) {
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
    }
    else if(memmem(rx_buf, Size, "OFF", 3) != NULL) {
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
    }
    
    // 3. 重启接收(关键!)
    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buf, RX_BUF_SIZE);
    __HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT); // 关闭过半中断
  }
}

3.3 初始化代码

/* 在main函数初始化部分添加 */
int main(void)
{
  // ...其他初始化
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buf, RX_BUF_SIZE);
  __HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT); // 首次启动时关闭HT中断
  
  while(1)
  {
    // 主循环无需处理接收
  }
}

四、6大经典错误与解决方案

4.1 数据接收不完整

现象:只能接收部分数据
根因:未关闭DMA过半中断
解决:在初始化及回调中添加:

__HAL_DMA_DISABLE_IT(huart2.hdmarx, DMA_IT_HT);

4.2 重复进入中断

现象:接收一次后不再响应
对策:必须在回调中重新启用接收:

HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buf, RX_BUF_SIZE);

4.3 内存越界

现象:系统随机复位
预防:检查缓冲区大小是否匹配:

// 确保CubeMX中DMA配置的Data Width与代码一致
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buf, RX_BUF_SIZE); 

4.4 指令解析失败

优化方案:使用memmem替代strstr,防止无终止符数据:

// 原问题代码
if(strstr((char*)rx_buf, "ON")) 

// 优化代码(处理二进制数据更安全)
if(memmem(rx_buf, Size, "ON", 2))

4.5 数据回显异常

调试技巧:在发送前等待DMA空闲:

// 添加发送状态检查
while(HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX);
HAL_UART_Transmit_DMA(&huart2, rx_buf, Size);

4.6 波特率不匹配

验证方法:使用逻辑分析仪抓取波形,确认实际波特率与代码设置一致。


五、完整测试流程

5.1 硬件连接

  • USB-TTL模块
    TX → PA3 (USART2_RX)
    RX → PA2 (USART2_TX)
    GND共地

5.2 串口助手设置

参数
波特率 115200
数据位 8
停止位 1
发送格式 ASCII模式

5.3 测试用例

发送数据 预期结果
ON LED亮,返回"ON"
OFF LED灭,返回"OFF"
TESTON123 LED亮,返回"TESTON123"
发送256字节数据 完整回显,无数据丢失

六、工程优化建议

  1. 环形缓冲区:添加缓冲机制处理高速数据流

    #define BUF_SIZE 512
    typedef struct {
        uint8_t data[BUF_SIZE];
        uint16_t head;
        uint16_t tail;
    } RingBuffer;
    
  2. 协议增强:增加帧头校验(示例):

    // 帧格式:[0xAA][长度][数据][校验和]
    if(rx_buf[0] == 0xAA && CheckSum(rx_buf)) {
        ProcessCommand(rx_buf[2]); // 处理有效数据
    }
    
  3. 超时机制:防止半包数据长时间占用缓冲区

    // 在空闲中断中启动定时器
    HAL_TIM_Base_Start_IT(&htim3);
    

源码下载https://github.com/Sticker-Sjh/STM32-DMA-UART
关联阅读STM32 HAL库开发常见陷阱大全


问题交流:如有疑问欢迎评论区留言,48小时内必复!原创不易,转载请附原文链接。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐