uart485_test.lua 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. --[[
  2. @module 485_uart
  3. @summary 485串口功能模块
  4. @version 1.0
  5. @date 2025.09.23
  6. @author 魏健强
  7. @usage
  8. 本demo演示的核心功能为:
  9. 1.开启串口,配置波特率等参数;
  10. 2.设置接收回调函数
  11. 3.定时向串口发送数据
  12. ]]
  13. local uartid = 12 -- 根据实际设备选取不同的uartid
  14. local uart485Pin = 141 -- 用于控制485接收和发送的使能引脚(根据实际设备选取不同引脚)
  15. local rdbuf = "" -- 全局接收缓存,用于拼接分包数据
  16. -- 协议格式
  17. local FRAME_HEAD = "\xFE\xFE" -- 帧头(2字节)
  18. local FRAME_HEAD_LEN = 12 --- 帧头长度12字节
  19. local CHECK_FIELD_LEN = 2 -- CRC校验位占2字节
  20. local function head_paser(data)
  21. local netHeader = {pro_ver=0,msg_id=0,msg_type1=0,msg_type2=0,msg_len=0}
  22. local nextpos = 1
  23. if #data <FRAME_HEAD_LEN then return false,nil end
  24. nextpos, netHeader.head = pack.unpack(data, "<H", nextpos) -- 校验帧头
  25. if netHeader.head ~= 0xFEFE then
  26. log.info("net","error","帧头错误,应为:FEFE,实际为:", string.format("0x%04X",netHeader.head))
  27. return false,nil
  28. end
  29. nextpos, netHeader.pro_ver = pack.unpack(data, "b", nextpos) -- 获取协议版本
  30. nextpos, netHeader.msg_id = pack.unpack(data, "<I", nextpos) -- 获取消息序列号
  31. nextpos, netHeader.msg_type1 = pack.unpack(data, "b", nextpos) -- 获取一级消息类型
  32. nextpos, netHeader.msg_type2 = pack.unpack(data, "<H", nextpos) -- 获取二级消息类型
  33. nextpos, netHeader.msg_len = pack.unpack(data, "<H", nextpos) -- 校验帧长
  34. -- log.info("head_paser", "解析帧头成功",
  35. -- "协议版本", netHeader.pro_ver,
  36. -- "消息ID", netHeader.msg_id,
  37. -- "数据长度N", netHeader.msg_len)
  38. return true, netHeader
  39. end
  40. local function parse(data)
  41. if not data or #data == 0 then
  42. return false, data, nil
  43. end
  44. log.info("parse", "开始解析数据", data:toHex())
  45. -- 步骤1:查找帧头FE FE的位置
  46. local head_pos = string.find(data, FRAME_HEAD, 1, true)
  47. if not head_pos then
  48. log.warn("parse", "未找到帧头FE FE,缓存长度:", #data)
  49. return false, data, nil -- 无帧头,返回原缓存
  50. end
  51. -- 步骤2:清理帧头前的无效数据(比如乱码、残留字节)
  52. local remain_buf = string.sub(data, head_pos)
  53. log.info("parse", "找到帧头,清理无效数据后缓存长度:", #remain_buf)
  54. -- 步骤3:判断是否够帧头长度(12字节),不够则返回等待后续数据
  55. if #remain_buf < FRAME_HEAD_LEN then
  56. log.warn("parse", "缓存仅"..#remain_buf.."字节,不足帧头12字节,等待分包")
  57. return false, remain_buf, nil
  58. end
  59. -- 步骤4:解析帧头,获取数据长度N
  60. local head_ok, netHeader = head_paser(remain_buf)
  61. if not head_ok then
  62. log.error("parse", "帧头解析失败,丢弃当前帧头,继续查找下一个")
  63. -- 跳过当前错误帧头,从下一个字节继续解析(避免死循环)
  64. return true, string.sub(remain_buf, 2), nil
  65. end
  66. -- 步骤5:计算完整帧总长度 = 帧头(12) + 数据长度(N)
  67. local frame_total_len = FRAME_HEAD_LEN + netHeader.msg_len
  68. log.info("parse", "计算完整帧长度:", frame_total_len, "(帧头12 + 数据"..netHeader.msg_len.." + CRC2)")
  69. -- 步骤6:判断缓存是否够完整帧长度,不够则返回等待分包
  70. if #remain_buf < frame_total_len then
  71. log.warn("parse", "缓存仅"..#remain_buf.."字节,不足完整帧"..frame_total_len.."字节,等待分包")
  72. return false, remain_buf, nil
  73. end
  74. -- 步骤7:提取完整帧,剩余数据留待下次解析
  75. local full_frame = string.sub(remain_buf, 1, frame_total_len) -- 完整帧
  76. local unproc_buf = string.sub(remain_buf, frame_total_len + 1) -- 剩余未解析数据
  77. log.info("parse", "提取完整帧,长度:", #full_frame, "十六进制:", full_frame:toHex())
  78. return true, unproc_buf, full_frame
  79. end
  80. local function proc(data)
  81. if not data or #data == 0 then return end
  82. -- 步骤1:追加新数据到全局缓存
  83. rdbuf = rdbuf .. data
  84. log.info("proc", "全局缓存总长度:", #rdbuf)
  85. local result, unproc_buf, full_frame
  86. unproc_buf = rdbuf
  87. -- 步骤2:循环解析所有完整帧(处理黏包)
  88. while true do
  89. -- 调用parse解析,返回:是否继续、剩余缓存、完整帧
  90. result, unproc_buf, full_frame = parse(unproc_buf)
  91. -- 解析出完整帧则发布事件
  92. if full_frame then
  93. sys.publish("UART_485_RECIVE", full_frame)
  94. end
  95. -- 终止条件:无剩余数据/解析失败/无完整帧
  96. if not result or not unproc_buf or unproc_buf == "" then
  97. break
  98. end
  99. end
  100. -- 步骤3:更新全局缓存为未解析的剩余数据
  101. rdbuf = unproc_buf or ""
  102. log.info("proc", "解析完成,剩余缓存长度:", #rdbuf)
  103. end
  104. local function uart_cb(id, len)
  105. local data = ""
  106. repeat
  107. data = uart.read(id, 1024) -- 一次读取最多1024字节
  108. if not data or #data == 0 then break end
  109. -- 打印十六进制(二进制数据必须看十六进制,字符打印会乱码)
  110. log.info("uart_cb", "接收数据长度:", #data)
  111. --log.info("uart_cb", "接收数据,长度:", #data, "十六进制:", data:toHex())
  112. -- 处理数据(拼接+解析)
  113. proc(data)
  114. until data == ""
  115. end
  116. sys.subscribe("UART_485_RECIVE", function(full_frame)
  117. if not full_frame or #full_frame == 0 then return end
  118. -- 解析帧头(再次确认,可选)
  119. local head_ok, netHeader = head_paser(full_frame)
  120. if not head_ok then
  121. log.error("业务解析", "帧头解析失败")
  122. return
  123. end
  124. local data_part = string.sub(full_frame, FRAME_HEAD_LEN + 1, #full_frame - CHECK_FIELD_LEN)
  125. local crc_data_part = string.sub(full_frame, 1, #full_frame - CHECK_FIELD_LEN)
  126. local crc_str = string.sub(full_frame, #full_frame - CHECK_FIELD_LEN + 1, #full_frame)
  127. local _, recv_crc = pack.unpack(crc_str, "<H", 1) -- 第一个返回值是nextpos,无用;第二个是CRC数值
  128. local calc_crc = crypto.crc16("IBM", crc_data_part)
  129. log.info("业务解析",
  130. "接收CRC数值:", recv_crc, "十六进制:", string.format("0x%04X", recv_crc),
  131. "计算CRC数值:", calc_crc, "十六进制:", string.format("0x%04X", calc_crc))
  132. if recv_crc ~= calc_crc then
  133. log.error("业务解析", "CRC校验失败!")
  134. return -- 校验失败,放弃处理该帧
  135. end
  136. log.info("业务解析", "CRC校验通过")
  137. -- 7. 校验通过后处理业务逻辑(示例:根据消息类型分支处理)
  138. log.info("业务解析完成",
  139. "一级消息类型:", netHeader.msg_type1,
  140. "二级消息类型:", netHeader.msg_type2,
  141. "消息ID:", netHeader.msg_id)
  142. -- 示例:根据不同消息类型处理数据
  143. if netHeader.msg_type1 == 0x02 and netHeader.msg_type2 == 0x2002 then
  144. log.info("业务解析", "处理控制类指令,数据:", data_part:toHex())
  145. sys.publish("UART_485_RECIVE_CONTROL", data_part)
  146. -- elseif netHeader.msg_type1 == 0x02 then
  147. -- log.info("业务解析", "处理数据上报指令,数据:", data_part:toHex())
  148. end
  149. end)
  150. -- local function uart_send()
  151. -- -- 循环两秒向串口发一次数据
  152. -- while true do
  153. -- sys.wait(2000)
  154. -- uart.write(uartid, "test data.")
  155. -- end
  156. -- end
  157. --初始化
  158. gpio.setup(3,0)
  159. uart.setup(uartid, 115200, 8, 1, uart.NONE, uart.LSB, 1024, uart485Pin, 0, 20000)
  160. -- 收取数据会触发回调, 这里的"receive" 是固定值
  161. uart.on(uartid, "receive", uart_cb)
  162. -- 发送数据完成会触发回调, 这里的"sent" 是固定值
  163. uart.on(uartid, "sent", uart_send_cb)
  164. --sys.taskInit(uart_send)