Modbus 与 exmodbus 学习要点整理

Modbus 是工业物联网设备主流通信协议,广泛应用于温湿度传感器、变频器、PLC、智能电表等设备。传统开发模式下,通信过程中的 CRC 校验、RS485 收发方向切换、超时重连等底层环节,均需开发者自主编码实现,开发效率低且易产生故障。

LuatOS 新增 exmodbus 扩展库,对 Modbus 通信底层能力进行封装,使用者通过简易 API 即可完成通信开发。该组件兼容合宙全系列 LuatOS 模组及工业引擎设备;Air780Exx、Air8000 等配套开发板集成 RS485 硬件电路,结合开源示例可直接开展功能调试。

44b9cd465c09505c3ae71d89def5369f.png

一、必看的Modbus基础知识

Modbus最初由Modicon(现为施耐德电气旗下品牌)于1979年开发,是一种用于可编程逻辑控制器(PLC)之间通信的工业通信协议。

由于其简单、开放、免费且易于实现,Modbus已成为工业自动化领域最广泛使用的通信协议之一,被广泛应用于工业控制、楼宇自动化、能源管理、智能仪表等场景。

1.1 Modbus三种常见通信模式

Modbus协议采用主从架构,最初基于串行通信(Modbus RTU和Modbus ASCII),后扩展支持以太网传输(Modbus TCP)。

1)Modbus RTU

  • 传输介质:RS485/RS232串行通信;

  • 编码格式:二进制,报文紧凑;

  • 校验方式:CRC16;

  • 报文结构:[从站地址][功能码][数据][CRC16校验]

  • 典型示例:

请求:01 03 00 00 00 02 C4 0B
响应:01 03 04 00 01 00 05 6B F0

2)Modbus ASCII

  • 传输介质:RS485/RS232串行通信;

  • 编码格式:ASCII字符,报文可读性强;

  • 校验方式:LRC;

  • 报文结构:[:][从站地址][功能码][数据][LRC校验][\r\n]

  • 典型示例:

:010300000002BA\r\n

3)Modbus TCP

  • 传输介质:以太网等;

  • 编码格式:二进制,在RTU基础上增加了MBAP头;

  • 校验方式:无需校验位,直接通过TCP/IP传输;

  • 报文结构:[MBAP头][功能码][数据]

  • 典型示例:

请求:00 01 00 00 00 06 01 03 00000002
响应:00 01 00 00 00 07 01 03 04 00 01 00 05

1.2 Modbus四种基本数据类型

Modbus定义了四种数据对象:线圈、离散输入、输入寄存器和保持寄存器,并通过功能码实现设备间的数据交换。

[图片上传失败...(image-902e19-1779871538906)]

二、exmodbus库核心API函数速览

LuatOS的exmodbus库从工业应用实际需求出发,将复杂的协议细节封装为简洁易用的API,使开发者能够专注于业务逻辑,从而有效缩短项目周期、降低维护成本。

最新API文档详见:https://docs.openluat.com/osapi/ext/exmodbus/

2.1 exmodbus.create(config)

函数功能: 创建并返回一个新的modbus主/从站实例。

简要示例:

-- 创建 modbus RTU 主站
local create_config = {
     -- 串口配置参数;
     mode = exmodbus.RTU_MASTER, -- 通信模式:RTU 主站
     uart_id = 1,                -- 串口 ID:uart1
     baud_rate = 115200,         -- 波特率:115200
     data_bits = 8,              -- 数据位:8
     stop_bits = 1,              -- 停止位:1
     parity_bits = uart.None,    -- 校验位:无校验
     byte_order = uart.LSB,      -- 字节顺序:小端序
     rs485_dir_gpio = 23,        -- RS485 方向转换 GPIO 引脚
     rs485_dir_rx_level = 0      -- RS485 接收方向电平:0 为低电平,1 为高电平
}

local rtu_master = exmodbus.create(config)

2.2 modbus:read(config)

函数功能: 主站向从站发送读取操作请求(阻塞接口);支持通过“字段参数方式”或“原始帧方式”传入config配置参数。

简要示例:

-- 1. 创建主站
-- 2. 执行读取请求
local result = modbus:read(config)
-- 3. 判断从站响应状态
if result.status == exmodbus.STATUS_SUCCESS then

    log.info("收到响应数据且数据有效")

elseif result.status == exmodbus.STATUS_DATA_INVALID then

    log.info("收到响应数据但数据损坏/校验失败")

elseif result.status == exmodbus.STATUS_EXCEPTION then

    log.info("收到 modbus 标准异常响应")

elseif result.status == exmodbus.STATUS_TIMEROUT then

    log.info("无任何响应(超时)")

end

2.3 modbus:write(config)

函数功能: 主站向从站发送写入操作请求(阻塞接口);支持通过“字段参数方式”或“原始帧方式”传入config配置参数。

简要示例:

-- 1. 创建主站
-- 2. 执行写入请求
local result = modbus:write(write_config)
-- 3. 判断从站响应状态
if result.status == exmodbus.STATUS_SUCCESS then

   log.info("收到响应数据且数据有效")

elseif result.status == exmodbus.STATUS_DATA_INVALID then

   log.info("收到响应数据但数据损坏/校验失败")

elseif result.status == exmodbus.STATUS_EXCEPTION then

   log.info("收到 modbus 标准异常响应")

elseif result.status == exmodbus.STATUS_TIMEROUT then

   log.info("无任何响应(超时)")

