气体传感器MQ-2芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

人体感应的真脚找过了。今天接气体报警。很多模块只有一根数字脚,而且这根脚可能和 I2C 时钟是同一根。

MQ-2 看起来像「模拟传感器」:金属网罩、加热丝、旁边一颗比较器。很多人第一反应是去读 ADC,换算出浓度 ppm。在这块 RK3568 底板上,扩展排针没有把 SARADC 引出来 给这个模块。你能用的只有比较器翻出来的那根 DO(数字报警)。

所以本章的验收不是「屏幕上出现 237 ppm」,而是:预热之后,酒精棉凑到网罩 1~2 厘米,DO 翻转;拿开,DO 回去。就这一件事。

另一件会让你卡一天的事:实测这根 DO 常常落到 GPIO11 ,也就是 GPIO0_B3。这颗球同时是 I2C1 的 SCL。红外测温 MLX90614 挂在 i2c1 上时,这根脚属于 i2c 控制器,sysfs 导出 GPIO11 会失败。MQ-2 和红外测温不要同一时刻抢这根脚。


1. 通信怎么走

模块上典型有三块:气敏元件(要加热)、比较器、排针。比较器把模拟电压和电位器阈值比一下,OUT 翻成数字。

bash 复制代码
酒精 / 烟雾分子
    │  凑近金属网罩 1~2 cm
    ▼
气敏元件(加热丝要先预热)
    │  模拟电压(本底板没有引到 SARADC)
    ▼
板上比较器
    │  DO:超过阈值翻转
    ▼
SoC GPIO(实测常为 GPIO0_B3 = linux 11)
    │
    ▼
/sys/class/gpio/gpio11/value
    │
    ▼
C 监测工具 / ArkTS 页面

AO(模拟)脚即使模块上有焊盘,底板这组排针也对不到 fe720000.saradc 的通道。第 29 章的 SARADC 在板载按键和专用 ADC 端子上。MQ-2 页面上如果留一个「读浓度」按钮,点下去找不到通道是预期,不要当故障修。

电位器在模块上,拧它改变报警阈值,不改变「有没有 DO 这根脚」。软件读不到浓度,硬件仍可能用电位器让它更灵敏或更钝。


2. 预热:加热丝常亮不是着火

上电之后金属网罩下面那根丝会红。这是加热,不是故障,更不是短路。数据手册要求预热,现场经验是 2~5 分钟 以后比较器才稳。刚上电立刻凑酒精,DO 可能完全不动,你会以为脚号又错了。

正确上电顺序:

  1. 插好模块,确认 VCC、GND。5 V 供电的模块不要接到 3.3 V 逻辑脚上当电源。
  2. 上电,看到加热丝亮。
  3. 走开,等至少两分钟,最好五分钟。
  4. 再开监测,用酒精棉凑网罩。

预热期内页面提示写成:「加热丝常亮正常,请等待预热,不要用明火。」不要弹「传感器异常」。

禁止:

  • 打火机火焰直接烧网罩。气敏元件会坏,也有安全问题。
  • 把模块塞进密闭盒子里长时间加热不管。
  • 用口吹气当测试。隔半米吹一口气,浓度到不了阈值。

推荐刺激源:酒精棉或沾了酒精的棉签,直接凑近金属网罩 1~2 厘米,停一两秒。烟雾也可以,同样要近。厨房里隔着一米「比划一下」无效。


3. GPIO 编号再算一次

第 39 章的公式这里还用:

bash 复制代码
linux 编号 = bank × 32 + 组内偏移 + 位号
A=0  B=8  C=16  D=24
名字 计算 编号 备注
GPIO0_B3 0×32+8+3 **11** 实测 DO;同时是 I2C1 SCL
GPIO0_B4 0×32+8+4 12 同时是 I2C1 SDA
GPIO2_B2 2×32+8+2 66 曾被误判为 DO,复测不动
GPIO2_B1 2×32+8+1 73 PIR,不是 MQ-2

