modbus_rtu.lua 9.2 KB

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