跳转至

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 型号的主频强相关,跨型号移植时先验证时序参数。

搜索