mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
2165 字
6 分钟
基于Modbus协议的水分仪通信系统设计与实现
2026-05-07

实验概述#

实验目的: 掌握 ModBus 通信协议的基本概念、调试方法以及串口转网口模块(NT1-B)的原理和配置步骤。

一句话概括: 用 STM32 通过串口连接 NT1-B 网口模块,模拟一台水分仪(Modbus 从机),然后在电脑上用 MThings 软件模拟上位机(Modbus 主机),通过网络对这台”假水分仪”进行读写操作。

GitHub 仓库: stm32-moisture-meter-modbus


系统架构#

整个系统构建了一条完整的工业数据采集链路:

MThings(电脑,Modbus主机)
↓ TCP/IP(以太网)
路由器
↓ 以太网
NT1-B 模块(串口转网口,透明转发)
↓ UART 串口(9600bps)
STM32F103RC(模拟水分仪,Modbus从机)

核心理解: NT1-B 就是一根”无形的串口线”。它把网络数据原样转发给 STM32,把 STM32 串口数据原样通过网络发回。STM32 完全不需要处理 TCP/IP 协议栈,只处理串口上的 Modbus RTU 帧。


硬件准备与接线#

所需硬件#

设备说明
STM32F103RC 开发板主控芯片,运行 Modbus 从机程序
NT1-B 串口转网口模块将串口数据透明转发到网络
路由器连接 NT1-B 和电脑
网线 ×2NT1-B 接路由器,电脑接路由器
TTL 转 USB 模块连接 USART1 调试口,查看 printf 输出
杜邦线若干连接 STM32 与 NT1-B、TTL 模块

接线方式#

STM32 ↔ NT1-B(Modbus 通信,USART2,9600bps):

STM32 NT1-B(串口侧)
PA2 (TX) ──→ RXD
PA3 (RX) ←── TXD
GND ─── GND
3.3V ─── VCC(看NT1-B规格,可能需要5V)

注意:TX 接 RX,RX 接 TX,交叉连接! TX 接 TX 是最常见的接线错误,会导致完全没有数据传输。

STM32 ↔ TTL-USB 模块(调试输出,USART1,115200bps):

STM32 TTL-USB 模块
PA9 (TX) ──→ RXD
PA10 (RX) ←── TXD
GND ─── GND

NT1-B ↔ 路由器 ↔ 电脑:

NT1-B 网口 ──网线──→ 路由器 LAN 口
电脑网口 ──网线──→ 路由器 LAN 口

NT1-B 模块配置#

NT1-B 通过 EBYTE 网络配置工具(V5.1)进行配置:

参数设置值说明
工作模式TCP Server水分仪协议要求从机作为 TCP 服务器
本地 IP192.168.0.10协议手册规定
本地端口502协议手册规定(Modbus 标准端口)
串口波特率9600必须与 STM32 代码中 UART_BAUD 一致
数据位8Modbus RTU 标准
停止位1Modbus RTU 标准
校验位Modbus RTU 标准

NT1-B 的本质: 它是一个透明转发器——网口收到 TCP 数据 → 原样从串口发出;串口收到数据 → 原样从网口通过 TCP 发回。它不做任何协议解析,对 STM32 来说完全感知不到网络的存在。


STM32 程序设计#

寄存器表#

程序用一个静态数组 g_regs[0x20] 模拟 32 个 16 位寄存器:

地址内容类型说明
0x0000水分仪地址设置读写默认值 1
0x0001水分参数设置读写
0x0010砂石种类读写1=粗砂,2=细砂…
0x0011实时水分值只读实际值×10,如 128=12.8%
0x0012温度值只读实际值×10,如 253=25.3℃

寄存器值采用实际值×10 的整数存储方式,是工业仪表的常规做法:避免浮点数运算,Modbus 协议寄存器为整数类型,MThings 通过配置系数 0.1 自动还原真实值。

支持的功能码#

功能码名称说明
0x03读寄存器主机请求读取一个或多个寄存器,从机返回值
0x06写单个寄存器主机写入一个寄存器,从机回显确认

Modbus RTU 帧格式#

[从机地址][功能码][数据...][CRC_L][CRC_H]
例:[01][03][00][11][00][01][C5][CE] → 读寄存器0x0011,数量1

CRC16 校验算法#

CRC16-Modbus 是 Modbus RTU 协议的校验机制,用于检测数据传输错误:

  1. 初始化 CRC 寄存器为 0xFFFF
  2. 逐字节处理帧数据(不含 CRC 本身)
  3. 每个字节:先与 CRC 低字节异或,然后逐位处理 8 次
  4. 每位处理:最低位为 1 则右移后异或多项式 0xA001,否则只右移
  5. 最终得到 16 位 CRC 值,低字节先发、高字节后发
uint16_t CRC16_Modbus(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
uint16_t i, j;
for(i = 0; i < len; i++)
{
crc ^= buf[i];
for(j = 0; j < 8; j++)
{
if(crc & 0x0001)
crc = (crc >> 1) ^ 0xA001;
else
crc >>= 1;
}
}
return crc;
}

帧处理流程#

USART2 中断接收字节 → 存入 g_rx_buf
主循环每 10ms 检查一次
g_rx_cnt > 0 ? → 再等 20ms 确保整帧收完
Modbus_Process():
1. 检查帧长度 >= 8 字节
2. 检查第1字节 == 从机地址 0x01
3. 验证最后2字节 CRC16
4. 根据功能码分发:
- 0x03 → Handle_FC03() 读寄存器
- 0x06 → Handle_FC06() 写寄存器
5. 清空接收缓冲,准备下一帧

传感器模拟#

