文章目录
一、前期准备
开发环境: 基于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校验: