第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);

posted @ 2025-08-13 14:16  hazy1k  阅读(453)  评论(0)    收藏  举报