HAL库中阻塞式的串口发送函数HAL_UART_Transmit()会导致程序阻塞,很容易破坏实时性。DMA作为专门搬运数据的工具,可以避免程序阻塞。HAL库提供的DMA串口发送函数为HAL_UART_Transmit_DMA(),但在实际应用中,不能简单的将HAL_UART_Transmit()直接替换为HAL_UART_Transmit_DMA(),还需要进行一些配置。
HAL_USART_Transmit(USART_HandleTypeDef *husart, uint8_t *pTxData, uint16_t Size, uint32_t Timeout)
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在函数参数配置上,二者大体相同,都需要填入串口句柄,待传输数据指针与长度。阻塞式传输还需要设定超时时间,单位ms。
以STM32F407为例,通过DMA连续将内存中的数据传输到串口外设USART2。
1 初始化相关DMA与串口
这一步可以通过cubeMX自动生成,实例代码如下:
DMA_HandleTypeDef g_dma_handle_usart2;
g_dma_handle_usart2.Instance = DMA1_Stream6;
g_dma_handle_usart2.Init.Channel = DMA_CHANNEL_4;
g_dma_handle_usart2.Init.Direction = DMA_MEMORY_TO_PERIPH;
g_dma_handle_usart2.Init.PeriphInc = DMA_PINC_DISABLE;
g_dma_handle_usart2.Init.MemInc = DMA_MINC_ENABLE;
g_dma_handle_usart2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
g_dma_handle_usart2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
g_dma_handle_usart2.Init.Mode = DMA_NORMAL;
g_dma_handle_usart2.Init.Priority = DMA_PRIORITY_HIGH;
/* interrupt configuration DMA中断初始化 */
HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 10, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
HAL_DMA_Init(&g_dma_handle_usart2);
__HAL_LINKDMA(&g_usart2_handler,hdmatx,g_dma_handle_usart2);
// g_usart2_handler是对应串口句柄
注意,一定要开启DMA传输完成中断。不开也能进行连续传输,但速度会莫名奇妙地很慢。
2 调用HAL_UART_Transmit_DMA()进行传输
直接在需要发送的地方调用此函数即可,其余操作均在中断中完成。
3 在传输完成中断中清除标志并关闭DMA,为下一次传输做准备。
在DMA中断处理函数中完成操作,一般在名字类似stm32f4xx_it.c的文件中。
void DMA1_Stream6_IRQHandler(void)
{
if (__HAL_DMA_GET_FLAG(&g_dma_handle_usart2, DMA_FLAG_TCIF2_6))
{
__HAL_DMA_CLEAR_FLAG(&g_dma_handle_usart2, DMA_FLAG_TCIF2_6);
__HAL_UART_CLEAR_FLAG(&g_usart2_handler, UART_FLAG_TC);
HAL_UART_DMAStop(&g_usart2_handler); /* 关闭串口DMA */
}
}
HAL自带的函数HAL_DMA_IRQHandler(&g_usart2_handler)也可以完成清除标值,进入中断后处理函数等操作,但是效率不是很高,不如自己来清标值。
对于Normal模式,好像是每传输一次需要关一下DMA再开始下一次传输,所以加上
HAL_UART_DMAStop(&g_usart2_handler); /* 关闭串口DMA */