一、驱动概述
AP3216C 是 Lite-On 出品的三合一传感器,集成在一颗 IC 内:
| 功能 | 英文 | 说明 |
|---|
| 环境光传感器 | ALS(Ambient Light Sensor) | 16-bit,量程 0.35~20661 lux |
| 接近传感器 | PS(Proximity Sensor) | 10-bit,内置 IR LED 发射 |
| 红外传感器 | IR | 10-bit,测量 PS IR LED 的环境反射 |
- 硬件平台:iMX6ULL ALPHA,I²C1 总线,地址
0x1E
- 驱动框架:Linux IIO(Industrial I/O)子系统
- 内核版本:4.14
二、硬件接口与寄存器
2.1 I²C 连接
iMX6ULL AP3216C
I2C1_SCL ───────── SCL
I2C1_SDA ───────── SDA
3.3V ───────── VCC
GND ───────── GND
2.2 关键寄存器
| 地址 | 名称 | 说明 |
|---|
| 0x00 | SYS_CONFIG | 0=掉电,1=ALS,2=PS+IR,3=ALS+PS+IR,4=SW Reset |
| 0x0A | IR_L | IR 低字节:bit7=IR_OF(无效标志),bits[1:0]=IR[1:0] |
| 0x0B | IR_H | IR 高字节:bits[7:0]=IR[9:2] |
| 0x0C | ALS_L | ALS 低字节:bits[7:0]=ALS[7:0] |
| 0x0D | ALS_H | ALS 高字节:bits[7:0]=ALS[15:8] |
| 0x0E | PS_L | PS 低字节:bit7=物体标志,bits[3:0]=PS[3:0] |
| 0x0F | PS_H | PS 高字节:bits[5:0]=PS[9:4] |
2.3 数据拼接公式
/* IR:注意 H/L 字节含义与 ALS/PS 相反 */
ir = ((u16)ir_h << 2) | (ir_l & 0x03); /* 10-bit */
/* ALS:标准 16-bit */
als = ((u16)als_h << 8) | als_l; /* 16-bit */
/* PS:标准拼接 */
ps = ((u16)(ps_h & 0x3F) << 4) | (ps_l & 0x0F); /* 10-bit */
⚠️ IR 字节序陷阱:IR 高字节是 IR[9:2](8位),低字节只用 bits[1:0]。
错误写法 (H & 0x03) << 8 | L 会把值放大 64 倍,导致 IR 出现 256/512/768 等异常值。
三、整体架构与初始化流程
3.1 软件层次
用户空间:cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw
↓
IIO 核心:sysfs 属性 → read_raw() 回调
↓
驱动层:ap3216c_read_raw → ap3216c_read_all
↓ 3 次独立 2 字节 i2c_transfer
I²C 总线:[write reg_addr][read 2 bytes]
↓
AP3216C 芯片:寄存器 0x0A~0x0F
3.2 Probe 初始化流程
flowchart TD
A[ap3216c_probe 入口] --> B{i2c_check_functionality<br/>检查 I2C_FUNC_I2C}
B -->|不支持| C[return -EOPNOTSUPP]
B -->|支持| D[devm_iio_device_alloc<br/>分配 iio_dev + ap3216c_data]
D --> E[iio_priv 获取私有指针<br/>绑定 client / mutex_init]
E --> F["i2c_smbus_write_byte_data<br/>REG_SYS = 0x04(SW Reset)"]
F --> G["msleep 50ms<br/>等待复位完成"]
G --> H["i2c_smbus_write_byte_data<br/>REG_SYS = 0x03(ALS+PS+IR 全开)"]
H --> I["msleep 160ms<br/>等待首次测量有效"]
I --> J[填充 indio_dev 字段<br/>name / info / channels / modes]
J --> K[devm_iio_device_register<br/>注册到 IIO 核心]
K -->|失败| L[dev_err + return ret]
K -->|成功| M["dev_info: probe ok<br/>sysfs 属性自动创建"]
四、数据结构
classDiagram
class ap3216c_data {
+i2c_client *client
+mutex lock
}
class iio_dev {
+char *name
+iio_info *info
+iio_chan_spec *channels
+int num_channels
+int modes
}
class iio_chan_spec {
+iio_chan_type type
+int modified
+int channel2
+long info_mask_separate
}
class iio_info {
+read_raw() int
+driver_module
}
class i2c_client {
+u16 addr
+i2c_adapter *adapter
}
iio_dev --> ap3216c_data : iio_priv 追加
iio_dev --> iio_info : info 指针
iio_dev --> iio_chan_spec : channels 数组
ap3216c_data --> i2c_client : client 指针
三个 IIO 通道声明:
static const struct iio_chan_spec ap3216c_channels[] = {
{ .type = IIO_LIGHT, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) },
{ .type = IIO_PROXIMITY, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) },
{ .type = IIO_INTENSITY, .modified = 1, .channel2 = IIO_MOD_LIGHT_IR,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) },
};
| 通道 | type | modifier | sysfs 属性 |
|---|
| ALS | IIO_LIGHT | — | in_illuminance_raw |
| PS | IIO_PROXIMITY | — | in_proximity_raw |
| IR | IIO_INTENSITY | IIO_MOD_LIGHT_IR | in_intensity_ir_raw |
五、关键代码说明
5.1 读取时序图
sequenceDiagram
participant U as 用户空间<br/>cat in_illuminance_raw
participant K as IIO 核心
participant D as read_raw 回调
participant H as ap3216c_read_all
participant I as i2c_transfer
participant HW as AP3216C 芯片
U->>K: open / read sysfs 属性
K->>D: read_raw(IIO_CHAN_INFO_RAW)
D->>D: mutex_lock
D->>H: ap3216c_read_all
H->>I: read_reg16(0x0A) IR 2B
I->>HW: write 0x0A → read 2 bytes
HW-->>I: ir_buf[0..1]
H->>I: read_reg16(0x0C) ALS 2B
I->>HW: write 0x0C → read 2 bytes
HW-->>I: als_buf[0..1]
H->>I: read_reg16(0x0E) PS 2B
I->>HW: write 0x0E → read 2 bytes
HW-->>I: ps_buf[0..1]
H-->>D: ir / als / ps 解码值
D->>D: mutex_unlock
D-->>K: *val = als,IIO_VAL_INT
K-->>U: "14n"
5.2 ap3216c_read_reg16:2 字节原子读
static int ap3216c_read_reg16(struct i2c_client *client, u8 reg, u8 buf[2])
{
struct i2c_msg msgs[2] = {
{ .addr = client->addr, .flags = 0, .len = 1, .buf = &reg },
{ .addr = client->addr, .flags = I2C_M_RD, .len = 2, .buf = buf },
};
int ret = i2c_transfer(client->adapter, msgs, 2);
return (ret == 2) ? 0 : (ret < 0 ? ret : -EIO);
}
一次 i2c_transfer 包含”写寄存器地址”和”读 2 字节”两个消息,但在同一 I²C 事务内完成(Repeated START),消除了高低字节之间的竞争窗口。
5.3 ap3216c_read_all:三路数据读取
static int ap3216c_read_all(struct ap3216c_data *data,
int *ir_val, int *als_val, int *ps_val)
{
u8 ir_buf[2], als_buf[2], ps_buf[2];
int ret;
ret = ap3216c_read_reg16(data->client, AP3216C_REG_IR_L, ir_buf);
if (ret) return ret;
ret = ap3216c_read_reg16(data->client, AP3216C_REG_ALS_L, als_buf);
if (ret) return ret;
ret = ap3216c_read_reg16(data->client, AP3216C_REG_PS_L, ps_buf);
if (ret) return ret;
*als_val = ((int)als_buf[1] << 8) | als_buf[0];
if (ir_buf[0] & BIT(7)) { /* IR_OF:IR 无效,PS 也不可信 */
*ir_val = 0;
*ps_val = 0;
} else {
*ir_val = ((int)ir_buf[1] << 2) | (ir_buf[0] & 0x03);
*ps_val = ((ps_buf[1] & 0x3F) << 4) | (ps_buf[0] & 0x0F);
}
return 0;
}
六、调试过程(四轮 Bug 修复记录)
Bug 1:独立字节读 — 竞争条件
| 项 | 说明 |
|---|
| 现象 | IR 在静置环境出现 0/257/512/515 等跳变 |
| 原因 | 用两次 i2c_smbus_read_byte_data 分别读 IR_L 和 IR_H;芯片以 ~12.5ms 刷新 PS/IR 寄存器,两次读之间可能发生更新 |
| 修复 | 改用 i2c_smbus_read_i2c_block_data 一次读 6 字节 |
Bug 2:SMBus Block Read — 无自增
| 项 | 说明 |
|---|
| 现象 | ALS=IR,PS=ALS/16(如 ALS=512,PS=32,IR=512) |
| 原因 | AP3216C 不支持 SMBus Block Read 协议;read_i2c_block_data 会把同一寄存器(0x0A)读 6 次,buf=[2,2,2,2,2,2] |
| 修复 | 改用 i2c_transfer 带 6 字节读消息 |
Bug 3:6 字节 i2c_transfer — 地址仅自增 2 字节
| 项 | 说明 |
|---|
| 现象 | 偶发全零行;部分行 ALS=IR 模式依然出现 |
| 原因 | AP3216C 地址指针每次自增后仅在 2 字节处回绕(读 0x0A→0x0B→0x0A→0x0B→0x0A→0x0B),导致三路通道读到相同的 2 字节 |
| 修复 | 改为 3 次独立的 ap3216c_read_reg16(每次 2 字节,一个 I²C 事务) |
Bug 4:IR 字节序解析错误
| 项 | 说明 |
|---|
| 现象 | IR 出现 256/512/768/769 等以 256 为步长的跳变(静置值不稳定) |
| 原因 | 代码写成 (ir_buf[1] & 0x03) << 8 | ir_buf[0],把 IR_H(实为 IR[9:2])当成 2-bit 高位;实际 IR 高字节是 8-bit,应左移 2 位 |
| 修复 | 改为 (ir_buf[1] << 2) | (ir_buf[0] & 0x03) |
七、测试验证
7.1 加载驱动
insmod /lib/modules/4.14.13-xxx/ap3216c.ko
dmesg | tail -3
# 期望:ap3216c 0-001e: AP3216C probe ok (ALS+PS+IR)
ls /sys/bus/iio/devices/iio:device0/
# 应看到:in_illuminance_raw in_proximity_raw in_intensity_ir_raw
7.2 使用测试 App
ap3216c_test # 持续轮询,每秒打印一次
ap3216c_test once # 读一次后退出
ap3216c_test watch # 仅在数值变化超过阈值时打印
7.3 典型测试数据(室内静置 + 手靠近)
Found device: /sys/bus/iio/devices/iio:device0
Polling AP3216C (Ctrl+C to exit)...
ALS(lux_raw)= 17 PS(prox)= 29 IR= 0 ← 静置
ALS(lux_raw)= 20 PS(prox)= 0 IR= 0
ALS(lux_raw)= 24 PS(prox)= 0 IR= 7
ALS(lux_raw)= 22 PS(prox)= 2 IR= 6
...
ALS(lux_raw)= 0 PS(prox)= 344 IR= 0 ← 手遮挡,ALS 暗,PS 大跳
ALS(lux_raw)= 3 PS(prox)= 143 IR= 0
ALS(lux_raw)= 5 PS(prox)= 152 IR= 15
| 指标 | 静置正常值 | 含义 |
|---|
| ALS | 10~35 | 室内弱光,合理 |
| PS | 0~50(噪声底) | 无遮挡时接近 0 |
| IR | 0~30 | 环境 IR 小值,稳定 |
手靠近时:ALS 跌至 0(被遮挡),PS 跳至 100~400(IR LED 反射增强)。
八、文件索引
drivers/ap3216c/
├── ap3216c.c ← IIO 驱动主文件
└── Makefile
app/ap3216c_test/
├── ap3216c_test.c ← 测试应用
└── Makefile
kernel4.14/arch/arm/boot/dts/
└── imxplore.dts ← i2c1 节点 + ap3216c@1e 子节点
九、参考
- AP3216C 数据手册(Lite-On, Rev 1.6)
- 15_Linux_IIO子系统详解 — IIO 子系统通用知识
drivers/iio/light/ 内核参考驱动(vcnl4000.c、bh1750.c)
延伸阅读
AP3216C 是 IIO 框架的 I2C 实践,可与 IIO 原理和 SPI IMU 实现对照。