⏩ 大家好哇!我是小光,嵌入式爱好者,一个想要成为系统架构师的大三学生。
⏩最近在开发一个STM32H723ZGT6的板子,使用STM32CUBEMX做了很多驱动,包括ADC、UART、RS485、EEPROM(IIC)、FLASH(SPI)等等。
⏩本篇文章对STM32CUBEMX配置RS485做一个详细的使用教程。
⏩感谢你的阅读,不对的地方欢迎指正。
一.RS485工作原理
简介
RS-485是美国电子工业协会(EIA)在1983年批准了一个新的平衡传输标准(balanced transmission standard),EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。目前标准名称为TIA-485,但工程师及应用指南仍继续使用RS-485来称呼此标准。
RS-485仅是一个电气标准,描述了接口的物理层,像协议、时序、串行或并行数据以及链路全部由设计者或更高层协议定义。 RS-485定义的是使用平衡(也称作差分)多点传输线的驱动器(driver)和接收器(receiver)的电气特性。
关键特性
差分传输增加噪声抗扰度,减少噪声辐射
长距离链路,最长可达4000英尺(约1219米)
数据速率高达10Mbps(40英寸内,约12.2米)
同一总线可以连接多个驱动器和接收器
宽共模范围允许驱动器和接收器之间存在地电位差异,允许最大共模电压-7-12V
信号电平
RS-485能够进行远距离传输主要得益于使用差分信号进行传输,当有噪声干扰时仍可以使用线路上两者差值进行判断,使传输数据不受噪声干扰。
RS-485差分线路包括以下2个信号:
- A:非反向(non-inverting)
- 信号 B:反向(inverting)信号
也可能会有第3个信号,为了平衡线路正常动作要求所有平衡线路上有一个共同参考点,称为SC或者G。该信号可以限制接收端收到的共模信号,收发器会以此信号作为基准值来测量AB线路上的电压。RS-485标准中提到:
- 若是MARK(逻辑1),线路B信号电压比线路A高
- 若是SPACE(逻辑0),线路A信号电压比线路B高
注:不同的IC使用的信号标示方式不同,不过EIA的标准中只使用A和B的名称。数据为1时,信号B会比信号A要高。不过因为标准其中也提到信号A是"非反向信号",信号B是"反向信号"。因此信号A、B的定义就更容易混淆了,许多组件制造商(错误的)依循了这个A/B的命名原则,所以具体定义需要实际参考设计厂家芯片手册。
为了不引起分歧,一种常用的命名方式是:
- TX+ / RX+ 或D+来代替B(信号1时为高电平)
- TX- / RX- 或D-来代替A(信号0时为低电平)
下图列出在RS-485利用"异步开始-停止"方式发送一个字符(0xD3,最低比特先发送)时,U+端子及 U−端子上的电压变化。
更多原理讲解可以看看参考
实验环境
软件环境
STM32CUBEMX -6.9.0
KEIL 5.38
硬件环境
串口转485电路
串口/RS485转USB转换器(方便调试)与串口转485接线:
- A - A
- B - B
- VCC - 5V
- GND - GND
STM32H723ZGT6开发板与串口转485接线:
- PB6 - RXD
- PB7 - TXD
- PB5 - 485T/R
MX配置
板子、时钟、调试之类的配置就不说了,具体可以看看这篇:
STM32CUBEMX配置ADC(多通道轮询)(STM32H7)--保姆级教程
这里只说一下RS485的具体配置
1.配置USART1作为我们连接RS485的接口
2.配置PB5为我们RS485收发的控制引脚
3.打开串口中断,并配置优先级
驱动编写
usart.h
加入接收缓冲区
c
/* USER CODE BEGIN Private defines */
#define USART1_RXBUFFERSIZE 1 //每次接收1个数据进入一次中断
#define USART1_REC_LEN 200 //定义最大接收字节数 200
#define RS485DIR_TX HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);//定义我们的控制引脚为发送状态
#define RS485DIR_RX HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);//定义我们的控制引脚为接收状态
/* USER CODE END Private defines */
usart.c
c
/* USER CODE BEGIN 0 */
#include <stdio.h>
#include <stdarg.h>
extern unsigned char USART1_aRxBuffer[USART1_RXBUFFERSIZE];//HAL库使用的串口接收缓冲
extern unsigned char USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
/* USER CODE END 0 */
在初始化中加入:
c
HAL_UART_Receive_IT(&huart1, (unsigned char *)USART1_aRxBuffer, USART1_RXBUFFERSIZE);//此处为添加的
这是为了将接收到的数据放到我们的数组中,USART1_aRxBuffer:存入的数组, USART1_RXBUFFERSIZE:一次存入的大小
stm32h7xx_it.c
编写中断和回调函数:
c
unsigned short USART1_RX_STA = 0; //接收状态标记
unsigned char USART1_aRxBuffer[USART1_RXBUFFERSIZE];//HAL库使用的串口接收缓冲
unsigned char USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
unsigned int timeout=0;
unsigned int maxDelay=0x1FFFF;
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
timeout=0;
while (HAL_UART_GetState(&huart1)!=HAL_UART_STATE_READY)//等待就绪
{
timeout++;//超时处理
if(timeout>maxDelay) break;
}
timeout=0;
while(HAL_UART_Receive_IT(&huart1,(unsigned char *)USART1_aRxBuffer, USART1_RXBUFFERSIZE)!=HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1
{
timeout++; //超时处理
if(timeout>maxDelay) break;
}
/* USER CODE END USART1_IRQn 1 */
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)//如果是串口1
{
if((USART1_RX_STA&0x8000)==0)//接收未完成
{
if(USART1_RX_STA&0x4000)//接收到了0x0d
{
if(USART1_aRxBuffer[0]!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
else USART1_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(USART1_aRxBuffer[0]==0x0d)USART1_RX_STA|=0x4000;
else
{
USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_aRxBuffer[0] ;
USART1_RX_STA++;
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
main.c
编写测试代码:
c
//RS485串口
extern unsigned short USART1_RX_STA; //接收状态标记
extern unsigned char USART1_RX_BUF[USART1_REC_LEN];//接收字符串缓冲区
extern unsigned char USART1_aRxBuffer[USART1_RXBUFFERSIZE];//HAL库使用的串口接收缓冲
while(1)
{
if(USART1_RX_STA&0x8000)
{
len=USART1_RX_STA&0x3fff;//得到此次接收到的数据长度
RS485DIR_TX;//拉高PB5,更改RS485模式为发送
HAL_UART_Transmit(&huart1,(uint8_t*)message,sizeof(message),1000); //发送提示信息
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET); //等待发送结束
HAL_UART_Transmit(&huart1,(uint8_t*)USART1_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET); //等待发送结束
RS485DIR_RX;//拉低PB5,更改RS485模式为接收
USART1_RX_STA=0;
if(USART1_RX_BUF[0] == '1')
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_10);
}
}
调试结果
这里就简单实现了一个发什么收什么,然后再把收到的数据发回来的实验,代码的编写和普通的串口没有什么区别,只是多了一个串口转485的模块,一般的USB转串口是用不了的,得用USB转串口/485才行。