C语言 STM32:在DMA模式下实现UART



我正在尝试在DMA模式下实现UART,以便在每次按下按钮时传输一个简单的字符串。

所以我使用 CubeMX 生成代码,并在正常(非圆形)模式下配置 UART2 TX DMA,也没有 FIFO 和突发。

每当我在调试模式下运行代码时,我都会看到我第一次尝试发送字符串时,它工作正常并发送字符串,但在 DMA IRQ 处理程序中,它调用 TxHalfCpltCallback 而不是 TxCpltCallback,并且 UART gState 将保持处于繁忙模式,所以我不能使用它来传输更多的字符串。

我的问题是为什么它调用TxHalfCpltCallback而不是TxCpltCallback?以及我应该如何处理它(因为HAL引用说它等待发送缓冲区的后半部分!

而且,发送数据的下半部分会释放UART的状态吗?

我想请某人给我们一个在项目中配置 UART 的示例。

如果您使用的是 DMA,那么您将有两个中断:一个在传输缓冲区的一半时,另一个在传输后半部分(整个)时中断。

如果一切正常,他们都应该被解雇。这背后的原因是,当发送大量数据时,您可以开始将新数据加载到TxHalfCpltCallback缓冲区的前半部分,而缓冲区的后半部分由 DMA 传输。同样,您可以在传输前半部分时将新数据加载到TxCpltCallback缓冲区的后半部分。

优点是,您不必等待整个传输完成,然后再将下一个数据块复制到缓冲区中,但您可以在传输仍在进行时开始加载它。

下面是一个示例:

在此示例中,将使用 DMA、传输半完成和传输完成中断传输2000 字节,以实现最佳性能。

传输缓冲区的前半部分由 CPU 在传输半完成中断回调中加载新数据,而缓冲区的后半部分由 DMA 在后台传输。

然后,在传输完成中,传输缓冲区的后半部分由 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 仍然繁忙错误的 DMA UART。 您应该启用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);
}

当您使用裸寄存器方法而不是巨大的 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));
}
}

对于那些将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 示例配置

如果您使用该函数

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) 

CubeMX库中,它将启用所有 DMA 中断。您可以通过清除DMA_SxCR寄存器中的 HTIE 位来禁用半传输中断。

对我来说,我在使用 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) ;
}

如果您使用 STM32CubeMX 生成了项目代码,您还可以在 USARTx 模式和配置面板上启用 USARTx 全局中断 -> NVIC 设置以解决 gState 保持繁忙时的问题。

最新更新