/sys/kernel/debug/gpio 里 gpiochip0 是 GPIO0,编号 0~31。11 就在这颗 chip 上。

为什么 11 会和 I2C 打架:i2c1 的 pinctrl 组 i2c1_xfer 就是 GPIO0_B3 / GPIO0_B4。设备树 &i2c1 { status = "okay"; pinctrl-0 = <&i2c1_xfer>; } 一生效,这两根脚属于 i2c 控制器。再 echo 11 > export,内核返回 busy。

第 35 章红外测温、第 34 章环境光、第 36 章血氧、第 37 章温湿度、第 38 章手势,都挂 i2c1。它们活着的时候,不要指望 GPIO11 还能当普通输入。


4. 脚号是怎么扫出来的,以及第一次扫错

不要一上来就信某张「模块引脚表」。扩展排针丝印、原理图连接器号、设备树注释,三份对不上是常态。PIR 已经示范过一次。MQ-2 再示范一次,而且第一次扫还扫错了。

4.1 候选脚

把该插槽能碰到的、当前没被矩阵键盘等驱动占死的 GPIO 列出来。现场能 export 成功的包括 32、33、66、67、73、85、86,以及 11、12(若 i2c1 没占用)。被 keypad pinctrl 占住的 GPIO1 那一组 export 直接失败,跳过。

4.2 扫描脚本

放到板上 /data/local/tmp/scan_do.sh:

bash 复制代码
#!/system/bin/sh
# 用法:scan_do.sh 11 66 32 33
# 每 200 ms 打一行各脚电平。凑酒精时盯着哪一列在翻。

PINS="$*"
[ -z "$PINS" ] && PINS="11 66 32 33 73"

for g in $PINS; do
    [ -d /sys/class/gpio/gpio$g ] || echo $g > /sys/class/gpio/export 2>/dev/null
    echo in > /sys/class/gpio/gpio$g/direction 2>/dev/null
done

echo "time $(echo $PINS | tr ' ' '_')"
i=0
while [ $i -lt 80 ]; do
    line="$i"
    for g in $PINS; do
        if [ -f /sys/class/gpio/gpio$g/value ]; then
            v=$(cat /sys/class/gpio/gpio$g/value)
        else
            v="x"
        fi
        line="$line $v"
    done
    echo "$line"
    i=$((i + 1))
    usleep 200000
done
bash 复制代码
hdc file send scan_do.sh /data/local/tmp/scan_do.sh
hdc shell chmod 0755 /data/local/tmp/scan_do.sh
hdc shell "/data/local/tmp/scan_do.sh 11 66 32 33"

预热完成后开始跑。跑到一半,酒精棉凑上网罩,再拿开。看输出哪一列跟着变。

4.3 第一次误判:66

某次扫描里,GPIO66 那一列出现过一次 0→1。当时认定 DO=66,应用默认填了 66,极性还写成高电平报警。客户现场凑烟,页面不动。

复测:同样的脚本再跑三遍。66 全程:

bash 复制代码
1 1 1 1 1 1 1 1 1 1

酒精棉贴上去,66 仍是 1。那一次「翻转」是接触抖动或者读的瞬间碰到了别的脚。单次跳变不能当结论。

4.4 复测:11 才跟着烟走

同一段日志里 GPIO11 在凑近时变成:

bash 复制代码
1 1 1 1 0 0 0 0 1 1 1

拿开酒精棉,回到 1。再凑一次,再变 0。三遍稳定。极性是 空闲 1、报警 0。

所以:

  • 默认 DO 填 11;
  • 判定 v === 0 为报警(以这颗模块为准);
  • 页面必须是连续监测,不能「点一下读一次」------报警可能只持续一两秒,单次采样会错过。

扫描纪律:

  1. 预热后再扫。
  2. 连续轮询,不要 cat 一次就下结论。
  3. 同一刺激源至少复现三次。
  4. 拔掉模块再读候选脚:如果拔掉后仍按「报警」节奏自己翻,那根脚不是 DO。
  5. 写下极性,写进页面提示,不要只写在聊天记录里。

