21 ioqueue io序列操作
作者:孟伟 | 最后修改:2026-04-13
一、ioqueue 概述
ioqueue 是 LuatOS 的高精度硬件定时器队列控制核心库,主要用于实现精确的时序控制和信号捕获。该核心库适用于需要高精度时序控制的场景,如单总线通信、精确脉冲生成等;可以通过硬件定时器精确控制 GPIO 的操作时序。
1.1 ioqueue 有什么用?
ioqueue 核心库为物联网设备提供硬件级的高精度时序控制能力,主要功能包括:
- 高精度时序控制:实现 20us 级别的精确延时和时序控制
- 硬件级信号捕获:精确捕获外部信号的边沿变化和时间戳
- 复杂波形生成:生成各种复杂的脉冲波形和通信时序
- 单总线协议支持:完美支持 DHT11、DS18B20 等单总线设备
- 非阻塞操作:基于硬件定时器,不占用主线程资源
二、演示功能概述
本教程将使用支持 ioqueue 的开发板演示该核心库的核心功能,主要包括:
(1)DHT11 温湿度传感器数据读取
(2)高精度固定间隔脉冲输出
(3)高精度可变间隔脉冲输出
三、准备硬件环境


1、Air8000 核心板一块 + DHT11 传感器一个:
2、TYPE-C USB 数据线一根 ,Air8000 核心板和数据线的硬件接线方式为:
- Air8000 核心板通过 TYPE-C USB 口供电;(外部供电/USB 供电 拨动开关 拨到 USB 供电一端)
- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
- DHT11 接线如下:
| DHT11 | Air8000 |
| VCC | VDD_EXT |
| DATA | GPIO25 |
| GND | GND |
四、软件环境
在开始实践本示例之前,先筹备一下软件环境:
1.烧录工具: Luatools 工具;
2.内核固件:本demo开发测试时使用的固件为Air8000 V2016 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
3.LuatOS 需要的脚本和资源文件
脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/ioqueue
准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000 开发板,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。
五、API 接口说明
详细 API 文档请参考:https://docs.openluat.com/osapi/core/ioqueue/
六、代码示例介绍
下面将演示使用 ioqueue 的几种典型应用场景。
6.1 DHT11 温湿度传感器读取
单总线协议要求严格的时序控制、主机需要先发送特定启动信号、传感器响应时间精确到微秒级、数据通过脉冲宽度编码
使用流程:

dht11 通讯协议介绍:
数据分为小数部分和整数部分,一次完整的数据传输为 40bit,高位先出
数据格式:8bit 湿度整数数据 +8bit 湿度小数数据 +8bit 温度整数数据 +8bit 温度小数数据 +8bit 校验
8bit 校验 = 8bit 湿度整数数据 +8bit 湿度小数数据 +8bit 温度整数数据 +8bit 温度小数数据
校验可以判断数据是否正确发送
DHT11 工作时序:
- 主机发送起始信号以后,DHT11 发送响应信号,然后发送 40bit 的数据,高位在前
总时序图如下:

- 起始信号:总线空闲状态由 DHT11 内置上拉电阻拉高,主机拉低总线至少 18ms 后释放总线 20-40us
- DHT11 响应:存在的 DHT11 会及时响应主机,同时拉低总线 80us 后,释放总线 80us,然后拉低总线,表示开始传送数据

- 发送数据:当总线是低电平时表示开始发送数据,同时存在 50us 低电平时隙,之后拉高总线,高电平的持续时间表示发送 0 或者 1,当高电平持续时间为 26us-28us 表示发送 0,高电平持续时间为 70us 时,表示发送 1
- 数据发送完毕,由上拉电阻拉高,置回空闲高电平状态


