第二十一章 RS485介绍及应用
1. RS485简介
RS485(也被称为TIA-485或EIA-485)是一种工业串行通信标准,它定义了通信系统中驱动器和接收器的电气特性。 它不是一种通信协议,而是一种物理层标准,通常被更高级别的协议(如Modbus RTU、ProfiBus、DMX512等)所引用。
RS485的主要特点:
差分信号传输: RS485采用差分信号传输方式,通过两根信号线(A线和B线)传输一对大小相等、极性相反的对称信号。 这种方式能有效抵御共模噪声和电磁干扰,从而大大增强了抗干扰能力,使其在工业噪声环境中仍能保持较高的可靠性。
长距离传输: 得益于差分信号的高抗噪性和较低的信号衰减率,RS485可以在相对较低的数据速率下实现远距离通信,通常可达1200米(4000英尺),在某些条件下甚至能达到更远。
多点通信能力: RS485支持在同一条总线上连接多个设备,实现多节点通信。 理论上可以连接多达32个标准单元负载设备,而现在许多收发器提供更低的单位负载(如1/8 UL),允许连接多达256个收发器到总线上。
半双工通信: RS485通常采用两线半双工工作模式,即数据可以在两个方向上传输,但不能同时进行收发。 这种模式简化了硬件设计,降低了成本,对于大多数控制和监控应用来说已经足够。 也有四线全双工的RS485实现,允许同时收发数据,但需要两对信号线。
拓扑结构: RS485网络通常采用线性、总线型(菊花链)或多点配置。 为了减少信号反射和数据错误,通常需要在总线两端使用终端电阻(通常为120Ω),特别是在长距离或高速通信时。
应用广泛: RS485因其可靠性、远距离传输和多设备连接能力,在工业自动化、楼宇自动化、过程控制、电机控制、智能仪表和安防系统等领域得到了广泛应用。 例如,在工业自动化中,它常用于连接传感器、PLC(可编程逻辑控制器)和HMI(人机界面)等设备。
2. RS485应用示例
2.1 RS485宏定义
#ifndef __RS485_H__
#define __RS485_H__
#include "sys.h"
#define RS485_EN_RX 1 // 使能接收使能
#define RS485_REC_LEN 64 // 定义最大接收字节数 64
/* 控制RS485_RE脚, 控制RS485发送/接收状态
* RS485_RE = 0, 进入接收模式
* RS485_RE = 1, 进入发送模式
*/
#define RS485_RE(x) do{ x ? \
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_9, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_9, GPIO_PIN_RESET); \
}while(0)
void rs485_init(uint32_t baudrate);
void rs485_send_data(uint8_t *buf, uint8_t len);
void rs485_receive_data(uint8_t *buf, uint8_t *len);
#endif /* __RS485_H__ */
2.2 RS485初始化
// 初始化RS485
void rs485_init(uint32_t baudrate)
{
// RE-PG8 TX-PA2 RX-PA3 UX-USART2
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART2_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIO 初始化 */
GPIO_InitStructure.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStructure.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_8;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);
/* USART2 初始化 */
rs485_handle.Instance = USART2;
rs485_handle.Init.BaudRate = baudrate;
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;
rs485_handle.Init.StopBits = UART_STOPBITS_1;
rs485_handle.Init.Parity = UART_PARITY_NONE;
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
rs485_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&rs485_handle);
__HAL_UART_DISABLE_IT(&rs485_handle, UART_IT_TC);
#ifdef RS485_EN_RX
__HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE); // 使能接收中断
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_NVIC_SetPriority(USART2_IRQn, 3, 3);
#endif // RS485_EN_RX
RS485_RE(0); // 默认接收模式
}
2.3 RS485发送和接受数据
// 发送数据
void rs485_send_data(uint8_t *buf, uint8_t len)
{
RS485_RE(1); // 打开发送模式
HAL_UART_Transmit(&rs485_handle, buf, len, 1000);
rs485_rx_cnt = 0;
RS485_RE(0); // 回到接收模式
}
/**
* @brief RS485查询接收到的数据
* @param buf : 接收缓冲区首地址
* @param len : 接收到的数据长度
* @arg 0 , 表示没有接收到任何数据
* @arg 其他, 表示接收到的数据长度
* @retval 无
*/
void rs485_receive_data(uint8_t *buf, uint8_t *len)
{
uint8_t rxlen = rs485_rx_cnt;
uint8_t i = 0;
*len = 0; // 默认为0
delay_ms(10); // 等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束
if (rxlen == rs485_rx_cnt && rxlen) // 接收到了数据,且接收完成了
{
for (i = 0; i < rxlen; i++)
{
buf[i] = rs485_rx_buf[i];
}
*len = rs485_rx_cnt; // 记录本次数据长度
rs485_rx_cnt = 0;
}
}
2.4 主函数测试
#include "bsp_init.h"
#include "stdio.h"
#include "rs485.h"
int main(void)
{
uint8_t key_value = 0;
uint8_t i, t, cnt = 0;
uint8_t rs485buf[5];
bsp_init();
rs485_init(115200);
LCD_ShowString(30,50,200,16,16,"STM32 RS485 Test");
LCD_ShowString(30,110,200,16,16,"KEY0:Send");
LCD_ShowString(30,130,200,16,16,"Count:");
LCD_ShowString(30,150,200,16,16,"Sned Data:");
LCD_ShowString(30,190,200,16,16,"Receive Data:");
while(1)
{
key_value = key_scan(0);
if(key_value == KEY0_Press) // Send data
{
for(i=0;i<5;i++)
{
rs485buf[i] = cnt+i; // 填充发送缓冲区
LCD_ShowxNum(30+i*32,170,rs485buf[i],3,16,0x80); // 显示发送数据
}
rs485_send_data(rs485buf,5); // 发送数据
}
rs485_receive_data(rs485buf,&key_value); // 接收数据
if(key_value) // 接收到数据
{
if(key_value > 5)
{
key_value = 5;
}
for(i=0;i { LCD_ShowxNum(30+i*32,210,rs485buf[i],3,16,0x80); // 显示接收数据 } } t++; delay_ms(10); if(t == 20) { LED_TOGGLE(LED0_GPIO_Pin); t = 0; cnt++; LCD_ShowxNum(78,130,cnt,3,16,0x80); // 显示计数 } } } 3. RS485相关函数(HAL库) 3.1硬件配置 3.1.1 GPIO 配置(方向控制引脚) // RS485 方向控制引脚初始化 void RS485_DIR_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIO时钟 GPIO_InitStruct.Pin = GPIO_PIN_8; // 方向控制引脚 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); // 默认接收模式 } 3.1.2 UART 配置(RS485 基础) UART_HandleTypeDef huart2; void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; // 收发模式 huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } 3.2 方向控制函数 // 设置为发送模式(使能驱动器) void RS485_EnableTx(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // DE/RE = HIGH HAL_Delay(1); // 等待硬件稳定(根据收发器规格调整) } // 设置为接收模式(禁用驱动器) void RS485_EnableRx(void) { HAL_Delay(1); // 确保最后一个字节发送完成 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); // DE/RE = LOW } 3.3 数据收发函数 3.3.1 阻塞模式发送 void RS485_SendData(uint8_t *pData, uint16_t Size) { RS485_EnableTx(); // 切换为发送模式 HAL_UART_Transmit(&huart2, pData, Size, 100); // UART发送 RS485_EnableRx(); // 切换回接收模式 } 3.3.2 中断模式接收 // 启动接收 void RS485_StartReceive(void) { HAL_UART_Receive_IT(&huart2, rxBuffer, RX_BUFFER_SIZE); } // 接收完成回调 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { // 处理接收到的数据 Process_RS485_Data(rxBuffer, RX_BUFFER_SIZE); // 重新启动接收 HAL_UART_Receive_IT(&huart2, rxBuffer, RX_BUFFER_SIZE); } } 3.3.3 DMA 模式收发(高效方案) uint8_t txBuffer[TX_BUFFER_SIZE]; uint8_t rxBuffer[RX_BUFFER_SIZE]; // 初始化DMA void RS485_DMA_Init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); // 配置TX DMA hdma_tx.Instance = DMA1_Stream6; hdma_tx.Init.Channel = DMA_CHANNEL_4; hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_tx.Init.Mode = DMA_NORMAL; hdma_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_tx); __HAL_LINKDMA(&huart2, hdmatx, hdma_tx); // 配置RX DMA hdma_rx.Instance = DMA1_Stream5; hdma_rx.Init.Channel = DMA_CHANNEL_4; hdma_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // ... 类似TX配置 HAL_DMA_Init(&hdma_rx); __HAL_LINKDMA(&huart2, hdmarx, hdma_rx); // 启动接收 HAL_UART_Receive_DMA(&huart2, rxBuffer, RX_BUFFER_SIZE); } // DMA发送函数 void RS485_SendData_DMA(uint8_t *data, uint16_t size) { RS485_EnableTx(); // 切换发送模式 HAL_UART_Transmit_DMA(&huart2, data, size); // 注意:需要在发送完成回调中切换回接收模式 } // 发送完成回调 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { RS485_EnableRx(); // 切换回接收模式 } } 3.4 高级控制函数 3.4.1 总线冲突检测 // 检查总线状态(可选) bool RS485_CheckBusConflict(void) { if(HAL_GPIO_ReadPin(RS485_RX_PIN_PORT, RS485_RX_PIN) == GPIO_PIN_SET) { return true; // 检测到总线冲突 } return false; } 3.4.2 超时处理 // 带超时的发送 HAL_StatusTypeDef RS485_SendData_Timeout(uint8_t *pData, uint16_t Size, uint32_t timeout) { RS485_EnableTx(); HAL_StatusTypeDef status = HAL_UART_Transmit(&huart2, pData, Size, timeout); RS485_EnableRx(); return status; } 3.4.3 自动方向控制(使用硬件流控制) // 使用RTS引脚自动控制方向(需硬件支持) void MX_USART2_UART_Init(void) { // ... 其他配置 huart2.Init.HwFlowCtl = UART_HWCONTROL_RTS; // 启用RTS流控 // ... } // 初始化RTS引脚 GPIO_InitStruct.Pin = GPIO_PIN_1; // RTS引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);