ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

血氧心跳传感器MAX30100芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

血氧心跳传感器MAX30100芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】 测温会了。今天换指尖光电估心率和血氧。必须指腹按紧轻触没有有效信号。第 33 章把 I2C 三条总线讲清楚了第 34、35 章两颗芯片都能从 IIO 或用户态拿到数。这一章你会碰到一种更气人的情况驱动绑上了IIO 节点也在读出来永远是空的。芯片是MAX301007 位地址0x57挂在i2c1上。它不是「写一个寄存器、读两个字节」那种温湿度计而是一颗带FIFO的光电传感器红外 LED 和红光 LED 轮流打进指腹光电二极管把反射光变成 16 位样本每秒一百次推进 FIFO。心率和血氧饱和度都是从这两路波形算出来的不是芯片直接吐一个百分比。内核里有现成的 IIO 驱动drivers/iio/health/max30100.cCONFIG_MAX30100y编进去就能 probe。它的设计假设是INT 脚会跳中断来了再去排空 FIFO。板上这根 INT 如果没接到、接到了但电平不对、或者 GPIO 被别人占用中断计数永远是 0FIFO 在芯片里涨驱动这边一个样本都不拿。你去读in_temp_raw得到EAGAIN读 IIO buffer 得到空于是开始怀疑芯片坏了。这章的结论先说在前面别跟中断较劲。用户态打开/dev/i2c-1自己把 MODE 写成 SpO2自己按写指针 / 读指针把 FIFO 排空。中断那条路可以当加分项主路径是轮询。指腹按紧 5 到 10 秒IR / RED 数值会明显跳起来。轻触无效。1. 这颗芯片在干什么MAX30100 的封装上有一个小窗口。窗口下面是两颗 LED红外、红光和一颗光电二极管。指腹按上去以后红外 LED 亮一小段时间二极管采一次反射得到 IR。红光 LED 再亮得到 RED。这一对 16 位值组成一条 FIFO 记录4 字节IR_H, IR_L, RED_H, RED_L。默认我们把采样配成 100 Hz也就是每 10 ms 一条。血红蛋白对红光和红外的吸收不一样含氧和不含氧又不一样。所以两路波形的交流分量之比能估出血氧IR 波形的峰间距能估心率。芯片自己不算这些它只负责点灯和填 FIFO。算法在你的 C 里。没有手指的时候LED 打到空气里IR / RED 往往是很小的数或者在一个很低的平台上抖。手指按紧以后数值会大一个数量级并且能看见脉搏起伏。这就是本章的验收铁证空气一组数按紧一组数两组对不上才叫通。2. 通信怎么走应用 / 命令行工具 │ open ioctl(I2C_RDWR) ▼ /dev/i2c-1 ← CONFIG_I2C_CHARDEVy │ ▼ i2c-rk3x 控制器i2c1100 kHz │ SDA / SCL ▼ MAX30100 0x57 │ ├─ 0xFE PART_ID / 0xFF REV_ID ├─ 0x06 MODE复位、关机、SpO2 ├─ 0x07 SPO2_CONFIG100 Hz ├─ 0x09 LED_CONFIG电流 ├─ 0x01 INT_ENABLE ├─ 0x02 FIFO_WR_PTR ├─ 0x04 FIFO_RD_PTR └─ 0x05 FIFO_DATA每次读 4 字节弹出一条读写方式用第 33 章的组合读i2cRdReg先写寄存器地址再 Repeated Start 读数据。FIFO 数据口 0x05 也是组合读连读 4 字节。不要对 0x05 做「裸读」——指针没设对读回来的就不是你以为的那条样本。和 HDC1080下一章正好相反。那颗芯片触发之后禁止再写指针。这颗芯片每次读 FIFO 都必须先告诉它「我要 0x05」。INT 脚在原理图上常常接到 GPIO2_D0linux gpio 编号 80 一带低电平有效。内核驱动靠它。用户态轮询完全不看这根脚。所以 INT 没接好不影响你这章把数读出来。3. 设备树板级文件device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dtsi2c1必须 100 kHz第 35 章已经为 MLX90614 降过速这颗芯片在 100 kHz 上很老实。节点i2c1 { status okay; clock-frequency 100000; max30100: max3010057 { compatible maxim,max30100; reg 0x57; interrupt-parent gpio2; interrupts RK_PD0 IRQ_TYPE_LEVEL_LOW; status okay; }; };compatible对上内核max30100.c的 of_match。interrupts写了 GPIO2_D0 低电平。写了不等于会来。probe 成功只说明 I2C 地址 ACK、驱动把 IIO 设备注册了。中断请求是否真的能进max30100_irq要看这根脚有没有被别的节点抢显示、按键、别的模块模块排针的 INT 有没有真的连到这颗 GPIO上拉是不是 1.8 V 逻辑SoC 这颗脚能不能认。改完 DTS 要重打boot_linux和resourcedtb 在 resource 分区才生效第 11、12 章。CONFIG_MAX30100y三处同步第 14 章。用户态主路径其实不依赖这个节点 okay。节点 okay 会让内核驱动占着 0x57。多数内核仍然允许i2c-dev对同一地址发I2C_RDWR我们实测这条路通。若你发现用户态读写被拒先把节点改disabled卸掉内核驱动只留 i2c1 本身 okay。4. 内核 IIO 这条死路我带你走一遍上电之后按习惯去找 IIOhdc shell ls /sys/bus/iio/devices/ # iio:device0 iio:device1 ... 名字各板不同 for d in /sys/bus/iio/devices/iio:device*; do echo -n $d cat $d/name done能看到一行max30100。再看ls /sys/bus/i2c/devices/1-0057/driver # .../drivers/max30100 ← 绑上了 cat /sys/bus/iio/devices/iio:deviceN/in_temp_raw # 经常是 I/O error 或空温度通道要先在 MODE 里打开 TEMP_EN驱动用中断或轮询去取。血氧真正的数据不在in_intensity_*_raw这种简单文件里而在 IIObuffer使能 scan_elements、echo 1 buffer/enable再读/dev/iio:deviceN。你按文档做完读出来还是 0 字节。这时候去看中断cat /proc/interrupts | grep -i max # 次数恒 0 dmesg | grep -i max30100max30100_irq一次都没进。FIFO 在芯片侧写指针在走你用下面的 C 去读 0x02 能看见驱动侧因为没中断根本不去 0x05。有人会改驱动把中断改成 hrtimer 轮询。能做但要重编内核、还要处理和 IIO kfifo 的锁。用户态 80 行 C 就能把 FIFO 掏干净。IIO 当诊断用能看见设备名说明 0x57 ACK要数据走 i2c-dev。5. 寄存器地图只记会用到的地址名字本章怎么用0x00INT_STATUS可读可清轮询路径可以不理0x01INT_ENABLE写成 0xF0把 FIFO/HR/SpO2 中断源打开即便脚没接寄存器还是建议按手册来0x02FIFO_WR_PTR芯片写到哪0x03FIFO_OVR_CTR溢出计数非 0 说明你读太慢0x04FIFO_RD_PTR你读到哪0x05FIFO_DATA读 4 字节弹出一条0x06MODEbit7 复位bit1bit0 0b11 为 SpO20x0b SpO2相关位0x07SPO2_CONFIG0x47高分辨率 100 Hz0x09LED_CONFIG0x33红/红外电流过小波形没起伏过大容易饱和0xFEPART_ID有应答时不是 0xFF0xFFREV_ID修订号FIFO 深度 16 条。写指针减读指针模 16 或模 32看手册实现下面 C 用模 32 再钳到 16就是待取条数。一次最多取 16避免在指针回绕时把旧数据当新的。初始化顺序是实测能点亮 LED、能出样本的那一套不要随便调换读 0xFE / 0xFF确认不是 0xFF 0xFF。MODE0x80 复位等 50 ms。MODE0x00 清复位。LED_CONFIG0x33。SPO2_CONFIG0x47。MODE0x0b 进入 SpO2。这一步如果 NACK后面全是空谈。INT_ENABLE0xF0。三个 FIFO 指针写 0。然后每 10 ms 看一次写/读指针把差值条数从 0x05 读走。6. 完整 C采样 1.2 秒打范围可选 5 秒算心率血氧把下面存成max30100_tool.c。用户态 32 位动态链接 musl。自己带了i2c_msg定义不依赖 NDK 里有没有linux/i2c-dev.h。#include stdio.h #include stdlib.h #include stdint.h #include string.h #include unistd.h #include fcntl.h #include errno.h #include math.h #include sys/ioctl.h #ifndef I2C_RDWR #define I2C_RDWR 0x0707 #endif #ifndef I2C_M_RD #define I2C_M_RD 0x0001 #endif struct i2c_msg { uint16_t addr; uint16_t flags; uint16_t len; uint8_t *buf; }; struct i2c_rdwr_ioctl_data { struct i2c_msg *msgs; uint32_t nmsgs; }; #define ADDR 0x57 #define REG_INT_EN 0x01 #define REG_FIFO_WR 0x02 #define REG_FIFO_OVR 0x03 #define REG_FIFO_RD 0x04 #define REG_FIFO_DATA 0x05 #define REG_MODE 0x06 #define REG_SPO2 0x07 #define REG_LED 0x09 #define REG_PART 0xFE #define REG_REV 0xFF static int i2cWr(int fd, uint8_t addr, uint8_t reg, const uint8_t *data, int len) { uint8_t b[24]; int i; b[0] reg; for (i 0; i len i 23; i) b[1 i] data[i]; struct i2c_msg m { addr, 0, (uint16_t)(len 1), b }; struct i2c_rdwr_ioctl_data d { m, 1 }; return ioctl(fd, I2C_RDWR, d); } /* 组合读S addrW reg Sr addrR data... P */ static int i2cRdReg(int fd, uint8_t addr, uint8_t reg, uint8_t *data, int len) { struct i2c_msg m[2] { { addr, 0, 1, reg }, { addr, I2C_M_RD, (uint16_t)len, data } }; struct i2c_rdwr_ioctl_data d { m, 2 }; return ioctl(fd, I2C_RDWR, d); } static int wr1(int fd, uint8_t reg, uint8_t v) { return i2cWr(fd, ADDR, reg, v, 1); } static int rd1(int fd, uint8_t reg, uint8_t *v) { return i2cRdReg(fd, ADDR, reg, v, 1); } static int chip_init(int fd, uint8_t *part, uint8_t *rev) { uint8_t v; *part 0xFF; *rev 0xFF; i2cRdReg(fd, ADDR, REG_PART, part, 1); i2cRdReg(fd, ADDR, REG_REV, rev, 1); if (*part 0xFF *rev 0xFF) { fprintf(stderr, ERR: 0x57 无应答 (PARTFF REVFF)查模块、i2c1、供电\n); return -1; } wr1(fd, REG_MODE, 0x80); /* 复位 */ usleep(50000); wr1(fd, REG_MODE, 0x00); wr1(fd, REG_LED, 0x33); /* LED 电流 */ wr1(fd, REG_SPO2, 0x47); /* 100 Hz 高分辨率 */ if (wr1(fd, REG_MODE, 0x0b) 0) { /* SpO2 模式 */ fprintf(stderr, ERR: 写 MODE0x0b 被拒0x57 中途不应答\n); return -1; } wr1(fd, REG_INT_EN, 0xF0); wr1(fd, REG_FIFO_WR, 0x00); wr1(fd, REG_FIFO_OVR, 0x00); wr1(fd, REG_FIFO_RD, 0x00); /* 读回 MODE确认还是 0x0b 一类不是全 FF */ if (rd1(fd, REG_MODE, v) 0 || v 0xFF) { fprintf(stderr, ERR: 读 MODE 失败\n); return -1; } printf(PART0x%02X REV0x%02X MODE0x%02X init ok\n, *part, *rev, v); return 0; } /* 按指针差排空最多 16 条。返回新追加的样本数 */ static int drain_fifo(int fd, unsigned *ir, unsigned *red, int cap, int *cnt) { uint8_t wr 0, rd 0, buf[4]; int n, k, added 0; if (rd1(fd, REG_FIFO_WR, wr) 0) return -1; if (rd1(fd, REG_FIFO_RD, rd) 0) return -1; n (wr - rd 32) % 32; if (n 16) n 16; for (k 0; k n; k) { if (i2cRdReg(fd, ADDR, REG_FIFO_DATA, buf, 4) 0) break; if (*cnt cap) break; ir[*cnt] ((unsigned)buf[0] 8) | buf[1]; red[*cnt] ((unsigned)buf[2] 8) | buf[3]; if (ir[*cnt] 0 red[*cnt] 0) continue; (*cnt); added; } return added; } static int cmd_quick(int fd) { unsigned ir[256], red[256]; int cnt 0, it; unsigned irMin 65535, irMax 0, redMin 65535, redMax 0; for (it 0; it 120; it) { /* ~1.2 s */ usleep(10000); drain_fifo(fd, ir, red, 256, cnt); } if (cnt 0) { uint8_t wr 0; rd1(fd, REG_FIFO_WR, wr); printf(无采样 FIFO_WR%u。指腹按紧再试仍 0 则 LED/芯片/型号不对\n, wr); return 1; } for (it 0; it cnt; it) { if (ir[it] irMin) irMin ir[it]; if (ir[it] irMax) irMax ir[it]; if (red[it] redMin) redMin red[it]; if (red[it] redMax) redMax red[it]; } printf(采样 %d IR[%u~%u] RED[%u~%u]\n, cnt, irMin, irMax, redMin, redMax); printf(空气里 IR 往往几百到一两千按紧后应到数千以上且 max-min 有起伏。\n); return 0; } static int cmd_spo2(int fd) { unsigned ir[800], red[800]; int cnt 0, it, peaks 0, refr 0; double sumIr 0, sumRed 0, dcIr, dcRed, acIr 0, acRed 0; double R, secs; int hr, spo2, i; const int WIN 25; printf(测量 5 秒指腹按紧窗口不要说话不要动。\n); for (it 0; it 500; it) { usleep(10000); drain_fifo(fd, ir, red, 800, cnt); } if (cnt 100) { printf(采样不足 (%d)。请按紧轻触无效。\n, cnt); return 1; } for (i 0; i cnt; i) { sumIr ir[i]; sumRed red[i]; } dcIr sumIr / cnt; dcRed sumRed / cnt; for (i 0; i cnt; i) { double di ir[i] - dcIr, dr red[i] - dcRed; acIr di * di; acRed dr * dr; } acIr sqrt(acIr / cnt); acRed sqrt(acRed / cnt); secs (double)cnt / 100.0; if (dcIr 1 || acIr / dcIr 0.002) { printf(脉冲太弱 IR%.0f±%.0f。按紧、遮环境光、手别冰。\n, dcIr, acIr); return 1; } for (i WIN; i 1 cnt; i) { double ma 0, cur, prev; int k; for (k -WIN; k WIN; k) ma (double)ir[i k]; ma / (2 * WIN 1); cur (double)ir[i] - ma; prev (double)ir[i - 1] - ma; if (refr 0) refr--; if (cur 0.5 * acIr cur prev (double)ir[i 1] (double)ir[i] refr 0) { peaks; refr 40; /* 0.4 s 不应期 */ } } hr (int)(60.0 * peaks / secs 0.5); R (acRed / dcRed) / (acIr / dcIr); spo2 (int)(110.0 - 25.0 * R 0.5); if (spo2 100) spo2 100; printf(采样 %d (%.1f 秒) IR%.0f±%.0f RED%.0f±%.0f R%.3f\n, cnt, secs, dcIr, acIr, dcRed, acRed, R); if (hr 30 || hr 220) printf(心率: 测不准 (峰 %d)\n, peaks); else printf(心率: %d 次/分\n, hr); if (R 0.2 R 1.0 spo2 70) printf(血氧: %d %% (参考 95~100偏低请复测不是医疗结论)\n, spo2); else printf(血氧: 本次 R 超经验范围按紧再测\n); return 0; } int main(int argc, char **argv) { const char *dev /dev/i2c-1; const char *cmd quick; uint8_t part 0, rev 0; int fd, rc; if (argc 2) cmd argv[1]; fd open(dev, O_RDWR); if (fd 0) { fprintf(stderr, open %s: %s (chmod 0666 ?)\n, dev, strerror(errno)); return 1; } if (chip_init(fd, part, rev) 0) { close(fd); return 1; } if (strcmp(cmd, spo2) 0) rc cmd_spo2(fd); else rc cmd_quick(fd); close(fd); return rc; }编译、送上板、跑OHOS$HOME/code/OpenHarmony # 改成你的源码根 CLANG$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang SYSROOT$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot $CLANG --targetarm-linux-ohos --sysroot$SYSROOT -O2 -Wall \ -Wl,--dynamic-linker/system/lib/ld-musl-arm.so.1 \ -o max30100_tool max30100_tool.c -lm hdc file send max30100_tool /data/local/tmp/max30100_tool hdc shell chmod 0755 /data/local/tmp/max30100_tool hdc shell chmod 0666 /dev/i2c-1先空测再按手指hdc shell /data/local/tmp/max30100_tool quick # 空气示意 # PART0x11 REV0x02 MODE0x0b init ok # 采样 110 IR[120~400] RED[80~260] hdc shell /data/local/tmp/max30100_tool quick # 指腹按紧 # 采样 118 IR[8000~14000] RED[6000~11000] hdc shell /data/local/tmp/max30100_tool spo2 # 按紧 5 秒 # 心率: 72 次/分 # 血氧: 98 %两组 IR 范围对得上「空气 vs 按紧」这一章的硬件通路就过了。心率血氧是经验公式手指凉、按得死、环境光直射窗口数字会飘。它是演示不是监护仪。7. 排障过程从「FIFO 全 0」走到「按紧有起伏」把下面当成一次真实对话。你按这个顺序做不要跳去改驱动。第一步0x57 在不在。hdc shell i2cdetect -y -r 157或UU都算芯片在。--空着模块没插好、没供电、i2c1 被 CAN 镜像关掉了第 33、58 章CAN 和 i2c1 共脚。先把总线救回来。第二步PART_ID。hdc shell /data/local/tmp/max30100_tool quick打印PART0xFF REV0xFF地址扫描也许扫到了残影真正读写被拒。查 3.3 V、地、SDA/SCL 有没有接反。第三步写 MODE 被拒。ERR: 写 MODE0x0b 被拒出现在 init 后半段。前面读 ID 成功、复位也写进去了说明中途总线挂了或者内核驱动同时在复位芯片。临时echo 1-0057 /sys/bus/i2c/drivers/max30100/unbind再跑工具。能通就把 DTS 节点改disabled避免每次开机抢。第四步有 ID、init ok、采样 0。看打印的FIFO_WR。若写指针也是 0芯片没在产样本MODE 没进 SpO2、LED 电流为 0、或者窗口被贴纸挡住。若写指针在走、读出来全 0是 0x05 的组合读长度不对确认i2cRdReg(..., 4)不是 1。第五步有数但空气和按紧差不多。典型是「轻触」。必须指腹把窗口盖严压力中等等 1 秒让 LED 稳定再采样。指甲、太冷、窗口上有胶都会让交流分量没了。spo2模式会明确说「脉冲太弱」。第六步数很大且不再变max 等于 min。饱和了。把LED_CONFIG从 0x33 改小例如 0x22。或者手指按得把窗口压变形光全反射。松一点。第七步以为自己在读 MAX30100其实模块是 MAX30102。30102 地址经常也是 0x57FIFO 宽度和寄存器不完全一样。PART_ID 对不上手册时别硬套 4 字节 IR/RED。换模块或换寄存器表。本章代码只对 MAX30100 负责。8. 算法你要心里有数别对外号称医疗级spo2子命令做了三件事。直流IR、RED 各自对时间取平均。它代表「窗口里有多少稳定反射」手指按上去直流会升高。交流去掉直流之后的均方根。它代表脉搏起伏。交流比直流小于千分之二当没脉搏。比值R (AC_red / DC_red) / (AC_ir / DC_ir) SpO2 ≈ 110 - 25 * R这是课本里那条经验直线不是芯片标定曲线。R 落在 0.21.0 才信算出超过 100 就钳到 100。低于 70 直接判失败免得吓自己。心率IR 减 51 点滑动均值去掉呼吸和基线漂移找超过0.5 * AC的局部峰峰之间至少 0.4 秒。5 秒窗口里峰数乘 12 就是次/分。手抖会多峰按死会没峰。把这些限制写进应用的文案里。用户问「为什么和医院的差 2%」你就指这段。9. ArkTS 页面开始测量轮询结果NAPI 不要在 UI 线程里采 5 秒。C 侧起一个线程ArkTS 每秒问一次状态。模块名libboard.so和第 33 章同一份 so。entry/src/main/cpp/types/libboard/index.d.ts增加export const spo2Start: () string; export const spo2Result: () string; export const max30100Quick: () string;页面节选可直接放进一个Entryimport board from libboard.so; Entry Component struct Spo2Page { State line: string 指腹按紧窗口后点「测量 5 秒」; State busy: boolean false; private timer: number -1; aboutToDisappear(): void { if (this.timer 0) { clearInterval(this.timer); this.timer -1; } } build() { Column() { Text(MAX30100 血氧).fontSize(20).margin({ bottom: 8 }) Text(地址 0x57用户态轮询 FIFO不靠 INT) .fontSize(14).fontColor(#666).margin({ bottom: 12 }) Row() { Button(this.busy ? 测量中... : 测量 5 秒) .enabled(!this.busy) .onClick(() { this.start(); }) Button(快速原始值).margin({ left: 8 }) .enabled(!this.busy) .onClick(() { this.line board.max30100Quick(); }) } Text(this.line) .fontSize(16) .margin({ top: 16 }) .fontColor(#1565C0) } .padding(16) .width(100%) .height(100%) .alignItems(HorizontalAlign.Start) } start(): void { let r: string board.spo2Start(); if (r BUSY) { this.line 上一次还在采等它结束; return; } this.busy true; this.line 测量中 0/5 秒按紧、别动; let n: number 0; this.timer setInterval(() { n; let s: string board.spo2Result(); if (s RUNNING) { this.line 测量中 n.toString() /5 秒按紧、别动; return; } clearInterval(this.timer); this.timer -1; this.busy false; this.line s; }, 1000); } }NAPI 侧把第 6 节的chip_init/drain_fifo/cmd_spo2挪进 so用std::thread跑cmd_spo2那套状态用原子变量0 空闲、1 跑、2 完成、3 错。spo2Start把状态从 0 换成 1 并 detach 线程返回OK。spo2Result在 1 时返回RUNNING在 2/3 时把字符串交出并回到 0。max30100Quick走 1.2 秒那条同步返回也可以因为只卡一秒调试按钮能忍。权限HAP 不是 root/dev/i2c-1必须开机chmod 0666。放到device/board/rk/rk3568_evb/下的放权脚本里init 的rk30board配置拉起来。只改应用、不重打 vendor板上会一直open: Permission denied。10. 和 IIO 共存时的权限、扫描调试阶段用 hdc 的 root shell 跑 C 工具不依赖放权。应用阶段hdc shell ls -l /dev/i2c-1 # 期望 crw-rw-rw- hdc shell for d in /sys/bus/iio/devices/iio:device*; do echo -n $d cat $d/name donename是max30100只说明内核驱动在。应用不要findIio(max30100)再去读 raw那是死路。扫描结果拿来做「模块在位」的绿勾可以做波形不行。若你坚持走 IIO buffer先确认cat /proc/interrupts | grep -i max # 按手指时计数要涨不涨就停。不要在 ArkTS 里对空的/dev/iio:deviceN做setInterval。11. 坑每一个都是真事轻触当按紧。窗口只有几毫米。指侧、指尖、悬空 1 毫米IR 都像空气。验收口令是「指腹把窗口盖住看到肉贴上去再数 5 秒」。空气和按紧的范围写进笔记。不要只记「通了」。下次模块换批次LED 电流差一档你还能对比。INT 脚当救命稻草。原理图画了 INTDTS 也写了驱动还是空 FIFO。量 GPIO2_D0 在采样时有没有低脉冲。没有脉冲就轮询不要为了「用上中断」卡两天内核。MODE 位含义记混。0x80 是复位不是 SpO2。只写 0x03 而不走 0x0b 那条实测序列有的批次 LED 不亮。先照抄本章序列再谈优化电流。和红外测温同时摸。同在 i2c1。总线本身允许共存。手却只有一只测血氧时按窗口测体温时要对准 13 cm别指望一个动作两颗芯片都满意。CAN 镜像。打开 CAN 会i2c1 disabled0x57 从世界上消失。先确认/sys/bus/i2c/devices/i2c-1还在。SELinux enforcing。开发镜像 permissive。一旦 enforceHAP 访问/dev/i2c-1会 403chmod 也不够要策略。交付前再收现在别给自己加戏。公式当真理。算出 87% 先复测三次。三次都低再看手指是不是按出白印缺血或窗口进了日光。12. 验收i2cdetect -y -r 1能看到 57 或 UU。max30100_tool quick在空气和按紧下 IR 范围明显不同按紧时max-min不是 0。max30100_tool spo2按紧 5 秒心率落在 50120 才算一次合格演示血氧 95100 常见偶尔一次失败允许重测。应用点「测量 5 秒」期间 UI 能点别的按钮不卡死。拔掉模块再跑工具必须打出0x57 无应答不许打印一组假的 98%。第 5 条是防「上次缓冲还在」。每次chip_init都清 FIFO 指针。应用线程结束要close(fd)。本章小结MAX30100 是 FIFO 光电传感器不是「读一个 raw 就是血氧」的 IIO 简单通道。内核驱动绑得上只说明地址 ACK。中断不来IIO 永远空。用户态轮询 0x02/0x04/0x05 才是稳的。初始化按复位 → LED → 100 Hz → MODE 0x0b → 清指针。写 MODE 被拒就停别往下算。验收看空气 vs 按紧的 IR/RED不看第一次算出来的百分比。算法是演示用经验公式。按紧 510 秒轻触无效。路径清单路径用途device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dtsi2c1 下 max3010057kernel/linux/linux-5.10/drivers/iio/health/max30100.c内核 IIO靠 INTkernel/linux/linux-5.10/Documentation/devicetree/bindings/iio/health/max30100.txt绑定文档defconfig 三处 CONFIG_MAX30100y驱动编进内核/dev/i2c-1用户态 I2C_RDWR/sys/bus/i2c/devices/1-0057/driver看有没有绑 max30100/sys/bus/iio/devices/iio:deviceN/name诊断用不要当数据源/proc/interruptsINT 来没来max30100_tool.c本章命令行验收entry/src/main/cpp/ 里 libboard.soNAPIspo2Start / spo2Result / max30100Quickvendor 放权脚本chmod 0666 /dev/i2c-1系列第 36 篇 · 芯片瑞芯微 RK3568 · OpenHarmony 4.1API 11 · Linux 5.10
返回列表