raw_frame.lua 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. --[[
  2. @module raw_frame
  3. @summary RTU 主站应用模块(原始帧方式)
  4. @version 1.0
  5. @date 2025.12.12
  6. @author 马梦阳
  7. @usage
  8. 本功能模块演示的内容为:
  9. 1、将设备配置为 modbus RTU 主站模式
  10. 2、与从站 1 和 从站 2 进行通信
  11. 1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
  12. 2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
  13. 注意事项:
  14. 1、该示例程序需要搭配 exmodbus 扩展库使用
  15. 2、本功能模块只适合使用非标准 modbus RTU 请求报文格式的用户
  16. 3、如果你使用的是标准 modbus RTU 请求报文格式,请参考 param_field 功能模块
  17. 本文件没有对外接口,直接在 main.lua 中 require "raw_frame" 就可以加载运行;
  18. ]]
  19. local exmodbus = require("exmodbus")
  20. gpio.setup(16, 1) -- Air8000 开发板 RS485 芯片供电引脚
  21. local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚
  22. -- 创建 RTU 主站配置参数;
  23. -- 说明:创建 RTU 主站时只需要配置如下参数即可;
  24. local create_config = {
  25. -- 串口配置参数;
  26. mode = exmodbus.RTU_MASTER, -- 通信模式
  27. uart_id = 1, -- UART 端口号
  28. baud_rate = 115200, -- 波特率
  29. data_bits = 8, -- 数据位
  30. stop_bits = 1, -- 停止位
  31. parity_bits = uart.None, -- 校验位
  32. byte_order = uart.LSB, -- 字节顺序
  33. rs485_dir_gpio = rs485_dir_gpio, -- RS485 方向引脚
  34. rs485_dir_rx_level = 0, -- RS485 接收方向电平
  35. }
  36. -- 初始化从站 1 数据结构
  37. -- 用于记录从站 1 保持寄存器 0-1 的值;
  38. local slave1_data = {}
  39. -- 配置读取从站 1 保持寄存器 0-1 的值;
  40. local read_config = {
  41. raw_request = string.char(
  42. 0x01, -- 从站地址
  43. 0x03, -- 功能码:读取保持寄存器
  44. 0x00, 0x00, -- 寄存器起始地址
  45. 0x00, 0x02, -- 寄存器数量
  46. 0xC4, 0x0B -- CRC16校验码
  47. ),
  48. timeout = 1000 -- 超时时间 1000 ms
  49. }
  50. -- 配置写入从站 2 保持寄存器 0-1 的值;
  51. local write_config = {
  52. raw_request = string.char(
  53. 0x02, -- 从站地址
  54. 0x10, -- 功能码:写入保持寄存器
  55. 0x00, 0x00, -- 寄存器起始地址
  56. 0x00, 0x02, -- 寄存器数量
  57. 0x04, -- 字节数量
  58. 0x00, 0x12, -- 寄存器 0 的值
  59. 0x00, 0x34, -- 寄存器 1 的值
  60. 0x5D, 0x39 -- CRC16校验码
  61. ),
  62. timeout = 1000, -- 超时时间 1000 ms
  63. }
  64. -- 创建 RTU 主站实例
  65. local rtu_master = exmodbus.create(create_config)
  66. -- 判断主站是否创建成功并记录日志
  67. if not rtu_master then
  68. log.info("exmodbus_test", "rtu_master 创建失败")
  69. else
  70. log.info("exmodbus_test", "rtu_master 创建成功")
  71. end
  72. -- 读取从站 1 保持寄存器数据的函数
  73. local function read_slave1_holding_registers()
  74. log.info("exmodbus_test", "开始读取从站 1 保持寄存器 0-1 的值")
  75. -- 执行读取操作
  76. local read_result = rtu_master:read(read_config)
  77. -- 根据返回状态处理结果
  78. if read_result.status == exmodbus.STATUS_SUCCESS then
  79. local resp = read_result.raw_response
  80. -- 特别说明:
  81. -- 接下来的判断是针对 modbus RTU 标准响应格式的应答原始帧来解析的
  82. -- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
  83. -- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
  84. -- 1. 检查总长度:必须为 9 字节(1 地址 + 1 功能码 + 1 字节数 + 4 数据 + 2 CRC)
  85. if #resp ~= 9 then
  86. log.info("exmodbus_test", "响应长度错误,期望 9 字节,实际:", #resp)
  87. return
  88. end
  89. -- 2. 检查从站地址
  90. if string.byte(resp, 1) ~= 0x01 then
  91. log.info("exmodbus_test", "从站地址不匹配,收到:", string.byte(resp, 1))
  92. return
  93. end
  94. -- 3. 检查功能码
  95. local func_code = string.byte(resp, 2)
  96. if func_code == 0x83 then
  97. local exc_code = string.byte(resp, 3)
  98. log.info("exmodbus_test", "从站返回异常响应,异常码:", exc_code)
  99. return
  100. elseif func_code ~= 0x03 then
  101. log.info("exmodbus_test", "功能码错误,收到:", func_code)
  102. return
  103. end
  104. -- 4. 检查字节数字段(应为 4)
  105. local byte_count = string.byte(resp, 3)
  106. if byte_count ~= 4 then
  107. log.info("exmodbus_test", "字节数字段错误,期望 4,实际:", byte_count)
  108. return
  109. end
  110. -- 5. 校验CRC
  111. -- 计算前 7 字节的 CRC
  112. local crc_calculated = crypto.crc16_modbus(resp:sub(1, 7))
  113. -- 提取接收到的 CRC
  114. local crc_received = string.unpack("<I2", resp, 8)
  115. -- 比较 CRC
  116. if crc_calculated ~= crc_received then
  117. log.info("exmodbus_test", "CRC 校验错误,计算值:", crc_calculated, ",接收值:", crc_received)
  118. return
  119. end
  120. -- 6. 解析寄存器数据(从第 4 字节开始,大端序)
  121. local data1 = string.unpack(">I2", resp, 4) -- 寄存器 0,偏移 4
  122. local data2 = string.unpack(">I2", resp, 6) -- 寄存器 1,偏移 6
  123. -- 7. 记录数据
  124. slave1_data[0] = data1
  125. slave1_data[1] = data2
  126. -- 8. 记录日志
  127. log.info("exmodbus_test", "成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为", slave1_data[0], ",寄存器 1 数值为", slave1_data[1])
  128. elseif read_result.status == exmodbus.STATUS_TIMEOUT then
  129. log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
  130. end
  131. end
  132. -- 写入从站 2 保持寄存器数据的函数
  133. local function write_slave2_holding_registers()
  134. log.info("exmodbus_test", "开始写入从站 2 保持寄存器 0-1 的值")
  135. -- 执行写入操作
  136. local write_result = rtu_master:write(write_config)
  137. -- 根据返回状态处理结果
  138. if write_result.status == exmodbus.STATUS_SUCCESS then
  139. local resp = write_result.raw_response
  140. -- 特别说明:
  141. -- 接下来的判断是针对 modbus RTU 标准响应格式的应答原始帧来解析的
  142. -- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
  143. -- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
  144. -- 1. 检查总长度:必须为 8 字节(1 地址 + 1 功能码 + 2 起始地址 + 2 寄存器数量 + 2 CRC)
  145. if #resp ~= 8 then
  146. log.info("exmodbus_test", "响应长度错误,期望 8 字节,实际:", #resp)
  147. return
  148. end
  149. -- 2. 检查从站地址
  150. if string.byte(resp, 1) ~= 0x02 then
  151. log.info("exmodbus_test", "从站地址不匹配,收到:", string.byte(resp, 1))
  152. return
  153. end
  154. -- 3. 检查功能码
  155. local func_code = string.byte(resp, 2)
  156. if func_code == 0x90 then
  157. local exc_code = string.byte(resp, 3)
  158. log.info("exmodbus_test", "从站返回异常响应,异常码:", exc_code)
  159. return
  160. elseif func_code ~= 0x10 then
  161. log.info("exmodbus_test", "功能码错误,收到:", func_code)
  162. return
  163. end
  164. -- 4. 检查起始地址(应为 0x0000)
  165. local start_addr = string.unpack(">I2", resp, 3)
  166. if start_addr ~= 0x0000 then
  167. log.info("exmodbus_test", "起始地址不匹配,收到:", start_addr)
  168. return
  169. end
  170. -- 5. 检查寄存器数量(应为 0x0002)
  171. local reg_count = string.unpack(">I2", resp, 5)
  172. if reg_count ~= 0x0002 then
  173. log.info("exmodbus_test", "寄存器数量错误,期望 0x0002,实际:", reg_count)
  174. return
  175. end
  176. -- 6. 校验CRC
  177. -- 计算前 6 字节的 CRC
  178. local crc_calculated = crypto.crc16_modbus(resp:sub(1, 6))
  179. -- 提取接收到的 CRC
  180. local crc_received = string.unpack("<I2", resp, 7)
  181. -- 比较 CRC
  182. if crc_calculated ~= crc_received then
  183. log.info("exmodbus_test", "CRC 校验错误,计算值:", crc_calculated, ",接收值:", crc_received)
  184. return
  185. end
  186. log.info("exmodbus_test", "成功写入从站 2 保持寄存器 0-1")
  187. elseif write_result.status == exmodbus.STATUS_TIMEOUT then
  188. log.info("exmodbus_test", "未收到从站 2 的响应(超时)")
  189. end
  190. end
  191. -- 定时任务函数:每 2 秒调用一次读取函数,每 4 秒调用一次写入函数
  192. local function task()
  193. local count = 0 -- 计数器
  194. while true do
  195. if rtu_master then
  196. -- 每 2 秒调用一次读取函数
  197. read_slave1_holding_registers()
  198. if count == 0 then
  199. -- 每 4 秒调用一次写入函数
  200. write_slave2_holding_registers()
  201. end
  202. count = (count + 1) % 2
  203. else
  204. log.info("exmodbus_test", "rtu_master 未创建,无法执行 read_slave1_holding_registers()")
  205. end
  206. sys.wait(2000)
  207. end
  208. end
  209. -- 初始化任务
  210. sys.taskInit(task)