69 sensor-传感器操作库
一、概述
sensor 是 LuatOS 的传感器操作库,全部用 GPIO 软件时序实现,不占硬件外设:单总线(1-Wire)的 DS18B20、DHT11/DHT12 温湿度、HX711 与 CS1237 两线同步 ADC、SC12A 电容触摸按键,以及 GPIO/PWM/SPI 三种方式点 WS2812B 灯带。
sensor 库主要功能:
1、单总线底层:w1_reset、w1_connect、w1_write、w1_read 四个接口,可以自己拼任意 1-Wire 器件的时序;ds18b20 是封装好的单总线测温;
2、温湿度:dht1x 读 DHT11/DHT12 的湿度、温度,并做累加校验;
3、两线同步 ADC:hx711(固定增益 128)与 cs1237,读 24 位原始码;
4、触摸按键:sc12a 读 12 通道电容触摸芯片的按键状态;
5、灯带:ws2812b(GPIO 翻转)、ws2812b_pwm(PWM)、ws2812b_spi(SPI)三种驱动方式。
注意事项:
1、本库编译开关是 LUAT_USE_SENSOR;ws2812b_pwm 额外要求固件带 LUAT_USE_PWM,ws2812b_spi 额外要求带 LUAT_USE_SPI,缺少对应开关时这些函数在 Lua 里是 nil,直接调用会报错;
2、所有接口都是「软件时序 + 关中断」,一次读取会阻塞几毫秒到几十毫秒,不要放在中断回调、ioqueue 回调里调用,也不要和需要精确定时的任务并行跑;
3、软件时序对主频敏感:ws2812b 的 4 个时间参数、sc12a 的 5us 级延时都要按实际型号验证,参数不对的表现是灯带偏色、触摸读不到。
二、核心示例
2.1 单总线读 DS18B20 温度
-- DS18B20:VDD 接 3.3V,GND 接 GND,DQ 接 GPIO,DQ 与 VDD 之间加 4.7k 上拉
sys.taskInit(function()
sys.wait(1000) -- 上电后等一下,避开 85℃ 的默认读数
while true do
local temp, ok = sensor.ds18b20(17, true) -- GPIO17,开启 CRC 校验
if ok then
log.info("ds18b20", temp / 10 .. "℃") -- 301 -> 30.1℃
else
log.info("ds18b20", "read fail", temp) -- 失败时第一个返回值是错误码
end
sys.wait(1000) -- 源码不等转换完成,周期不要低于 1s
end
end)
2.2 读 DHT11 温湿度
sys.taskInit(function()
while true do
local humi, temp, ok = sensor.dht1x(17, true) -- 返回顺序是湿度在前、温度在后
if ok then
log.info("dht11", humi / 100 .. "%", temp / 100 .. "℃")
else
log.info("dht11", "check fail")
end
sys.wait(2000) -- DHT11 本身要求 ≥1s 一次
end
end)
2.3 点 WS2812B 灯带
-- zbuff 里是 RGB 顺序,WS2812B 要 GRB,这里逐像素换位
local cnt = 8
local buff = zbuff.create(cnt * 3)
for i = 0, cnt - 1 do
buff[i * 3 + 0] = 0x00 -- G
buff[i * 3 + 1] = 0xFF -- R
buff[i * 3 + 2] = 0x00 -- B
end
-- 4 个时间参数是空转循环次数,不是微秒,换型号必须重调
sensor.ws2812b(7, buff, 300, 700, 700, 700)
灯珠较多、又要同时跑网络时,可以改用 ws2812b_spi(5MHz 硬件 SPI 展开)或 ws2812b_pwm,这两种不长时间关中断。
2.4 读 HX711 称重
sys.taskInit(function()
sys.wait(1000)
local zero = sensor.hx711(0, 7) -- 先读一次空秤作零点
while true do
sys.wait(2000)
local val = sensor.hx711(0, 7) - zero
log.info("hx711", val) -- 原始码/100 之后的差值,单位靠自己标定
end
end)
三、函数详解
3.1 sensor.ds18b20(pin, check_crc)
功能
读 DS18B20 温度。内部时序为:复位 → 0xCC(跳过 ROM)→ 0x44(启动转换)→ 再复位 → 0xCC → 0xBE(读暂存器),可选对 9 个字节做 CRC8/MAXIM 校验。
参数
pin
参数含义:DS18B20 数据线(DQ)所接的 GPIO 编号
数据类型:number
取值范围:该型号可用的 GPIO 编号
是否必选:是
注意事项:是 LuatOS 的 GPIO 编号,不是模组物理管脚号;DQ 必须外接 4.7k 上拉到 3.3V。
check_crc
参数含义:是否校验读回的 9 字节 CRC
数据类型:boolean
取值范围:true / false
是否必选:否,不传时按 true 处理
注意事项:关掉校验能提高成功率,但可能读到错值;线长、干扰大时建议保持开启。
返回值
int:温度值,单位 0.1℃,例如 301 表示 30.1℃;读取失败时返回错误码。
boolean:成功返回 true,失败返回 false。
失败时的错误码(第一个返回值):
| 错误码 | 含义 |
|---|---|
| -1 | 第一次复位后没有器件应答(接线、上拉、供电问题) |
| -2 | 第二次复位后没有器件应答(读到一半掉线) |
| -4 | 9 字节 CRC 校验不通过(干扰或线太长) |
| 0 | 时序正常但换算结果超出 -55~125℃,判为无效 |
示例
local temp, ok = sensor.ds18b20(17, true)
if ok then
log.info("temp", temp / 10)
else
log.info("temp fail", temp) -- temp 此时是 -1/-2/-4/0
end
注意事项
1、源码发出 0x44 后没有等转换完成(12 位精度需要约 750ms)就复位读暂存器,读到的其实是上一次转换的结果。想要「读一次就是最新值」,请把调用周期放到 1s 以上,或者自己在两次调用之间留 750ms;
2、DS18B20 上电默认温度寄存器是 85℃(0x0550),刚上电就读会读到 85.0℃,属正常现象,sys.wait(1000) 之后再读即可;
3、整个读取在临界区内完成(内部 luat_os_entry_cri),耗时数毫秒,会推迟同优先级协程的时序;
4、判断成败先看 boolean,不要只看数值:超范围时第一个返回值是 0,容易被误当成 0℃。
3.2 sensor.w1_reset(pin)
功能
单总线复位:把总线拉低约 550us 再释放,并保持约 40us,让总线上的从机准备接收命令。
参数
pin:GPIO 编号,number,必选。数据线需 4.7k 上拉到 3.3V。
返回值
无返回值(nil)。
示例
sensor.w1_reset(17)
if sensor.w1_connect(17) then
log.info("w1", "device online")
end
注意事项
1、本接口只负责发出复位脉冲,不判断器件是否应答;判断在位要用 w1_connect;
2、复位之后要紧接着 w1_connect,中间插入别的耗时操作会错过应答窗口。
3.3 sensor.w1_connect(pin)
功能
检测单总线上有没有器件应答:把总线切成输入,等它被从机拉低(最多 200us),再等它释放(最多 240us)。
参数
pin:GPIO 编号,number,必选。
返回值
boolean:有器件应答返回 true,超时返回 false。
示例
sensor.w1_reset(17)
local ok = sensor.w1_connect(17)
log.info("w1", "presence", ok)
注意事项
1、必须在 w1_reset 之后立刻调用;单独调用没有意义;
2、返回 false 的常见原因:GPIO 配错、没上拉、器件没供电(寄生供电时更容易出问题)、线太长。
3.4 sensor.w1_write(pin, data1, data2, ...)
功能
向单总线写字节。每字节按低位先发(LSB first),写 1 是「拉低 2us + 释放 60us」,写 0 是「拉低 60us + 释放 2us」。
参数
pin:GPIO 编号,number,必选。
data1, data2, ...:要写入的字节,number,至少给一个,每个取值 0~255;可以一次给多个,按参数顺序依次发出。
返回值
无返回值(nil)。
示例
-- DS18B20:跳过 ROM + 启动转换
sensor.w1_reset(17)
sensor.w1_connect(17)
sensor.w1_write(17, 0xCC, 0x44)
注意事项
1、写之前必须先 w1_reset + w1_connect,否则从机不会接收;
2、超过 255 的值会被截断成低 8 位,不要传负数。
3.5 sensor.w1_read(pin, len)
功能
从单总线连续读 len 个字节,每个字节高位先收(内部逐位移位组装)。
参数
pin:GPIO 编号,number,必选。
len:要读的字节数,number,必选,建议 1~16;每字节耗时约 60us 量级。
返回值
按读取长度返回 len 个 int(每个 0~255),按参数顺序压栈。
示例
sensor.w1_reset(17)
sensor.w1_connect(17)
sensor.w1_write(17, 0xCC, 0xBE) -- 跳过 ROM + 读暂存器
local b0, b1 = sensor.w1_read(17, 2) -- 低字节、高字节
log.info("raw", b0, b1)
注意事项
1、读之前要先发对应的读命令,本接口只负责收字节,不做协议解析;
2、len 传太大会读到全 0xFF(总线空闲电平),不是错误但无意义。
3.6 sensor.dht1x(pin, check_crc)
功能
读 DHT11/DHT12 类单总线温湿度传感器。内部先拉高 100ms、再拉低 20ms 发开始信号,然后连读 5 个字节并换算。
参数
pin:GPIO 编号,number,必选。数据线需 4.7k~10k 上拉。
check_crc:boolean,是否做「前 4 字节之和 == 第 5 字节」的累加校验。不传时等于 false(源码用 lua_toboolean 直接取第二个参数,注释写的「默认 true」与实现不一致),要做校验请显式传 true。
返回值
int:湿度,单位 0.01%RH(如 9010 表示 90.10%);
int:温度,单位 0.01℃(如 2322 表示 23.22℃);
boolean:校验通过返回 true;开启校验但校验失败、或传感器没有应答时返回 false(此时前两个返回值都是 0)。
示例
-- 注意返回值顺序:湿度在前,温度在后
local humi, temp, ok = sensor.dht1x(17, true)
log.info("dht11", humi / 100, temp / 100, ok)
注意事项
1、0℃ 以下读数不可靠:源码把符号位判在第一组字节(湿度)上,而 DHT11/DHT12 的温度符号位在第二组字节的最高位,源码没有处理。低温场景请改用 ds18b20;
2、DHT11 的小数位恒为 0,所以读出来都是 100 的整数倍;DHT12/DHT22 才有小数;
3、每次调用固定先等 100ms + 20ms,最快也就 8Hz 左右;DHT11 本身要求 ≥1s 读一次,读太快会拿到旧值;
4、读取阶段在临界区内执行,false 时不要立刻重试,等一个完整周期再读。
3.7 sensor.hx711(pin_date, pin_clk)
功能
按 HX711 的时序读一次 24 位转换结果(固定发 24 个时钟,即增益 128 / A 通道)。
参数
pin_date:HX711 的 DOUT 所接 GPIO 编号,number,必选。
pin_clk:HX711 的 PD_SCK 所接 GPIO 编号,number,必选。
返回值
int:读到的原始码除以 100 之后的整数(源码为了放进 int 做了缩放)。没有成功/失败标志。
示例
local zero = sensor.hx711(0, 7) -- 空秤零点
sys.wait(1000)
local val = sensor.hx711(0, 7) - zero
log.info("hx711", val)
注意事项
1、器件不在位也不会报错:内部最多等 5ms(50 次 × 100us)就继续按时序读,拿到的值没有意义,所以首次使用务必对比已知负载确认方向与量级;
2、原始码被除以 100,分辨率随之降低,微小重量变化可能看不出来;需要完整 24 位精度请改用 cs1237(不缩放);
3、源码不配置 RATE 脚,实际输出速率由硬件 RATE 引脚电平决定(10Hz 或 80Hz);
4、称重一定要自己做零点标定与去皮,不同批次传感器的零点漂移不一样。
3.8 sensor.cs1237(pin_date, pin_clk)
功能
按 CS1237 的时序读一次 24 位转换结果,时序与 HX711 相同(共用同一段读码实现)。
参数
pin_date:CS1237 的数据脚所接 GPIO 编号,number,必选。
pin_clk:CS1237 的时钟脚所接 GPIO 编号,number,必选。
返回值
int:24 位原始码,不做缩放;读取失败(数据脚一直不拉低)时返回的是无效值而不是错误码。
示例
local raw = sensor.cs1237(0, 7)
log.info("cs1237", raw)
注意事项
1、返回值按无符号整数给到 Lua,负值要自己按 24 位补码换算:if raw >= 0x800000 then raw = raw - 0x1000000 end;
2、同样最多等 5ms 就继续读,器件不在位时读到的是无效码;
3、CS1237 的增益、速率由芯片引脚/寄存器决定,本接口不做配置。
3.9 sensor.sc12a(sda, scl)
功能
用软件 I2C 时序(从机地址 0x81)读 SC12A 电容触摸芯片的 16 位按键状态。
参数
sda:数据脚 GPIO 编号,number,必选;需上拉。
scl:时钟脚 GPIO 编号,number,必选。
返回值
int:已按位取反的按键状态,位为 1 表示该通道被触摸。bit15 对应通道 0、bit14 对应通道 1……bit4 对应通道 11,共 12 个通道。
从机无应答(NACK)时返回 0xFFFF0000(32 位整数下表现为 -65536),可据此判断读取失败。
示例
local v = sensor.sc12a(4, 7)
if v == 0xFFFF0000 then
log.info("sc12a", "no ack")
else
for ch = 0, 11 do
if bit.band(v, bit.lshift(1, 15 - ch)) ~= 0 then
log.info("sc12a", "key down", ch)
end
end
end
注意事项
1、本接口是纯软件时序,一次读 16 位大约耗时几百微秒,轮询周期建议 ≥100ms;
2、SDA 必须上拉(外部电阻或 gpio 内部上拉都可以,但外部 4.7k 更稳);
3、芯片的 I2C 地址、通道映射由 SC12A 固件决定,本接口按 0x81 读、12 通道映射写死。
3.10 sensor.ws2812b(pin, data, T0H, T0L, T1H, T1L)
功能
用 GPIO 位翻转(配合 gpio.pulse)发送 WS2812B 数据,发送全程关中断以保证时序精度。
参数
pin:灯带数据线所接 GPIO 编号,number,必选。
data:要发送的数据,string 或 zbuff,必选。传 zbuff 时会忽略指针位置,固定从 0 偏移开始,长度按 zbuff 实际长度。
T0H、T0L、T1H、T1L:bit0/bit1 的高、低电平「空转循环次数」,number,必填且没有默认值。单位不是微秒,需要按主控主频实测调整。
返回值
无返回值(nil)。
示例
local buff = zbuff.create({8, 8, 24})
buff:drawLine(1, 2, 5, 6, 0x00ffff)
sensor.ws2812b(7, buff, 300, 700, 700, 700)
注意事项
1、数据按字节高位先发;WS2812B 每颗灯珠期望的是 G-R-B 顺序,直接把 RGB 帧缓冲送进去会出现红绿互换,需要自己调换字节顺序;
2、发送期间关中断,灯珠越多占用越久(1 bit ≈ 1.25us,300 颗 RGB 约 9ms),会打断串口/WiFi 等对时序敏感的任务;长灯带建议拆段刷新或改用 ws2812b_spi;
3、4 个时间参数与主频强相关,抄别人的参数通常偏色,必须自己调;调好之前建议先只接 1 颗灯珠验证;
4、data 传 string 时按字节原样发出,长度不校验,注意不要超过灯珠数 ×3。
3.11 sensor.ws2812b_pwm(channel, data)
功能
用 PWM 逐位输出 WS2812B 数据:源码把 PWM 频率固定为 800kHz、分频精度 100,bit0/bit1 的占空比写死(约 33% / 66%),每发 1 bit 重新配置一次 PWM。
参数
channel:PWM 通道号(不是 GPIO 编号),number,必选。通道对应的引脚由硬件固定,不能任意选。
data:要发送的数据,string 或 zbuff,必选;zbuff 同样从 0 偏移开始。
返回值
无返回值(nil);PWM 配置失败时会提前结束并打印一条 warning 日志,灯带停在当前状态。
示例
local buff = zbuff.create({8, 8, 24})
buff:setFrameBuffer(8, 8, 24, 0x0000ff)
sensor.ws2812b_pwm(7, buff)
注意事项
1、要求固件包含 pwm 库,且该通道没被 pwm.setup 占用(同一通道同时用会互相踩);
2、每 bit 都要重新 setup,灯珠多时耗时明显,适合少量灯珠做状态指示;
3、PWM 时钟精度不足的型号会出现偏色,属于硬件限制,此时请改用 ws2812b_spi。
3.12 sensor.ws2812b_spi(spi_id, data)
功能
用硬件 SPI 输出 WS2812B 数据:内部固定 5MHz、MSB 先发、模式 1、全双工、不使用硬件片选;每个输入字节按位展开成 8 个字节(bit1 → 0xF8,bit0 → 0xC0),发送前先发 1 个 0x00 字节做总线复位。
参数
spi_id:SPI 通道号(不是 GPIO 编号),number,必选。
data:要发送的数据,string 或 zbuff,必选;zbuff 同样从 0 偏移开始。
返回值
无返回值(nil);展开缓冲申请不到内存时会直接返回,不报错。
示例
local buff = zbuff.create({8, 8, 24})
buff:setFrameBuffer(8, 8, 24, 0x0000ff)
sensor.ws2812b_spi(2, buff)
注意事项
1、要求固件包含 spi 库,且该 SPI 能稳定跑 5MHz;频率不够会偏色,频率过高会丢位;
2、内部会 malloc(数据长度 × 8) 做展开缓冲,灯珠多时注意内存(300 颗 RGB = 900 字节,展开后 7200 字节);
3、发送完会自己 spi.close,不要和正在使用的 SPI 外设抢同一个 id。
四、产品支持说明
1、本库由编译开关 LUAT_USE_SENSOR 控制,ws2812b_pwm 需要 LUAT_USE_PWM、ws2812b_spi 需要 LUAT_USE_SPI;
2、公开固件的核心库清单里能看到 sensor(例如 22/122 号、23/123 号固件的库清单中都有 sensor),但固件差异表目前没有单列 sensor 行。使用前最直接的确认方式:在目标固件里跑 log.info("sensor", sensor),打印出 table 说明库在,打印 nil 说明没编进去;
3、本页所有接口都是软件时序实现,与 mcu 型号的主频强相关,跨型号移植时先验证时序参数。