STM32:在DMA模式下实现UART
Posted
技术标签:
【中文标题】STM32:在DMA模式下实现UART【英文标题】:STM32: Implementing UART in DMA mode 【发布时间】:2017-09-04 01:16:59 【问题描述】:我正在尝试在 DMA 模式下实现 UART,以便在每次按下按钮时传输一个简单的字符串。
所以我使用 CubeMX 来生成代码,并且我已经将 UART2 TX DMA 配置为正常(非循环)模式,并且没有 FIFO 和突发。
每当我在调试模式下运行代码时,我看到我第一次尝试发送字符串时,它工作正常并发送字符串,但在 DMA IRQ 处理程序中,它调用 TxHalfCpltCallback 而不是 TxCpltCallback 并且 UART gState 将保持在 BUSY 模式,所以我不能用它来传输更多的字符串。
我的问题是为什么它调用 TxHalfCpltCallback 而不是 TxCpltCallback?以及我应该如何处理它(因为 HAL 参考说它等待发送缓冲区的后半部分!什么?)
另外,发送下半部分数据会释放 UART 的 gState 吗?
想请人给我们一个项目中配置UART的例子。
【问题讨论】:
【参考方案1】:如果您使用的是 DMA,那么您将有两个中断:一个是在传输一半缓冲区时,另一个是在传输另一半缓冲区(整个)时。
如果一切正常,他们都应该被解雇。这背后的原因是,当发送大量数据时,您可以开始将新数据加载到TxHalfCpltCallback
中缓冲区的前半部分,而缓冲区的后半部分正在由 DMA 传输。同样,您可以在传输前半部分时将新数据加载到 TxCpltCallback
中缓冲区的后半部分。
优点是在将下一个数据块复制到缓冲区之前,您不必等待整个传输完成,但您可以在传输仍在进行时开始加载它。
这是一个例子:
在此示例中,将使用 DMA、Transmit Half Complete 和 Transmit Complete 中断传输 2000 个字节,以实现最佳性能。
发送缓冲区的前半部分由 CPU 在 Transmit Half Complete 中断回调中加载新数据,而缓冲区的后半部分则由 DMA 在后台发送。
然后,在 Transmit Complete 中,发送缓冲区的后半部分由 CPU 加载新数据,而前半部分(先前更新的)由 DMA 在后台传输。
#include "stm32f4xx.h"
uint8_t dma_buffer[2000];
volatile uint8_t toggle = 0;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_tx;
void uart_gpio_init()
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
void uart_dma_init()
/* DMA controller clock enable */
__DMA1_CLK_ENABLE();
/* Peripheral DMA init*/
hdma_usart2_tx.Instance = DMA1_Stream6;
hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4;
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_tx.Init.PeriphDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_tx.Init.Mode = DMA_NORMAL;
hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_usart2_tx);
__HAL_LINKDMA(&huart2,hdmatx,hdma_usart2_tx);
/* DMA interrupt init */
HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn);
void uart_init()
__USART2_CLK_ENABLE();
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart2);
/* Peripheral interrupt init*/
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* This function handles DMA1 stream6 global interrupt. */
void DMA1_Stream6_IRQHandler(void)
HAL_DMA_IRQHandler(&hdma_usart2_tx);
void USART2_IRQHandler(void)
HAL_UART_IRQHandler(&huart2);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
uint16_t i;
toggle = !toggle;
for(i = 1000; i < 1998; i++)
if(toggle)
dma_buffer[i] = '&';
else
dma_buffer[i] = 'z';
dma_buffer[1998] = '\r';
dma_buffer[1999] = '\n';
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
uint16_t i;
for(i = 0; i < 1000; i++)
if(toggle)
dma_buffer[i] = 'y';
else
dma_buffer[i] = '|';
int main(void)
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
uart_gpio_init();
uart_dma_init();
uart_init();
uint16_t i;
for(i = 0; i < 1998; i++)
dma_buffer[i] = 'x';
dma_buffer[1998] = '\r';
dma_buffer[1999] = '\n';
while(1)
HAL_UART_Transmit_DMA(&huart2, dma_buffer, 2000);
该示例是为 STM32F4 探索板 (STM32F407VG) 编写的。相应的 DMA 实例、UART-DMA 通道、GPIO 和备用功能设置应根据使用的 STM32 微控制器进行更改。
【讨论】:
"优点是在将下一个数据块复制到缓冲区之前您不必等待整个传输完成,但您可以在传输仍在进行时开始加载它。” - 这就是为什么 HAL_UART_TxHalfCpltCallback 和 HAL_UART_TxCpltCallback 在传输半完成/完成之前实际触发的原因吗? @AlexShenfield WellHAL_UART_TxHalfCpltCallback
实际上在发送一半缓冲区时触发,HAL_UART_TxCpltCallback
在发送整个缓冲区时触发。
我认为不一定是缓冲区中的数据实际上“在线”上?我将我的电路板连接到逻辑分析仪并获得类似于visualgdb.com/w/wp-content/uploads/2017/09/21-dma.png 中所示的跟踪,其中 tx 半完成和 tx 完成中断在传输中途/结束之前触发。
附言。对不起,n00b 问题:-)
@Alex 好吧,是的,GPIO 切换也增加了一些测量误差,发送的数据也没有那么多。我认为使用 1-2 千字节会更居中。但是,是的,它并不那么准确。 :)【参考方案2】:
您的问题类似于DMA UART with HAL remain busy bug。
你应该启用HAL_UART_IRQHandler()
即 在“main.c”(或初始化硬件的任何地方)内添加:
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
在“stm32f4xx_it.c”内:
void USART2_IRQHandler(void)
HAL_UART_IRQHandler(&huart2);
【讨论】:
虽然这不能回答实际的 OP 问题,但它是非常重要的信息。我花了一个小时试图弄清楚为什么我的代码只传输一次然后永远处于 HAL_BUSY 状态。 这应该是忙状态的解决办法。【参考方案3】:当您使用裸寄存器方法而不是主宰 HAL 怪物时,编写 DMA 传输(当然还有接收)要容易得多。
STM32F446 示例(假设寄存器中有复位值)
DMA1_Stream6 -> NDTR = nTransfers;
DMA1_Stream6 -> PAR = (uint32_t)&(USART2 -> DR);
DMA1_Stream6 -> M0AR = (uint32_t)&dataBuff;
DMA1_Stream6 -> CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE; // you can enable half transfer enable as well
USART2 -> BRR = FCLK / LOWSPEED;
USART2 -> CR3 |= USART_CR3_DMAT;
USART2 -> CR1 = (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);
DMA1_Stream6 -> CR |= DMA_SxCR_EN;
很容易 - 不是吗?
void DMA1_Stream6_IRQHandler(void) // now it does nothing only clears the flag
if(DMA1 -> HISR & (DMA_HISR_TCIF6))
DMA1 -> HIFCR |= DMA_HISR_TCIF6;
while(!(USART2 -> SR & USART_SR_TC));
【讨论】:
【参考方案4】:对于那些将 STM32CubeIDE 与 FreeRTOS 一起使用的用户,问题可能在于中断优先级。 FreeRTOS 使用configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
设置最高中断优先级,从中可以调用中断安全的 FreeRTOS API 函数。此值默认设置为 5,如果 DMA 和 UART 中断具有相同的优先级,它们将不会触发!
通常,DMA 和 UART 中断函数不调用 FreeRTOS API 函数,因此可以更高。 STM32 微控制器 4 到 0 的方法。
要在 SM32CubeIDE 中实现这一点,您需要在 NVIC 配置中删除选项使用 FreeRTOS 功能的勾号,然后相应地设置 DMA 和 UART 中断的优先级:
NVIC example configuration
【讨论】:
【参考方案5】:如果你使用函数
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在CubeMX
库中,它将启用所有 DMA 中断。您可以通过清除DMA_SxCR
寄存器中的HTIE 位来禁用半传输中断。
【讨论】:
【参考方案6】:对我来说,我在使用 DMA 时遇到了传输错误。该问题已通过启用 TXE 中断解决:
void sas_write(char* buf, uint16_t size)
HAL_UART_Transmit_DMA(&uart_handle, buf, size) ;
__HAL_UART_ENABLE_IT(&uart_handle, UART_IT_TXE) ;
【讨论】:
【参考方案7】:如果您使用 STM32CubeMX 生成项目代码,您还可以在 USARTx 模式和配置面板 -> NVIC 设置上启用 USARTx 全局中断,以解决 gState 保持忙碌时的问题。
【讨论】:
以上是关于STM32:在DMA模式下实现UART的主要内容,如果未能解决你的问题,请参考以下文章
HAL库 STM32CubeMX--USART串口通信--补充
stm32f4 dma + uart idle + double 调试小记