RT-Thread论坛
直播中

硬件工程师1

11年用户 1664经验值
擅长:可编程逻辑
私信 关注
[问答]

stm32h7 串口 dma 发送长时间运行似乎会卡死,为什么?

最近在公司内部的一个使用了 STM32h743 的模块在长时间运行好几个小时过后,出现了奇怪的现象。原本正常定时外发协议数据的一个串口,突然不发送数据了。 结合调试打印,感觉似乎是发送线程在进入 rt_device_write的逻辑后, 就没有再次退出了。但 shell等其他正常的, shell 输入 ps 后的结果也挺奇怪的,居然有2个线程同时进入了 RUNING 的状态 (shell 和这个串口的发送线程),这显然不正常。请问大家觉得可能是什么原因? 谢谢!
异常情况下的 ps 结果:

与重新上电恢复后的正常情况对比:

部分业务逻辑展示: 为了便于使用,我们公司的前辈封装了一个 Serial 类,用于异步发送串口数据,因为部分逻辑涉及公司内部协议,这里只展示了部分串口发送相关的逻辑:

  • #include "rtthread.h"
  • #include "rtdevice.h"

  • #define LOG_TAG "app.serial"
  • #define LOG_LVL LOG_LVL_DBG
  • #include "ulog.h"

  • struct serial_app_config
  • {
  •     char usname[RT_NAME_MAX];
  •     int baudrate;

  •     // .....
  • };

  • class Serial
  • {
  • public:
  •     Serial(struct serial_app_config serial_cfg);
  •     ~Serial();

  •     bool StartTxProcess();
  •     void TxDone();

  • private:
  •     char _us_name[RT_NAME_MAX];

  •     rt_device_t dev_serial;
  •     rt_ringbuffer Serial_Tx_ringbuffer;
  •     rt_uint8_t *_serial_tx_bufTransfer;
  •     rt_uint8_t *_serial_tx_buf;

  •     rt_sem_t serial_tx_sem;
  •     rt_sem_t serial_txRing_sem;

  •     bool SendData(rt_uint8_t *data, int len);
  •     rt_mutex_t dynamic_mutex = RT_NULL;

  •     bool isread_exit;
  • };

  • void Serial::TxDone()
  • {
  •     log_d("Tx Done %s", _us_name);
  •     rt_sem_release(serial_tx_sem);
  • }

  • bool Serial::SendData(rt_uint8_t  * data, int len)
  • {
  •     bool result = false;

  •     if(rt_mutex_take(dynamic_mutex, 10) == RT_EOK)
  •     {
  •         int buff_left =rt_ringbuffer_space_len(&Serial_Tx_ringbuffer);
  •         if(buff_left == 0  || len > buff_left)
  •         {
  •             rt_ringbuffer_reset(&Serial_Tx_ringbuffer);
  •             buff_left =rt_ringbuffer_space_len(&Serial_Tx_ringbuffer);
  •         }
  •         if(len > buff_left)
  •         {
  •             len = buff_left;
  •         }
  •         if(len <= buff_left)
  •         {
  •             int putlen = rt_ringbuffer_put(&Serial_Tx_ringbuffer,data,len);
  •             if(putlen != len)
  •             {
  •                 rt_ringbuffer_reset(&Serial_Tx_ringbuffer);
  •             }
  •             else
  •             {
  •                 result = true;
  •             }

  •         }
  •         rt_mutex_release(dynamic_mutex);
  •     }

  •     if(result == true)
  •     {
  •         rt_sem_release(serial_txRing_sem);
  •     }

  •     return result;
  • }

  • bool Serial::StartTxProcess()
  • {
  •     bool result = false;
  •     rt_uint16_t waittime = 2000;

  •     while(!isread_exit)
  •     {
  •         rt_sem_take(serial_txRing_sem,waitTime);

  •         if(rt_sem_take(serial_tx_sem,500) == RT_EOK)
  •         {
  •             result = rt_mutex_take(dynamic_mutex, 10);
  •             if(result == RT_EOK)
  •             {
  •                 int len = rt_ringbuffer_cpy(&Serial_Tx_ringbuffer, _serial_tx_buf, len);
  •                 rt_mutex_release(dynamic_mutex);

  •                 if(len > 0)
  •                 {
  •                     // 发送疑似进入后就没有再退出。。。
  •                     rt_uint16_t len_write = rt_device_write(dev_serial,0,_serial_tx_buf,len);
  •                     if(len != len_write)
  •                     {
  •                         log_e("%s device write err:%d %d",_us_name,len,len_write);
  •                         waitTime = 10;
  •                     }
  •                     else
  •                     {
  •                         waitTime = 2000;
  •                     }

  •                     if(rt_mutex_take(dynamic_mutex, 10) == RT_EOK)
  •                     {
  •                         rt_ringbuffer_del(&Serial_Tx_ringbuffer, len_write);
  •                         rt_mutex_release(dynamic_mutex);
  •                     }
  •                     result = true;
  •                 }
  •                 else
  •                 {
  •                     rt_sem_release(serial_tx_sem);
  •                 }
  •             }
  •             else
  •             {
  •                 log_e("%s mutex err",_us_name);
  •                 rt_sem_release(serial_tx_sem);
  •             }
  •         }
  •         else
  •         {
  •             log_e("not Tx Com Sem %s",_us_name);
  •             rt_ringbuffer_reset(&Serial_Tx_ringbuffer);
  •         }
  •     }

  •     return result;
  • }

  • static rt_err_t _tx_done(rt_device_t dev, void *buffer)
  • {
  •     Serial *serial = (Serial *)dev->user_data;
  •     if (serial != RT_NULL)
  •     {
  •         serial->TxDone();
  •     }

  •     return RT_EOK;
  • }

  • void _tx_thread(void *p)
  • {
  •     Serial *serial = (Serial *)p;
  •     serial->StartTxProcess();
  • }

  • Serial::Serial(struct serial_app_config serial_cfg)
  • {
  •     rt_strncpy(_us_name, serial_cfg.usname, sizeof(_us_name));
  •     dev_serial = rt_device_find(_us_name);
  •     if (dev_serial == RT_NULL)
  •     {
  •         log_e("Device %s not found!", _us_name);
  •         return;
  •     }

  •     // Initialize other members and resources
  •     // ...

  •     rt_device_set_tx_complete(dev_serial, _tx_done);
  •     rt_device_open(dev_serial, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_DMA_TX);

  •     // ....

  •     rt_thread_t th;
  •     th = rt_thread_create("tx_thread", _tx_thread, this, 1024, 25, 10);
  •     rt_thread_startup(th);
  • }