5. 和 I2C1 抢脚:export 失败不是脚本写错

GPIO11 = GPIO0_B3 = I2C1_SCL。设备树默认 &i2c1 { status = "okay"; },因为环境光、红外测温、血氧都在这条总线上。

此时:

bash 复制代码
hdc shell "echo 11 > /sys/class/gpio/export"

典型输出:

bash 复制代码
sh: write error: Device or resource busy

debugfs:

bash 复制代码
hdc shell "cat /sys/kernel/debug/gpio" | grep gpio-11

可能看到这根脚的名字是 i2c 相关,而不是 sysfs。pinctrl:

bash 复制代码
hdc shell "cat /sys/kernel/debug/pinctrl/pinctrl-rockchip/pinmux-pins" | grep -i 'gpio0-11\|0-b3\|i2c1'

会看到 i2c1_xfer 占用。

board-perm.sh 里对 11 的导出失败,串口会出现:

bash 复制代码
[BOARD_PERM] gpio11: 导出失败。73 查 pinctrl;11 查是否被 i2c1 占用

这不是脚本 bug。三条路,选一条:

路 A:测 MQ-2 时关掉 i2c1。 设备树临时 &i2c1 { status = "disabled"; },重打 dtb / boot_linux。红外测温、环境光、血氧、手势这一组全部消失。测完 MQ-2 改回 okay。这和 CAN 镜像是同一类「共脚二选一」,见第 33 章、第 58 章。

路 B:DO 改接到另一根空闲 GPIO。 如果模块排针允许你把 OUT 飞到 73 以外的空脚(不要和 PIR 抢 73),软件把编号改成那根脚。硬件改动要画清楚,不要口头传说。

路 C:不要同时测。 交付演示分时段:先演示 I2C 传感器,再切镜像或改接线演示 MQ-2。页面上写明「GPIO11 被 i2c1 占用时读不到 DO」。

不要走「强行 unbind i2c 驱动再 export」这种演示技巧。能临时通,重启又回去,客户无法复现。

CAN0 的 TX/RX 也是 GPIO0_B3/B4。开 CAN 的镜像同样会让 GPIO11 消失。MQ-2、I2C1 传感器、CAN,三选一坐这组脚。


6. 开机预导出:沿用第 39 章脚本

HAP 依然写不了 export。第 39 章的 board-perm.sh 已经循环 for g in 11 73。这里把和 MQ-2 相关的部分再强调一遍,脚本全文仍以第 39 章为准,不要维护两份互相覆盖的版本。

关键片段:

bash 复制代码
# 11 = MQ-2 DO(GPIO0_B3,⚠与 i2c1 SCL 共球)
# 73 = PIR OUT(GPIO2_B1)
for g in 11 73; do
    [ -d /sys/class/gpio/gpio$g ] || echo $g > /sys/class/gpio/export 2>/dev/null
    echo in > /sys/class/gpio/gpio$g/direction 2>/dev/null
    chmod 0666 /sys/class/gpio/gpio$g/value 2>/dev/null
    chmod 0666 /sys/class/gpio/gpio$g/direction 2>/dev/null
done

for g in 11 73; do
    if [ -d /sys/class/gpio/gpio$g ]; then
        log "gpio$g: direction=$(cat /sys/class/gpio/gpio$g/direction) value=$(cat /sys/class/gpio/gpio$g/value)"
    else
        log "gpio$g: 导出失败。73 查 pinctrl;11 查是否被 i2c1 占用"
    fi
done

init 配置、BUILD.gn、刷 vendor 分区,全部和第 39 章同一套。改脚本只重打 vendor.img,刷 p8。只装 HAP 不够。

临时、没重打镜像时:

bash 复制代码
hdc shell "echo 11 > /sys/class/gpio/export"
hdc shell "echo in > /sys/class/gpio/gpio11/direction"
hdc shell "chmod 0666 /sys/class/gpio/gpio11/value"

若第一步 busy,先解决 i2c1,不要在应用里死循环 export。


7. 完整 C:mq2_watch.c

