【STM32】RTT Studio中HAL库RS485-DMA串行通信开发进阶教程二

文章目录

  • 一、前期准备
  • 二、实验步骤
  • 1.使用STM32CubeMX配置初始化代码
  • 2.常用函数解析
  • 3.相关程序
  • 4.实验效果
  • 三、参考文章
  • 一、前期准备

    开发环境: 基于RT-Thread Studio软件的开发
    辅助软件: STM32CubeMX初始化代码生成
    调试软件: 串口助手
    使用芯片: STM32F407VET6
    硬件环境: 外接USB转RS485通信线,控制板

    二、实验步骤

    1.使用STM32CubeMX配置初始化代码

    (1)配置时钟:
    配置RCC,将RCC配置为外部时钟,外部低速时钟和外部高速时钟都可以配置,并且会自动配置IO引脚:

    在SYS中配置程序烧录口,使用SWD进行程序烧录:

    配置时钟树,外部时钟配置为8MHz,将HCLK时钟配置为168MHz,保证时钟性能最佳:

    (2)配置串口USART3的参数

  • Mode:设置为异步通信(Asynchronous)
  • 基础参数:波特率为9600Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能 (默认的就行)
  • (3)DMA中断设置:DMA Settings ——> Add,选择USART3_RX,接着同样步骤再把USART3_TX,添加。选择Normal发送模式。

  • DMA Request(DMA请求): USART3_RX、USART3_TX
  • Dirction (DMA传输方向): Peripheral To Memory(外设到内存)、Memory To Peripheral(内存到外设 )
  • Priority(优先级): 低、低
  • Mode(模式): 正常、正常
  • (4)打开串口中断以及设置优先级

    (6)配置RS485控制引脚:设置成GPIO输出模式,默认为低电平接收模式,并且设置为上拉:

    (7)生成代码:


    2.常用函数解析

    // 发送和接收函数
    HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);  // 串口发送数据,使用超时管理机制
    HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);   // 串口接收数据,使用超时管理机制
    HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);		// 串口中断模式发送(只触发一次中断)
    HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);		// 串口中断模式接收(只触发一次中断)
    HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 	// 串口DMA模式发送
    HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);	// 串口DMA模式接收
    HAL_UART_GetState(UART_HandleTypeDef *huart);	// 判断接收与发送是否结束
    
    // 回调函数,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);		// 接收中断回调函数
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); 	    // 发送中断回调函数
    void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); 	// 串口发送一半中断回调函数
    void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);	// 串口接收一半回调函数
    
  • LUART_HandleTypeDef *huart 串口的别名 如 : 我们使用串口USART1的别名就是huart1。
  • *pData 需要发送的数据
  • Size 发送的字节数
  • Timeout 最大发送时间
  • HAL_UART_STATE_BUSY_RX,接收完成标志
  • HAL_UART_STATE_BUSY_TX,发送完成标志
  • 3.相关程序

    (1)RS485.c相关程序

    #include <rs485.h>
    
    /*======================================================### 静态函数调用 ###==================================================*/
    /**
     * @brief RS485GPIO控制引脚初始化
     */
    static void RS485_GPIO_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
    
        /* GPIO Ports Clock Enable */
        __HAL_RCC_GPIOB_CLK_ENABLE();
    
        /*Configure GPIO pin Output Level */
        HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_RESET);
    
        /*Configure GPIO pins : PBPin PBPin */
        GPIO_InitStruct.Pin = DE_485_Pin | RE_485_Pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Pull = GPIO_PULLDOWN;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
    
    /**
     * @brief RS485的DMA中断使能和优先级配置
     */
    static void RS485_DMA_Init(void)
    {
        /* DMA controller clock enable */
        __HAL_RCC_DMA1_CLK_ENABLE();
    
        /* DMA interrupt init */
        /* DMA1_Stream1_IRQn interrupt configuration */
        HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 4, 0);
        HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
    
        /* DMA1_Stream3_IRQn interrupt configuration */
        HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 3, 0);
        HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
    }
    
    /**
     * @brief RS485串口3初始化
     */
    static void RS485_USART3_Init(void)
    {
        huart3.Instance = USART3;
        huart3.Init.BaudRate = 9600;
        huart3.Init.WordLength = UART_WORDLENGTH_8B;
        huart3.Init.StopBits = UART_STOPBITS_1;
        huart3.Init.Parity = UART_PARITY_NONE;
        huart3.Init.Mode = UART_MODE_TX_RX;
        huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
        huart3.Init.OverSampling = UART_OVERSAMPLING_16;
        if (HAL_UART_Init(&huart3) != HAL_OK)
        {
            Error_Handler();
        }
    
        // 使能IDLE中断
        __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
    
        // DMA接收函数
        HAL_UART_Receive_DMA(&huart3, g_rs485.RxBuff, BUFFER_SIZE);
    }
    
    /**
     * @brief 串口初始化
     * @param uartHandle
     */
    void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        if (uartHandle->Instance == USART3)
        {
            /* USART3 clock enable */
            __HAL_RCC_USART3_CLK_ENABLE();
            __HAL_RCC_GPIOD_CLK_ENABLE();
    
            /**USART3 GPIO Configuration
            PD8     ------> USART3_TX
            PD9     ------> USART3_RX
             */
            GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
            GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
            GPIO_InitStruct.Pull = GPIO_PULLUP;
            GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
            GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
            HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    
            /* USART3 DMA Init */
            /* USART3_RX Init */
            hdma_usart3_rx.Instance = DMA1_Stream1;
            hdma_usart3_rx.Init.Channel = DMA_CHANNEL_4;
            hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
            hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
            hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_usart3_rx.Init.Mode = DMA_NORMAL;
            hdma_usart3_rx.Init.Priority = DMA_PRIORITY_LOW;
            hdma_usart3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            if (HAL_DMA_Init(&hdma_usart3_rx) != HAL_OK)
            {
                Error_Handler();
            }
            __HAL_LINKDMA(uartHandle, hdmarx, hdma_usart3_rx);
    
            /* USART3_TX Init */
            hdma_usart3_tx.Instance = DMA1_Stream3;
            hdma_usart3_tx.Init.Channel = DMA_CHANNEL_4;
            hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
            hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;
            hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_usart3_tx.Init.Mode = DMA_NORMAL;
            hdma_usart3_tx.Init.Priority = DMA_PRIORITY_LOW;
            hdma_usart3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            if (HAL_DMA_Init(&hdma_usart3_tx) != HAL_OK)
            {
                Error_Handler();
            }
            __HAL_LINKDMA(uartHandle, hdmatx, hdma_usart3_tx);
    
            /* USART3 interrupt Init */
            HAL_NVIC_SetPriority(USART3_IRQn, 5, 0);
            HAL_NVIC_EnableIRQ(USART3_IRQn);
        }
    }
    /*=====================================================#######  END  #######=================================================*/
    
    /*======================================================##### 外部调用 #####==================================================*/
    /**
     * @brief RS485初始化
     */
    void RS485_Init(void)
    {
        RS485_GPIO_Init();
        RS485_DMA_Init();
        RS485_USART3_Init();
    }
    
    /**
     * @brief RS485串口发送函数
     * @param buf:发送数据
     * @param len:发送数据长度
     */
    void RS485_UART3_DMA_Send(uint8_t *buf, uint8_t len)
    {
        // 发送模式
        RS485_RE(1);
        HAL_UART_Transmit_DMA(&huart3, buf, len);
    }
    /*=====================================================#######  END  #######=================================================*/
    
    /*======================================================##### 中断函数 #####==================================================*/
    /**
     * @brief 串口3中断函数
     */
    void USART3_IRQHandler(void)
    {
        uint32_t temp;
    
        if ((__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET))
        {
            /* 清除状态寄存器和串口数据寄存器 */
            __HAL_UART_CLEAR_IDLEFLAG(&huart3);
    
            /* 失能DMA接收 */
            HAL_UART_DMAStop(&huart3);
    
            /* 读取接收长度,总大小-剩余大小 */
            temp = __HAL_DMA_GET_COUNTER(&hdma_usart3_rx);
            g_rs485.RxLen = BUFFER_SIZE - temp;
    
            /* 接收标志位置1 */
            g_rs485.RxEndFlag = 1;
    
            /* 使能接收DMA接收 */
            HAL_UART_Receive_DMA(&huart3, g_rs485.RxBuff, BUFFER_SIZE);
    
            /* 开启空闲中断,当时没有数据的时候中断*/
            __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
        }
    
        HAL_UART_IRQHandler(&huart3);
    }
    
    /**
     * @brief 接收中断
     */
    void DMA1_Stream1_IRQHandler(void)
    {
        HAL_DMA_IRQHandler(&hdma_usart3_rx);
    }
    
    /**
     * @brief 发送中断
     */
    void DMA1_Stream3_IRQHandler(void)
    {
        HAL_DMA_IRQHandler(&hdma_usart3_tx);
    }
    
    /**
     * @brief DMA 发送完成回调函数
     * @param huart
     */
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
        if (huart == &huart3)
        {
            // 切换为 RS485 接收模式
            RS485_RE(0);
    
            // 清除接收到的数据
            memset(g_rs485.RxBuff, 0, g_rs485.RxLen);
    
            // 清除计数
            g_rs485.RxLen = 0;
        }
    }
    /*=====================================================#######  END  #######=================================================*/
    

    (2)RS485.h相关程序

    #ifndef APPLICATIONS_RS485_H_
    #define APPLICATIONS_RS485_H_
    
    #include <rtthread.h>
    #include <drv_common.h>
    #include <string.h>
    
    /**=====================================================###### 宏定义 ######==================================================*/
    #define DE_485_Pin          GPIO_PIN_14         // DE发送使能,高电平有效
    #define DE_485_GPIO_Port    GPIOB
    #define RE_485_Pin          GPIO_PIN_15         // RE接收使能,低电平有效
    #define RE_485_GPIO_Port    GPIOB
    
    #define BUFFER_SIZE         255                 // 接收数据大小
    /**====================================================#######  END  #######=================================================*/
    
    /**=====================================================### 全局变量定义 ####=================================================*/
    UART_HandleTypeDef huart3;
    DMA_HandleTypeDef hdma_usart3_rx;
    DMA_HandleTypeDef hdma_usart3_tx;
    
    /* 控制RS485_RE脚, 控制RS485发送/接收状态
     * RS485_RE = 0, 进入接收模式
     * RS485_RE = 1, 进入发送模式
     */
    #define RS485_RE(x)   do{ x ? \
                              HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_SET) :  \
                              HAL_GPIO_WritePin(GPIOB, DE_485_Pin | RE_485_Pin, GPIO_PIN_RESET); \
                          }while(0)
    
    typedef struct
    {
        uint8_t RxBuff[BUFFER_SIZE];    // 接收缓冲器
        uint8_t TxBuff[BUFFER_SIZE];    // 发送缓冲器
        uint8_t TxEndFlag;              // 发送完成标志
        uint8_t RxEndFlag;              // 接收完成标志
        uint8_t RxLen;                  // 接收数据长度
    
    } rs485_t;
    
    rs485_t g_rs485;
    /**====================================================#######  END  #######=================================================*/
    
    /**==================================================##### 函数及变量声明 #####===============================================*/
    extern void RS485_Init(void);                                   // RS485初始化
    extern void RS485_UART3_DMA_Send(uint8_t *buf, uint8_t len);    // RS485串口发送函数
    /**====================================================#######  END  #######=================================================*/
    
    #endif /* APPLICATIONS_RS485_H_ */
    

    (3)main.c相关程序

    #include <rtthread.h>
    #include <drv_common.h>
    #define DBG_TAG "main"
    #define DBG_LVL DBG_LOG
    #include <rtdbg.h>
    #include "rs485.h"
    
    int main(void)
    {
        int count = 1;
    
        // RS485初始化
        RS485_Init();
    
        // 测试发送函数
        for (int i = 0; i < 5; ++i)
        {
            g_rs485.TxBuff[i] = 0x0A + i;
        }
        // 发送数据
        RS485_UART3_DMA_Send(g_rs485.TxBuff, 5);
    
        while (count)
        {
            // 接收完成标志
            if (g_rs485.RxEndFlag == 1)
            {
                // 发送接收到的数据
                RS485_UART3_DMA_Send(g_rs485.RxBuff, g_rs485.RxLen);
    
                // 清除接收结束标志位
                g_rs485.RxEndFlag = 0;
            }
    
            rt_thread_mdelay(1);
        }
    
        return RT_EOK;
    }
    

    4.实验效果

    通过发送ModBus的指令,来测试程序是否可以使用,并且串口可以自动加CRC16校验:

    三、参考文章

    ===》》》RS485使用中断进行数据收发

    作者:0南城逆流0

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】RTT Studio中HAL库RS485-DMA串行通信开发进阶教程二

    发表回复