-- 测试单总线DHT11
-- 第一步:确保硬件定时器空闲
ioqueue.stop(hw_timer_id)
-- 第二步:初始化io队列
ioqueue.init(hw_timer_id, 100, 1)
-- 第三步:初始状态设置
ioqueue.setgpio(hw_timer_id, capture_pin, true, gpio.PULLUP)
-- 参数详解:
-- 10000: 延时10ms(10000微秒)
-- 0: 时间微调值
-- false: 单次延时(非连续模式)
-- 作用:初始空闲状态时长为10ms,给传感器足够的准备时间
ioqueue.setdelay(hw_timer_id, 10000, 0, false)
-- 第四步:配置DHT11传感器的启动信号,主机主动拉低总线开始通信
ioqueue.setgpio(hw_timer_id, capture_pin, false, 0, 0)
-- 18000: 延时18ms,这是DHT11协议要求的启动信号最小时间
ioqueue.setdelay(hw_timer_id, 18000, 0, false)
-- 第五步:配置捕获参数,为接收传感器数据做准备,此命令仅配置参数,实际捕获需配合后续的capture()命令执行
-- 参数详解:
-- gpio.PULLUP: 设置引脚为上拉输入模式(释放总线控制权)
-- gpio.FALLING: 只捕获下降沿,捕获到后会记录io编号,电平高低,以及时间
-- 100000 * tick_us: 单个capture()命令的最大等待时间100ms,超时后继续执行后续命令,每个capture()都是独立的100ms等待窗口
ioqueue.set_cap(hw_timer_id, capture_pin, gpio.PULLUP, gpio.FALLING, 100000 * tick_us)
--[[关于一个捕获周期含义:
set_cap不等于开始捕获,只是配置参数捕获参数
其中set_cap配置的最大等待时间是防止因传感器故障导致程序永久卡住
开始:当执行 ioqueue.capture() 命令时开始一个捕获周期
结束:满足以下任一条件时结束:
检测到下降沿 → 立即记录时间戳并结束本次捕获周期,然后执行下一条命令
达到100ms超时 → 直接结束,不记录数据,然后执行下一条命令
调用cap_done() → 强制结束命令队列
以上面代码为例:
在100ms内,一次下降沿也没有检测到,则直接结束,执行队列中的下一条命令
在100ms内,检测到一次下降沿,立即记录时间戳并结束,然后执行队列中的下一条命令;
]]
-- 第六步:预分配捕获缓冲区 ,对io操作队列增加42次捕获IO状态命令
for i = 1, 42, 1 do
ioqueue.capture(hw_timer_id)
end
-- 停止捕获,不再监听该引脚的边沿变化
ioqueue.cap_done(hw_timer_id, capture_pin)
-- 恢复数据线为上拉输入状态,释放总线
ioqueue.setgpio(hw_timer_id, capture_pin, true, gpio.PULLUP)
-- 第七步:执行整个命令序列
-- 开始按顺序执行前面设置的所有命令
ioqueue.start(hw_timer_id)
-- 等待执行完成,系统会在完成时发布这个事件
-- 这是异步操作,不会阻塞其他任务
sys.waitUntil("IO_QUEUE_DONE_"..hw_timer_id)
-- 停止硬件定时器
ioqueue.stop(hw_timer_id)
-- 第八步:读取捕获的数据
cnt1, cnt2 = ioqueue.get(hw_timer_id, buff1, buff2)
-- 参数详解:
-- buff1: 存储输入数据的缓冲区
-- buff2: 存储捕获数据的缓冲区
-- cnt1: 读取io数据的数量,此返回值对应ioqueue.input()接口所配置的对读取gpio命令数量,此代码中是nil
-- cnt2: 捕获数据的数量(应该是42)
if cnt2 ~= 42 then
log.info('test fail')
goto TEST_OUT
end
-- 如果捕获数据不是42个,说明通信失败
-- 第九步:解析数据
-- 从捕获缓冲区读取第二个下降沿的时间戳
-- 数据结构:每个捕获点占6字节
-- 字节0: GPIO ID编号
-- 字节1: 电平状态(0=下降沿,1=上升沿)
-- 字节2-5: 32位时间戳(4字节)
-- 所以第二个捕获点在偏移量6处,时间戳在6+2处
lastTick = buff2:query(6 + 2, 4, false)
j = 0
bit = 8
buff1[0] = 0
for i = 2, 41, 1 do -- 遍历40个数据位(跳过第1个下降沿的DHT11响应信号)
-- 验证数据完整性
if buff2[i * 6 + 0] ~= capture_pin or buff2[i * 6 + 1] ~= 0 then
log.error("capture", i, buff2[i * 6 + 0], buff2[i * 6 + 1])
end
-- 计算时间间隔
-- 当前位的时间戳
nowTick = buff2:query(i * 6 + 2, 4, false)
-- 左移1位,为新的数据位腾出空间
buff1[j] = buff1[j] << 1
-- DHT11数据编码原理:
-- 每个数据位都以50us低电平开始
-- 然后高电平持续时间不同:
-- 26-28us → 数据0
-- 70us → 数据1
-- bit1Tick 是100us阈值,总时间 > 100us → 判断为位1,≤ 100us → 判断为位0
if (nowTick - lastTick) > bit1Tick then
buff1[j] = buff1[j] + 1 -- 设置最低位为1
end
bit = bit - 1
if bit == 0 then -- 完成1字节(8位)
j = j + 1 -- 移动到下一字节
bit = 8 -- 重置位计数器
end
lastTick = nowTick -- 更新参考时间戳
end
-- 第十步:数据校验
buff1[5] = buff1[0] + buff1[1] + buff1[2] + buff1[3]
-- DHT11协议:第5字节是前4字节的校验和
if buff1[4] ~= buff1[5] then
log.info('check fail', buff1[4], buff1[5])
else
log.info("湿度", buff1[0] .. '.' .. buff1[1], "温度", buff1[2] .. '.' .. buff1[3])
-- buff1[0]: 湿度整数部分
-- buff1[1]: 湿度小数部分
-- buff1[2]: 温度整数部分
-- buff1[3]: 温度小数部分
-- buff1[4]: 校验和
end
::TEST_OUT::
-- 释放硬件定时器资源,可以重新分配使用
ioqueue.release(hw_timer_id)
6.2 输出精确脉冲场景
需要不同的脉冲宽度、软件循环无法满足精度要求、需要连续输出大量脉冲
使用流程:

