--[[ @module modbus_rtu @summary modbus_rtu MODBUS_RTU协议 @version 1.0 @date 2025.01.10 @author HH @usage --注意: --注意: -- 用法实例 local modbus_rtu = require "modbus_rtu" -- 初始化modbus_rtu modbus_rtu.init({ uartid = 1, -- 接收/发送数据的串口id baudrate = 4800, -- 波特率 gpio_485 = 25, -- 转向GPIO编号 tx_delay = 50000 -- 转向延迟时间,单位us -- 定义modbus_rtu数据接收回调 local function on_modbus_rtu_receive(frame) log.info("modbus_rtu frame received:", json.encode(frame)) if frame.fun == 0x03 then -- 功能码0x03表示读取保持寄存器 local byte = frame.byte local payload = frame.payload -- log.info("modbus_rtu payload (hex):", payload:toHex()) -- 解析数据(假设数据为16位寄存器值) local values_big = {} -- 大端序解析结果 for i = 1, #payload, 2 do local msb = payload:byte(i) local lsb = payload:byte(i + 1) -- 大端序解析 local result_big = (msb * 256) + lsb table.insert(values_big, result_big) end -- 输出大端序的解析结果 log.info("输出大端序的解析结果:", table.concat(values_big, ", ")) -- 第一个寄存器是湿度,第二个是温度,除以10以获取实际值 if #values_big == 2 then log.info("测试同款485温湿度计") local humidity = values_big[1] / 10 local temperature = values_big[2] / 10 -- 打印湿度和温度 log.info(string.format("湿度: %.1f%%", humidity)) log.info(string.format("温度: %.1f°C", temperature)) else log.info("用户自己的485下位机,共有" .. #values_big .. "组数据") for index, value in ipairs(values_big) do log.info(string.format("寄存器 %d: %d (大端序)", index, value)) end end else log.info("功能码不是03") end end -- 设置modbus_rtu数据接收回调 modbus_rtu.set_receive_callback(1, on_modbus_rtu_receive) local function send_modbus_rtu_command() local addr = 0x01 -- 设备地址,此处填客户自己的 local fun = 0x03 -- 功能码(03为读取保持寄存器),此处填客户自己的 local data = string.char(0x00, 0x00, 0x00, 0x02) -- 起始地址和寄存器数量(此处填客户自己的起始地址进而寄存器数量) -- modbus_rtu.send_command(1, addr, fun, data) -- 只发送一次命令并等待响应处理 modbus_rtu.send_command(1, addr, fun, data, 5000) -- 循环5S发送一次 end sys.taskInit(function() sys.wait(5000) send_modbus_rtu_command() end) ]] local modbus_rtu = {} -- 默认配置 local DEFAULT_CONFIG = { uartid = 1, -- 串口ID baudrate = 4800, -- 波特率 databits = 8, -- 数据位 stopbits = 1, -- 停止位 parity = uart.None, -- 校验位 endianness = uart.LSB, -- 字节序 buffer_size = 1024, -- 缓冲区大小 gpio_485 = 25, -- 485转向GPIO rx_level = 0, -- 485模式下RX的GPIO电平 tx_delay = 10000, -- 485模式下TX向RX转换的延迟时间(us) } --[[ modbus_rtu初始化 @api modbus_rtu.init(config) @number config为table,table里元素如下,用户根据自己实际情况修改对应参数,如果某个参数缺失会自动补充默认值,默认值为下 config= { uartid = 1, -- 串口ID baudrate = 4800, -- 波特率 databits = 8, -- 数据位 stopbits = 1, -- 停止位 parity = uart.None, -- 校验位 endianness = uart.LSB, -- 字节序 buffer_size = 1024, -- 缓冲区大小 gpio_485 = 25, -- 485转向GPIO rx_level = 0, -- 485模式下RX的GPIO电平 tx_delay = 10000, -- 485模式下TX向RX转换的延迟时间(us) } @table 485转串口设置 @return 无 @usage modbus_rtu.init(config) ]] -- 初始化modbus_rtu function modbus_rtu.init(config) config = config or {} -- 遍历 DEFAULT_CONFIG,为缺省的参数赋值 for key, default_value in pairs(DEFAULT_CONFIG) do if config[key] == nil then config[key] = default_value end end -- 初始化UART uart.setup( config.uartid, config.baudrate, config.databits, config.stopbits, config.parity, config.endianness, config.buffer_size, config.gpio_485, config.rx_level, config.tx_delay ) log.info("modbus_rtu 当前串口初始化配置为:", json.encode(config)) end --[[ 对数据进行CRC16_RTU校验 @api modbus_rtu.crc16() @return int 原始数据对应的CRC16值 @usage local crc16_data = modbus_rtu.crc16("01024B") log.info("crc16_data", crc16_data) ]] function modbus_rtu.crc16(data) local crc16_data = crypto.crc16_modbus(data) -- log.info("crc16end = ",crc16_data) return crc16_data end --[[ 对下位机返回过来的数据进行modbus_rtu解析 @api modbus_rtu.parse_frame() @number 下位机返回的数据(一般是hex的) @return 成功返回table(地址码,功能码,有效字节数 ,真实数据值,crc校验值)失败返回nil和"CRC error" @usage local crc16_data = modbus_rtu.parse_frame("01030401E6FF9F1BA0") log.info("crc16_data", crc16_data.addr,crc16_data.fun,crc16_data.byte,crc16_data.payload,crc16_data.crc) ]] function modbus_rtu.parse_frame(data) local str = data or "" local addr = str:byte(1) or"" -- 地址位 local fun = str:byte(2) or "" -- 功能码 local byte = str:byte(3)or "" -- 有效字节数 local payload = str:sub(4, 4 + byte - 1)or"" -- 数据部分(根据有效字节数动态截取) local crc_data = str:sub(-2, -1) or "" local idx,crc = pack.unpack(crc_data, "H") -- CRC校验值 -- 校验CRC if crc == modbus_rtu.crc16(str:sub(1, -3)) then log.info("modbus_rtu CRC校验成功") return { addr = addr, fun = fun, byte = byte, payload = payload, crc = crc, } else log.info("modbus_rtu CRC校验失败",crc) return nil, "CRC error" end end -- 确保build_frame使用正确的CRC和帧结构 function modbus_rtu.build_frame(addr, fun, data) local frame = string.char(addr, fun) .. data local crc = modbus_rtu.crc16(frame) -- log.info("CRC部分为",crc:toHex()) local pack_crc = pack.pack("H", crc) -- log.info("pack后CRC为",aaa:toHex()) return frame .. pack_crc end --[[ 对发送给下位机的数据进行校验和发送次数的设置 @api modbus_rtu.send_command(uartid, addr, fun, data, interval) @number uartid 485转串口对应的串口id,main_uart为1,aux_uart为2 @number addr 发送给下位机命令里的地址码 @number fun 发送给下位机命令里的功能码 @number data 发送给下位机命令里的有效字节数和命令码 @number interval(可选) 为nil时命令只发一次,为数字时时间隔发送命令的秒数 @return 成功返回table(地址码,功能码,有效字节数 ,真实数据值,crc校验值)失败返回nil和"CRC error" @usage local crc16_data = modbus_rtu.parse_frame("01030401E6FF9F1BA0") log.info("crc16_data", crc16_data.addr,crc16_data.fun,crc16_data.byte,crc16_data.payload,crc16_data.crc) ]] -- 发送modbus_rtu命令 function modbus_rtu.send_command(uartid, addr, fun, data, interval) local cmd = modbus_rtu.build_frame(addr, fun, data) if interval then -- 如果传入了interval,则启用循环发送 sys.timerLoopStart(function () -- log.info("每隔"..interval.."秒发一次指令",cmd:toHex()) uart.write(uartid,cmd) end,interval) -- sys.timerLoopStart(uart.write, interval, uartid, cmd) -- log.info("modbus_rtu 循环发送的间隔时间为", interval, "ms",cmd:toHex()) else -- 否则只发送一次 uart.write(uartid, cmd) -- log.info("modbus_rtu 只发送一次", cmd:toHex()) end end -- 设置modbus_rtu数据接收回调 function modbus_rtu.set_receive_callback(uartid, callback) uart.on(uartid, "receive", function(id, len) local s = "" repeat s = uart.read(id, 128) if #s > 0 then log.info("modbus_rtu 收到的下位机回复:", s:toHex()) local frame, err = modbus_rtu.parse_frame(s) if frame then callback(frame) else log.info("modbus_rtu 数据错误", err) end end until s == "" end) end -- 设置modbus_rtu数据发送回调 function modbus_rtu.set_sent_callback(uartid, callback) uart.on(uartid, "sent", function(id) log.info("modbus_rtu 数据发送:", id) if callback then callback(id) end end) end return modbus_rtu