作为工业通信的标准协议,Modbus 大量应用于温湿度传感器、PLC、变频器等设备。传统开发方式要处理大量底层通信细节,增加了开发工作量与出错概率。
针对这一行业痛点,LuatOS 上线exmodbus扩展库,一站式封装协议底层能力,完美兼容合宙 LuatOS 模组及工业引擎产品,依托配套硬件与开源案例,助力开发者快速完成 Modbus 通信功能开发。

一、必看的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 02C40B
响应: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 00 00 00 02
p应:00 01 00 00 00 07 01 03 04 00 01 00 05

1.2 Modbus四种基本数据类型

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

01 四种数据类型26041701.png

二 exmodbus库核心API函数速览

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

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

2.1 exmodbus.create(config)

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

简要示例:

含义说明:modbus 实例对象;
数据类型:table;
取值范围:暂无;
返回值示例:-- 创建 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(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开关,开启后会打印接收和发送的原始数据。

简要示例:

1-- 开启 debug模式;
2 exmodbus.debug(true)
3
4-- 关闭debug模式;
5 exmodbus.debug(false)
6

三 开源示例快速上手

为帮助行业客户快速评估和落地,在LuatOS官方仓库的demo/modbus目录下提供了完整的开源示例代码,涵盖TCP主站/从站应用、RTU主站/从站应用、485温湿度传感器读取等功能模块。

以Air8000系列开发板为例:

  • **Modbus最新示例源码:**https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/modbus

  • Modbus实操教程详见:https://docs.openluat.com/air8000/luatos/app/modbus/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