Project

General

Profile

issue #30 » 一主一从RS485读取温度.md

鑫阳 张, 08/07/2026 06:44 AM

 
# 一主一从 RS485 读取 DS18B20 温度说明

## 项目目标

本项目实现一个主机通过 RS485 总线读取从机 DS18B20 温度,并在主机端通过串口和 OLED 显示温度结果。

当前验证通过的结构是:

​```text
主机 STM32  <->  主机 RS485 模块  <==== A/B 差分总线 ====>  从机 RS485 模块  <->  从机 STM32  <->  DS18B20

主机负责轮询从机,从机收到查询帧后读取 DS18B20 温度,并通过 RS485 返回温度数据。


系统功能

当前系统包含以下功能:

  1. 主机定时发送温度查询帧。
  2. 从机接收主机查询帧。
  3. 从机读取本地 DS18B20 温度。
  4. 从机将温度打包成协议帧并通过 RS485 返回。
  5. 主机解析从机回复帧。
  6. 主机通过串口打印温度。
  7. 主机通过 OLED 显示温度。
  8. 主机代码保留从机地址数组,后续可扩展多个从机地址。

硬件组成

主机端

主机端主要硬件包括:

STM32F407
RS485 模块
OLED 显示屏
调试串口

主机端作用:

发送查询命令
接收从机回复
解析温度数据
显示温度

从机端

从机端主要硬件包括:

STM32F407
RS485 模块
DS18B20 温度传感器

从机端作用:

等待主机查询
读取 DS18B20 温度
回复温度数据

RS485 通信原理

RS485 使用差分信号传输数据,主要依靠 A/B 两根线之间的电压差判断逻辑状态。

RS485 总线不是看 A 对地或 B 对地的单独电压,而是看:

A - B

或者:

B - A

只要 A/B 之间存在有效差分电压,就说明总线上有设备在驱动信号。

本项目使用 TTL 转 RS485 模块。STM32 的 UART 信号先进入 RS485 芯片,再由 RS485 芯片转换成 A/B 差分信号。


RS485 模块连接

主机 STM32 与主机 RS485 模块连接

主机使用 USART1 与 RS485 模块通信。

STM32 主机引脚 功能 RS485 模块 TTL 侧
PA9 USART1_TX RS485_IN_TX
PA10 USART1_RX RS485_IN_RX
3.3V 电源 3V3
GND 地 GND

注意:

RS485_IN_TX 接 MCU 的 TX
RS485_IN_RX 接 MCU 的 RX

这里不要按普通串口模块的“TX 接 RX、RX 接 TX”思路交叉接。
本模块的 RS485_IN_TX 表示“输入到 RS485 模块的 TX 信号”,所以应接 STM32 的 TX,也就是 PA9。

从机 STM32 与从机 RS485 模块连接

从机同样使用 USART1 与 RS485 模块通信。

STM32 从机引脚 功能 RS485 模块 TTL 侧
PA9 USART1_TX RS485_IN_TX
PA10 USART1_RX RS485_IN_RX
3.3V 电源 3V3
GND 地 GND

RS485 A/B 总线连接

主机 RS485 模块 从机 RS485 模块
A A
B B
GND GND

建议主机和从机之间共地:

主机 GND 连接 从机 GND

虽然 RS485 是差分通信,但实际调试和短距离通信中,共地可以减少共模干扰和异常电平问题。


DS18B20 连接

当前从机 DS18B20 使用 PE6 作为数据引脚。

DS18B20 引脚 连接
VCC 3.3V
GND GND
DQ PE6

DS18B20 的 DQ 数据线需要上拉到 3.3V。

推荐:

DQ 通过 4.7kΩ 电阻上拉到 3.3V

如果代码中配置了内部上拉,也建议保留外部 4.7kΩ 上拉,DS18B20 通信会更稳定。


STM32 外设配置

主机 USART1

主机使用 USART1 与 RS485 模块通信。

配置如下:

参数 配置
串口 USART1
TX PA9
RX PA10
波特率 115200
数据位 8
停止位 1
校验位 None
模式 TX_RX

主机调试串口

主机使用另一个串口输出调试信息,例如:

Slave 1: 25.62 C
Slave 1 timeout, rx=0

当前代码中 _write() 重定向到了 huart2,所以主机调试打印使用 USART2。

主机 OLED

主机 OLED 用于显示温度结果。

显示内容示例:

RS485 Temp
S1: 25.6 C

如果通信失败,则显示:

S1: ----

从机 USART1

从机使用 USART1 与 RS485 模块通信。

配置如下:

参数 配置
串口 USART1
TX PA9
RX PA10
波特率 115200
数据位 8
停止位 1
校验位 None
模式 TX_RX

从机 TIM3

DS18B20 使用微秒级时序,因此从机工程中使用 TIM3 提供 delay_us()。

当前 TIM3 配置目标是:

1 个计数 = 1 us

典型配置:

系统时钟 168MHz
TIM3 在 APB1
Prescaler = 83
Counter Period = 65535

这样 delay_us(480) 才能产生接近 480us 的延时。


通信协议

主机和从机之间使用自定义帧格式。

帧格式

SOF + addr + func + data_len + data + crc16 + EOF
字段 长度 说明
SOF 1 字节 帧头,固定 0xAA
addr 1 字节 从机地址
func 1 字节 功能码
data_len 1 字节 数据长度
data N 字节 数据内容
crc16 2 字节 Modbus CRC16,低字节在前
EOF 1 字节 帧尾,固定 0x55

功能码

功能码 名称 说明
0x02 FUNC_QUERY_TEMP 主机查询温度
0x01 FUNC_REPORT_TEMP 从机上报温度

查询帧

主机查询温度时,数据长度为 0。

格式:

AA addr 02 00 crcL crcH 55

示例:

AA 01 02 00 21 E0 55

含义:

字段 值
帧头 AA
从机地址 01
功能码 02
数据长度 00
CRC 21 E0
帧尾 55

回复帧

从机回复温度时,数据长度为 2。

温度用 int16_t 表示,单位为 0.01℃。

例如:

25.34℃

会转换为:

2534

再拆成两个字节:

高字节 = 2534 >> 8
低字节 = 2534 & 0xFF

回复格式:

AA addr 01 02 tempH tempL crcL crcH 55

主机工作流程

主机启动后执行以下流程:

初始化 RS485 接收模式
初始化环形缓冲区
开启 USART1 接收中断
初始化 OLED
进入循环

循环中:

选择当前从机地址
发送查询帧
切换到接收状态
等待从机回复
解析回复帧
更新串口输出
更新 OLED 显示
切换到下一个从机

当前主机代码保留了从机地址数组:

static const uint8_t slave_addrs[] = {1, 2, 3};

如果以后要增加从机,只需要改这个数组:

static const uint8_t slave_addrs[] = {1, 2, 3, 4};

SLAVE_COUNT 会自动根据数组长度计算。


从机工作流程

从机启动后执行以下流程:

初始化 RS485 接收模式
初始化 DS18B20
开启 USART1 接收中断
进入循环

从机平时处于接收状态。

当从机收到主机查询帧后:

解析帧头
检查地址是否等于 DEVICE_ADDR
检查功能码是否为 FUNC_QUERY_TEMP
检查 CRC 是否正确
置位 pending_query

主循环检测到 pending_query 后:

读取 DS18B20 温度
打包回复帧
通过 RS485 发送给主机
回到接收状态

从机地址配置

从机地址通过编译宏 DEVICE_ADDR 指定。

例如第 1 个从机:

-DDEVICE_ADDR=1

第 2 个从机:

-DDEVICE_ADDR=2

第 3 个从机:

-DDEVICE_ADDR=3

从机代码中有默认地址:

#ifndef DEVICE_ADDR
#pragma message("DEVICE_ADDR not defined, defaulting to 1")
#define DEVICE_ADDR 1
#endif

如果编译时没有指定 DEVICE_ADDR,默认地址就是 1。


DS18B20 读取方式

当前一主一从测温中,每个从机只接一个 DS18B20,因此可以使用 Skip ROM 方式读取温度。

核心命令如下:

ds18b20_write_byte(0xCC);   // Skip ROM
ds18b20_write_byte(0x44);   // Convert T

读取温度寄存器时:

ds18b20_write_byte(0xCC);   // Skip ROM
ds18b20_write_byte(0xBE);   // Read Scratchpad

这种方式适合:

一块从机板上只有一个 DS18B20

如果一块从机板上挂多个 DS18B20,才需要使用 Search ROM 搜索每个传感器的 ROM ID,再通过 Match ROM 读取指定传感器。

当前实际结构如果是:

多个从机,每个从机一个 DS18B20

不需要 DS18B20 搜索 ROM。
主机通过从机地址区分不同从机即可。


RS485 方向控制

本项目中 RS485 模块使用 THVD1406DR 这类自动方向控制芯片时,软件中保留了接口:

void RS485_SetTxMode(void);
void RS485_SetRxMode(void);

但函数内部可以为空:

void RS485_SetRxMode(void)
{
    /* THVD1406 自动方向控制,不需要 MCU 控制方向 */
}

void RS485_SetTxMode(void)
{
    /* THVD1406 自动方向控制,不需要 MCU 控制方向 */
}

保留这两个函数的原因是:

方便兼容以后更换普通 MAX485 模块

如果以后换成普通 MAX485 模块,需要用 GPIO 控制 DE/RE,例如:

发送前拉高 DE
发送完成后拉低 DE

调试过程中的关键问题

PA9 和 PA10 接反

调试过程中曾出现:

MCU PA9 = 3.3V
模块 D = 0.6V
主机 rx=0

这说明 MCU 的 TX 没有真正接到 RS485 模块的 D / RS485_IN_TX。

最终通过交换 PA9 和 PA10 对应接线后,RS485 通信恢复正常。

正确连接是:

PA9  -> RS485_IN_TX
PA10 -> RS485_IN_RX

A/B 有差分信号但主机收不到

如果 A/B 有差分电压,只能说明 RS485 总线上有人在驱动信号。
但主机能否收到数据,还取决于:

RS485 模块 R 输出是否接到 MCU RX
MCU RX 引脚是否配置正确
串口接收中断是否开启
TX/RX 是否接反

所以 A/B 有信号不等于 MCU 一定收到数据。

屏幕显示 0℃

如果屏幕显示 0℃,通信不一定有问题。

常见原因:

DS18B20 没初始化
DS18B20 读取函数返回 0
DS18B20_SensorNum 为 0
DQ 没有上拉
DQ 引脚配置不对
DS18B20 线序接错
使用了不匹配的 Search ROM 版本

本次最终确认:
原始 DS18B20 单探头版本可以正常读数,搜索 ROM 版本不适合当前一块从机一个探头的结构。


常见现象和判断

现象 可能原因 判断方向
主机显示 timeout, rx=0 主机完全没有收到字节 查 PA9/PA10、RS485_IN_TX/RX、A/B、GND
A/B 有差分但 rx=0 总线有信号,但 MCU RX 没收到 查模块 R 到 PA10
屏幕显示 0℃ 通信正常但温度为 0 查 DS18B20
固定 25℃ 能显示 RS485 和显示正常 问题在 DS18B20 读取
捏 DS18B20 温度变化 DS18B20 读取正常 系统完成
只显示 S1: ---- 主机没有成功解析回复 查协议、CRC、从机地址
显示 Slave 1 timeout, rx=6 收到不完整帧 查帧尾、CRC、解析长度
显示乱码或偶发错误 串口参数或接线干扰 查波特率、GND、线长、终端电阻

当前推荐连接汇总

主机

主机 STM32 连接
PA9 主机 RS485 模块 RS485_IN_TX
PA10 主机 RS485 模块 RS485_IN_RX
3.3V 主机 RS485 模块 3V3
GND 主机 RS485 模块 GND

从机

从机 STM32 连接
PA9 从机 RS485 模块 RS485_IN_TX
PA10 从机 RS485 模块 RS485_IN_RX
3.3V 从机 RS485 模块 3V3
GND 从机 RS485 模块 GND
PE6 DS18B20 DQ

总线

主机 RS485 从机 RS485
A A
B B
GND GND

DS18B20

DS18B20 从机 STM32
VCC 3.3V
GND GND
DQ PE6

当前代码思路

主机代码思路

主机核心逻辑:

1. 构造查询帧
2. 通过 USART1 发送到 RS485 模块
3. 接收从机回复
4. 根据 data_len 计算完整帧长度
5. 校验 SOF、EOF、CRC
6. 提取温度
7. 串口打印并刷新 OLED

温度打印使用整数方式格式化,避免 printf 浮点支持问题:

static void print_temp_2dec(uint8_t addr, float temp)
{
    int32_t raw = (int32_t)(temp * 100.0f + (temp >= 0.0f ? 0.5f : -0.5f));
    int32_t abs_raw = raw >= 0 ? raw : -raw;

    if (raw < 0) {
        printf("Slave %d: -%ld.%02ld C\r\n", addr, abs_raw / 100, abs_raw % 100);
    } else {
        printf("Slave %d: %ld.%02ld C\r\n", addr, abs_raw / 100, abs_raw % 100);
    }
}

从机代码思路

从机核心逻辑:

1. USART1 中断接收主机查询帧
2. 根据帧长度接收完整协议帧
3. 校验地址和功能码
4. 收到查询后设置 pending_query
5. 主循环读取 DS18B20 温度
6. 构造回复帧
7. 通过 USART1 返回给主机

从机不建议在中断里直接读取 DS18B20,因为 DS18B20 读取需要延时。
正确做法是:

中断里只解析帧并置位 pending_query
主循环里读取温度并发送回复

后续扩展

扩展多个从机

如果后续从 1 个从机扩展为 3 个从机,主机只需要配置:

static const uint8_t slave_addrs[] = {1, 2, 3};

每个从机编译时分别设置:

-DDEVICE_ADDR=1
-DDEVICE_ADDR=2
-DDEVICE_ADDR=3

接线方式:

主机 A 接所有从机 A
主机 B 接所有从机 B
所有设备 GND 共地

扩展一个从机多个 DS18B20

如果一个从机板上挂多个 DS18B20,就不能再用 Skip ROM。

需要改成:

Search ROM 搜索所有 DS18B20
保存每个 DS18B20 的 64 位 ROM ID
读取时用 Match ROM 指定某个传感器

这种模式下,ds18b20_get_temp(index) 的 index 才有实际意义。

当前一主一从、每个从机一个 DS18B20 的结构不需要搜索 ROM。


最终结论

本系统的核心思路是:主机通过 RS485 查询,从机收到查询后读取本地 DS18B20,并把温度按自定义协议返回。调试中最关键的问题不是协议,而是 RS485 模块 TTL 侧 TX/RX 接线方向。修正 PA9/PA10 与模块 RS485_IN_TX/RS485_IN_RX 的连接后,通信恢复正常。

当前结构下,每个从机只接一个 DS18B20,因此使用 Skip ROM 方式读取最简单、最稳定。主机通过从机地址区分不同温度采集节点。


(6-6/9)