和 PIR 的 gpio_watch 同类,但带极性提示、busy 诊断,以及「预热倒计时」口吻。可以和上一章共用同一个二进制,只是默认脚和文案不同。这里给一份独立文件,复制就能编。

bash 复制代码
/* mq2_watch.c
 * OpenHarmony 32 位 musl。
 * 用法:
 *   mq2_watch              # 默认 gpio11,间隔 200 ms
 *   mq2_watch 11 200
 *   mq2_watch 11 200 0     # 第三个参数:报警电平,0=低有效(本模块实测)
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int gpio_exported(int num)
{
    char dir[64];
    snprintf(dir, sizeof(dir), "/sys/class/gpio/gpio%d", num);
    return access(dir, F_OK) == 0;
}

static int gpio_export(int num)
{
    if (gpio_exported(num))
        return 0;
    char buf[8];
    int len = snprintf(buf, sizeof(buf), "%d", num);
    int fd = open("/sys/class/gpio/export", O_WRONLY);
    if (fd < 0) {
        fprintf(stderr, "open export: %s\n", strerror(errno));
        fprintf(stderr, "HAP 会在这里失败;命令行若也失败,查是否 root。\n");
        return -1;
    }
    ssize_t n = write(fd, buf, (size_t)len);
    int err = errno;
    close(fd);
    if (n < 0) {
        fprintf(stderr, "export gpio%d: %s\n", num, strerror(err));
        if (err == EBUSY && num == 11) {
            fprintf(stderr,
                    "gpio11=GPIO0_B3=I2C1_SCL。i2c1 okay 时不能当 DO。\n"
                    "关掉 i2c1,或把 DO 改接到空闲脚,或不要同时测红外测温。\n");
        }
        return -1;
    }
    usleep(50000);
    return 0;
}

static int write_str(const char *path, const char *s)
{
    int fd = open(path, O_WRONLY);
    if (fd < 0)
        return -1;
    ssize_t n = write(fd, s, strlen(s));
    close(fd);
    return n < 0 ? -1 : 0;
}

static int gpio_read(int num)
{
    char path[64];
    snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", num);
    int fd = open(path, O_RDONLY);
    if (fd < 0)
        return -1;
    char c = 0;
    ssize_t n = read(fd, &c, 1);
    close(fd);
    if (n <= 0)
        return -1;
    return (c == '1') ? 1 : 0;
}

int main(int argc, char **argv)
{
    int num = (argc >= 2) ? atoi(argv[1]) : 11;
    int interval = (argc >= 3) ? atoi(argv[2]) : 200;
    int alarm_level = (argc >= 4) ? atoi(argv[3]) : 0; /* 本模块实测低有效 */

    printf("MQ-2 DO watch gpio%d interval=%dms alarm_when=%d\n",
           num, interval, alarm_level);
    printf("预热 2-5 分钟;加热丝亮是正常的。酒精棉凑网罩 1-2cm。\n");
    printf("禁止明火。隔空吹气无效。本底板无 ADC,只认 DO。\n");

    if (gpio_export(num) < 0)
        return 1;

    char dirp[64];
    snprintf(dirp, sizeof(dirp), "/sys/class/gpio/gpio%d/direction", num);
    if (write_str(dirp, "in") < 0) {
        fprintf(stderr, "direction: %s\n", strerror(errno));
        return 1;
    }

    int last = -1;
    int flips = 0;
    int alarms = 0;
    int i = 0;
    for (;;) {
        int v = gpio_read(num);
        if (v < 0) {
            fprintf(stderr, "read: %s(value 是否 0666?)\n", strerror(errno));
            return 1;
        }
        if (last >= 0 && v != last)
            flips++;
        int is_alarm = (v == alarm_level);
        if (is_alarm)
            alarms++;
        printf("[%03d] gpio%d=%d  %s  flips=%d\n",
               i, num, v,
               is_alarm ? "ALARM" : "idle ",
               flips);
        last = v;
        i++;
        usleep((useconds_t)interval * 1000);
    }
    return 0;
}