下面演示利用硬件定时器生成固定频率或可变宽度的精确脉冲序列,支持连续和单次延时模式,实现微秒级脉冲控制。
6.2.1 高精度固定间隔脉冲输出
输出脉冲信息:
输出固定间隔对称方波
- 低电平持续时间:20 微秒(固定)
- 高电平持续时间:20 微秒(固定)
- 脉冲周期:40 微秒(完整周期)
- 占空比 50%
- 脉冲数量:41 个完整周期(通过循环 40 次生成)
- 使用 ioqueue.setdelay 的连续模式,所有延时间隔自动保持 20us
-- 第一步:初始化
-- 100个命令,循环1次(生成100个脉冲)
ioqueue.init(hw_timer_id, 100, 1)
-- 设置GPIO为输出模式,初始输出高电平
ioqueue.setgpio(hw_timer_id, out_pin, false, 0, 1)
-- 第二步:配置连续延时模式
ioqueue.setdelay(hw_timer_id, 20, tick_us - 3, true)
-- 第三步:生成脉冲序列
-- 每个循环生成1个完整周期:低电平20us + 高电平20us = 40us周期
for i = 0, 40, 1 do
ioqueue.output(hw_timer_id, out_pin, 0) -- 输出低电平
ioqueue.delay(hw_timer_id) -- 延时20us
ioqueue.output(hw_timer_id, out_pin, 1) -- 输出高电平
ioqueue.delay(hw_timer_id) -- 延时20us
end
-- 第四步:执行
ioqueue.start(hw_timer_id)
-- 等待执行完成,系统会在完成时发布这个事件
sys.waitUntil("IO_QUEUE_DONE_"..hw_timer_id)
-- 停止硬件定时器
ioqueue.stop(hw_timer_id)
-- 释放硬件定时器资源,可以重新分配使用
ioqueue.release(hw_timer_id)
6.2.2 高精度可变间隔脉冲输出
输出脉冲信息:
输出可变间隔非对称脉冲
- 10 次完整序列
- 输出波形:低电平 20us → 高电平 30us → 低电平 40us → 高电平 50us→ 低电平 60us → 高电平 70us
- 使用 ioqueue.setdelay 单次模式,每个延时独立配置
log.info('output 2 start')
--测试高精度可变间隔定时输出
ioqueue.init(hw_timer_id, 100, 10)
--设置成输出口,电平1
ioqueue.setgpio(hw_timer_id, out_pin, false, 0, 1)
--单次延迟20us,如果不准,对time_tick微调
ioqueue.setdelay(hw_timer_id, 20, tick_us - 3)
--低电平
ioqueue.output(hw_timer_id, out_pin, 0)
--单次延迟30us
ioqueue.setdelay(hw_timer_id, 30, tick_us - 3)
--高电平
ioqueue.output(hw_timer_id, out_pin, 1)
--单次延迟40us
ioqueue.setdelay(hw_timer_id, 40, tick_us - 3)
--低电平
ioqueue.output(hw_timer_id, out_pin, 0)
--单次延迟50us
ioqueue.setdelay(hw_timer_id, 50, tick_us - 3)
--高电平
ioqueue.output(hw_timer_id, out_pin, 1)
--单次延迟60us
ioqueue.setdelay(hw_timer_id, 60, tick_us - 3)
--低电平
ioqueue.output(hw_timer_id, out_pin, 0)
--单次延迟70us
ioqueue.setdelay(hw_timer_id, 70, tick_us - 3)
--高电平
ioqueue.output(hw_timer_id, out_pin, 1)
ioqueue.start(hw_timer_id)
sys.waitUntil("IO_QUEUE_DONE_" .. hw_timer_id)
log.info('output 2 done')
ioqueue.stop(hw_timer_id)
ioqueue.release(hw_timer_id)
sys.wait(500)
6.3 功能验证
6.3.1 Luatools 日志输出
ioqueue 操作执行状态日志

6.3.2 DHT11 数据读取验证
DHT11 温湿度数据读取波形




6.3.3 脉冲输出波形验证
使用逻辑分析仪观察生成的精确脉冲波形:
固定间隔脉冲输出:

可变间隔脉冲输出:

七、总结
至此,本示例详细介绍了如何使用 ioqueue 核心库实现高精度时序控制和信号捕获。ioqueue 提供了硬件级的高精度控制能力,特别适合需要精确时序的应用场景。