end

2.4 modbus:destroy()

函数功能: 销毁已创建的主/从站示例对象。

简要示例:

1 modbus:destroy()

2.5 modbus:on(callback)

函数功能: 此接口仅限设备做从站时使用;当收到主站请求数据时,通过callback通知应用脚本处理;应用脚本处理完之后,在callback中通知返回值,告知exmodbus扩展库返回给主站。

简要示例:

-- 0. 初始化一些参数
-- 当前从站地址(ID 号)
local SLAVE_ID = 1

-- 寄存器映射表(按类型组织)
local modbus_data = {
    coils = {},            -- 线圈,可读可写,布尔值 (0/1)
    inputs = {},           -- 输入状态,只读,布尔值 (0/1)
    input_registers = {},  -- 输入寄存器,只读,16 位无符号整数
    holding_registers = {} -- 保持寄存器,可读可写,16 位无符号整数
}

-- 初始化一些默认值,便于测试
for i = 0, 3 do
    modbus_data.coils[i] = 0
    modbus_data.inputs[i] = 1
    modbus_data.input_registers[i] = 100 + i
    modbus_data.holding_registers[i] = 200 + i
end

-- 1. 创建从站
-- 2. 注册主站请求处理回调函数
local function callback(request)
    log.info("exmodbus_test", "收到主站请求")

    -- 检查从站 ID 是否匹配
    if request.slave_id ~= SLAVE_ID then
        log.info("exmodbus_test", "从站 ID 不匹配,请求从站 ID 为", request.slave_id, ",当前从站 ID 为", SLAVE_ID)
        return nil
    end

    -- 根据功能码和寄存器类型,匹配对应的数据表
    local data_table = nil
    local is_write = false -- 标记是否为写操作

    -- 检查请求的功能码是否支持
    if request.func_code == exmodbus.READ_COILS then -- 读线圈
        data_table = modbus_data.coils
    elseif request.func_code == exmodbus.READ_DISCRETE_INPUTS then -- 读离散输入
        data_table = modbus_data.inputs
    elseif request.func_code == exmodbus.READ_HOLDING_REGISTERS then -- 读保持寄存器
        data_table = modbus_data.holding_registers
    elseif request.func_code == exmodbus.READ_INPUT_REGISTERS then -- 读输入寄存器
        data_table = modbus_data.input_registers
    elseif request.func_code == exmodbus.WRITE_SINGLE_COIL or request.func_code == exmodbus.WRITE_MULTIPLE_COILS then -- 写单个/多个线圈
        is_write = true
        data_table = modbus_data.coils
    elseif request.func_code == exmodbus.WRITE_SINGLE_HOLDING_REGISTER or request.func_code == exmodbus.WRITE_MULTIPLE_HOLDING_REGISTERS then -- 写单个/多个保持寄存器
        is_write = true
        data_table = modbus_data.holding_registers
    else
        -- 不支持的功能码
        log.info("exmodbus_test", "不支持的功能码: ", request.func_code)
        return exmodbus.ILLEGAL_FUNCTION
    end

    -- 检查数据地址是否有效
    local end_addr = request.start_addr + request.reg_count - 1

    -- 假设每种寄存器的最大地址是 3 (即 0 - 3)
    if request.start_addr < 0 or end_addr > 3 then
        log.info("exmodbus_test", "数据地址超出范围,起始地址为", request.start_addr, "结束地址为", end_addr)
        return exmodbus.ILLEGAL_DATA_ADDRESS
    end

    -- 处理读取操作
    if not is_write then
        -- 构造响应数据表
        local response = {}
        for i = 0, request.reg_count - 1 do
            local addr = request.start_addr + i
            response[addr] = data_table[addr]
        end
        log.info("exmodbus_test", "读取成功,返回数据: ", table.concat(response, ", "))
        return response
    end

    -- 处理写入操作
    if is_write then
        -- 执行写入操作
        for i = 0, request.reg_count - 1 do
            local addr = request.start_addr + i
            data_table[addr] = request.data[addr]
            log.info("exmodbus_test", "写入成功,写入地址: ", addr, "写入数据: ", request.data[addr])
        end
        return {} -- 返回空表表示成功
    end
end

-- 3. 注册主站请求处理回调函数
modbus:on(callback)

2.6 exmodbus.debug(enable)

函数功能: 设置debug开关,开启后会打印接收和发送的原始数据。

简要示例:

-- 开启 debug 模式;
exmodbus.debug(true)

-- 关闭 debug 模式;
exmodbus.debug(false)

三、开源示例快速上手

在LuatOS官方仓库的demo/modbus目录下提供了完整的开源示例代码,涵盖TCP主站/从站应用、RTU主站/从站应用、485温湿度传感器读取等功能模块。

以Air8000系列开发板为例,详情可以参考资料中心。

d516c1f8cb32a674b8ec2b9987314f49.png
a1322427251de1e5933cad4f3e515468.png
6f843f1dc1a9220b8809eacb9ce2562f.png

工业设备大量使用 Modbus,开发难点在底层校验、方向切换、重传处理。exmodbus 库把这些细节封装好,只需调用简单 API。Modbus 分 RTU、ASCII、TCP,数据类型有四种。exmodbus 支持主站读写、从站回调、调试日志,适配合宙全系硬件,官方提供完整 demo,可直接用于温湿度传感器、PLC、电表等设备通信开发。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容