第21章 RS485介绍及应用
第二十一章 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<key_value;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);

浙公网安备 33010602011771号