回帖(1)

李鸿

2025-9-17 17:45:28

针对 STM32H7 串口 DMA 发送长时间运行卡死的问题,结合您的现象描述(线程卡在 rt_device_write、异常 ps 结果),以下是可能的原因和排查建议:




可能原因分析




  1. DMA 传输未完成导致死锁



    • DMA 发送未触发传输完成中断(TCIF),导致 rt_device_write 等待 DMA_TxComplete 信号量时永久阻塞。

    • 触发场景

      • UART 硬件错误(如噪声干扰导致 FE/NE 错误标志置位)。

      • DMA 配置错误(如数据传输量超出配置范围)。

      • 中断冲突(其他高优先级中断抢占 DMA 中断)。





  2. 线程调度异常(两个 RUNNING 线程)



    • 线程控制块 (TCB) 内存被破坏,导致调度器状态异常。

    • 常见根源

      • 堆栈溢出:发送线程或中断处理栈溢出,覆盖了相邻线程的 TCB。

      • 内存越界:业务代码写内存时踩踏了 TCB 或 DMA 缓冲区。

      • 竞态条件:多个线程访问共享资源(如串口设备)时未正确加锁。





  3. UART/DMA 硬件状态卡死



    • UART 寄存器进入错误状态(如 TE 发送使能位异常复位)。

    • DMA 传输被挂起(如 EN 位意外清零),且驱动未处理错误恢复。




  4. 驱动层缺陷



    • DMA 发送完成中断丢失(如未清除中断标志)。

    • 未处理 UART 错误中断(如 ORE(溢出错误)、FE(帧错误))。

    • 资源释放逻辑缺陷(如重复调用 rt_device_close 导致状态机错乱)。






