一、总体方案(工业标准做法)
┌──────────────────────────────────────┐
│ STM32 开发板 │
│ │
│ USART1 ──────────▶ RS485/USB-TTL │
│ Modbus RTU (9600,8,N,1) │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ Modbus 协议│ │ 设备控制 │ │
│ │ 解析层 │──│ 任务/驱动 │ │
│ └────────────┘ └────────────┘ │
└──────────────────────────────────────┘
最常用 :Modbus RTU + USART + RS485
裸机 / FreeRTOS 都可
二、硬件连接
1、STM32 ↔ 设备(RS485)
| STM32 | RS485 模块 | 说明 |
|---|---|---|
| USART_TX | DI | 发送 |
| USART_RX | RO | 接收 |
| GPIO | DE/RE | 方向控制 |
| GND | GND | 共地 |
| --- | A/B | 接设备 |
DE/RE 接同一个 GPIO
- 高电平:发送
- 低电平:接收
2、串口参数
波特率:9600 / 19200 / 38400
数据位:8
校验位:None / Even(常用 None)
停止位:1
三、Modbus RTU 协议速查
1、帧格式
| 地址 | 功能码 | 数据 | CRC低 | CRC高 |
| 1B | 1B | N B | 1B | 1B |
2、常用功能码
| 功能码 | 说明 |
|---|---|
| 0x03 | 读保持寄存器 |
| 0x06 | 写单个寄存器 |
| 0x10 | 写多个寄存器 |
3、示例(读寄存器)
请求:
01 03 00 00 00 02 C4 0B
| 字段 | 含义 |
|---|---|
| 01 | 从机地址 |
| 03 | 读保持寄存器 |
| 00 00 | 起始地址 |
| 00 02 | 读 2 个寄存器 |
| C4 0B | CRC |
响应:
01 03 04 00 64 00 C8 FA XX
→ 返回 2 个寄存器:
- 0x0064 = 100
- 0x00C8 = 200
四、STM32 工程结构
Modbus_RTU/
├── Core/
│ ├── Inc/
│ │ ├── modbus.h
│ │ ├── usart.h
│ │ └── device.h
│ └── Src/
│ ├── main.c
│ ├── modbus.c
│ ├── usart.c
│ └── device.c
五、核心代码实现
1、CRC16 校验(Modbus 必用)
c
uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
for (uint16_t pos = 0; pos < len; pos++) {
crc ^= (uint16_t)buf[pos];
for (int i = 8; i != 0; i--) {
if ((crc & 0x0001) != 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
2、USART + DMA 接收
usart.c
c
UART_HandleTypeDef huart1;
uint8_t rx_buf[256];
void USART1_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;
HAL_UART_Init(&huart1);
HAL_UART_Receive_DMA(&huart1, rx_buf, 256);
}
3、Modbus 状态机
modbus.h
c
typedef enum {
MODBUS_IDLE = 0,
MODBUS_RX,
MODBUS_PROCESS,
MODBUS_TX
} Modbus_State_t;
typedef struct {
uint8_t addr;
uint8_t func;
uint16_t reg_addr;
uint16_t reg_value;
uint16_t crc;
} Modbus_Frame_t;
modbus.c
c
Modbus_State_t mb_state = MODBUS_IDLE;
Modbus_Frame_t mb_rx;
void Modbus_Process(uint8_t *data, uint16_t len)
{
if (len < 4) return;
uint16_t crc = Modbus_CRC16(data, len - 2);
if (crc != (data[len - 2] | (data[len - 1] << 8)))
return;
mb_rx.addr = data[0];
mb_rx.func = data[1];
mb_rx.reg_addr = (data[2] << 8) | data[3];
mb_rx.reg_value = (data[4] << 8) | data[5];
switch (mb_rx.func) {
case 0x03: // 读寄存器
Device_Read_Register(mb_rx.reg_addr);
break;
case 0x06: // 写单个寄存器
Device_Write_Register(mb_rx.reg_addr, mb_rx.reg_value);
break;
default:
break;
}
}
4、设备控制示例
device.c
c
uint16_t device_regs[10] = {0};
void Device_Write_Register(uint16_t addr, uint16_t value)
{
if (addr < 10) {
device_regs[addr] = value;
// 实际控制
if (addr == 0x00) {
PWM_SetDuty(value); // 控制设备
}
}
}
void Device_Read_Register(uint16_t addr)
{
uint8_t tx_buf[8];
tx_buf[0] = 0x01; // 从机地址
tx_buf[1] = 0x03; // 功能码
tx_buf[2] = 0x02; // 字节数
tx_buf[3] = device_regs[addr] >> 8;
tx_buf[4] = device_regs[addr] & 0xFF;
uint16_t crc = Modbus_CRC16(tx_buf, 5);
tx_buf[5] = crc & 0xFF;
tx_buf[6] = crc >> 8;
HAL_UART_Transmit(&huart1, tx_buf, 7, 100);
}
5、主循环 / FreeRTOS 任务
c
void Modbus_Task(void)
{
while (1) {
if (rx_complete_flag) {
Modbus_Process(rx_buf, rx_len);
rx_complete_flag = 0;
HAL_UART_Receive_DMA(&huart1, rx_buf, 256);
}
osDelay(10);
}
}
参考代码 stm32开发板下,使用modbus协议,usart通讯控制设备 www.youwenfan.com/contentcsv/72776.html
六、Modbus 调试神器
| 工具 | 用途 |
|---|---|
| Modbus Poll | 主机测试 |
| Modbus Slave | 从机模拟 |
| USB-RS485 | 硬件连接 |
| 串口助手 | 原始帧查看 |
七、常见坑
| 问题 | 原因 | 解决 |
|---|---|---|
| 无响应 | 地址不对 | 确认从机地址 |
| CRC 错 | 校验算法 | 用标准 CRC16 |
| 乱码 | 波特率不一致 | 统一 9600 |
| 丢包 | 未延时 | 帧间 ≥ 3.5 字符 |
| 总线冲突 | 多主机 | 改为单主机 |