| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- --[[
- @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
|