modbus_rtu.lua 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. --[[
  2. @module modbus_rtu
  3. @summary modbus_rtu MODBUS_RTU协议
  4. @version 1.0
  5. @date 2025.01.10
  6. @author HH
  7. @usage
  8. --注意:
  9. --注意:
  10. -- 用法实例
  11. local modbus_rtu = require "modbus_rtu"
  12. -- 初始化modbus_rtu
  13. modbus_rtu.init({
  14. uartid = 1, -- 接收/发送数据的串口id
  15. baudrate = 4800, -- 波特率
  16. gpio_485 = 25, -- 转向GPIO编号
  17. tx_delay = 50000 -- 转向延迟时间,单位us
  18. -- 定义modbus_rtu数据接收回调
  19. local function on_modbus_rtu_receive(frame)
  20. log.info("modbus_rtu frame received:", json.encode(frame))
  21. if frame.fun == 0x03 then -- 功能码0x03表示读取保持寄存器
  22. local byte = frame.byte
  23. local payload = frame.payload
  24. -- log.info("modbus_rtu payload (hex):", payload:toHex())
  25. -- 解析数据(假设数据为16位寄存器值)
  26. local values_big = {} -- 大端序解析结果
  27. for i = 1, #payload, 2 do
  28. local msb = payload:byte(i)
  29. local lsb = payload:byte(i + 1)
  30. -- 大端序解析
  31. local result_big = (msb * 256) + lsb
  32. table.insert(values_big, result_big)
  33. end
  34. -- 输出大端序的解析结果
  35. log.info("输出大端序的解析结果:", table.concat(values_big, ", "))
  36. -- 第一个寄存器是湿度,第二个是温度,除以10以获取实际值
  37. if #values_big == 2 then
  38. log.info("测试同款485温湿度计")
  39. local humidity = values_big[1] / 10
  40. local temperature = values_big[2] / 10
  41. -- 打印湿度和温度
  42. log.info(string.format("湿度: %.1f%%", humidity))
  43. log.info(string.format("温度: %.1f°C", temperature))
  44. else
  45. log.info("用户自己的485下位机,共有" .. #values_big .. "组数据")
  46. for index, value in ipairs(values_big) do
  47. log.info(string.format("寄存器 %d: %d (大端序)", index, value))
  48. end
  49. end
  50. else
  51. log.info("功能码不是03")
  52. end
  53. end
  54. -- 设置modbus_rtu数据接收回调
  55. modbus_rtu.set_receive_callback(1, on_modbus_rtu_receive)
  56. local function send_modbus_rtu_command()
  57. local addr = 0x01 -- 设备地址,此处填客户自己的
  58. local fun = 0x03 -- 功能码(03为读取保持寄存器),此处填客户自己的
  59. local data = string.char(0x00, 0x00, 0x00, 0x02) -- 起始地址和寄存器数量(此处填客户自己的起始地址进而寄存器数量)
  60. -- modbus_rtu.send_command(1, addr, fun, data) -- 只发送一次命令并等待响应处理
  61. modbus_rtu.send_command(1, addr, fun, data, 5000) -- 循环5S发送一次
  62. end
  63. sys.taskInit(function()
  64. sys.wait(5000)
  65. send_modbus_rtu_command()
  66. end)
  67. ]] local modbus_rtu = {}
  68. -- 默认配置
  69. local DEFAULT_CONFIG = {
  70. uartid = 1, -- 串口ID
  71. baudrate = 4800, -- 波特率
  72. databits = 8, -- 数据位
  73. stopbits = 1, -- 停止位
  74. parity = uart.None, -- 校验位
  75. endianness = uart.LSB, -- 字节序
  76. buffer_size = 1024, -- 缓冲区大小
  77. gpio_485 = 25, -- 485转向GPIO
  78. rx_level = 0, -- 485模式下RX的GPIO电平
  79. tx_delay = 10000 -- 485模式下TX向RX转换的延迟时间(us)
  80. }
  81. --[[
  82. modbus_rtu初始化
  83. @api modbus_rtu.init(config)
  84. @number
  85. config为table,table里元素如下,用户根据自己实际情况修改对应参数,如果某个参数缺失会自动补充默认值,默认值为下
  86. config= {
  87. uartid = 1, -- 串口ID
  88. baudrate = 4800, -- 波特率
  89. databits = 8, -- 数据位
  90. stopbits = 1, -- 停止位
  91. parity = uart.None, -- 校验位
  92. endianness = uart.LSB, -- 字节序
  93. buffer_size = 1024, -- 缓冲区大小
  94. gpio_485 = 25, -- 485转向GPIO
  95. rx_level = 0, -- 485模式下RX的GPIO电平
  96. tx_delay = 10000, -- 485模式下TX向RX转换的延迟时间(us)
  97. }
  98. @table 485转串口设置
  99. @return 无
  100. @usage
  101. modbus_rtu.init(config)
  102. --]]
  103. -- 初始化modbus_rtu
  104. function modbus_rtu.init(config)
  105. config = config or {}
  106. -- 遍历 DEFAULT_CONFIG,为缺省的参数赋值
  107. for key, default_value in pairs(DEFAULT_CONFIG) do
  108. if config[key] == nil then
  109. config[key] = default_value
  110. end
  111. end
  112. -- 初始化UART
  113. uart.setup(config.uartid, config.baudrate, config.databits, config.stopbits, config.parity, config.endianness,
  114. config.buffer_size, config.gpio_485, config.rx_level, config.tx_delay)
  115. log.info("modbus_rtu 当前串口初始化配置为:", json.encode(config))
  116. end
  117. --[[
  118. 对数据进行CRC16_RTU校验
  119. @api modbus_rtu.crc16()
  120. @return int 原始数据对应的CRC16值
  121. @usage
  122. local crc16_data = modbus_rtu.crc16("01024B")
  123. log.info("crc16_data", crc16_data)
  124. ]]
  125. function modbus_rtu.crc16(data)
  126. local crc16_data = crypto.crc16_modbus(data)
  127. -- log.info("crc16end = ",crc16_data)
  128. return crc16_data
  129. end
  130. --[[
  131. 对下位机返回过来的数据进行modbus_rtu解析
  132. @api modbus_rtu.parse_frame()
  133. @number 下位机返回的数据(一般是hex的)
  134. @return 成功返回table(地址码,功能码,有效字节数 ,真实数据值,crc校验值)失败返回nil和"CRC error"
  135. @usage
  136. local crc16_data = modbus_rtu.parse_frame("01030401E6FF9F1BA0")
  137. log.info("crc16_data", crc16_data.addr,crc16_data.fun,crc16_data.byte,crc16_data.payload,crc16_data.crc)
  138. ]]
  139. function modbus_rtu.parse_frame(data)
  140. local str = data or 0X00
  141. local addr = str:byte(1) or 0X00 -- 地址位
  142. local fun = str:byte(2) or 0X00 -- 功能码
  143. local byte = str:byte(3) or 0X00 -- 有效字节数
  144. local payload = str:sub(4, 4 + byte - 1) or 0X00 -- 数据部分(根据有效字节数动态截取)
  145. local crc_data = str:sub(-2, -1) or 0X00
  146. local idx, crc = pack.unpack(crc_data, "H") -- CRC校验值
  147. -- 校验CRC
  148. if crc == modbus_rtu.crc16(str:sub(1, -3)) then
  149. log.info("modbus_rtu CRC校验成功")
  150. return {
  151. addr = addr,
  152. fun = fun,
  153. byte = byte,
  154. payload = payload,
  155. crc = crc
  156. }
  157. else
  158. log.info("modbus_rtu CRC校验失败", crc)
  159. return nil, "CRC error"
  160. end
  161. end
  162. -- 确保build_frame使用正确的CRC和帧结构
  163. function modbus_rtu.build_frame(addr, fun, data)
  164. local frame = string.char(addr, fun) .. data
  165. local crc = modbus_rtu.crc16(frame)
  166. -- log.info("CRC部分为",crc:toHex())
  167. local pack_crc = pack.pack("H", crc)
  168. -- log.info("pack后CRC为",aaa:toHex())
  169. return frame .. pack_crc
  170. end
  171. --[[
  172. 对发送给下位机的数据进行校验和发送次数的设置
  173. @api modbus_rtu.send_command(uartid, addr, fun, data, interval)
  174. @number uartid 485转串口对应的串口id,main_uart为1,aux_uart为2
  175. @number addr 发送给下位机命令里的地址码
  176. @number fun 发送给下位机命令里的功能码
  177. @number data 发送给下位机命令里的有效字节数和命令码
  178. @number interval(可选) 为nil时命令只发一次,为数字时时间隔发送命令的秒数
  179. @return 成功返回table(地址码,功能码,有效字节数 ,真实数据值,crc校验值)失败返回nil和"CRC error"
  180. @usage
  181. local crc16_data = modbus_rtu.parse_frame("01030401E6FF9F1BA0")
  182. log.info("crc16_data", crc16_data.addr,crc16_data.fun,crc16_data.byte,crc16_data.payload,crc16_data.crc)
  183. ]]
  184. -- 发送modbus_rtu命令
  185. function modbus_rtu.send_command(uartid, addr, fun, data, interval)
  186. local cmd = modbus_rtu.build_frame(addr, fun, data)
  187. if interval then
  188. -- 如果传入了interval,则启用循环发送
  189. sys.timerLoopStart(function()
  190. log.info("每隔" .. interval .. "秒发一次指令", cmd:toHex())
  191. uart.write(uartid, cmd)
  192. end, interval)
  193. -- sys.timerLoopStart(uart.write, interval, uartid, cmd)
  194. -- log.info("modbus_rtu 循环发送的间隔时间为", interval, "ms",cmd:toHex())
  195. else
  196. -- 否则只发送一次
  197. uart.write(uartid, cmd)
  198. -- log.info("modbus_rtu 只发送一次", cmd:toHex())
  199. end
  200. end
  201. -- 设置modbus_rtu数据接收回调
  202. function modbus_rtu.set_receive_callback(uartid, callback)
  203. uart.on(uartid, "receive", function(id, len)
  204. local s = ""
  205. repeat
  206. s = uart.read(id, 128)
  207. if #s > 0 then
  208. log.info("modbus_rtu 收到的下位机回复:", s:toHex())
  209. local frame, err = modbus_rtu.parse_frame(s)
  210. if frame then
  211. callback(frame)
  212. else
  213. log.info("modbus_rtu 数据错误", err)
  214. end
  215. end
  216. until s == ""
  217. end)
  218. end
  219. -- 设置modbus_rtu数据发送回调
  220. function modbus_rtu.set_sent_callback(uartid, callback)
  221. uart.on(uartid, "sent", function(id)
  222. log.info("modbus_rtu 数据发送:", id)
  223. if callback then
  224. callback(id)
  225. end
  226. end)
  227. end
  228. return modbus_rtu