onewire_multi_app.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. --[[
  2. @module onewire_multi_app
  3. @summary OneWire多DS18B20温度传感器应用演示模块(54和23切换版本)
  4. @version 1.0.0
  5. @date 2025.11.25
  6. @author 王棚嶙
  7. @usage
  8. 本模块演示多DS18B20温度传感器的完整功能:
  9. 1. 双传感器切换控制(引脚54和23)
  10. 2. 电源管理(GPIO控制)
  11. 3. 按键切换传感器
  12. 4. 双路温度同时监测
  13. 5. 使用引脚复用功能(pins.setup)
  14. ]]
  15. log.info("onewire_multi_app", "多传感器模块版本: 1.0.0")
  16. -- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
  17. pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
  18. -- 和GPIO31控制传感器电源使能,确保DS18B20供电正常
  19. gpio.setup(31, 1)
  20. -- 硬件配置(双设备模式:支持引脚54和23切换)
  21. local onewire_pin = 54
  22. local switchover_pin = gpio.PWR_KEY
  23. -- DS18B20命令定义
  24. local CMD_CONVERT_T = 0x44
  25. local CMD_READ_SCRATCHPAD = 0xBE
  26. local CMD_READ_ROM = 0x33
  27. -- 全局状态变量
  28. local pwr_key_pressed = false
  29. -- PWR_KEY按键中断处理函数
  30. -- 功能:处理引脚切换按键事件,设置标志位供主循环查询
  31. local function handle_pwr_key_interrupt()
  32. pwr_key_pressed = true
  33. log.info("onewire_multi_app", "切换按键被按下")
  34. end
  35. -- 初始化硬件配置
  36. local function init_hardware()
  37. log.info("onewire_multi_app", "初始化硬件配置...")
  38. -- 配置PWR_KEY按键,使用上升沿触发并添加防抖
  39. gpio.debounce(switchover_pin, 100)
  40. gpio.setup(switchover_pin, handle_pwr_key_interrupt, gpio.PULLUP, gpio.RISING)
  41. -- 初始配置当前引脚为ONEWIRE功能
  42. pins.setup(onewire_pin, "ONEWIRE")
  43. log.info("onewire_multi_app", "硬件初始化完成")
  44. log.info("onewire_multi_app", "初始引脚: 引脚" .. onewire_pin .. " (ONEWIRE功能)")
  45. log.info("onewire_multi_app", "切换按键: PWR_KEY")
  46. log.info("onewire_multi_app", "支持引脚: 54 和 23 循环切换")
  47. log.info("onewire_multi_app", "电源控制: GPIO31/GPIO2 (已设置为高电平)")
  48. return true
  49. end
  50. -- 时序要求:DS18B20上电后需要稳定时间,100ms延时确保电源稳定
  51. -- 技术背景:DS18B20在电源切换后需要tREC(恢复时间)完成内部初始化
  52. -- 实际测试:无延时可能导致设备检测失败或温度读取异常
  53. -- 建议值:最小50ms,推荐100ms以确保可靠性
  54. local function power_stabilization_delay()
  55. log.info("onewire_multi_app", "电源稳定延时(确保DS18B20内部电路就绪)")
  56. sys.wait(100) -- DS18B20 tREC恢复时间,最小50ms,推荐100ms
  57. end
  58. -- 单总线分时使用引脚切换(同一条总线,分时复用)
  59. -- 核心逻辑:使用GPIO54和GPIO23两个引脚连接同一条OneWire总线,实现分时复用
  60. -- 应用场景:当需要在同一总线上分时访问不同设备时使用
  61. -- 技术原理:通过切换总线连接引脚,实现同一条物理总线的分时使用
  62. -- 切换效果:
  63. -- - GPIO54:当前时间段连接设备A(ROM ID: 28-9F-C4-93-00-00-00-14)
  64. -- - GPIO23:切换到时间段连接设备B(ROM ID: 28-59-F2-53-00-00-00-14)
  65. -- 注意:这不是多总线并行,而是单总线的分时复用策略
  66. local function switch_onewire_pin()
  67. log.info("onewire_multi_app", "切换OneWire引脚...")
  68. -- 关闭当前OneWire总线
  69. onewire.deinit(0)
  70. -- 分时复用切换逻辑
  71. -- 技术原理:将当前不使用的引脚配置为GPIO功能并输出高电平
  72. -- 目的:确保非活动设备处于高阻态,避免干扰当前连接的设备
  73. -- 电气特性:GPIO设置为开漏输出模式,高电平由上拉电阻提供
  74. if onewire_pin == 54 then
  75. -- 从54切换到23
  76. -- 将PIN54配置为GPIO3功能,不再作为OneWire使用
  77. log.info("onewire_multi_app", "将PIN54配置为GPIO3", pins.setup(54, "GPIO3"))
  78. -- 设置GPIO3为高电平输出(开漏模式,高电平由上拉电阻提供)
  79. log.info("onewire_multi_app", "将GPIO3设置为高电平输出(OneWire总线空闲状态)", gpio.setup(3, 1))
  80. onewire_pin = 23
  81. log.info("onewire_multi_app", "切换到引脚23")
  82. else
  83. -- 从23切换到54
  84. -- 将PIN23配置为GPIO2功能,不再作为OneWire使用
  85. log.info("onewire_multi_app", "将PIN23配置为GPIO2", pins.setup(23, "GPIO2"))
  86. -- 设置GPIO2为高电平输出(开漏模式,高电平由上拉电阻提供)
  87. log.info("onewire_multi_app", "将GPIO2设置为高电平输出(OneWire总线空闲状态)", gpio.setup(2, 1))
  88. onewire_pin = 54
  89. log.info("onewire_multi_app", "切换到引脚54")
  90. end
  91. log.info("onewire_multi_app", "当前使用引脚:", onewire_pin)
  92. -- 配置新引脚为ONEWIRE功能
  93. -- 分时复用原理:将选中的引脚配置为OneWire功能,连接到对应设备
  94. -- 连接过程:先断开之前的设备连接,再连接新的设备
  95. -- 电气特性:确保当前连接的设备具有完整的OneWire通信能力
  96. log.info("onewire_multi_app", "将引脚" .. onewire_pin .. "配置为ONEWIRE功能", pins.setup(onewire_pin, "ONEWIRE"))
  97. log.info("onewire_multi_app", "引脚切换完成,当前使用: 引脚" .. onewire_pin)
  98. end
  99. -- 初始化OneWire总线
  100. local function init_onewire_bus()
  101. log.info("onewire_multi_app", "初始化OneWire总线,通道: 0")
  102. -- 配置当前引脚
  103. pins.setup(onewire_pin, "ONEWIRE")
  104. -- 初始化OneWire总线
  105. onewire.init(0)
  106. -- 配置DS18B20标准时序参数
  107. onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
  108. log.info("onewire_multi_app", "OneWire总线初始化完成,通道: 0,引脚:" .. onewire_pin)
  109. return true
  110. end
  111. -- 检测DS18B20设备是否存在(分时复用场景)
  112. -- 分时逻辑:在当前连接的引脚上发送复位脉冲,检测该设备响应
  113. -- 单总线场景:只有当前连接的引脚上的设备会响应复位脉冲
  114. -- 返回值:true表示当前引脚连接的设备响应,false表示无设备响应
  115. local function detect_ds18b20_device()
  116. log.info("onewire_multi_app", "检测DS18B20设备,引脚: " .. onewire_pin)
  117. -- 发送复位脉冲并检测设备
  118. local present = onewire.reset(0, true)
  119. if present then
  120. log.info("onewire_multi_app", "检测到DS18B20设备响应")
  121. return true
  122. else
  123. log.warn("onewire_multi_app", "未检测到DS18B20设备响应")
  124. return false
  125. end
  126. end
  127. -- 读取DS18B20温度(单总线分时复用)
  128. -- 核心流程:读ROM ID → 选设备 → 温度转换 → 读数据 → CRC校验
  129. local function read_ds18b20_temperature()
  130. log.info("onewire_multi_app", "开始读取DS18B20温度,引脚: " .. onewire_pin)
  131. local tbuff = zbuff.create(10)
  132. local succ, crc8c, range, t
  133. local rbuff = zbuff.create(9)
  134. -- 读取设备ROM ID(每个设备唯一)
  135. log.info("onewire_multi_app", "读取设备ROM ID(64位唯一标识)")
  136. local id = zbuff.create(8)
  137. id:set()
  138. succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
  139. if not succ then
  140. log.warn("onewire_multi_app", "读取ROM ID失败")
  141. return nil
  142. end
  143. -- 检查设备类型码(DS18B20应为0x28)
  144. if id[0] ~= 0x28 then
  145. log.warn("onewire_multi_app", "非DS18B20设备,类型码:", mcu.x32(id[0]))
  146. return nil
  147. end
  148. -- CRC校验设备ID
  149. crc8c = crypto.crc8(id:query(0, 7), 0x31, 0, true)
  150. if crc8c ~= id[7] then
  151. log.warn("onewire_multi_app", "ROM ID CRC校验不对",
  152. "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(id[7]))
  153. log.info("onewire_multi_app", "完整ROM ID:", id:query(0, 7):toHex())
  154. return nil
  155. end
  156. log.info("onewire_multi_app", "ROM ID校验成功:", id:query(0, 7):toHex())
  157. -- 通过MATCH ROM选择设备(确保只选中目标设备)
  158. log.info("onewire_multi_app", "开始温度转换(通过ROM匹配选择设备)")
  159. -- 构建命令缓冲区:MATCH ROM(0x55) + 目标设备ROM ID + 温度转换命令(0x44)
  160. -- 0x55是MATCH ROM命令,后面必须跟64位目标设备的ROM ID
  161. tbuff:write(0x55) -- MATCH ROM命令
  162. tbuff:copy(nil, id) -- 复制64位ROM ID(确保选择正确的设备)
  163. tbuff:write(0xb8)
  164. tbuff[tbuff:used() - 1] = 0x44 -- CONVERT T温度转换命令
  165. succ = onewire.tx(0, tbuff, false, true, true)
  166. if not succ then
  167. log.warn("onewire_multi_app", "发送温度转换命令失败")
  168. return nil
  169. end
  170. -- 第三步:等待转换完成
  171. log.info("onewire_multi_app", "等待温度转换完成")
  172. -- 等待一段时间让转换完成
  173. sys.wait(750)
  174. -- 发送复位脉冲检查设备
  175. succ = onewire.reset(0, true)
  176. if not succ then
  177. log.warn("onewire_multi_app", "等待转换完成时设备未响应")
  178. return nil
  179. end
  180. -- 检查转换是否完成
  181. if onewire.bit(0) > 0 then
  182. log.info("onewire_multi_app", "温度转换完成")
  183. end
  184. -- 第四步:读取温度数据
  185. log.info("onewire_multi_app", "读取温度数据")
  186. -- 构建读取命令:匹配ROM(0x55) + ROM ID + 读取暂存器命令(0xBE)
  187. tbuff[tbuff:used() - 1] = 0xbe
  188. succ = onewire.tx(0, tbuff, false, true, true)
  189. if not succ then
  190. log.warn("onewire_multi_app", "发送读取命令失败")
  191. return nil
  192. end
  193. -- 接收9字节温度数据
  194. succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
  195. if not succ then
  196. log.warn("onewire_multi_app", "温度数据接收失败")
  197. return nil
  198. end
  199. -- 第五步:CRC校验和温度计算
  200. log.info("onewire_multi_app", "CRC校验和温度计算")
  201. -- CRC校验
  202. crc8c = crypto.crc8(rbuff:toStr(0, 8), 0x31, 0, true)
  203. if crc8c == rbuff[8] then
  204. -- 计算温度值
  205. range = (rbuff[4] >> 5) & 0x03
  206. t = rbuff:query(0, 2, false, true)
  207. t = t * (5000 >> range)
  208. t = t / 10000
  209. -- 范围检查
  210. if t >= -55.0 and t <= 125.0 then
  211. log.info("onewire_multi_app", "温度读取成功:", string.format("%.2f°C", t))
  212. return t
  213. else
  214. log.warn("onewire_multi_app", "温度值超出有效范围:", t)
  215. return nil
  216. end
  217. else
  218. log.warn("onewire_multi_app", "温度数据CRC校验不对",
  219. "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(rbuff[8]))
  220. return nil
  221. end
  222. end
  223. -- 简化版温度读取(用于快速测试)
  224. local function quick_read_ds18b20()
  225. log.info("onewire_multi_app", "快速读取温度,引脚: " .. onewire_pin)
  226. -- 首先检测设备是否存在
  227. if not detect_ds18b20_device() then
  228. return nil
  229. end
  230. -- 使用完整读取函数
  231. return read_ds18b20_temperature()
  232. end
  233. -- 单总线分时复用主函数(同一条总线,分时访问不同设备)
  234. local function multi_sensor_app_main()
  235. log.info("onewire_multi_app", "启动双传感器应用(引脚54和23)")
  236. -- 初始化硬件
  237. if not init_hardware() then
  238. log.error("onewire_multi_app", "硬件初始化失败,任务无法启动")
  239. return
  240. end
  241. -- 初始化OneWire总线
  242. init_onewire_bus()
  243. -- 电源稳定延时:确保DS18B20内部电路就绪
  244. power_stabilization_delay()
  245. -- 检测设备
  246. local device_present = detect_ds18b20_device()
  247. if not device_present then
  248. log.error("onewire_multi_app", "未检测到设备响应")
  249. log.warn("onewire_multi_app", "硬件连接提示:")
  250. log.warn("onewire_multi_app", "1. 传感器连接引脚54或23")
  251. log.warn("onewire_multi_app", "2. 确保GPIO31/GPIO2已设置为高电平供电")
  252. log.warn("onewire_multi_app", "3. 确保4.7kΩ上拉电阻正确安装")
  253. log.warn("onewire_multi_app", "4. 检查传感器VDD、GND、DQ连接")
  254. -- 关闭OneWire总线
  255. onewire.deinit(0)
  256. return
  257. end
  258. log.info("onewire_multi_app", "开始双传感器连续监测...")
  259. log.info("onewire_multi_app", "按PWR_KEY按键可切换引脚(54和23)")
  260. -- 主循环:按键切换设备,分时读取温度
  261. local read_count = 0
  262. local success_count = 0
  263. while true do
  264. read_count = read_count + 1
  265. -- 检查按键状态
  266. if pwr_key_pressed then
  267. pwr_key_pressed = false
  268. switch_onewire_pin()
  269. -- 重新初始化OneWire总线
  270. init_onewire_bus()
  271. end
  272. log.info("onewire_multi_app", "第" .. read_count .. "次读取,引脚:" .. onewire_pin)
  273. -- 尝试读取温度
  274. local temperature = read_ds18b20_temperature()
  275. if temperature then
  276. success_count = success_count + 1
  277. log.info("onewire_multi_app", "引脚" .. onewire_pin .. "温度:",
  278. string.format("%.2f°C", temperature),
  279. "成功率:", string.format("%.1f%%", success_count/read_count*100))
  280. -- 简单的温度报警逻辑
  281. if temperature > 30 then
  282. log.warn("onewire_multi_app", "温度偏高:", string.format("%.2f°C", temperature))
  283. elseif temperature < 10 then
  284. log.warn("onewire_multi_app", "温度偏低:", string.format("%.2f°C", temperature))
  285. end
  286. else
  287. log.warn("onewire_multi_app", "本次读取失败")
  288. log.info("onewire_multi_app", "成功率:", string.format("%.1f%%", success_count/read_count*100))
  289. end
  290. -- 等待下一次读取
  291. sys.wait(2000)
  292. end
  293. end
  294. log.info("onewire_multi_app", "双传感器应用模块加载完成(54和23切换)")
  295. -- 启动多传感器应用任务
  296. sys.taskInit(multi_sensor_app_main)