编译:

bash 复制代码
OHOS=$HOME/code/opcode/OpenHarmony
CLANG=$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang
SYSROOT=$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot

$CLANG --target=arm-linux-ohos --sysroot=$SYSROOT \
  -O2 -Wall \
  -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1 \
  -o mq2_watch mq2_watch.c

file mq2_watch
hdc file send mq2_watch /data/local/tmp/mq2_watch
hdc shell chmod 0755 /data/local/tmp/mq2_watch

预热后:

bash 复制代码
hdc shell "/data/local/tmp/mq2_watch 11 200 0"

空闲应连续 gpio11=1 idle。酒精棉凑上:

bash 复制代码
[042] gpio11=1  idle   flips=0
[043] gpio11=1  idle   flips=0
[044] gpio11=0  ALARM  flips=1
[045] gpio11=0  ALARM  flips=1
[046] gpio11=1  idle   flips=2

flips 增加,功能通。若全程 idle:

  • 还在预热;
  • 酒精太远;
  • 脚不是 11(再跑 scan_do.sh);
  • 极性反了,把第三个参数改成 1 试;
  • 电位器拧到极钝。

若一启动就 export ... Busy,停,去看 i2c1,不要改 C 里的重试次数。重试消除不了共脚。

对比复测 66:

bash 复制代码
hdc shell "/data/local/tmp/mq2_watch 66 200 0"

酒精棉贴上去,flips 仍接近 0。不要把 66 写回默认值。


8. NAPI 仍然只用 gpioRead

和 PIR 共用 libboard.so 的 gpioRead。不要为 MQ-2 再开一个 ioctl。不要在 so 里写 ADC 换算假装有浓度。

bash 复制代码
/* 与第 39 章相同:只 open value,不 export */
static napi_value GpioRead(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value a[1];
    napi_get_cb_info(env, info, &argc, a, nullptr, nullptr);
    int num = -1;
    napi_get_value_int32(env, a[0], &num);
    int val = -1;
    if (num >= 0) {
        char p[64];
        snprintf(p, sizeof(p), "/sys/class/gpio/gpio%d/value", num);
        int fd = open(p, O_RDONLY);
        if (fd >= 0) {
            char c = 0;
            if (read(fd, &c, 1) > 0)
                val = (c == '1') ? 1 : 0;
            close(fd);
        }
    }
    napi_value ret;
    napi_create_int32(env, val, &ret);
    return ret;
}

返回 -1 时,ArkTS 要把「i2c1 占用 GPIO11」和「没放权」分成两句人话,否则现场会去重编应用。


9. ArkTS 页面

bash 复制代码
import board from 'libboard.so';

@Entry
@Component
struct Mq2Page {
  @State gpioNum: string = '11';
  @State live: number = -1;
  @State watching: boolean = false;
  @State hint: string = '加热丝常亮正常。预热 2~5 分钟后点监测,酒精棉凑网罩 1~2cm。';
  @State alarmLow: boolean = true; /* 实测 GPIO11:0=报警 */
  private timer: number = -1;

  aboutToDisappear() {
    this.stop();
  }

  statusText(): string {
    if (this.live < 0) {
      return 'DO: -';
    }
    let alarm: boolean = this.alarmLow ? (this.live === 0) : (this.live === 1);
    return alarm ? '报警(检测到气体)' : '正常(无报警)';
  }

  toggle() {
    if (this.watching) {
      this.stop();
      this.hint = '已停止';
      return;
    }
    let n: number = parseInt(this.gpioNum);
    if (Number.isNaN(n)) {
      this.hint = '先填 DO 的 GPIO 号';
      return;
    }
    let probe: number = board.gpioRead(n);
    if (probe < 0) {
      this.hint =
        'GPIO' + n + ' 读不到。两种可能:\n' +
        '1) 开机 board-perm 没预导出 / value 不是 0666,需重打 vendor;\n' +
        '2) GPIO11 是 I2C1 SCL,红外测温占用时 export 失败,二者不要同时测。';
      return;
    }
    this.watching = true;
    this.live = probe;
    this.hint = '监测中。酒精棉贴网罩 1~2cm,看电平翻不翻。远距离吹气无效。';
    this.timer = setInterval(() => {
      let v: number = board.gpioRead(n);
      if (v >= 0) {
        this.live = v;
      }
    }, 300);
  }

