Modbus 辅助函数
Modbus 辅助函数提供了便利的 Lua API 用于发送 Modbus RTU 和 Modbus TCP 请求。这些函数自动处理协议帧、参数验证和特定编解码器的有效负载格式。
概述
Modbus 辅助函数支持8 个标准 Modbus 功能码,涵盖最常见的工业自动化操作:
| 功能码 | 操作 | 辅助函数 |
|---|---|---|
| 0x01 | 读线圈 | modbus_read_coils() |
| 0x02 | 读离散输入 | modbus_read_discrete_inputs() |
| 0x03 | 读保持寄存器 | modbus_read_holding_registers() |
| 0x04 | 读输入寄存器 | modbus_read_input_registers() |
| 0x05 | 写单个线圈 | modbus_write_single_coil() |
| 0x06 | 写单个寄存器 | modbus_write_single_register() |
| 0x0F | 写多个线圈 | modbus_write_multiple_coils() |
| 0x10 | 写多个寄存器 | modbus_write_multiple_registers() |
自动 RTU/TCP 检测
所有辅助函数自动检测编解码器类型(modbus_rtu_codec 或 modbus_tcp_codec)并相应格式化有效负载:
- Modbus RTU:在有效负载中包含从机地址
- Modbus TCP:省略从机地址(由 MBAP 头处理)
多连接支持
所有函数接受可选的 connection_id 参数以目标特定连接。如果省略,消息将发送到连接 0(默认连接)。
API 参考
modbus_read_coils(slave, start, qty, delay, connection_id) → boolean
读取线圈状态(功能码 0x01)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始线圈地址 (0-65535)qty(number) - 要读取的线圈数 (1-2000)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败(通道关闭或错误)
示例:
-- 从从机 1 读取从地址 100 开始的 10 个线圈
local last_poll_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 1000 then -- 每秒轮询一次
modbus_read_coils(1, 100, 10, 0) -- 发送到连接 0
last_poll_time = elapsed_ms
end
end
-- 多连接示例:在不同连接上轮询不同的从机
local last_multi_poll = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_multi_poll >= 1000 then
modbus_read_coils(1, 100, 10, 0, 0) -- 连接 0 上的从机 1
modbus_read_coils(2, 100, 10, 0, 1) -- 连接 1 上的从机 2
last_multi_poll = elapsed_ms
end
end
modbus_read_discrete_inputs(slave, start, qty, delay, connection_id) → boolean
读取离散输入状态(功能码 0x02)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始输入地址 (0-65535)qty(number) - 要读取的输入数 (1-2000)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
示例:
-- 从地址 0 读取 8 个离散输入
function on_start()
modbus_read_discrete_inputs(1, 0, 8, 100) -- 100ms 后读取
end
modbus_read_holding_registers(slave, start, qty, delay, connection_id) → boolean
读取保持寄存器值(功能码 0x03)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始寄存器地址 (0-65535)qty(number) - 要读取的寄存器数 (1-125)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
示例:
-- 从地址 0(Modbus 地址 40001)开始读取 10 个保持寄存器
local last_poll_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 500 then
modbus_read_holding_registers(1, 0, 10, 0)
last_poll_time = elapsed_ms
end
end
-- 多寄存器读取和连接路由
local last_multi_read = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_multi_read >= 1000 then
local count = get_connection_count()
for conn_id = 0, count - 1 do
if get_codec(conn_id) == "modbus_rtu" then
modbus_read_holding_registers(1, 0, 10, 0, conn_id)
end
end
last_multi_read = elapsed_ms
end
end
modbus_read_input_registers(slave, start, qty, delay, connection_id) → boolean
读取输入寄存器值(功能码 0x04)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始寄存器地址 (0-65535)qty(number) - 要读取的寄存器数 (1-125)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
示例:
-- 读取 5 个输入寄存器(传感器数据)
function on_start()
modbus_read_input_registers(1, 0, 5, 0)
end
modbus_write_single_coil(slave, addr, value, delay, connection_id) → boolean
写入单个线圈值(功能码 0x05)。
参数:
slave(number) - 从机/单元 ID (1-247)addr(number) - 线圈地址 (0-65535)value(boolean) - 线圈值 (true= ON/0xFF00,false= OFF/0x0000)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
示例:
-- 打开地址 100 处的线圈
function on_receive()
local payload = message.payload
if #payload > 0 and string.byte(payload, 1) == 0x01 then
-- 当我们收到命令 0x01 时打开线圈 100
modbus_write_single_coil(1, 100, true, 0)
end
end
-- 特定连接的线圈控制
function on_receive()
if message.connection_id == 0 then
-- 基于来自连接 0 的数据控制连接 1 上的线圈
modbus_write_single_coil(1, 100, true, 0, 1)
end
end
modbus_write_single_register(slave, addr, value, delay, connection_id) → boolean
写入单个寄存器值(功能码 0x06)。
参数:
slave(number) - 从机/单元 ID (1-247)addr(number) - 寄存器地址 (0-65535)value(number) - 寄存器值 (0-65535, 无符号 16 位)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
示例:
-- 每 5 秒向保持寄存器 0 写入值 1234
local last_write_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_write_time >= 5000 then
modbus_write_single_register(1, 0, 1234, 0)
last_write_time = elapsed_ms
end
end
modbus_write_multiple_coils(slave, start, values_table, delay, connection_id) → boolean
写入多个线圈值(功能码 0x0F)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始线圈地址 (0-65535)values_table(table) - Lua 布尔值数组 (1-2000 个元素)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
验证:
- 表必须包含 1-2000 个布尔值
- 每个值必须是
true或false - 值按照 Modbus 规范打包到字节中(LSB 优先)
示例:
-- 从地址 100 开始写入 8 个线圈值
function on_start()
local coils = {true, false, true, true, false, false, true, false}
modbus_write_multiple_coils(1, 100, coils, 100)
end
-- 用于测试的图案生成
local last_pattern_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_pattern_time >= 2000 then
-- 创建交替图案
local pattern = {}
for i = 1, 16 do
pattern[i] = (i % 2 == 1)
end
modbus_write_multiple_coils(1, 0, pattern, 0)
last_pattern_time = elapsed_ms
end
end
modbus_write_multiple_registers(slave, start, values_table, delay, connection_id) → boolean
写入多个寄存器值(功能码 0x10)。
参数:
slave(number) - 从机/单元 ID (1-247)start(number) - 起始寄存器地址 (0-65535)values_table(table) - Lua 整数值数组 (1-125 个元素)delay(number) - 发送前的延迟(毫秒)connection_id(number, 可选) - 目标连接 ID(默认:0)
返回值:
true- 请求成功调度false- 调度失败
验证:
- 表必须包含 1-125 个整数值
- 每个值必须在 0-65535 范围内(无符号 16 位)
- 值编码为大端序 u16
示例:
-- 向保持寄存器写入配置
function on_start()
local config = {100, 200, 300, 400, 500}
modbus_write_multiple_registers(1, 0, config, 0)
end
-- 从一个连接读取传感器数据并写入另一个连接
function on_receive()
if message.connection_id == 0 then
-- 解析传感器数据
local temp = read_u16_be(message.payload, 1)
local humidity = read_u16_be(message.payload, 3)
-- 写入到连接 1 上的 Modbus 设备
modbus_write_multiple_registers(1, 0, {temp, humidity}, 0, 1)
end
return false
end
完整示例:Modbus 轮询和控制
此示例演示了具有轮询、控制和多连接支持的完整 Modbus 主机实现:
-- Modbus 主机与轮询和控制
-- 轮询保持寄存器并根据寄存器值控制线圈
-- 配置
local POLL_INTERVAL_MS = 1000
local SLAVE_ID = 1
local HOLDING_REG_START = 0
local HOLDING_REG_COUNT = 10
local COIL_START = 0
-- 跟踪最后轮询时间
local last_poll_time = 0
function on_start()
local count = get_connection_count()
log("info", "Modbus 主机启动,共 " .. count .. " 个连接")
-- 记录连接配置
for i = 0, count - 1 do
local codec = get_codec(i)
local transport = get_transport(i)
log("info", "连接 " .. i .. ":" .. transport .. " 使用 " .. codec)
end
end
function on_timer(elapsed_ms)
-- 每 POLL_INTERVAL_MS 轮询每个 Modbus 连接
if elapsed_ms - last_poll_time >= POLL_INTERVAL_MS then
local count = get_connection_count()
for conn_id = 0, count - 1 do
local codec = get_codec(conn_id)
-- 仅轮询 Modbus 连接
if codec == "modbus_rtu" or codec == "modbus_tcp" then
-- 读取保持寄存器
modbus_read_holding_registers(
SLAVE_ID,
HOLDING_REG_START,
HOLDING_REG_COUNT,
0,
conn_id
)
end
end
last_poll_time = elapsed_ms
end
end
function on_receive()
local conn_id = message.connection_id
local payload = message.payload
-- 解析 Modbus 响应(功能码 0x03 - 读保持寄存器)
if #payload >= 3 and string.byte(payload, 1) == 0x03 then
local byte_count = string.byte(payload, 2)
local register_count = byte_count / 2
log("info", "连接 " .. conn_id .. ":接收 " .. register_count .. " 个寄存器")
-- 解析寄存器值
for i = 0, register_count - 1 do
local offset = 3 + (i * 2) -- 偏移 3 = 跳过功能码 + 字节计数
if offset + 1 <= #payload then
local value = read_u16_be(payload, offset)
local reg_addr = HOLDING_REG_START + i
-- 添加到图表
message:add_int_value("寄存器 " .. reg_addr, value)
-- 控制逻辑:如果寄存器值超过阈值则打开线圈
if value > 500 then
log("warn", "寄存器 " .. reg_addr .. " 超过阈值:" .. value)
modbus_write_single_coil(SLAVE_ID, COIL_START + i, true, 0, conn_id)
else
modbus_write_single_coil(SLAVE_ID, COIL_START + i, false, 0, conn_id)
end
end
end
return true -- 我们添加了值
end
return false
end
function on_send()
local conn_id = message.connection_id
log("debug", "向连接 " .. conn_id .. " 发送 Modbus 请求")
return false
end
function on_send_confirm()
-- 可选:跟踪成功的发送
return false
end
常见模式
定期轮询
使用 on_timer() 和全局变量来跟踪轮询间隔:
-- 跟踪最后轮询时间
local last_poll_time = 0
function on_timer(elapsed_ms)
-- 每秒轮询一次(1000ms)
if elapsed_ms - last_poll_time >= 1000 then
modbus_read_holding_registers(1, 0, 10, 0)
last_poll_time = elapsed_ms
end
end
注意:由于 on_timer() 每 100ms 调用一次,使用 elapsed_ms % 1000 == 0 不会可靠工作。始终使用 elapsed_ms - last_time >= interval 的模式和全局变量。
顺序请求
使用延迟来序列化多个请求:
function on_start()
-- 使用错开延迟读取不同的寄存器块
modbus_read_holding_registers(1, 0, 10, 0) -- 立即
modbus_read_holding_registers(1, 10, 10, 100) -- 100ms 后
modbus_read_holding_registers(1, 20, 10, 200) -- 200ms 后
end
响应驱动的写入
解析响应并根据值进行写入:
function on_receive()
local payload = message.payload
if #payload >= 5 and string.byte(payload, 1) == 0x03 then
local reg_value = read_u16_be(payload, 3)
-- 根据寄存器值写入线圈
if reg_value > 1000 then
modbus_write_single_coil(1, 100, true, 0)
end
end
return false
end
多连接路由
将 Modbus 命令路由到特定连接:
local last_poll_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 1000 then
-- 轮询连接 0 (串行) 上的从机 1
modbus_read_holding_registers(1, 0, 10, 0, 0)
-- 轮询连接 1 (TCP) 上的从机 2
modbus_read_holding_registers(2, 0, 10, 0, 1)
last_poll_time = elapsed_ms
end
end
错误处理
所有 Modbus 辅助函数执行参数验证并在出错时返回 false:
-- 无效数量(寄存器超过 125)
local success = modbus_read_holding_registers(1, 0, 200, 0)
if not success then
log("error", "未能调度 Modbus 请求")
end
-- 无效寄存器值(超过 65535)
local values = {70000} -- 超出范围
modbus_write_multiple_registers(1, 0, values, 0) -- 引发 Lua 错误
常见验证错误:
- 线圈:数量必须为 1-2000
- 离散输入:数量必须为 1-2000
- 保持寄存器:数量必须为 1-125
- 输入寄存器:数量必须为 1-125
- 寄存器值:必须为 0-65535(无符号 16 位)
- 线圈值:必须为布尔值 (
true/false)
调试
启用调试日志以跟踪 Modbus 操作:
function on_send()
log("debug", "向从机 " .. message.connection_id .. " 发送 Modbus 请求")
return false
end
function on_send_confirm()
log("debug", "Modbus 请求已确认")
return false
end
function on_receive()
local payload = message.payload
log("debug", "接收 " .. #payload .. " 字节")
-- 记录功能码
if #payload > 0 then
local func_code = string.byte(payload, 1)
log("debug", "功能码:0x" .. string.format("%02X", func_code))
end
return false
end
性能考虑
- 请求时序:适当间隔请求以避免压倒从机
- 批量操作:使用写多个函数而不是多个单独写入
- 连接过滤:仅轮询具有 Modbus 编解码器的连接
- 响应解析:在读取前验证响应长度以避免错误
-- 好:批量写入
modbus_write_multiple_registers(1, 0, {100, 200, 300}, 0)
-- 效率较低:多个单独写入
modbus_write_single_register(1, 0, 100, 0)
modbus_write_single_register(1, 1, 200, 100)
modbus_write_single_register(1, 2, 300, 200)