AP3216C IIO 驱动开发 文章封面

AP3216C IIO 驱动开发

一、驱动概述

AP3216C 是 Lite-On 出品的三合一传感器,集成在一颗 IC 内:

功能英文说明
环境光传感器ALS(Ambient Light Sensor)16-bit,量程 0.35~20661 lux
接近传感器PS(Proximity Sensor)10-bit,内置 IR LED 发射
红外传感器IR10-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 关键寄存器

地址名称说明
0x00SYS_CONFIG0=掉电,1=ALS,2=PS+IR,3=ALS+PS+IR,4=SW Reset
0x0AIR_LIR 低字节:bit7=IR_OF(无效标志),bits[1:0]=IR[1:0]
0x0BIR_HIR 高字节:bits[7:0]=IR[9:2]
0x0CALS_LALS 低字节:bits[7:0]=ALS[7:0]
0x0DALS_HALS 高字节:bits[7:0]=ALS[15:8]
0x0EPS_LPS 低字节:bit7=物体标志,bits[3:0]=PS[3:0]
0x0FPS_HPS 高字节: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) },
};

通道typemodifiersysfs 属性
ALSIIO_LIGHT—in_illuminance_raw
PSIIO_PROXIMITY—in_proximity_raw
IRIIO_INTENSITYIIO_MOD_LIGHT_IRin_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-&gt;addr, .flags = 0,        .len = 1, .buf = &amp;reg },
        { .addr = client-&gt;addr, .flags = I2C_M_RD, .len = 2, .buf = buf },
    };
    int ret = i2c_transfer(client-&gt;adapter, msgs, 2);
    return (ret == 2) ? 0 : (ret &lt; 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-&gt;client, AP3216C_REG_IR_L,  ir_buf);
    if (ret) return ret;
    ret = ap3216c_read_reg16(data-&gt;client, AP3216C_REG_ALS_L, als_buf);
    if (ret) return ret;
    ret = ap3216c_read_reg16(data-&gt;client, AP3216C_REG_PS_L,  ps_buf);
    if (ret) return ret;

    *als_val = ((int)als_buf[1] &lt;&lt; 8) | als_buf[0];

    if (ir_buf[0] &amp; BIT(7)) {          /* IR_OF:IR 无效,PS 也不可信 */
        *ir_val = 0;
        *ps_val = 0;
    } else {
        *ir_val = ((int)ir_buf[1] &lt;&lt; 2) | (ir_buf[0] &amp; 0x03);
        *ps_val = ((ps_buf[1] &amp; 0x3F) &lt;&lt; 4) | (ps_buf[0] &amp; 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

指标静置正常值含义
ALS10~35室内弱光,合理
PS0~50(噪声底)无遮挡时接近 0
IR0~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 实现对照。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部