【物联网设备端开发】ESP32 使用RS485模块实现Modbus通信

🌈 个人主页:帐篷Li
🔥 系列专栏:物联网设备端开发

💪🏻 gitee地址:IOTDeviceSDK物联网设备端开发工具包
🤵‍♂️ 物联网设备上云提供开箱即用接入SDK,提供物联网设备端开发工具包

目录

一、实验效果

1.1 元件说明

1.2 引脚说明

1.3 BOM表

二、接线图

三、线圈和寄存器

3.2 寄存器地址分配

四、Modbus 功能码

五、功能码详解

5.1 0x01 读取线圈/离散量输出值

5.2 0x02 读取离散量输入值

5.3 0x03 读取保持寄存器值

5.4 0x04 读取输入寄存器值

5.5 0x05 写单个线圈/单个离散输出

5.6 0x06 写单个保持寄存器

5.7 0x08 诊断功能

5.8 0x0B 获取通信事件计数器

5.9 0x0C 获取通信事件记录

5.10 0x0F 写多个线圈

5.11 0x10 写多个保持寄存器

5.12 0x11 报告从站 ID

总结


MODBUS是一种广泛使用的工业通信协议,它允许通过串行线路在不同设备之间进行通信和数据交换。RS485模块是一个在ESP32上实现MODBUS协议的硬件。在本教程中,我们将使用RS485模块在ESP32开发板上创建一个MODBUS主机和从机设备,并实现与MODBUS主机的通信。

一、实验效果

多个Modbus(Server)从机设备与一个Modbus主机(Client)设备进行通信。

1.1 元件说明

mnb4v465

  1. 使用工业级芯片,传输距离可达上千米
  2. 具有高达正负15KV的防静电保护
  3. 芯片内置限摆率控制,大大减少信号干扰
  4. 接收器输入阻抗仅1/4单位,支持多设备连接,可连接128个设备
  5. 工作温度范围广,-40°C到85°C正常工作
  6. 支持热插拔,不会出现信号锁死问题
  7. 使用贴片大容量电解电容进行电源滤波
  8. 双瞬态抑制二极管过压保护
  9. 10欧电流保护电阻,提高信号完整性
  10. RS485和TTL信号单面布线,保证信号质量
  11. 大面积铺铜层,提高抗干扰能力
  12. 提供2.54mm间距洞洞焊接,方便二次开发
  13. 常用M3螺钉固定孔,安装可靠
  14. 120欧端接电阻,支持自动适配
  15. 提供发射/接收指示灯,方便调试
  16. 弯角插针设计,方便测试
  17. 支持3.3V和5V控制,兼容性强

1.2 引脚说明

  • RXD —— 接受数据
  • TXD ——传输数据
  • VCC —— 5V供电
  • GND —— 接地
  • A —— 非反相接收器输入和非反相驱动器输出
  • B —— 反相接收器输入和反相驱动器输出

1.3 BOM表

  • ESP32 N个
  • RS485模块 N个
  • 跳线
  • 屏蔽信号线

二、接线图

gfd98g8hg8fh7

ESP32连接RS485模块
GND<->GND
GPIO 17<->RXD
GPIO 16<->TXD
5V<->VCC

把所有RS485的模块的A和B,分别A连接A,B连接B,全部连接起来。

三、线圈和寄存器

在 Modbus 的内容中多次提到线圈(coil)和寄存器(register)的概念,尤其是 Moddbus功能码中,操作的对象基本上都是线圈和寄存器。

在 Modbus 协议中之所以仍然称为线圈和寄存器,完全是历史原因。在 PLC 应用领域,一个线圈就代表一个 PLC 输出点,也称为输出继电器。通过控制线圈导通与否来改变继电器输出状态,实现弱电控制强电。

4lhk53lhk36

但实际上,在如今的 Modbus 设备中,它们都只是对应一块内存区域而已。其中,线圈代表位操作(bit),表示一个布尔变量;寄存器代表字操作(word),表示一个整型变量(当然也可以通过多个字的组合,表示浮点数以及其他复合数据结构)。在 Modbus 协议中,字(word)的长度是 16 位,即 2 个字节。

3.1 寄存器种类说明

在 Modbus 协议中,所有数据均存放于寄存器中。根据存放的数据类型以及各自读写特性,可以将寄存器分为四个部分,这四个部分可以连续也可以不连续,完全由开发者决定。

下表展示了四类寄存器的含义以及与 PLC 的类比。

寄存器种类含义PLC示例
线圈状态 (Coil Status)输出端口(可读可写)DO(数字量输出)电磁阀输出、LED 显示
离散输入状态 (Input Status)输入端口(只读)DI(数字量输入)拨码开关、微动开关
保持寄存器 (Holding Register)输出参数(可读可写)AO(模拟量输出)PID 运行参数、阈值上下限
输入寄存器 (Input Register)输入参数(只读)AI(模拟量输入)传感器数据输入