  stop() {
    if (this.timer >= 0) {
      clearInterval(this.timer);
      this.timer = -1;
    }
    this.watching = false;
  }

  build() {
    Column({ space: 10 }) {
      Text('气体传感器 MQ-2').fontSize(20).fontWeight(FontWeight.Bold)
      Text('只读 DO,本排针没有 ADC。不要指望 ppm 读数。').fontSize(13)
      Text('加热丝常亮是预热。禁止明火烧网罩。').fontSize(13)
      Row() {
        Text('DO GPIO').margin({ right: 8 })
        TextInput({ text: this.gpioNum })
          .width(80)
          .onChange((v: string) => { this.gpioNum = v; })
        Button(this.watching ? '停止监测' : '开始监测')
          .onClick(() => { this.toggle(); })
          .margin({ left: 8 })
      }
      Button(this.alarmLow ? '极性:低电平=报警(点此对调)'
                           : '极性:高电平=报警(点此对调)')
        .onClick(() => { this.alarmLow = !this.alarmLow; })
      Text('电平: ' + (this.live < 0 ? '-' : this.live.toString())).fontSize(18)
      Text(this.statusText()).fontSize(22).fontWeight(FontWeight.Bold)
      Text(this.hint).fontSize(14)
    }
    .padding(16)
    .width('100%')
  }
}

监测间隔 300 ms 和 PIR 一样。气体报警沿不是微秒级。单次「读 DO」按钮可以留,但文档要写:报警脉冲短,点不准。演示一律走监测。

不要在页面上放一个 ADC 通道输入还声称「浓度」。若留调试入口,注明「本槽无 SARADC,读到的是板载通道,与 MQ-2 无关」。


10. 排障过程

按这个顺序,不要先改应用默认脚号来回试。

1. 加热丝亮不亮。

不亮:没供电、插反、模块坏。亮:进入预热等待。看表两分钟起。

2. i2c1 还在不在。

bash 复制代码
hdc shell "ls -l /sys/bus/i2c/devices/i2c-1"
hdc shell "cat /proc/device-tree/i2c@fe5c0000/status"
# 节点名以你板子 /proc/device-tree 实际路径为准
hdc shell "i2cdetect -y -r 1"

i2cdetect 能扫到 0x5a(MLX90614)说明 i2c1 活着,GPIO11 大概率 busy。先决定这次要测哪一个。

3. export 11。

bash 复制代码
hdc shell "echo 11 > /sys/class/gpio/export"
hdc shell "ls -l /sys/class/gpio/gpio11/value"

busy → 回步骤 2。成功 → direction 设 in,chmod 0666。

4. 空闲值。

人、酒精、烟雾都远离。连续读 20 次。记下多数值,那是 idle。本模块常见 1。

5. 酒精棉 1~2 cm。

mq2_watch 11 200 0 跑着,棉签贴网罩。要看到 ALARM。拿开回到 idle。复现三次。

6. 对照 66。

同样刺激,66 不应跟着翻。防止旧默认值混进页面。

7. HAP -1。

bash 复制代码
hdc shell "ls -l /sys/class/gpio/gpio11/value"
hdc shell "grep BOARD_PERM /proc/kmsg"
hdc shell "ls -l /vendor/bin/board-perm.sh"

value 不是 0666,HAP 读失败。root 的 mq2_watch 能跑、HAP 不能,就是放权问题,不是脚号问题。

8. HAP 能读但不报警。

  • 预热不够;
  • 棉签太远;
  • 极性反了,点页面「对调」;
  • 电位器过钝;
  • 监测没开,只点了一次读。

9. 误报不停。

