STM32 教學系列 第 11 節:RS-485 通訊

🎯 學習目標

  1. 理解 RS-485 差分信號 - 掌握半雙工通訊
  2. 配置 MAX485 收發器 - 實現長距離多點通訊
  3. 實現發送/接收自動切換 - 控制方向線 (DE/RE)

🎓 RS-485 基礎概念

RS-485 vs RS-232

特性 RS-232 RS-485
信號線 單端(信號 + GND) 差分(A、B)
距離 ≤ 15 公尺 ≤ 1200 公尺
速度 最高 115.2 kbps 最高 10 Mbps
設備數 1 對 1 多點(最多 32)
模式 全雙工 半雙工

RS-485 訊號線

信號 說明
A 線 不反轉資料線
B 線 反轉資料線(邏輯電平反轉)
GND 共同接地

邏輯定義:

MAX485 收發器引腳

引腳 名稱 方向 功能
DI Data In 來自 UART TX
RO Receiver Out 至 UART RX
DE Driver Enable 發送使能(高=發送)
RE Receiver Enable 接收使能(低=接收)
A、B 差分線 連接到總線

🛠️ 硬體接線

所需元件

元件 數量 說明
MAX485 收發器 2 一發一收
STM32 Nucleo 2 主控板
跳線 多條 連接
120Ω 終端電阻 2 總線兩端(可選)

接線圖

Nucleo 1 (發送)        MAX485             RS-485 總線          MAX485          Nucleo 2 (接收)
──────────────        ──────────          ────────────         ──────────       ──────────────
PA9 (TX) ────────────→ DI                                                       
PA10 (RX) ←────────── RO                                                        
PA0 ────────────────→ DE                                                        
      ─────────────→ RE                                                        
3.3V ───────────────→ VCC                                                       ← VCC ────── 3.3V
GND ────────────────→ GND                                                       ← GND ────── GND
               ┌─────→ A  ═════════════════════════════════════════════════ A  ──┐
               │                                                           │
               └─────→ B  ═════════════════════════════════════════════════ B  ──┘
                                      [120Ω]              [120Ω]
                                        │                   │
                                       GND                 GND

                                                    ↓ 
                                      ┌────────────────────────┐
                                      │   TX ─→ PA9           │
                                      │   RX ← PA10           │
                                      │   DE ← PA0 (GPIO OUT) │
                                      │   RE ← PA0 (GPIO OUT) │
                                      │   3.3V               │
                                      │   GND                │
                                      └────────────────────────┘

⚙️ CubeMX 配置步驟

步驟 1:啟用 USART1

  1. 在 Pinout 中配置:
    • PA9:USART1_TX
    • PA10:USART1_RX
    • PA0:GPIO_Output(用於 DE/RE 控制)

步驟 2:配置 UART 參數

  1. 左側選 ConnectivityUSART1
  2. Mode:Asynchronous
  3. 設定:
    • Baud Rate:9600
    • Word Length:8 Bits
    • Parity:None
    • Stop Bits:1

步驟 3:啟用中斷

NVIC Settings:

步驟 4:生成程式碼


💻 完整程式碼

發送方:main.c

/* STM32 Lesson 11 - RS-485 (Sender)
 * 功能:透過 RS-485 發送資料
 * 難度:中級
 */

#include "main.h"
#include "usart.h"
#include <stdio.h>
#include <string.h>

#define DE_PORT GPIOA
#define DE_PIN GPIO_PIN_0
#define RE_PORT GPIOA
#define RE_PIN GPIO_PIN_0

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void RS485_SetTx(void);   /* 設為發送模式 */
void RS485_SetRx(void);   /* 設為接收模式 */
void RS485_Send(uint8_t *data, uint16_t size);

UART_HandleTypeDef huart1;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  uint8_t counter = 0;

  while (1)
  {
    char buffer[50];
    
    /* 設為發送模式 */
    RS485_SetTx();
    HAL_Delay(10);  /* 等待方向穩定 */

    /* 準備資料 */
    sprintf(buffer, "Msg%d\r\n", counter++);
    
    /* 發送 */
    RS485_Send((uint8_t *)buffer, strlen(buffer));
    
    /* 設為接收模式 */
    RS485_SetRx();

    HAL_Delay(1000);
  }
}

/**
  * @brief 設置為發送模式(DE=高, RE=高)
  */
void RS485_SetTx(void)
{
  HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_SET);
  HAL_GPIO_WritePin(RE_PORT, RE_PIN, GPIO_PIN_SET);
}

/**
  * @brief 設置為接收模式(DE=低, RE=低)
  */
void RS485_SetRx(void)
{
  HAL_GPIO_WritePin(DE_PORT, DE_PIN, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(RE_PORT, RE_PIN, GPIO_PIN_RESET);
}

/**
  * @brief 透過 RS-485 發送資料
  */
void RS485_Send(uint8_t *data, uint16_t size)
{
  HAL_UART_Transmit(&huart1, data, size, HAL_MAX_DELAY);
  HAL_Delay(5);  /* 等待發送完成 */
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 7;

  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    Error_Handler();

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                              | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    Error_Handler();
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();

  GPIO_InitStruct.Pin = GPIO_PIN_0;
  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_0, GPIO_PIN_RESET);
}

static void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 9600;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;

  if (HAL_UART_Init(&huart1) != HAL_OK)
    Error_Handler();
}

void Error_Handler(void)
{
  while(1);
}

🔍 測試與除錯

預期結果

發送方 UART 輸出

Msg0
Msg1
Msg2

接收方 UART 輸出

Msg0
Msg1
Msg2

常見問題

問題 原因 解決方案
無法接收 方向線控制錯誤 檢查 PA0 GPIO 配置
資料錯亂 波特率不匹配 驗證兩端波特率都是 9600
訊號干擾 缺少終端電阻 在總線兩端各加 120Ω 電阻

📚 進階應用

MODBUS 協定實現

RS-485 常用於 MODBUS 協定。基本框架:

/* MODBUS RTU 訊息格式 */
typedef struct {
  uint8_t slave_id;          /* 從設備地址 */
  uint8_t function_code;     /* 功能碼 */
  uint8_t data[252];         /* 資料 */
  uint16_t crc;              /* CRC 校驗(結合第 12 節) */
} ModbusFrame;

🔗 延伸學習

下節預覽 - 第 12 節:硬體 CRC 計算

第 12 節將實現:


✨ RS-485 遠距通訊完成!🚀


Revision #2
Created 2026-04-01 02:06:21 UTC by TaipeiTechRacing
Updated 2026-04-06 06:23:50 UTC