STM32H743+DMA+串口空闲中断接收不定长数据,并使用DMA发送数据

在串口使用中,经常会进行数据的收发,但直接串口中断,当波特率高的情况下,或其他任务时间要求比较严格的情况下。串口中断频率过高,会影响到其他运算,并占用大量的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,提高效率。

相关推荐
芯岭技术1 小时前
全新一代2.4GHz 单片无线收发芯片 XL2400T,性能更强,距离更远
单片机·嵌入式硬件
dump linux2 小时前
Linux DRM GPU 驱动框架详解
linux·驱动开发·嵌入式硬件
天骄t2 小时前
ARM时钟初始化与GPT定时器深度解析
stm32·单片机·fpga开发
Zeku2 小时前
20260120 - Linux驱动学习笔记:SPI子系统核心层到具体硬件驱动
stm32·freertos·linux驱动开发·linux应用开发
阿华hhh2 小时前
day4(IMX6ULL)<定时器>
c语言·开发语言·单片机·嵌入式硬件
钰珠AIOT2 小时前
在电源的滤波电路中10uf 和100nF 的电容滤波的频率大概是多少?如何计算?
单片机·物联网
CQ_YM2 小时前
ARM中断
arm开发·嵌入式硬件·arm
羽获飞3 小时前
51单片机UART-串口通讯的配置方法
stm32·单片机·嵌入式硬件
猫猫的小茶馆3 小时前
【Linux 驱动开发】一. 搭建开发环境
linux·汇编·arm开发·驱动开发·stm32·嵌入式硬件·mcu