电位器过灵、旁边有酒精瓶敞口、预热未完输出乱跳。先拿开所有刺激源,等预热结束,看 idle 是否稳定。稳定后再谈阈值。


11. 和别的章的关系

章 关系
33 I2C i2c1 占用 GPIO0_B3/B4,MQ-2 用 11 时要让路
35 MLX90614 同抢 I2C1 SCL,不要同时测
39 PIR 同一套 `board-perm.sh`、同一个 `gpioRead`;脚是 73,不是 11
29 SARADC MQ-2 这组排针用不上;别把按键通道当浓度
58 CAN can0m0 也是 GPIO0_B3/B4,三选一

演示脚本建议写成时段表,不要指望一张镜像、一个插槽把 I2C 传感器、MQ-2、CAN 同时点亮。共脚不是软件 bug。


12. 验收

同时满足才算过:

  1. 加热丝上电常亮,预热满 2 分钟(演示给 5 分钟更稳)。
  2. gpio11 能导出,或你已经改接到另一根空闲脚并在页面写明编号。
  3. 空闲连续 10 秒电平稳定。
  4. 酒精棉或烟雾 1~2 cm 凑近网罩,DO 翻转;拿开后恢复。复现三次。
  5. 远距离吹气不要求翻转。
  6. 未使用明火。
  7. HAP 监测模式能显示报警/正常,且 HAP 自己没有写 export。
  8. 若 i2c1 保持 okay,允许 GPIO11 导出失败,但页面必须给出「与红外测温抢脚」的说明,而不是空白 -1。
  9. 不把 ADC 读数当作本模块浓度指标。

第 8 条是给「默认交付镜像开着 i2c1」留的口。你可以选择交两份镜像,也可以选择演示时拔掉 I2C 模块、关掉 i2c1。选哪条写进你的说明,不要让客户自己猜。


本章小结

MQ-2 在这块底板上是 DO 数字报警 ,不是浓度计。加热丝亮、预热数分钟是正常的。刺激源贴网罩 1~2 厘米。实测 DO 常为 GPIO11,空闲 1、报警 0,仍以监测为准。GPIO11 与 I2C1 SCL 共球,和红外测温冲突时换时段或换脚。扫描脚号要连续轮询并复测,66 那次误判不要再传。HAP 继续走第 39 章的预导出。


路径清单

路径 干什么
`device/board/rk/rk3568_evb/kernel/*.dts` 的 `&i2c1` okay 时 GPIO11 属于 SCL
`device/board/rk/rk3568_evb/board_perm/board-perm.sh` 预导出 11 和 73
`/sys/class/gpio/gpio11/value` DO 电平
`/sys/kernel/debug/gpio` 看 11 被谁占
`/data/local/tmp/mq2_watch` 命令行监测
`/data/local/tmp/scan_do.sh` 扫脚,复测三次
`libboard.so` `gpioRead` HAP 只读

系列第 40 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

相关推荐
程序边界44 分钟前
用了三年数据同步软件,我想好好聊聊KFS这套东西到底强在哪(上)
后端
sp421 小时前
简单的 Spring 文件上传与下载组件
后端
量化分析码农1 小时前
【Python量化实盘工程 #04】回测收益虚高30%?滑点与冲击成本建模让模拟更真实
后端
QuantiCore_IO1 小时前
量化回测复权数据的工程实践:从 API 获取到数据质量验证
后端·github·api
程序边界1 小时前
数据同步软件下半场:断了丢不丢、错了能不能修(下)
后端
wei_shuo1 小时前
KFS数据同步软件实战:异构数据同步、实时复制与产品力解析
后端
极派科技1 小时前
凌晨收到告警:MySQL CPU 100%,几千台 IoT 设备同时上报把系统打崩了
后端
王中阳Go1 小时前
做了 8 年前端,简历改漂亮后面一个挂一个:差的不是前端,是这 3 块底子
人工智能·后端·面试
SimonKing1 小时前
数据不出本机!本地全文检索+知识图谱,可以试试这个开源项目:KnowSeek
后端·程序员