3.2 寄存器地址分配

Modbus 寄存器地址分配如下表所示,同样参照了 PLC 寄存器地址的分配方法。

寄存器种类寄存器PLC地址寄存器Modbus协议地址简称
线圈状态00001~099990000H~FFFFH0x
离散输入状态10001~199990000H~FFFFH1x
保持寄存器40001~499990000H~FFFFH4x
输入寄存器30001~399990000H~FFFFH3x

该表中的 PLC 地址可以理解为 Modbus 协议地址的变种,在触摸屏和 PLC 编程中应用较为广泛。

  • 寄存器 PLC 地址指存放于控制器中的地址,这些控制器可以是 PLC,也可以是触摸屏,或者文本显示器。PLC 地址一般采用十进制描述,共有 5 位,其中第一位数字代表寄存器类型。
  • 寄存器 Modbus 协议地址指的是通信时使用的寄存器寻址地址,例如 PLC 地址 40001 对应寻址地址 0x0000,40002 对应寻址地址 0x0001。寄存器寻址地址一般使用十六进制描述。

细心的你会发现,PLC 寄存器地址 40003 对应的协议地址是 0x0002,PLC 寄存器地址 30003 对应的协议地址也是 0x0002,虽然通信时使用两个相同的 Modbus 协议地址,但是因为不同寄存器的功能码是不相同的,因此并不存在访问冲突。

四、Modbus 功能码

Modbus 功能码是 Modbus 消息帧(报文)的重要组成部分,是 Modbus 协议中通信事务处理的基础。

img

Modbus 功能码占用一个字节,取值范围是 1~127(即 0x01~0x7F)。同时,使用功能码 + 0x80 表示异常状态,即 129~255 代表异常码。

在 Modbus 标准协议中,一共规定了三类 Modbus 功能码。

  1. 公共功能码
    • 被明确定义的功能码;
    • 保证唯一性;
    • 由 Modbus 协会确认,并提供公开的文档;
    • 可进行一致性测试;
    • 包括协议定义的功能码和保留将来使用的功能码。
  2. 用户自定义功能码
    • 有两个用户自定义功能码区域,分别是 65~72 和 100~110;
    • 用户自定义,不保证唯一性。
  3. 保留功能码
    • 保留功能码是因为历史遗留原因,某些公司的传统产品上现行使用的功能码不作为公共使用。

本教程主要介绍公共功能码,下表展示了 Modbus 协议中的部分公共功能码。

代码名称英文寄存器 PLC 地址位/字操作操作数量
01读线圈状态Read Coils00001~09999位操作单个或多个
02读离散输入状态Read Discrete Inputs10001~19999位操作单个或多个
03读保持寄存器Read Holding Registers40001~49999字操作单个或多个
04读输入寄存器Read Input Registers30001~39999字操作单个或多个
05写单个线圈Write Single Coil00001~09999位操作单个
06写单个保持寄存器Write Single Register40001~49999字操作单个
15写多个线圈Write Multiple Coils00001~09999位操作多个
16写多个保持寄存器Write Multiple Registers40001~49999字操作多个

功能码的操作可分为两种:

  • 位操作 —— 最小单位为一位(bit),包括读线圈状态功能码 01、读离散输入状态功能码 02、写单个线圈功能码 05 和写多个线圈功能码 15;
  • 字操作 —— 最小单位为两个字节,包括读保持寄存器功能码 03、读输入寄存器功能码 04、写单个保持寄存器功能码 06 和写多个保持寄存器功能码 16。

五、功能码详解

5.1 0x01 读取线圈/离散量输出值

  • 该功能码用于读取从设备的线圈或离散量输出的状态,即各 DO(Discrete Output,离散输出)的 ON/OFF 状态。
  • 消息帧中指定了需读取的线圈起始地址和线圈数目。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 线圈数量由 2 个字节构成,取值范围为 0x0001 到 0x07D0(即十进制 1~2000)。
  • 需要注意,在 Modbus 协议规定的 PDU 中,规定所有线圈或寄存器地址从 0 开始计算。

5.2 0x02 读取离散量输入值

  • 该功能码用于读取从设备的离散输入,即 DI(Discrete Input)的 ON/OFF 状态。
  • 消息帧中指定了需读取的离散输入寄存器起始地址和数目,可读取 1~2000 个连续的离散量输入状态。
  • 如果从设备接受主设备的请求则回复功能码 02,并返回离散量输入各变量的当前状态(如果返回的 DI 数量不是 8 的整数倍,将用 0 填充最后数据字节的剩余位)。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 离散量数量由 2 个字节构成,取值范围为 0x0001 到 0x07D0(即十进制 1~2000),最多一次可读取 2000 个离散输入状态值。

