01 Modbus应用
一、MODBUS 概述
Modbus 是一种广泛应用于工业自动化领域的串行通信协议,由 Modicon 公司(现为施耐德电气旗下)于 1979 年推出,主要用于可编程逻辑控制器(PLC)与工业设备之间的通信。其设计简单、开放、易于实现,成为工业通信的事实标准。
1. 1 核心特点
主从架构:由主设备(Master)发起请求,从设备(Slave)响应,支持单主或多从模式。
典型应用:一台工控机(主)与多个传感器/执行器(从)通信。
1.2 常用协议变体
Modbus RTU:基于二进制编码,通过串行接口(RS-232/RS-485)传输,紧凑高效。
Modbus ASCII:使用 ASCII 字符编码,可读性强但效率低。
Modbus TCP/IP:基于以太网,适用于现代工业网络。
协议类型对比:
特性 |
Modbus RTU |
Modbus ASCII |
Modbus TCP/IP |
---|---|---|---|
传输介质 |
RS-232/RS-485 |
RS-232/RS-485 |
以太网P |
帧起始/结束标志 |
静默时间(3.5字符间隔) |
冒号:(起始),CRLF(结束) |
TCP包头(事务标识符) |
校验方式 |
CRC-16 |
LRC(纵向冗余校验) |
无(依赖TCP校验) |
1.3 数据模型
1.3.1 Modbus 定义四种数据类型,每种通过不同功能码访问:
线圈(Coils):可读可写的布尔量(功能码 01 读,05 写单个,15 写多个)。
离散输入(Discrete Inputs):只读布尔量(功能码 02 读)。
保持寄存器(Holding Registers):可读可写的 16 位整数(功能码 03 读,06 写单个,16 写多个)。
输入寄存器(Input Registers):只读的 16 位整数(功能码 04 读)。
1.3.2 简单报文结构
请求帧:功能码 + 数据地址 + 数据长度 + CRC 校验(RTU)或 TCP 头(Modbus TCP)。
响应帧:功能码 + 返回数据 + 校验。
1.4 优缺点
1.4.1 优点:
- 开放免费,兼容性强。
- 在多种电气接口(RS232、RS485)及多种通信介质(以太网,串行电路,蓝牙,wifi 等)中运行 。
- 报文帧简单紧凑。
1.4.2 缺点:
- 无内置安全机制(需依赖网络隔离或加密层)。
- 仅支持基础数据类型(需扩展协议处理浮点数等)。
二、演示功能概述
本篇文章演示的内容为:通过 RTU、ASCII 和 TCP 三种常用协议,Air8000 开发板作为主站(客户端)与从站连接通讯的过程,或开发板作为从站(服务器)与主站连接通讯的过程。
三、准备硬件环境
3.1 硬件准备
参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
485/232 转 USB:
网线:
3.2 开发板组合演示
3.2.1 RTU 协议和 ASCII 协议测试连接
3.2.2 TCP 协议测试连接
四、准备软件环境
在开始实践本示例之前,先筹备一下软件环境:
2. LuatOS-SoC_V2006_Air8000_LVGL;此页面有新版本固件的话选用最新版本固件。
3.LuatOS 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/modbus
- lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000 开发板中,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。
5.1 API 接口介绍
https://docs.openluat.com/osapi/core/modbus/
5.2 代码解析
5.2.1 RTU
5.2.1.1 master_rtu
1.初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 24 -- 用于控制485接收和发送的使能引脚
gpio.setup(1, 1) --打开电源(开发板485供电脚是gpio1,用开发板测试需要开机初始化拉高gpio1)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)
2.Modbus 主站创建
mb_rtu = modbus.create_master(modbus.MODBUS_RTU, uartid,2000)
3.添加从站
mb_slave1 = modbus.add_slave(mb_rtu, 1)
4.创建数据区和通信消息
slave1_msg1_buf = zbuff.create(1)
mb_slave1_msg1 = modbus.create_msg(mb_rtu, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf)
slave1_msg1_buf:clear()
5.启动 Modbus 设备
modbus.master_start(mb_rtu)
6.定时状态检查
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_rtu)
log.info("modbus", status)
end, 5000)
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
7.数据读取并转化为 json
-- 获取从站1的状态,每1秒获取一次数据并转换为JSON
sys.timerLoopStart(function()
-- 检查从站状态
local status = modbus.get_slave_state(mb_slave1)
if status == 0 then -- 0表示正常
-- 读取缓冲区数据
slave1_msg1_buf:seek(0) -- 重置指针到起始位置
-- 读取4个寄存器的值(每个寄存器2字节)
local reg1 = slave1_msg1_buf:readU16()
local reg2 = slave1_msg1_buf:readU16()
local reg3 = slave1_msg1_buf:readU16()
local reg4 = slave1_msg1_buf:readU16()
-- 创建数据表
local data = {
addr = 1, -- 从站地址
fun = 3, -- 功能码03
reg1 = reg1 , -- 假设原始数据需要除以10得到实际值
reg2 = reg2 ,
reg3 = reg3 ,
reg4 = reg4 ,
timestamp = os.time() -- 添加时间戳
}
-- 转换为JSON
local json_str = json.encode(data)
log.info("Modbus数据转JSON:", json_str)
else
log.warn("从站1状态异常:", status)
end
end, 1000)
5.2.1.2 slave_rtu
1.初始化设置
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 24 -- 用于控制485接收和发送的使能引脚
gpio.setup(1, 1) --打开电源(开发板485供电脚是gpio1,用开发板测试需要开机初始化拉高gpio1)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)
2.Modbus 从站创建:创建了一个 RTU 模式的 Modbus 从站,添加了两个数据块:保持寄存器区和线圈区
local slave_id = 1
mb_rtu_s = modbus.create_slave(modbus.MODBUS_RTU, slave_id, uartid)
-- 添加一块寄存器内存区
registers = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
-- 创建线圈数据区
ciols = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
3.启动 modbus 从站
modbus.slave_start(mb_rtu_s)
4.数据更新
local counter = 0
-- 修改和读取modbus值
function modify_data()
counter = counter + 1
-- 写入寄存器数据 (16位无符号整数)
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536) -- 写入递增数字,限制在0-65535
end
-- 写入线圈数据 (1位布尔值)
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2) -- 交替写入0和1
end
-- 读取并打印部分数据用于调试
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
5.2.1 ASCII
5.2.2.1 master_ascii
1.初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 24 -- 用于控制485接收和发送的使能引脚
gpio.setup(1, 1) --打开电源(开发板485供电脚是gpio1,用开发板测试需要开机初始化拉高gpio1)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)
2.Modbus 主站创建
mb_ascii = modbus.create_master(modbus.MODBUS_ASCII, uartid,3000,2000,1,5000)
3.添加从站
mb_slave1 = modbus.add_slave(mb_ascii, 1)
4.创建数据区和通信消息
slave1_msg1_buf = zbuff.create(1)
mb_slave1_msg1 = modbus.create_msg(mb_ascii, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf)
slave1_msg1_buf:clear()
5.启动 Modbus 设备
modbus.master_start(mb_ascii)
6.定时状态检查
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_ascii)
log.info("modbus", status)
end, 5000)
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
7.数据读取并转化为 json
-- 获取从站1的状态,每1秒获取一次数据并转换为JSON
sys.timerLoopStart(function()
-- 检查从站状态
local status = modbus.get_slave_state(mb_slave1)
if status == 0 then -- 0表示正常
-- 读取缓冲区数据
slave1_msg1_buf:seek(0) -- 重置指针到起始位置
-- 读取4个寄存器的值(每个寄存器2字节)
local reg1 = slave1_msg1_buf:readU16()
local reg2 = slave1_msg1_buf:readU16()
local reg3 = slave1_msg1_buf:readU16()
local reg4 = slave1_msg1_buf:readU16()
-- 创建数据表
local data = {
addr = 1, -- 从站地址
fun = 3, -- 功能码03
reg1 = reg1 , -- 假设原始数据需要除以10得到实际值
reg2 = reg2 ,
reg3 = reg3 ,
reg4 = reg4 ,
timestamp = os.time() -- 添加时间戳
}
-- 转换为JSON
local json_str = json.encode(data)
log.info("Modbus数据转JSON:", json_str)
else
log.warn("从站1状态异常:", status)
end
end, 1000)
5.2.2.2 slave_ascii
1.初始化设置
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 24 -- 用于控制485接收和发送的使能引脚
gpio.setup(1, 1) --打开电源(开发板485供电脚是gpio1,用开发板测试需要开机初始化拉高gpio1)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)
2.Modbus 从站创建:创建了一个 RTU 模式的 Modbus 从站,添加了两个数据块:保持寄存器区和线圈区
local slave_id = 1
mb_ascii_s = modbus.create_slave(modbus.MODBUS_ASCII, slave_id, uartid)
-- 添加一块寄存器内存区
registers = zbuff.create(1)
modbus.add_block(mb_ascii_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
-- 创建线圈数据区
ciols = zbuff.create(1)
modbus.add_block(mb_ascii_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
3.启动 modbus 从站
modbus.slave_start(mb_ascii_s)
4.数据更新
local counter = 0
-- 修改和读取modbus值
function modify_data()
counter = counter + 1
-- 写入寄存器数据 (16位无符号整数)
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536) -- 写入递增数字,限制在0-65535
end
-- 写入线圈数据 (1位布尔值)
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2) -- 交替写入0和1
end
-- 读取并打印部分数据用于调试
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
5.2.3 TCP
5.2.3.1 master_tcp
1.初始化
gpio.setup(20, 1) --打开lan供电
2.SPI 和以太网驱动初始化
require "lan" -- 实际网络配置在lan.lua中
-- lan.lua中的关键代码:
netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spiid=0, cs=8}) -- 初始化CH390以太网控制器(SPI0,片选GPIO8)
sys.wait(3000) -- 等待3秒确保硬件就绪
local ipv4, mark, gw = netdrv.ipv4(socket.LWIP_ETH, "192.168.4.1", "255.255.255.0", "192.168.4.1") -- 设置静态IP
dhcps.create({adapter=socket.LWIP_ETH}) -- 启动DHCP服务器
dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_ETH) -- 启用DNS代理
netdrv.napt(socket.LWIP_ETH) -- 启用NAPT(网络地址端口转换)
3.创建主站
mb_tcp = modbus.create_master(
modbus.MODBUS_TCP, -- TCP模式
socket.LWIP_ETH, -- 以太网接口
3000, -- 通信间隔时间(ms)
1, -- 超时重试次数
5000 -- 断线重连间隔(ms)
)
4.添加从站
mb_slave1 = modbus.add_slave(mb_tcp, 1, "192.168.4.100", 6000)
5.创建数据请求消息
-- 为从站1创建读取保持寄存器的请求(地址0开始,读10个寄存器)
slave1_msg1_buf = zbuff.create(1) -- 创建缓冲区
mb_slave1_msg1 = modbus.create_msg(
mb_tcp, mb_slave1,
modbus.REGISTERS, -- 寄存器类型
modbus.READ, -- 读操作
0, -- 起始地址
10, -- 寄存器数量
slave1_msg1_buf -- 存储数据的缓冲区
)
6.启动 Modbus 主站
modbus.master_start(mb_tcp)
log.info("start modbus master")
7.从站状态监控
-- 每5秒检查所有从站状态
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_tcp)
log.info("modbus", status)
end, 5000)
-- 每5秒检查从站1单独状态
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
8.数据处理
addvar = 0
function modify_data()
-- 读取从站1的寄存器数据
slave1_msg1_buf:seek(0)
log.info("slave1 reg: ",
slave1_msg1_buf:readU16(), slave1_msg1_buf:readU16(),
slave1_msg1_buf:readU16(), slave1_msg1_buf:readU16()
)
end
sys.timerLoopStart(modify_data, 1000)
5.2.3.2 slave_tcp
1.网络硬件初始化
log.info("ch390", "打开LDO供电")
gpio.setup(20, 1) --打开lan供电
require "lan"
2.SPI 和网络驱动初始化
local result = spi.setup(0, nil, 0, 0, 8, 25600000)
netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spiid=0,cs=8})
3.等待网络连接
while netdrv.link(socket.LWIP_ETH) ~= true do
sys.wait(100)
end
4.IP 地址配置
local ipv4,mark, gw = netdrv.ipv4(socket.LWIP_ETH, "192.168.4.1", "255.255.255.0", "192.168.4.1")
5.DHCP 和 DNS 服务
dhcps.create({adapter=socket.LWIP_ETH})
dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_ETH)
netdrv.napt(socket.LWIP_ETH)
6.系统监控任务
sys.taskInit(function()
sys.waitUntil("IP_READY")
while 1 do
sys.wait(300000)
log.info("lua", rtos.meminfo())
log.info("sys", rtos.meminfo("sys"))
end
end)
7.创建 Modbus TCP 从站
local slave_id = 1
mb_tcp_s = modbus.create_slave(modbus.MODBUS_TCP, slave_id, 6000, socket.LWIP_ETH)
8.创建数据存储区
registers = zbuff.create(1)
modbus.add_block(mb_tcp_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
ciols = zbuff.create(1)
modbus.add_block(mb_tcp_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
9.启动 Modbus 从站
modbus.slave_start(mb_tcp_s)
log.info("start modbus slave")
10.定时数据更新
local counter = 0
function modify_data()
counter = counter + 1
-- 更新寄存器数据
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536)
end
-- 更新线圈数据
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2)
end
-- 日志输出部分数据
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
sys.timerLoopStart(modify_data,1000)
六、运行结果展示
6.1 RTU 协议运行结果
6.1.1 modbus.master_rtu
6.1.1.1 完整代码展示
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_master_rtu"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
--初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 17 -- 用于控制485接收和发送的使能引脚
gpio.setup(16, 1) --打开电源(开发板485供电脚是gpio16,用开发板测试需要开机初始化拉高gpio16)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)--用户如果接的是自己的下位机,请将uart.setup传入的参数成自己下位机的配置参数
-- 创建主站设备,RTU模式
-- 设置通讯间隔时间,主站将按每隔 设置时间 的频率向从站问询数据(默认100ms),当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- 设置通讯超时时间和消息发送超时重发次数,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送,默认1)
-- 设置断线重连时间间隔,当从站与主站断连后,主站将在设置时间内重新连接从站(默认5000ms)
mb_rtu = modbus.create_master(modbus.MODBUS_RTU, uartid,3000,2000,1,5000)
-- 为主站添加从站,从站ID为1,可使用modbus.add_slave(master_handler, slave_id)接口添加多个从站,最多可以添加247个
mb_slave1 = modbus.add_slave(mb_rtu, 1)
-- -- 为主站添加从站,从站ID为2
-- mb_slave2 = modbus.add_slave(mb_rtu, 2)
-- 为从站1创建数据存储区,并创建通讯消息,默认为自动loop模式
slave1_msg1_buf = zbuff.create(1)
mb_slave1_msg1 = modbus.create_msg(mb_rtu, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf)
slave1_msg1_buf:clear()
-- -- 为从站1创建数据存储区,并创建通讯消息,如需要使用手动模式,须在这里设置为手动模式
-- slave1_msg1_buf = zbuff.create(1)
-- mb_slave1_msg1 = modbus.create_msg(mb_rtu, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf,1,modbus.EXEC)
-- slave1_msg1_buf:clear()
-- -- 为从站2创建数据存储区,并创建通讯消息,如设置多个从站,需要给每个从站创建数据储存区
-- slave2_msg1_buf = zbuff.create(1)
-- mb_slave2_msg1 = modbus.create_msg(mb_rtu, mb_slave2, modbus.REGISTERS, modbus.READ, 0, 10, slave2_msg1_buf)
-- slave2_msg1_buf:clear()
-- 启动Modubs设备
modbus.master_start(mb_rtu)
-- -- 设置通讯间隔时间,设置后主站将按每隔 设置时间 的频率向从站问询数据,当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- modbus.set_comm_interval_time(mb_rtu, 3000)
-- -- 设置通讯超时时间,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送)
-- modbus.set_comm_timeout(mb_rtu, 2000)
-- -- 设置消息发送失败、超时重发次数,如果主站在设置超时时间内未接收到数据,将按设置次数问询数据
-- modbus.set_comm_resend_count(mb_rtu,2)
-- -- 设置消息通讯周期,搭配modbus.create_master/modbus.set_comm_interval_time(mb_rtu, 3000)设置通讯时间使用,若设置通讯周期为2次,将在2倍的通讯时间后向从站问询数据
-- modbus.set_msg_comm_period(mb_slave1_msg1, 2)
-- 获取所有从站状态,如果所有从站状态为正常,返回true,其他情况返回false,将在每隔5秒的时间获取所有从站状态,并在日志中打印状态(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_rtu)
log.info("modbus", status)
end, 5000)
-- 获取从站1的状态,每隔5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
-- -- 获取从站2的状态,每隔5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
-- sys.timerLoopStart(function()
-- local status = modbus.get_slave_state(mb_slave2)
-- log.info("modbus2", status)
-- end, 5000)
-- -- 每隔5秒执行一次mb_slave1_msg1消息,使用modbus.exec(master_handler, msg_handler)接口须先在modbus.set_msg_comm_period(msg_handler, comm_period)接口中设置为手动模式;成功返回true,其他情况返回false
-- sys.timerLoopStart(function()
-- local status=modbus.exec(mb_rtu, mb_slave1_msg1)
-- log.info("msg",status)
-- end,5000)
-- -- 测试删除一个从站对象,并删除与之相关的通讯消息句柄。需在主站停止时(modbus.master_stop)执行该操作,否则无效。
-- -- 将在3分钟后删除从站1(主站已关闭),删除与之相关的通讯消息句柄,并在5秒后重启主站,可以观察从站是否删除成功。
-- sys.timerStart(function()
-- local status = modbus.remove_slave(mb_rtu, mb_slave1)
-- log.info("modbus", "slave1 remove after 3 minutes")
-- log.info("remove", status)
-- -- 移除从站后,5秒后重新启动Modbus主站
-- sys.timerStart(function()
-- modbus.master_start(mb_rtu)
-- log.info("modbus", "Modbus master restarted after slave removal")
-- end, 5000)
-- end, 180000)
-- 获取从站1的状态,每1秒获取一次数据并转换为JSON
sys.timerLoopStart(function()
-- 检查从站状态
local status = modbus.get_slave_state(mb_slave1)
if status == 0 then -- 0表示正常
-- 读取缓冲区数据
slave1_msg1_buf:seek(0) -- 重置指针到起始位置
-- 读取4个寄存器的值(每个寄存器2字节)
local reg1 = slave1_msg1_buf:readU16()
local reg2 = slave1_msg1_buf:readU16()
local reg3 = slave1_msg1_buf:readU16()
local reg4 = slave1_msg1_buf:readU16()
-- 创建数据表
local data = {
addr = 1, -- 从站地址
fun = 3, -- 功能码03
reg1 = reg1 , -- 假设原始数据需要除以10得到实际值
reg2 = reg2 ,
reg3 = reg3 ,
reg4 = reg4 ,
timestamp = os.time() -- 添加时间戳
}
-- 转换为JSON
local json_str = json.encode(data)
log.info("Modbus数据转JSON:", json_str)
else
log.warn("从站1状态异常:", status)
end
end, 1000)
-- -- 将在主站开启2分钟后停止modbus主站
-- sys.timerStart(function()
-- modbus.master_stop(mb_rtu)
-- log.info("modbus", "Modbus stopped after 2 minutes")
-- end, 120000)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.1.1.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后可以看到 4 个 com 口,选择 485 通道的 com 口进行配置。
由设备管理器可知 485 通道是 com20
5.点入配置,按照代码中 uart.setup 设置参数后,点击确定即可,设置完毕后在通道管理界面配置的通道显示属于正常状态,用户如果接的是自己的下位机,请将 uart.setup 传入的参数成自己下位机的配置参数。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置过的 485 通道 COM20,设备类型是模拟从机,地址是添加从站的 id。
8.增加数据配置,数据条目是寄存器的数量。
点击确定后可以看到增加了 4 个条目,双击数值一栏,然后再双击固定值一栏即可按需选择模拟数据的方法
9.数据配置完成后,在主界面可以看到数值按照设置的通讯时间间隔开始变换,在日志中也可以看到收到的数据,modbus 连接和通讯成功。
6.1.2 modbus.slave_rtu
6.1.2.1 完整代码展示
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_slave_rtu"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
--初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 17 -- 用于控制485接收和发送的使能引脚
gpio.setup(16, 1) --打开电源(开发板485供电脚是gpio1,用开发板测试需要开机初始化拉高gpio1)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)--用户如果接的是自己的下位机,请将uart.setup传入的参数成自己下位机的配置参数
-- 创建从站设备,可选择RTU、ASCII、TCP,此demo仅用作测试RTU和ASCII。
local slave_id = 1
mb_rtu_s = modbus.create_slave(modbus.MODBUS_RTU, slave_id, uartid)
-- 添加一块寄存器内存区
registers = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
-- 创建线圈数据区
ciols = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
-- 启动modbus从站
modbus.slave_start(mb_rtu_s)
local counter = 0
-- 修改和读取modbus值
function modify_data()
counter = counter + 1
-- 写入寄存器数据 (16位无符号整数)
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536) -- 写入递增数字,限制在0-65535
end
-- 写入线圈数据 (1位布尔值)
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2) -- 交替写入0和1
end
-- 读取并打印部分数据用于调试
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
sys.timerLoopStart(modify_data,1000)
-- -- 测试停止modbus从站,将在从站启动两分钟后关闭
-- sys.timerStart(function()
-- modbus.slave_stop(mb_rtu_s)
-- log.info("Modbus", "2分钟时间到,停止Modbus从站")
-- end, 2 * 60 * 1000) -- 2分钟(单位:毫秒)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.1.2.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后可以看到 4 个 com 口,选择 485 通道的 com 口进行配置。
由设备管理器可知 485 通道是 com20
5.点入配置,按照代码中 uart.setup 设置参数后,选择 RTU 传输协议,点击确定即可,设置完毕后在通道管理界面配置的通道显示属于正常状态。用户如果接的是自己的下位机,请将 uart.setup 传入的参数成自己下位机的配置参数。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置过的 485 通道 COM20,设备类型是模拟主站,地址是创建从站的 id。
8.增加数据配置,根据需要选择读取的寄存器与线圈数据。
9.添加成功后,双击数值栏就可以获取目前从站的数据了,开发板做从站和主站连接通讯成功。
6.2 ASCII 协议运行结果
6.2.1 modbus.master_ascii
6.2.1.1 完整代码展示
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_master_ascii"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
--初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 17 -- 用于控制485接收和发送的使能引脚
gpio.setup(16, 1) --打开电源(开发板485供电脚是gpio16,用开发板测试需要开机初始化拉高gpio16)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)--用户如果接的是自己的下位机,请将uart.setup传入的参数成自己下位机的配置参数
-- 创建主站设备,ASCII模式
-- 设置通讯间隔时间,主站将按每隔 设置时间 的频率向从站问询数据(默认100ms),当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- 设置通讯超时时间和消息发送超时重发次数,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送,默认1)
-- 设置断线重连时间间隔,当从站与主站断连后,主站将在设置时间内重新连接从站(默认5000ms)
mb_ascii = modbus.create_master(modbus.MODBUS_ASCII, uartid,2000,2000,1,5000)
-- 为主站添加从站,从站ID为1,可使用modbus.add_slave(master_handler, slave_id)接口添加多个从站,最多可以添加247个
mb_slave1 = modbus.add_slave(mb_ascii, 1)
-- -- 为主站添加从站,从站ID为2
-- mb_slave2 = modbus.add_slave(mb_ascii, 2)
-- 为从站1创建数据存储区,并创建通讯消息,默认为自动loop模式
slave1_msg1_buf = zbuff.create(1)
mb_slave1_msg1 = modbus.create_msg(mb_ascii, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf)
slave1_msg1_buf:clear()
-- -- 为从站1创建数据存储区,并创建通讯消息,如需要使用手动模式,须在这里设置为手动模式
-- slave1_msg1_buf = zbuff.create(1)
-- mb_slave1_msg1 = modbus.create_msg(mb_ascii, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf,1,modbus.EXEC)
-- slave1_msg1_buf:clear()
-- -- 为从站2创建数据存储区,并创建通讯消息,如设置多个从站,需要给每个从站创建数据储存区
-- slave2_msg1_buf = zbuff.create(1)
-- mb_slave2_msg1 = modbus.create_msg(mb_ascii, mb_slave2, modbus.REGISTERS, modbus.READ, 0, 10, slave2_msg1_buf)
-- slave2_msg1_buf:clear()
-- 启动Modubs设备
modbus.master_start(mb_ascii)
-- -- 设置通讯间隔时间,设置后主站将按每隔 设置时间 的频率向从站问询数据,当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- modbus.set_comm_interval_time(mb_ascii, 3000)
-- -- 设置通讯超时时间,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送)
-- modbus.set_comm_timeout(mb_ascii, 2000)
-- -- 设置消息发送失败、超时重发次数,如果主站在设置超时时间内未接收到数据,将按设置次数问询数据
-- modbus.set_comm_resend_count(mb_ascii,2)
-- -- 设置消息通讯周期,搭配modbus.create_master/modbus.set_comm_interval_time(mb_rtu, 3000)设置通讯时间使用,若设置通讯周期为2次,将在2倍的通讯时间后向从站问询数据
-- modbus.set_msg_comm_period(mb_slave1_msg1, 2)
-- 获取所有从站状态,如果所有从站状态为正常,返回true,其他情况返回false,将在每隔5秒的时间获取所有从站状态,并在日志中打印状态(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_ascii)
log.info("modbus", status)
end, 5000)
-- 获取从站1的状态,每个5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
-- -- 获取从站2的状态,每个5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
-- sys.timerLoopStart(function()
-- local status = modbus.get_slave_state(mb_slave2)
-- log.info("modbus2", status)
-- end, 5000)
-- -- 每个5秒执行一次mb_slave1_msg1消息,使用modbus.exec(master_handler, msg_handler)接口须先在modbus.set_msg_comm_period(msg_handler, comm_period)接口中设置为手动模式;成功返回true,其他情况返回false
-- sys.timerLoopStart(function()
-- local status=modbus.exec(mb_ascii, mb_slave1_msg1)
-- log.info("msg",status)
-- end,5000)
-- -- 测试删除一个从站对象,并删除与之相关的通讯消息句柄。需在主站停止时(modbus.master_stop)执行该操作,否则无效。
-- -- 将3分钟后删除从站1(主站已关闭),删除与之相关的通讯消息句柄,并在5秒后重启主站,可以观察从站是否删除成功。
-- sys.timerStart(function()
-- local status = modbus.remove_slave(mb_ascii, mb_slave1)
-- log.info("modbus", "slave1 remove after 3 minutes")
-- log.info("remove", status)
-- -- 移除从站后,5秒后重新启动Modbus主站
-- sys.timerStart(function()
-- modbus.master_start(mb_ascii)
-- log.info("modbus", "Modbus master restarted after slave removal")
-- end, 5000)
-- end, 180000)
-- 获取从站1的状态,每1秒获取一次数据并转换为JSON
sys.timerLoopStart(function()
-- 检查从站状态
local status = modbus.get_slave_state(mb_slave1)
if status == 0 then -- 0表示正常
-- 读取缓冲区数据
slave1_msg1_buf:seek(0) -- 重置指针到起始位置
-- 读取4个寄存器的值(每个寄存器2字节)
local reg1 = slave1_msg1_buf:readU16()
local reg2 = slave1_msg1_buf:readU16()
local reg3 = slave1_msg1_buf:readU16()
local reg4 = slave1_msg1_buf:readU16()
-- 创建数据表
local data = {
addr = 1, -- 从站地址
fun = 3, -- 功能码03
reg1 = reg1 , -- 假设原始数据需要除以10得到实际值
reg2 = reg2 ,
reg3 = reg3 ,
reg4 = reg4 ,
timestamp = os.time() -- 添加时间戳
}
-- 转换为JSON
local json_str = json.encode(data)
log.info("Modbus数据转JSON:", json_str)
else
log.warn("从站1状态异常:", status)
end
end, 1000)
-- -- 将在主站开启2分钟后停止modbus主站
-- sys.timerStart(function()
-- modbus.master_stop(mb_ascii)
-- log.info("modbus", "Modbus stopped after 2 minutes")
-- end, 120000)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.2.1.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后可以看到 4 个 com 口,选择 485 通道的 com 口进行配置。
由设备管理器可知 485 通道是 com20
5.点入配置,按照代码中 uart.setup 设置参数后,选择 ASCII 传输协议,点击确定即可,设置完毕后在通道管理界面配置的通道显示属于正常状态。用户如果接的是自己的下位机,请将 uart.setup 传入的参数成自己下位机的配置参数。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置过的 485 通道 COM20,设备类型是模拟从机,地址是添加从站的 id。
8.增加数据配置,数据条目是寄存器的数量。
点击确定后可以看到增加了 4 个条目,双击数值一栏,然后再双击固定值一栏即可按需选择模拟数据的方法
9.数据配置完成后,在主界面可以看到数值按照设置的通讯时间间隔开始变换,在日志中也可以看到收到的数据,modbus 连接和通讯成功。
6.2.2 modbus.slave_ascii
6.2.2.1 完整代码展示
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_slave_rtu"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
--初始化通讯串口
local uartid = 1 -- 根据实际设备选取不同的uartid
local uart485Pin = 17 -- 用于控制485接收和发送的使能引脚
gpio.setup(16, 1) --打开电源(开发板485供电脚是gpio16,用开发板测试需要开机初始化拉高gpio16)
uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 2000)--用户如果接的是自己的下位机,请将uart.setup传入的参数成自己下位机的配置参数
-- 创建从站设备,可选择RTU、ASCII、TCP,此demo仅用作测试RTU和ASCII。
local slave_id = 1
mb_rtu_s = modbus.create_slave(modbus.MODBUS_RTU, slave_id, uartid)
-- 添加一块寄存器内存区
registers = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
-- 创建线圈数据区
ciols = zbuff.create(1)
modbus.add_block(mb_rtu_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
-- 启动modbus从站
modbus.slave_start(mb_rtu_s)
local counter = 0
-- 修改和读取modbus值
function modify_data()
counter = counter + 1
-- 写入寄存器数据 (16位无符号整数)
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536) -- 写入递增数字,限制在0-65535
end
-- 写入线圈数据 (1位布尔值)
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2) -- 交替写入0和1
end
-- 读取并打印部分数据用于调试
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
sys.timerLoopStart(modify_data,1000)
-- -- 测试停止modbus从站,将在从站启动两分钟后关闭
-- sys.timerStart(function()
-- modbus.slave_stop(mb_rtu_s)
-- log.info("Modbus", "2分钟时间到,停止Modbus从站")
-- end, 2 * 60 * 1000) -- 2分钟(单位:毫秒)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.2.2.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后可以看到 4 个 com 口,选择 485 通道的 com 口进行配置。
由设备管理器可知 485 通道是 com20
5.点入配置,按照代码中 uart.setup 设置参数后,选择 ASCII 传输协议,点击确定即可,设置完毕后在通道管理界面配置的通道显示属于正常状态。用户如果接的是自己的下位机,请将 uart.setup 传入的参数成自己下位机的配置参数。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置过的 485 通道 COM20,设备类型是模拟主站,地址是创建从站的 id。
8.增加数据配置,根据需要选择读取的寄存器与线圈数据。
9.添加成功后,双击数值栏就可以获取目前从站的数据了,开发板做从站和主站连接通讯成功。
6.3 TCP 协议运行结果
6.3.1 modbus.master_tcp
6.3.1.1 完整代码展示
mian.lua
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_master_tcp"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")
--初始化网络
log.info("ch390", "打开LDO供电")
gpio.setup(140, 1) --打开lan供电
mcu.hardfault(0) -- 死机后停机,一般用于调试状态
require "lan"
-- 创建主站设备,TCP模式
-- 设置连接方式为socket.LWIP_ETH
-- 设置通讯间隔时间,主站将按每隔 设置时间 的频率向从站问询数据(默认100ms),当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- 设置通讯超时时间和消息发送超时重发次数,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送,默认1)
-- 设置断线重连时间间隔,当从站与主站断连后,主站将在设置时间内重新连接从站(默认5000ms)
mb_tcp = modbus.create_master(modbus.MODBUS_TCP, socket.LWIP_ETH,3000,1,5000)
-- 为主站添加从站,从站ID为1,ip地址为 1192.168.4.100,端口号为 6000,最多可添加247个从站
mb_slave1 = modbus.add_slave(mb_tcp, 1, "192.168.4.100", 6000)
-- 为主站添加从站,从站ID为2,ip地址为 192.168.4.100,端口号为 6001
-- mb_slave2 = modbus.add_slave(mb_tcp, 2, "192.168.4.100", 6001)
-- 为从站1创建数据存储区,并创建通讯消息,默认为自动loop模式
slave1_msg1_buf = zbuff.create(1)
mb_slave1_msg1 = modbus.create_msg(mb_tcp, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf)
slave1_msg1_buf:clear()
-- -- 为从站1创建数据存储区,并创建通讯消息,如需要使用手动模式,须在这里设置为手动模式
-- slave1_msg1_buf = zbuff.create(1)
-- mb_slave1_msg1 = modbus.create_msg(mb_tcp, mb_slave1, modbus.REGISTERS, modbus.READ, 0, 10, slave1_msg1_buf,1,modbus.EXEC)
-- slave1_msg1_buf:clear()
-- 为从站2创建数据存储区,并创建通讯消息
-- slave2_msg1_buf = zbuff.create(1)
-- mb_slave2_msg1 = modbus.create_msg(mb_tcp, mb_slave2, modbus.REGISTERS, modbus.WRITE, 0, 10, slave2_msg1_buf)
-- slave2_msg1_buf:clear()
-- 启动Modubs设备
modbus.master_start(mb_tcp)
log.info("start modbus master")
-- -- 设置通讯间隔时间,设置后主站将按每隔 设置时间 的频率向从站问询数据,当添加了多个从站后,主站向每个从站问询的时间间隔将叠加
-- modbus.set_comm_interval_time(mb_tcp,3000)
-- -- 设置通讯超时时间,当主站未在 设置的时间 内接收到从站数据,将向从站再次发送问询(问询次数按设置的 消息超时重发次数 发送)
-- modbus.set_comm_timeout(mb_tcp, 3000)
-- -- 设置消息发送失败、超时重发次数,如果主站在设置超时时间内未接收到数据,将按设置次数问询数据
-- modbus.set_comm_resend_count(mb_tcp,1)
-- 设置断线重连时间间隔,若从站与主站断连,主站将在设置时间内重新连接从站
-- modbus.set_comm_reconnection_time(mb_tcp, 5000)
-- -- 设置消息通讯周期,搭配modbus.create_master/modbus.set_comm_interval_time(mb_tcp,3000)设置通讯时间使用,若设置通讯周期为2次,将在2倍的通讯时间后向从站问询数据
-- modbus.set_msg_comm_period(mb_slave1_msg1, 2)
-- 获取所有从站状态,如果所有从站状态为正常,返回true,其他情况返回false,将在每隔5秒的时间获取所有从站状态,并在日志中打印状态(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_all_slave_state(mb_tcp)
log.info("modbus", status)
end, 5000)
-- 获取从站1的状态,每个5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
sys.timerLoopStart(function()
local status = modbus.get_slave_state(mb_slave1)
log.info("modbus1", status)
end, 5000)
-- -- 获取从站2的状态,每个5秒获取从站状态并在日志打印出来(仅方便调试使用,量产时可删除)
-- sys.timerLoopStart(function()
-- local status = modbus.get_slave_state(mb_slave2)
-- log.info("modbus2", status)
-- end, 5000)
-- -- 每个5秒执行一次 mb_slave1_msg1 消息,使用modbus.exec(master_handler, msg_handler)接口须先在modbus.set_msg_comm_period(msg_handler, comm_period)接口中设置为手动模式;成功返回true,其他情况返回false
-- sys.timerLoopStart(function()
-- local status=modbus.exec(mb_tcp, mb_slave1_msg1)
-- log.info("msg",status)
-- end,5000)
-- -- 测试删除一个从站对象,并删除与之相关的通讯消息句柄。需在主站停止时(modbus.master_stop)执行该操作,否则无效。
-- -- 将在3分钟后删除从站2(主站已关闭),删除与之相关的通讯消息句柄,并在5秒后重启主站,可以观察从站是否删除成功。
-- sys.timerStart(function()
-- local status = modbus.remove_slave(mb_tcp, mb_slave2)
-- log.info("modbus", "slave remove after 3 minutes")
-- log.info("remove", status)
-- -- 移除从站后,5秒后重新启动Modbus主站
-- sys.timerStart(function()
-- modbus.master_start(mb_tcp)
-- log.info("modbus", "Modbus master restarted after slave removal")
-- end, 5000)
-- end, 180000)
-- 获取从站1的状态,每1秒获取一次数据并转换为JSON
sys.timerLoopStart(function()
-- 检查从站状态
local status = modbus.get_slave_state(mb_slave1)
if status == 0 then -- 0表示正常
-- 读取缓冲区数据
slave1_msg1_buf:seek(0) -- 重置指针到起始位置
-- 读取4个寄存器的值(每个寄存器2字节)
local reg1 = slave1_msg1_buf:readU16()
local reg2 = slave1_msg1_buf:readU16()
local reg3 = slave1_msg1_buf:readU16()
local reg4 = slave1_msg1_buf:readU16()
-- 创建数据表
local data = {
addr = 1, -- 从站地址
fun = 3, -- 功能码03
reg1 = reg1 , -- 假设原始数据需要除以10得到实际值
reg2 = reg2,
reg3 = reg3,
reg4 = reg4,
timestamp = os.time() -- 添加时间戳
}
-- 转换为JSON
local json_str = json.encode(data)
log.info("Modbus数据转JSON:", json_str)
else
log.warn("从站1状态异常:", status)
end
end, 1000)
-- -- 将在主站开启2分钟后停止modbus主站
-- sys.timerStart(function()
-- modbus.master_stop(mb_tcp)
-- log.info("modbus", "Modbus stopped after 2 minutes")
-- end, 120000)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
lan.lua
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
sysplus = require("sysplus")
dhcps = require "dhcpsrv"
dnsproxy = require "dnsproxy"
sys.taskInit(function ()
-- sys.wait(3000)
local result = spi.setup(
0,--串口id
nil,
0,--CPHA
0,--CPOL
8,--数据宽度
25600000--,--频率
-- spi.MSB,--高低位顺序 可选,默认高位在前
-- spi.master,--主模式 可选,默认主
-- spi.full--全双工 可选,默认全双工
)
log.info("main", "open",result)
if result ~= 0 then--返回值为0,表示打开成功
log.info("main", "spi open error",result)
return
end
netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spiid=0,cs=8})
sys.wait(3000)
local ipv4,mark, gw = netdrv.ipv4(socket.LWIP_ETH, "192.168.4.1", "255.255.255.0", "192.168.4.1")
log.info("ipv4", ipv4,mark, gw)
while netdrv.link(socket.LWIP_ETH) ~= true do
sys.wait(100)
end
dhcps.create({adapter=socket.LWIP_ETH})
-- dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_GP)
-- netdrv.napt(socket.LWIP_GP)
dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_ETH)
netdrv.napt(socket.LWIP_ETH)
end)
sys.taskInit(function()
-- sys.waitUntil("IP_READY")
while 1 do
sys.wait(300000)
-- log.info("http", http.request("GET", "http://httpbin.air32.cn/bytes/4096", nil, nil, {adapter=socket.LWIP_ETH}).wait())
log.info("lua", rtos.meminfo())
log.info("sys", rtos.meminfo("sys"))
-- log.info("psram", rtos.meminfo("psram"))
end
end)
6.3.1.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后选择网络通道,然后进行网络参数配置。
5.连接模式选择 tcp 服务器,本地 ip 在把脚本烧录后可以看到开发板分配的 ip,端口号设置为代码中 mb_slave1 = modbus.add_slave(mb_tcp, 1, "192.168.4.100", 6000)创建从站的端口号。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置的 NET001 网络通道,设备类型是模拟从机,地址是创建从站的 id。
8.增加数据配置,根据需要选择寄存器或线圈数据。
9.点击确定后可以看到增加了 4 个条目,双击数值一栏,然后再双击固定值一栏即可按需选择模拟数据的方法。
10.设置完成后则可以看到模拟从机数据开始变化,日志上显示开发板获取数据,主站与从站连接通讯成功。
6.3.2 modbus.slave_tcp
6.3.2.1 完整代码展示
main.lua
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "modbus_slave_tcp"
VERSION = "1.0.0"
log.style(1)
log.info("main", PROJECT, VERSION)
-- sys库是标配
_G.sys = require("sys")
_G.sysplus = require("sysplus")
log.info("ch390", "打开LDO供电")
gpio.setup(140, 1) --打开lan供电
require "lan"
-- 创建从站设备,可选择RTU、ASCII、TCP,此demo仅用作测试TCP。设置该从站端口号为6000,网卡适配器序列号为socket.LWIP_ETH。
local slave_id = 1
mb_tcp_s = modbus.create_slave(modbus.MODBUS_TCP, slave_id, 6000, socket.LWIP_ETH)
-- 创建寄存器数据区
registers = zbuff.create(1)
modbus.add_block(mb_tcp_s, modbus.REGISTERS, 0, 32, registers)
registers:clear()
-- 创建线圈数据区
ciols = zbuff.create(1)
modbus.add_block(mb_tcp_s, modbus.CIOLS, 0, 32, ciols)
ciols:clear()
-- 启动modbus从站
modbus.slave_start(mb_tcp_s)
log.info("start modbus slave")
local counter = 0
-- 修改和读取modbus值
function modify_data()
counter = counter + 1
-- 写入寄存器数据 (16位无符号整数)
registers:seek(0)
for i=0,31 do
registers:writeU16((counter + i) % 65536) -- 写入递增数字,限制在0-65535
end
-- 写入线圈数据 (1位布尔值)
ciols:seek(0)
for i=0,31 do
ciols:writeU8((counter + i) % 2) -- 交替写入0和1
end
-- 读取并打印部分数据用于调试
registers:seek(0)
ciols:seek(0)
log.info("registers:", registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16(), registers:readU16())
log.info("ciols :", ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8(), ciols:readU8())
end
sys.timerLoopStart(modify_data,1000)
-- -- 测试停止modbus从站,从站将在开启两分钟后关闭
-- sys.timerStart(function()
-- modbus.slave_stop(mb_rtu_s)
-- log.info("Modbus", "2分钟时间到,停止Modbus从站")
-- end, 120000)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
lan.lua
-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")
sysplus = require("sysplus")
dhcps = require "dhcpsrv"
dnsproxy = require "dnsproxy"
sys.taskInit(function ()
-- sys.wait(3000)
local result = spi.setup(
0,--串口id
nil,
0,--CPHA
0,--CPOL
8,--数据宽度
25600000--,--频率
-- spi.MSB,--高低位顺序 可选,默认高位在前
-- spi.master,--主模式 可选,默认主
-- spi.full--全双工 可选,默认全双工
)
log.info("main", "open",result)
if result ~= 0 then--返回值为0,表示打开成功
log.info("main", "spi open error",result)
return
end
netdrv.setup(socket.LWIP_ETH, netdrv.CH390, {spiid=0,cs=8})
sys.wait(3000)
local ipv4,mark, gw = netdrv.ipv4(socket.LWIP_ETH, "192.168.4.1", "255.255.255.0", "192.168.4.1")
log.info("ipv4", ipv4,mark, gw)
while netdrv.link(socket.LWIP_ETH) ~= true do
sys.wait(100)
end
dhcps.create({adapter=socket.LWIP_ETH})
-- dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_GP)
-- netdrv.napt(socket.LWIP_GP)
dnsproxy.setup(socket.LWIP_ETH, socket.LWIP_ETH)
netdrv.napt(socket.LWIP_ETH)
-- netdrv.dhcp(socket.LWIP_ETH, true)
end)
sys.taskInit(function()
sys.waitUntil("IP_READY")
while 1 do
sys.wait(300000)
-- log.info("http", http.request("GET", "http://httpbin.air32.cn/bytes/4096", nil, nil, {adapter=socket.LWIP_ETH}).wait())
log.info("lua", rtos.meminfo())
log.info("sys", rtos.meminfo("sys"))
-- log.info("psram", rtos.meminfo("psram"))
end
end)
6.3.2.2 效果展示
1.打开 MThings(第四章中 第五项提到的上位机下位机软件 摩尔信使),界面如下。
2.点击右上角进入通道管理。
3.点击通道管理。
4.进入后选择网络通道,然后进行网络参数配置。
5.连接模式选择 tcp 客户端,本地 ip 在把脚本烧录后可以看到开发板分配的 ip,目标 ip 是开发板的 IP,为 192.168.4.1,目标端口号为脚本中 mb_tcp_s = modbus.create_slave(modbus.MODBUS_TCP, slave_id, 6000, socket.LWIP_ETH)设置的。
6.返回初始界面,点击添加设备。
7.通道选择刚刚配置的 NET001 网络通道,设备类型是模拟主站,地址是创建从站的 id。
8.增加数据配置,根据需要选择寄存器或线圈数据。
9.点击数值栏获取到从站寄存器和线圈的数据,主站与从站连接与通讯成功。
七、总结
本教程演示了 Air8000 modbus 在 RTU、ASCII 和 TCP 三种协议下的使用过程,请根据具体场景选择您需要的 demo 即可。
八、常见问题
1.删除从站地址和句柄位删除失败
删除失败可能是主站为关闭,删除从站需要在主站关闭后,删除后可再次打开主站观察从站是否删除成功。
2.开发板用 TCP 协议,如何修改开发板与 PC 端的连接方式
在 lan.lua 中修改 socket API。