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

一、前期准备

开发环境: 基于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使用中断进行数据收发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值