在串口使用中,经常会进行数据的收发,但直接串口中断,当波特率高的情况下,或其他任务时间要求比较严格的情况下。串口中断频率过高,会影响到其他运算,并占用大量的CPU时间。使用DMA是个不错的选项。可以大大减少中断,避免打断其他任务。
上次分享了一个STM32F407的,这次分享一全STM32H743的,主要注意DMA的使用与407有些不同,另外因H743使用了cache,所以一定要注意清cache的使用。
下面直接上代码:
1,以串口5为例,其他串口参考,都可以实现。头文件:
cpp
#include "main.h"
#include "string.h"
#include "stm32h7xx_hal.h"
#include "FreeRTOS.h"
#define RX_MAX_BUF_LEN 1500
#define TX_MAX_BUF_LEN 1500
#define TEMP_RX_LEN 200
typedef struct
{
uint8_t rx_buf[RX_MAX_BUF_LEN];
uint8_t tx_buf[TX_MAX_BUF_LEN];
uint16_t rx_len;
uint8_t rx_complete;
} RS232_TYPE;
typedef struct
{
uint8_t buf[TEMP_RX_LEN];
uint16_t len;
} RS232_RX_TYPE;
extern __align(32) RS232_TYPE rs485_uart5_para;
extern __align(32) RS232_RX_TYPE rs485_uart5_deal_rx;
extern UART_HandleTypeDef huart5;
void MX_UART4_Init(uint32_t baudrate);
uint8_t USART5_485_SendData(uint8_t *data, uint16_t len);
2,初始化
cpp
UART_HandleTypeDef huart5;
DMA_HandleTypeDef hdma_uart5_rx;
DMA_HandleTypeDef hdma_uart5_tx;
QueueHandle_t xUART5RxQueue;
SemaphoreHandle_t xUART5TxSemaphore;
__align(32) RS232_TYPE rs485_uart5_para;
__align(32) RS232_RX_TYPE rs485_uart5_deal_rx;
void MX_UART5_Init(uint32_t baudrate)
{
if((baudrate == 4800) || (baudrate == 9600) || (baudrate == 19200) || (baudrate == 38400) || (baudrate == 57600) || (baudrate == 115200))
{
}
else
{
baudrate = 9600;
}
huart5.Instance = UART5;
huart5.Init.BaudRate = baudrate;
huart5.Init.WordLength = UART_WORDLENGTH_8B;
huart5.Init.StopBits = UART_STOPBITS_1;
huart5.Init.Parity = UART_PARITY_NONE;
huart5.Init.Mode = UART_MODE_TX_RX;
huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart5.Init.OverSampling = UART_OVERSAMPLING_16;
huart5.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart5.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart5.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&huart5) != HAL_OK)
{
Error_Handler();
}
if(HAL_UARTEx_SetTxFifoThreshold(&huart5, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if(HAL_UARTEx_SetRxFifoThreshold(&huart5, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if(HAL_UARTEx_DisableFifoMode(&huart5) != HAL_OK)
{
Error_Handler();
}
xUART5RxQueue = xQueueCreate(1, sizeof(rs485_uart5_deal_rx)); // 接收队列
xUART5TxSemaphore = xSemaphoreCreateBinary(); // 发送信号量
xSemaphoreGive(xUART5TxSemaphore); // 初始化为可用
__HAL_UART_ENABLE_IT(&huart5, UART_IT_IDLE); // 使能空闲中断
__HAL_UART_ENABLE_IT(&huart5, UART_IT_TC); // 使能发送完成中断
arm_fill_q7(0, (q7_t *)rs485_uart5_para.rx_buf, RX_MAX_BUF_LEN); //清0
HAL_GPIO_WritePin(RS485_UART5_EN_GPIO_Port, RS485_UART5_EN_Pin, GPIO_PIN_RESET);
HAL_UART_Receive_DMA(&huart5, rs485_uart5_para.rx_buf, RX_MAX_BUF_LEN);
}
void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(uartHandle->Instance == UART5)
{
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_UART5;
PeriphClkInitStruct.Usart234578ClockSelection = RCC_USART234578CLKSOURCE_D2PCLK1;
if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_UART5_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**UART5 GPIO Configuration
PB5 ------> UART5_RX
PB6 ------> UART5_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF14_UART5;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* UART5 DMA Init */
/* UART5_RX Init */
hdma_uart5_rx.Instance = DMA2_Stream0;
hdma_uart5_rx.Init.Request = DMA_REQUEST_UART5_RX;
hdma_uart5_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_uart5_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_uart5_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_uart5_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart5_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart5_rx.Init.Mode = DMA_NORMAL;
hdma_uart5_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_uart5_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if(HAL_DMA_Init(&hdma_uart5_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle, hdmarx, hdma_uart5_rx);
/* UART5_TX Init */
hdma_uart5_tx.Instance = DMA2_Stream1;
hdma_uart5_tx.Init.Request = DMA_REQUEST_UART5_TX;
hdma_uart5_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_uart5_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_uart5_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_uart5_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart5_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart5_tx.Init.Mode = DMA_NORMAL;
hdma_uart5_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_uart5_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if(HAL_DMA_Init(&hdma_uart5_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle, hdmatx, hdma_uart5_tx);
/* UART5 interrupt Init */
HAL_NVIC_SetPriority(UART5_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(UART5_IRQn);
}
}
3,中断处理函数,因为我串口作485用,所以代码中会有485的收发使能操作,在中断函数中可以看到调用了SCB_CleanInvalidateDCache();函数,主要用于清cache,使CPU能拿到最新的数据,避免DMA操作的内存与cache内容不一致。
cpp
void UART5_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 处理接收空闲中断(IDLE)
if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_IDLE) != RESET) // 判断空闲中断标志
{
__HAL_UART_CLEAR_IDLEFLAG(&huart5); // 清除空闲中断标志
// 停止DMA接收,计算接收长度(总缓冲区大小 - DMA剩余待接收长度)
HAL_UART_DMAStop(&huart5);
// 清理 D 缓存
SCB_CleanInvalidateDCache();
rs485_uart5_para.rx_len = RX_MAX_BUF_LEN - __HAL_DMA_GET_COUNTER(huart5.hdmarx);
/* 发送数据到队列 */
if((xUART5RxQueue != NULL) &&
(rs485_uart5_para.rx_len < 20)) //<20是因为上位机发来的命令字节数为8,若存在大于8的情况,需要改此值,并同步修改xUART2RxQueue的大小,目前>20的数据为其他设备返回的数据,不需要处理,直接丢掉。
{
// 复制数据到发送缓冲区(避免原数据被修改)
arm_copy_q7((const q7_t *)rs485_uart5_para.rx_buf, (q7_t *)rs485_uart5_deal_rx.buf, rs485_uart5_para.rx_len); //数据拷贝,比memcpy效率高
rs485_uart5_deal_rx.len = rs485_uart5_para.rx_len;
xQueueSendFromISR(xUART5RxQueue, &rs485_uart5_deal_rx, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
arm_fill_q7(0, (q7_t *)rs485_uart5_para.rx_buf, RX_MAX_BUF_LEN); //清0
HAL_UART_Receive_DMA(&huart5, rs485_uart5_para.rx_buf, RX_MAX_BUF_LEN); //数据处理完成后,再调用。
}
// 处理发送完成中断(TC):通过判断TC标志实现(无需DMA中断)
if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_TC) != RESET) // 判断发送完成标志
{
__HAL_UART_CLEAR_FLAG(&huart5, UART_CLEAR_TCF); // 清除发送完成标志
LED3_ON();
// 485切换为接收模式
HAL_GPIO_WritePin(RS485_UART5_EN_GPIO_Port, RS485_UART5_EN_Pin, GPIO_PIN_RESET);
if(xUART5TxSemaphore != NULL)
{
xSemaphoreGiveFromISR(xUART5TxSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// 调用HAL库默认中断处理
HAL_UART_IRQHandler(&huart5);
}
4,发送函数,在调用HAL_UART_Transmit_DMA后,函数会立即返回,此时不能将485切换为接收模式,一定要在发送完成中断后,再将485切换为接收模式,否则会丢数据。
cpp
uint8_t USART5_485_SendData(uint8_t *data, uint16_t len)
{
if(data == NULL || len == 0 || len > TX_MAX_BUF_LEN)
{
return pdFALSE;
}
// 等待前一次发送完成(超时时间1500ms,防止永久阻塞,若超时,则丢掉上一次数据,重新发送新的数据)
if(xSemaphoreTake(xUART5TxSemaphore, pdMS_TO_TICKS(1500)) != pdPASS)
{
return pdFALSE; // 前一次发送未完成
}
// 复制数据到发送缓冲区(避免原数据被修改)
arm_copy_q7((const q7_t *)data, (q7_t *)rs485_uart5_para.tx_buf, len); //数据拷贝,比memcpy效率高
/* 切换到发送模式 */
HAL_GPIO_WritePin(RS485_UART5_EN_GPIO_Port, RS485_UART5_EN_Pin, GPIO_PIN_SET);
LED3_OFF();
__HAL_UART_ENABLE_IT(&huart5, UART_IT_TC); // 使能发送完成中断
// 启动DMA发送
if(HAL_UART_Transmit_DMA(&huart5, rs485_uart5_para.tx_buf, len) != HAL_OK)
{
EPRINTF("dma send err\r\n");
return pdFALSE;
}
return pdTRUE;
}
调用此函数可以将data内的数据发送出去。可以看到,在发送前会判断上一次是否发送完成。xUART2TxSemaphore是在回调函数中释放的。若发送频率不高,可删除。
以上就是串口DMA的收发。因为收发数据长度都不是定值,无法打开FIFO进一步减少DMA请求总线的次数。若接收数据类似激光雷达类的数据,给雷达发送一条命令后,就一直在接收数据,完全可以启用FIFO+ringbuf,提高效率。