排查与解决方案


1. 检查内存完整性



  • 堆栈溢出检测

    • .ld 链接脚本中为线程栈添加哨兵值(Magic Number),定期检查哨兵值是否被修改。

    • 增大发送线程和 DMA 中断处理栈大小(至少增加 20-30%)。


  • 堆内存检测

    • 使用 rt_memory_info() 检查堆碎片和分配情况。

    • 启用 heap 溢出保护(如 RT_DEBUG_MEM)。



2. 增强 DMA 驱动鲁棒性



  • 添加超时机制

    修改 rt_device_write 底层驱动,在等待 DMA 完成信号量时增加超时(例如 500ms),超时后重置 DMA 和 UART:
     if (rt_sem_take(&dma->tx_done, RT_TICK_PER_SECOND / 2) != RT_EOK) {
         HAL_UART_AbortTransmit(&huart);
         HAL_DMA_Abort(&hdma_uart_tx);
         HAL_UART_Init(&huart);  // 重新初始化 UART
         // 记录错误日志
    }

  • 处理错误中断

    在 UART 中断服务函数中检查错误标志并恢复:
     void UART_IRQHandler(void) {
         if (__HAL_UART_GET_FLAG(&huart, UART_FLAG_ORE | UART_FLAG_FE)) {
             __HAL_UART_CLEAR_FLAG(&huart, UART_CLEAR_OREF | UART_CLEAR_FEF);
             rt_sem_release(&dma->tx_done);  // 强制释放信号量
         }
         // ... 其他中断处理
    }


3. 线程安全与资源保护




  • 检查串口设备访问冲突



    • 确保所有调用 rt_device_write 的线程 未共享同一串口设备实例(如有必要,为每个线程创建独立的设备句柄)。


    • 在业务代码中使用互斥锁保护串口设备:


      static rt_mutex_t uart_mutex = RT_NULL;
      /* 初始化代码中创建互斥锁 */
      uart_mutex = rt_mutex_create("uart_tx", RT_IPC_FLAG_PRIO);

      /* 发送线程中加锁 */
      rt_mutex_take(uart_mutex, RT_WAITING_FOREVER);
      rt_device_write(device, 0, data, size);
      rt_mutex_release(uart_mutex);





4. 监控硬件状态



  • 异常时寄存器快照
    卡死时通过调试器读取以下寄存器:

    • USARTx->ISR(检查 TC, BUSY, FE, ORE 等标志)。

    • DMAx->LISR/HISR(检查 TCIFx, TEIFx 标志)。


  • 添加调试日志
    在驱动关键位置添加日志(需考虑性能):
     // 在 DMA 完成中断中
    void DMA_IRQHandler(void) {
         RT_KPRINTF("DMA TC IRQ, ISR=0x%08X", DMAx->ISR);
         // ...
    }


5. 长期稳定性测试



  • 压力测试工具
    编写脚本通过串口持续发送数据(如每 100ms 发送 1KB 数据),加速问题复现。

  • Watchdog 复位
    启用独立看门狗(IWDG),设置超时时间 10s,卡死时自动复位。




关键优化点总结































方向 具体措施
内存安全 栈溢出检测、增大线程栈、内存分配监控
驱动健壮性 DMA 超时恢复、UART 错误中断处理、信号量异常释放
线程同步 互斥锁保护串口设备、避免多线程竞争
调试手段 寄存器快照、关键日志、压力测试脚本
灾备设计 看门狗复位、异常后自动重初始化外设



通过以上步骤,重点排查 DMA 传输中断丢失线程栈溢出 两大高频问题。若仍无法解决,建议在卡死阶段通过 JTAG 读取内存和寄存器状态,分析 TCB 是否被破坏或 DMA/USART 寄存器是否处于异常值。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分