5.3 0x03 读取保持寄存器值

  • 该功能码用于读取从设备保持寄存器的内容,不支持广播模式。
  • 消息帧中指定了需读取的保持寄存器的起始地址和数目,而保持寄存器中各地址的具体内容和意义则由设备开发者自行规定。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 寄存器数量由 2 个字节构成,取值范围为 0x0001 到 0x007D(即十进制 1~125),最多一次可连续读取 125 个寄存器值。
  • 需要注意,Modbus 的保持寄存器和输入寄存器是以字(Word)为基本单位的(1Word 等于 2Bytes)。因此,在读取时需要注意字节序(大小端)问题。

5.4 0x04 读取输入寄存器值

  • 该功能码用于读取从设备输入寄存器的内容,不支持广播模式。(与 03 功能码类似)
  • 消息帧中指定了需读取的输入寄存器的起始地址和数目,而输入寄存器中各地址的具体内容和意义则由设备开发者自行规定。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 寄存器数量由 2 个字节构成,取值范围为 0x0001 到 0x007D(即十进制 1~125),最多一次可连续读取 125 个寄存器值。
  • 同样需要注意字节序问题。

5.5 0x05 写单个线圈/单个离散输出

  • 该功能码用于将单个线圈寄存器(或离散输出)设置为 ON 或 OFF,支持广播模式。
  • 在广播模式下,所有从站设备的同一地址的值将被统一修改。
  • 消息帧中指定了需要变更的线圈地址和设定的状态值。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 目标数据(即查询报文中的 ON/OFF 状态)由报文数据字段的常数指定,0xFF00 表示 ON 状态,0x0000 表示 OFF 状态,其余所有值均是非法的。
  • 需要注意,在 Modbus 协议规定的 PDU 中,规定所有线圈或寄存器地址从 0 开始计算。

5.6 0x06 写单个保持寄存器

  • 该功能码用于更新从设备的单个保持寄存器的值,支持广播模式。
  • 在广播模式下,所有从站设备的同一地址的值将被统一修改。
  • 消息帧中需要指定从设备地址以及需要变更的保持寄存器地址和设定值。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 变更目标数据由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 保持寄存器以字(Word)为基本单位,写入时需要注意目标数据的字节序问题。

5.7 0x08 诊断功能

  • 该功能码仅用于串行链路,主要用于检测主设备和从设备之间的通信故障,或检测从设备的各种内部故障,该功能不支持广播。
  • 查询报文中需要指定从设备地址、功能码(Modbus Command)以及子功能码(Diagnostic Sub-function)。其中,子功能码字段为 2 个字节,用于区别各诊断类型。
  • 在正常的响应报文中,从设备将原样回复功能码和子功能码。

常用的 Modbus 诊断子功能码定义如下:

功能码子功能码描述说明
0800(0x00)Return Data Query (Loop-back)原样返回查询报文
0801(0x01)Restart Communications用于初始化并重新启动从站设备 其中,报文字段 0x00, 0x00 表示保持事件记录 0xFF, 0x00 表示清除事件记录
0802(0x02)Return Diagnostic Register返回诊断寄存器内容
0803(0x03)Change ASCII Input Delimiter
0804(0x04)Force Listen Only Mode强制被寻址的从站设备进入只听模式 使其与网络中的其他设备断开,不返回响应
0810(0x0A)Clear Counters and Diagnostic Registers清除计数器和诊断寄存器
0811(0x0B)Return Bus Message Count返回总线报文计数值
0812(0x0C)Return Bus Communication Error Count返回总线通信 CRC 出错计数
0813(0x0D)Return Bus Exception Error Count返回总线异常计数
0814(0x0E)Return Slave Message Count返回从站设备接收的报文数量
0815(0x0F)Return Slave No Response Count返回从站设备没有返回响应的报文数量
0816(0x10)Return Slave NAK Count
0817(0x11)Return Slave Busy Count返回从站设备响应忙的报文数量
0818(0x12)Return Bus Character Overrun Count返回总线字符超限的报文数量
0819(0x13)Return IOP Overrun Count (884)
0820(0x14)Clear Overrun Counter and Flag (884)

5.8 0x0B 获取通信事件计数器

  • 该功能码主要用于获取从设备通信计数器中的状态字和事件计数的值,不支持广播模式。
  • 可以通过在通信报文之前和之后读取通信事件计数值,来确定从设备是否正常处理报文。
  • 对于正常完成报文处理和传输的场合,事件计数器增加 1;而对于异常响应、轮询命令或读事件计数器(即 0x0B 功能码)的场合,则计数器不变。
  • 通过 0x08 诊断功能中的 0x01 子功能和 0x0A 子功能,可以复位事件寄存器。