Sensor_Simulate() 函数每 1 秒更新一次水分和温度值,模拟真实传感器的读数变化:

参数寄存器值范围实际含义每次变化幅度
水分值50~2005.0%~20.0%±1~±5(即 ±0.1%~0.5%)
温度值150~35015.0℃~35.0℃±1~±3(即 ±0.1℃~0.3℃)

变化方式是带边界的随机游走,到达上下限时被限制住,不会超出合理区间。


MThings 上位机配置#

协议选择(关键!)#

新建连接时必须选择 “Modbus RTU over TCP”不能选 “Modbus TCP”

参数设置值
协议类型Modbus RTU over TCP(关键!)
IP 地址192.168.0.10(NT1-B 的 IP)
端口502
从机地址1

添加读取点位#

点位名称寄存器地址功能码数据类型倍率说明
砂石种类0x001003INT161直接显示
实时水分0x001103INT160.1除以10显示(如128→12.8%)
温度值0x001203INT160.1除以10显示(如253→25.3℃)

Modbus RTU vs Modbus TCP#

这是本实验中遇到并解决的关键问题:

特性Modbus RTUModbus TCP
传输介质串口(RS232/RS485)以太网(TCP/IP)
帧头从机地址(1字节)MBAP 头(7字节)
校验CRC16(2字节)无(TCP 自带校验)
标准端口502

NT1-B 透传场景下的区别:

  • 用 Modbus TCP:STM32 收到的第一字节是 0x00(事务 ID 高字节),不是 0x01(从机地址),地址校验直接失败,帧被丢弃
  • 用 Modbus RTU over TCP:MThings 发送的就是标准 RTU 帧直接塞进 TCP payload,NT1-B 转发后 STM32 收到的和串口发的一模一样

遇到的问题与解决方案#

问题1:MThings 显示”写错误”#

现象: 用串口助手手动发送 Modbus 帧,STM32 能正常应答。但用 MThings 通过网络发送就失败。

原因: MThings 的协议选错了,选了 “Modbus TCP” 而不是 “Modbus RTU over TCP”。

解决: MThings 连接设置中,协议类型改为 “Modbus RTU over TCP”

问题2:Keil 编译时 printf 浮点数不显示#

现象: printf("Moisture=%.1f%%", val / 10.0) 输出为空或乱码。

原因: Keil ARMCC 编译器默认不链接浮点 printf 支持库。

解决: 改用整数运算代替浮点打印:

printf("Moisture=%d.%d%%", val / 10, val % 10);

问题3:程序卡死在 delay_ms 循环#

现象: Keil 调试时断点命中在 delay.c 的 SysTick 等待循环。

原因: main.c 中自定义了一个 Delay_ms()(裸循环实现),与正点原子 delay.c 冲突,且 delay_init() 未被调用。

解决: 删除自定义 Delay_ms(),在 main() 开头调用 delay_init(),所有延时改为框架函数 delay_ms()。


调试顺序建议#

  1. 先只连 STM32 和电脑串口(不接网络)

    • 烧录程序后,用串口助手连接 USART1(115200bps)查看调试输出
    • 确认程序正常启动,能看到启动信息
    • 确认传感器数据每秒更新一次
  2. 用串口助手手动测试 Modbus 帧

    • 连接 USART2(9600bps),手动发送:01 03 00 11 00 01 C5 CE
    • 应该收到应答帧,包含水分值
  3. 接入 NT1-B 模块

    • 按接线图连接 STM32 USART2 ↔ NT1-B
    • 浏览器访问 NT1-B 配置页,确认参数正确
    • ping 192.168.0.10 确认网络连通
  4. 打开 MThings 进行测试

    • 新建连接:Modbus RTU over TCP,192.168.0.10:502,从机地址 1
    • 读寄存器 0x0011(水分值),观察是否返回数据
    • 写寄存器 0x0010(砂石种类),写入后读回确认

实验结果#

读操作验证#

点位名称寄存器原始值MThings 显示值说明
砂石种类11直接显示
实时水分值128(初始)12.8%(动态变化)每秒随机游走
温度值253(初始)25.3℃(动态变化)每秒随机游走

写操作验证#

操作MThings 发送帧STM32 应答帧结果
写入值301 06 00 10 00 03 C8 0E01 06 00 10 00 03 C8 0E写入成功,回显相同
再次读取01 03 00 10 00 01 84 0A01 03 02 00 03 F8 45读回值=3,写入有效

调试口输出样例#

=== Moisture Meter Modbus Slave ===
Slave Addr: 0x01
USART2: 9600 bps (Modbus)
USART1: 115200 bps (Debug)
Waiting for Modbus requests...
[SENSOR] Moisture=12.8% Temp=25.3C
[RX] 8 bytes: 01 03 00 11 00 01 D5 CA
FC03: read reg 0x0011, count 1
[TX] 7 bytes: 01 03 02 00 80 B9 FC

知识点总结#

整个实验的本质很简单,记住这三句话:

  1. NT1-B 就是一根”无形的串口线”——它把网络数据透明转发给 STM32,STM32 完全不需要处理 TCP,只需要处理串口上的 Modbus RTU 帧。

  2. STM32 模拟的是一张”寄存器表”——主机读就返回表里的值,主机写就更新表里的值,其他什么都不做。

  3. MThings 是调试工具——它替你自动构造 Modbus 帧、计算 CRC、发送请求、解析应答,你只需要填寄存器地址和数值。


完整代码和工程文件请访问 GitHub 仓库:stm32-moisture-meter-modbus

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

基于Modbus协议的水分仪通信系统设计与实现
https://github.com/WuWingKit/-stm32-moisture-meter-modbus
作者
千板な
发布于
2026-05-07
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录