dht11_capture.lua 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. --[[
  2. @module test_ioqueue
  3. @summary IO队列功能测试
  4. @version 1.0
  5. @date 2025.10.18
  6. @author 孟伟
  7. @usage
  8. 本功能模块演示的内容为:
  9. DHT11 温湿度传感器数据读取
  10. 本文件没有对外接口,直接在main.lua中require "dht11_capture"就可以加载运行;
  11. ]]
  12. -- 定义硬件定时器ID和捕获引脚号,这里使用硬件定时器0,捕获引脚25
  13. local hw_timer_id, capture_pin = 0, 25
  14. -- 测试单总线DHT11
  15. function dht11_capture()
  16. local _, tick_us = mcu.tick64()
  17. --确保为GPIO功能
  18. gpio.setup(capture_pin, nil, nil)
  19. local buff1 = zbuff.create(100)
  20. local buff2 = zbuff.create(100)
  21. local cnt1, cnt2, i, lastTick, bit1Tick, nowTick, j, bit
  22. bit1Tick = 100 * tick_us
  23. -- 第一步:确保硬件定时器空闲
  24. ioqueue.stop(hw_timer_id)
  25. -- 第二步:初始化io队列
  26. ioqueue.init(hw_timer_id, 100, 1)
  27. -- 第三步:初始状态设置
  28. ioqueue.setgpio(hw_timer_id, capture_pin, true, gpio.PULLUP)
  29. -- 参数详解:
  30. -- 10000: 延时10ms(10000微秒)
  31. -- 0: 时间微调值
  32. -- false: 单次延时(非连续模式)
  33. -- 作用:初始空闲状态时长为10ms,给传感器足够的准备时间
  34. ioqueue.setdelay(hw_timer_id, 10000, 0, false)
  35. -- 第四步:配置DHT11传感器的启动信号,主机主动拉低总线开始通信
  36. ioqueue.setgpio(hw_timer_id, capture_pin, false, 0, 0)
  37. -- 18000: 延时18ms,这是DHT11协议要求的启动信号最小时间
  38. ioqueue.setdelay(hw_timer_id, 18000, 0, false)
  39. -- 第五步:配置捕获参数,为接收传感器数据做准备,此命令仅配置参数,实际捕获需配合后续的capture()命令执行
  40. -- 参数详解:
  41. -- gpio.PULLUP: 设置引脚为上拉输入模式(释放总线控制权)
  42. -- gpio.FALLING: 只捕获下降沿,捕获到后会记录io编号,电平高低,以及时间
  43. -- 100000 * tick_us: 单个capture()命令的最大等待时间100ms,超时后继续执行后续命令,每个capture()都是独立的100ms等待窗口
  44. ioqueue.set_cap(hw_timer_id, capture_pin, gpio.PULLUP, gpio.FALLING, 100000 * tick_us)
  45. --[[关于一个捕获周期含义:
  46. set_cap不等于开始捕获,只是配置参数捕获参数
  47. 其中set_cap配置的最大等待时间是防止因传感器故障导致程序永久卡住
  48. 开始:当执行 ioqueue.capture() 命令时开始一个捕获周期
  49. 结束:满足以下任一条件时结束:
  50. 检测到下降沿 → 立即记录时间戳并结束本次捕获周期,然后执行下一条命令
  51. 达到100ms超时 → 直接结束,不记录数据,然后执行下一条命令
  52. 调用cap_done() → 强制结束命令队列
  53. 以上面代码为例:
  54. 在100ms内,一次下降沿也没有检测到,则直接结束,执行队列中的下一条命令
  55. 在100ms内,检测到一次下降沿,立即记录时间戳并结束,然后执行队列中的下一条命令;
  56. ]]
  57. -- 第六步:预分配捕获缓冲区 ,对io操作队列增加42次捕获IO状态命令
  58. for i = 1, 42, 1 do
  59. ioqueue.capture(hw_timer_id)
  60. end
  61. -- 停止捕获,不再监听该引脚的边沿变化
  62. ioqueue.cap_done(hw_timer_id, capture_pin)
  63. -- 恢复数据线为上拉输入状态,释放总线
  64. ioqueue.setgpio(hw_timer_id, capture_pin, true, gpio.PULLUP)
  65. -- 第七步:执行整个命令序列
  66. -- 开始按顺序执行前面设置的所有命令
  67. ioqueue.start(hw_timer_id)
  68. -- 等待执行完成,系统会在完成时发布这个事件
  69. -- 这是异步操作,不会阻塞其他任务
  70. sys.waitUntil("IO_QUEUE_DONE_" .. hw_timer_id)
  71. -- 停止硬件定时器
  72. ioqueue.stop(hw_timer_id)
  73. -- 第八步:读取捕获的数据
  74. cnt1, cnt2 = ioqueue.get(hw_timer_id, buff1, buff2)
  75. -- 参数详解:
  76. -- buff1: 存储输入数据的缓冲区
  77. -- buff2: 存储捕获数据的缓冲区
  78. -- cnt1: 读取io数据的数量,此返回值对应ioqueue.input()接口所配置的对读取gpio命令数量,此代码中是nil
  79. -- cnt2: 捕获数据的数量(应该是42)
  80. if cnt2 ~= 42 then
  81. log.info('test fail')
  82. goto TEST_OUT
  83. end
  84. -- 如果捕获数据不是42个,说明通信失败
  85. -- 第九步:解析数据
  86. -- 从捕获缓冲区读取第二个下降沿的时间戳
  87. -- 数据结构:每个捕获点占6字节
  88. -- 字节0: GPIO ID编号
  89. -- 字节1: 电平状态(0=下降沿,1=上升沿)
  90. -- 字节2-5: 32位时间戳(4字节)
  91. -- 所以第二个捕获点在偏移量6处,时间戳在6+2处
  92. lastTick = buff2:query(6 + 2, 4, false)
  93. j = 0
  94. bit = 8
  95. buff1[0] = 0
  96. for i = 2, 41, 1 do -- 遍历40个数据位(跳过第1个下降沿的DHT11响应信号)
  97. -- 验证数据完整性
  98. if buff2[i * 6 + 0] ~= capture_pin or buff2[i * 6 + 1] ~= 0 then
  99. log.error("capture", i, buff2[i * 6 + 0], buff2[i * 6 + 1])
  100. end
  101. -- 计算时间间隔
  102. -- 当前位的时间戳
  103. nowTick = buff2:query(i * 6 + 2, 4, false)
  104. -- 左移1位,为新的数据位腾出空间
  105. buff1[j] = buff1[j] << 1
  106. -- DHT11数据编码原理:
  107. -- 每个数据位都以50us低电平开始
  108. -- 然后高电平持续时间不同:
  109. -- 26-28us → 数据0
  110. -- 70us → 数据1
  111. -- bit1Tick 是100us阈值,总时间 > 100us → 判断为位1,≤ 100us → 判断为位0
  112. if (nowTick - lastTick) > bit1Tick then
  113. buff1[j] = buff1[j] + 1 -- 设置最低位为1
  114. end
  115. bit = bit - 1
  116. if bit == 0 then -- 完成1字节(8位)
  117. j = j + 1 -- 移动到下一字节
  118. bit = 8 -- 重置位计数器
  119. end
  120. lastTick = nowTick -- 更新参考时间戳
  121. end
  122. -- 第十步:数据校验
  123. buff1[5] = buff1[0] + buff1[1] + buff1[2] + buff1[3]
  124. -- DHT11协议:第5字节是前4字节的校验和
  125. if buff1[4] ~= buff1[5] then
  126. log.info('check fail', buff1[4], buff1[5])
  127. else
  128. log.info("湿度", buff1[0] .. '.' .. buff1[1], "温度", buff1[2] .. '.' .. buff1[3])
  129. -- buff1[0]: 湿度整数部分
  130. -- buff1[1]: 湿度小数部分
  131. -- buff1[2]: 温度整数部分
  132. -- buff1[3]: 温度小数部分
  133. -- buff1[4]: 校验和
  134. end
  135. ::TEST_OUT::
  136. -- 释放硬件定时器资源,可以重新分配使用
  137. ioqueue.release(hw_timer_id)
  138. end
  139. sys.taskInit(dht11_capture)