STM32 教學系列 第 11 節:RS-485 通訊
🎯 學習目標
- 理解 RS-485 差分信號 - 掌握半雙工通訊
- 配置 MAX485 收發器 - 實現長距離多點通訊
- 實現發送/接收自動切換 - 控制方向線 (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 | 共同接地 |
邏輯定義:
- 邏輯 1:A > B(A 電壓高於 B)
- 邏輯 0:A < B(A 電壓低於 B)
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
- 在 Pinout 中配置:
- PA9:USART1_TX
- PA10:USART1_RX
- PA0:GPIO_Output(用於 DE/RE 控制)
步驟 2:配置 UART 參數
- 左側選 Connectivity → USART1
- Mode:Asynchronous
- 設定:
- Baud Rate:9600
- Word Length:8 Bits
- Parity:None
- Stop Bits:1
步驟 3:啟用中斷
NVIC Settings:
- USART1 global interrupt ✓
步驟 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 節將實現:
- CRC 多項式與校驗原理
- STM32 硬體 CRC 引擎
- 資料完整性驗證
✨ RS-485 遠距通訊完成!🚀