STM32通过DMA接收不定长串口数据

6 minute read

半年多前做课设,用到HAL库,体验上,用STM32CubeMX和HAL库开发真的很快,但是代码效率确实低不少。 在课设中GPS模块用的串口传输。为了处理接收方便,使用串口的Idle中断来实现串口的不定长接收,再配合DMA就可以减轻CPU负担,把算力分配到更重要的任务上。文中讨论内容适用于Normal模式的DMA。

Idle中断就是空闲中断,当总线上在一个字节对应的周期内未再有新的数据接收时,就会触发中断。

使用HAL的串口DMA

在STM32CubeMX里面的配置就不说了。 主要用到下面两个函数:

1HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
2
3__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)

HAL_UARTEx_RxEventCallback()是串口事件回调函数之一。

Reception is initiated by this function call. Further progress of reception is achieved thanks to DMA services, transferring automatically received data elements in user reception buffer and calling registered callbacks at half/end of reception. UART IDLE events are also used to consider reception phase as ended. In all cases, callback execution will indicate number of received data elements.

在DMA半传输完成,传输完成和串口产生Idle中断时,都会触发这个回调函数。因为这个函数在HAL库中是__weak定义,就是要自己在外部再定义重名的回调函数来处置自己需要的工作。

HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint16_t Size) 需要调用这个函数才会进入接收状态。Size被设置到DMA相关寄存器中的CNDTR项,当DMA传数据输量达到Size的一半是会触发DMA_IT_HT中断,触发回调函数HAL_UARTEx_RxEventCallback();当DMA传数据输量达到Size是会触发DMA_IT_TC中断,同样调用回调函数。 其中DMA_IT_HT和DMA_IT_TC是由DMA产生,Idle中断由串口产生。

之前遇到的问题

当时做课设时,发现接收数据到Size一半就会触发回调函数,但是那时赶着完成课设,没有深究,也忘记了DMA有半传输完成中断,就直接把Size和接收数组变成待接收数据最大长度的两倍。糊弄过去了。

现在想一想怎么避免半传输完成对传输的影响。

  1. HAL_UARTEx_RxEventCallback()函数只有两个参数,Size和huart都与事件类型无关,则无法通过参数分辨事件类型。
  2. HAL库中是先去除中断标志位再调用回调函数,所以没办法在回调函数中通过查询中断标志位判断事件类型。HAL库中是先去除中断标志位再调用回调函数,详细看这个版本的HAL库STM32Cube FW_F4 V1.26.1。stm32f1xx_hal_uart.c中的3591行。
  3. 在每次调用HAL_UARTEx_ReceiveToIdle_DMA()后马上关闭半传输中断。但这样我觉得会在某些情况下引发问题。
  4. 修改库函数,在HAL库中修改HAL_UARTEx_ReceiveToIdle_DMA(),在函数内部修改掉会导致半传输中断的代码。

综上,没有什么好的办法,因为我觉得没到不得已地步不应该去修改库文件。

在HAL库中,回调函数的风格都是每个事件只会触发一个函数名语义化对应的函数。比如,

  • HAL_UART_TxHalfCpltCallback
  • HAL_UART_TxCpltCallback
  • HAL_UART_RxHalfCpltCallback
  • HAL_UART_RxCpltCallback

但是HAL_UARTEx_RxEventCallback这个函数处理了4个事件,半传输完成,传输完成,Idle中断,传输出错。与Hal库一贯风格不像,或许以后会重新修改这个函数吧。

HAL_UARTEx_RxEventCallback使用的例子。传送门