5.9 0x0C 获取通信事件记录

  • 该功能码主要用于从从设备获取状态字、事件计数、报文计数以及事件字节字段。
  • 其中状态字和事件计数与功能码 0x0B 获取的值一致。
  • 报文计数器包含了加电重启、清除计数器之后的报文数量,报文计数与通过 0x08 诊断功能中的 0x0B 子功能码获取的值一致。
  • 事件字节字段包含 0~64 个字节,定义各种事件。
  • 正常情况下响应报文包括一个 2 Bytes 状态字字段、一个 2 Bytes 事件计数字段、一个 2 Bytes 消息计数字段以及 0~64 个字节的事件字段。
  • 由于事件字段是变长的,因此增加了一个 1 Byte 的数据长度字段,以方便读取响应数据。

5.10 0x0F 写多个线圈

  • 该功能码用于将连续的多个线圈(或离散输出)设置为 ON 或 OFF,支持广播模式。
  • 在广播模式下,所有从站设备的同一地址的值将被统一修改。
  • 消息帧中指定了需要变更的线圈起始地址和线圈数目。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 寄存器数量字段由 2 个字节构成,取值范围为 0x0001 到 0x07B0。
  • 数据字段中为逻辑 1 的位对应 ON,逻辑 0 的位对应 OFF。

5.11 0x10 写多个保持寄存器

  • 该功能码用于设置或写入从设备保持寄存器的多个连续的地址块(1~123个寄存器),支持广播模式。
  • 在广播模式下,所有从站设备的同一地址的值将被统一修改。
  • 消息帧中需要指定从设备地址以及需要变更的保持寄存器地址和数量。
  • 起始地址由 2 个字节构成,取值范围为 0x0000 到 0xFFFF。
  • 寄存器数量字段由 2 个字节构成,取值范围为 0x0001 到 0x007B(即十进制 1~123)。
  • 在实际开发中,该功能码常用于方便用户写入多字节类型的数据,例如浮点数值。因此,需要注意字节序问题。

5.12 0x11 报告从站 ID

  • 该功能码仅适用于串行链路,用于读取从站设备的 ID、类型描述、当前状态以及其他信息,不支持广播模式。
  • 查询报文中没有数据字段。
  • 响应消息的构成由从站设备决定。

总结

常用 Modbus 公共功能码下表所示。

img

其中,支持广播模式的功能码有:

  • 0x05 写单个线圈
  • 0x06 写单个保持寄存器
  • 0x0F 写多个线圈
  • 0x10 写多个保持寄存器

除了广播模式的报文以外,其他所有查询报文都希望能够获取一个正常的响应报文。如果一切正常,则从站设备将返回一个正常响应报文,该响应报文的功能码与请求报文的功能码一致。

另外,对于字操作的功能码,存在多字节存储的大小端问题,因此主站设备和从站设备必须保持一致的规则处理,约定 Modbus 传输中的数据字段的字节序。

🐋 希望大家多多支持,我们一起进步!😄如果文章对你有帮助的话, 
欢迎评论 💬 点赞👍🏻 收藏 📂 加关注+ 
技术交流,需求定制可以扫码添加,相互学习交流!

  • 39
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
您好!对于ESP32和Arduino,您可以使用ESP32的SPIFFS(SPI Flash File System)库来读取文件。SPIFFS库允许您在ESP32的闪存上创建和处理文件。 首先,您需要在Arduino IDE中安装ESP32开发板支持。然后,按照以下步骤进行文件读取: 1. 创建一个SPIFFS文件系统: - 在您的Arduino代码中包含SPIFFS库:`#include <SPIFFS.h>` - 在`setup()`函数中使用`SPIFFS.begin()`来初始化SPIFFS文件系统。 2. 打开要读取的文件: - 使用`File`对象打开文件并获取其句柄,例如:`File file = SPIFFS.open("/filename.txt", "r");` - 在`open()`函数中,第一个参数是要打开的文件路径,第二个参数是打开模式。使用"r"来以只读模式打开文件。 3. 检查文件是否成功打开: - 使用`file`对象的`available()`函数来检查文件是否成功打开。如果返回值大于0,则文件已成功打开。 4. 读取文件内容: - 使用`file`对象的`read()`函数来读取文件内容,并将其保存在适当的变量中。例如:`String content = file.readString();` 5. 关闭文件: - 使用`file`对象的`close()`函数来关闭文件,以释放资源:`file.close();` 以下是一个完整的示例代码: ```cpp #include <SPIFFS.h> void setup() { Serial.begin(115200); SPIFFS.begin(); File file = SPIFFS.open("/filename.txt", "r"); if (file.available()) { String content = file.readString(); Serial.println(content); } file.close(); } void loop() { // your code here } ``` 请确保将"/filename.txt"替换为您要读取的实际文件路径。读取到的文件内容将通过串口输出。 希望这可以帮助到您!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帐篷Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值