ICM-20608 SPI IIO 驱动开发 文章封面

ICM-20608 SPI IIO 驱动开发

一、驱动概述

ICM-20608 是 InvenSense 出品的 6 轴 IMU(惯性测量单元),集成:

功能说明
3 轴加速度计16-bit,量程可选 ±2/4/8/16g
3 轴陀螺仪16-bit,量程可选 ±250/500/1000/2000 dps
温度传感器内部结温,灵敏度 326.8 LSB/°C
  • 硬件平台:iMX6ULL ALPHA,ECSPI3,CS = GPIO1_IO20
  • 驱动框架:Linux IIO 子系统 + Workqueue
  • 内核版本:4.14
  • WHO_AM_I:0xAE(ICM-20608)/ 0xAF(ICM-20608G)

二、硬件接口与寄存器

2.1 SPI 接线

iMX6ULL (ECSPI3)          ICM-20608
  GPIO1_IO20  ──────────  CS   (软件片选,低有效)
  UART2_RX  → SCLK ─────  SCL
  UART2_CTS → MOSI ─────  SDA/SDI
  UART2_RTS → MISO ─────  SDO
  (可选) GPIO ──────────  INT  (DATA_RDY 中断)

⚠️ ECSPI3 复用了 UART2 引脚,需在 DTS 中禁用 uart2 节点释放引脚。

2.2 SPI 读写协议

读操作:┌─────────────────┬──────────────────┐
        │ 0x80 | reg_addr │    data (×N)     │
        └─────────────────┴──────────────────┘

写操作:┌─────────────────┬──────────────────┐
        │    reg_addr     │      data        │
        └─────────────────┴──────────────────┘
第 1 字节 bit7:1 = 读,0 = 写

2.3 关键寄存器

地址名称说明
0x19SMPLRT_DIV采样率分频,200Hz = 1kHz/(1+4)
0x1ACONFIGDLPF 低通滤波配置
0x1BGYRO_CONFIG陀螺仪量程,0x18=±2000dps
0x1CACCEL_CONFIG加速度量程,0x18=±16g
0x1DACCEL_CONFIG2加速度 DLPF
0x38INT_ENABLEbit0=DATA_RDY_EN
0x3BACCEL_XOUT_H数据起始,连续 14 字节
0x6BPWR_MGMT_1bit7=复位,bit6=睡眠
0x75WHO_AM_I设备 ID(0xAE/0xAF)

2.4 数据寄存器布局(0x3B ~ 0x48,14 字节)

buf[0..1]  → ACCEL_X  (s16, big-endian)
buf[2..3]  → ACCEL_Y
buf[4..5]  → ACCEL_Z
buf[6..7]  → TEMP
buf[8..9]  → GYRO_X
buf[10..11]→ GYRO_Y
buf[12..13]→ GYRO_Z

2.5 物理量换算

通道量程LSB 灵敏度换算公式
加速度±16g2048 LSB/ga(m/s²) = raw × 9.80665 / 2048
陀螺仪±2000dps16.4 LSB/dpsω(rad/s) = raw × π / (180 × 16.4)
温度—326.8 LSB/°CT(°C) = raw / 326.8 + 25

三、整体架构与初始化流程

3.1 软件层次

用户空间:cat in_accel_z_raw  /  icm20608_test phys
              ↓
IIO 核心:sysfs 属性 → read_raw() 回调
              ↓
驱动层(两种模式):
  Polling:read_raw → mutex → spi_write_then_read
  IRQ 模式:IRQ 上半部 → schedule_work → work 下半部 → SPI read → 缓存 raw[]
              ↓  spi_write_then_read / spi_write
SPI 总线:ECSPI3,8MHz,SPI_MODE_0
              ↓
ICM-20608 芯片

3.2 Probe 初始化流程

flowchart TD
    A[icm20608_probe 入口] --> B[spi_setup<br/>bits=8 mode=SPI_MODE_0]
    B --> C[devm_iio_device_alloc<br/>分配 iio_dev + icm20608_data]
    C --> D[mutex_init<br/>INIT_WORK 初始化 workqueue]
    D --> E[icm20608_hw_init]
    E --> F[读 WHO_AM_I<br/>验证 0xAE 或 0xAF]
    F -->|不匹配| G[return -ENODEV]
    F -->|匹配| H["写 PWR_MGMT_1=0x80<br/>SW Reset"]
    H --> I[msleep 100ms]
    I --> J["写 PWR_MGMT_1=0x01<br/>退出睡眠 PLL 时钟"]
    J --> K[配置 SMPLRT_DIV<br/>CONFIG / GYRO / ACCEL]
    K --> L{spi->irq > 0?}
    L -->|有 INT 引脚| M["INT_ENABLE=0x01<br/>devm_request_irq"]
    L -->|无 INT 引脚| N[polling 模式]
    M --> O[填充 indio_dev<br/>name/info/channels]
    N --> O
    O --> P[devm_iio_device_register]
    P --> Q[probe ok<br/>sysfs 属性自动创建]

四、数据结构

classDiagram
    class icm20608_data {
        +spi_device *spi
        +mutex lock
        +work_struct work
        +s16 raw[7]
    }
    class iio_dev {
        +char *name
        +iio_info *info
        +iio_chan_spec *channels
        +int num_channels
    }
    class iio_chan_spec {
        +iio_chan_type type
        +int modified
        +int channel2
        +int address
        +long info_mask_separate
        +long info_mask_shared_by_type
    }
    class iio_info {
        +read_raw() int
    }
    class spi_device {
        +u32 max_speed_hz
        +u8 bits_per_word
        +u8 mode
        +int irq
    }
    class work_struct {
        +func icm20608_work_handler
    }

    iio_dev --> icm20608_data : iio_priv 追加
    iio_dev --> iio_info : .info
    iio_dev "1" --> "7" iio_chan_spec : .channels[]
    icm20608_data --> spi_device : .spi
    icm20608_data --> work_struct : .work

7 个 IIO 通道:

通道typemodifieraddresssysfs 属性
加速度 XIIO_ACCELIIO_MOD_X0in_accel_x_raw
加速度 YIIO_ACCELIIO_MOD_Y1in_accel_y_raw
加速度 ZIIO_ACCELIIO_MOD_Z2in_accel_z_raw
温度IIO_TEMP—3in_temp_raw
陀螺仪 XIIO_ANGL_VELIIO_MOD_X4in_anglvel_x_raw
陀螺仪 YIIO_ANGL_VELIIO_MOD_Y5in_anglvel_y_raw
陀螺仪 ZIIO_ANGL_VELIIO_MOD_Z6in_anglvel_z_raw

五、关键代码说明

5.1 IRQ 模式读取时序图

sequenceDiagram
    participant HW as ICM-20608 芯片
    participant IRQ as IRQ 上半部<br/>icm20608_irq_handler
    participant WQ as Workqueue 下半部<br/>icm20608_work_handler
    participant R as read_raw 回调
    participant U as 用户空间

    HW->>IRQ: DATA_RDY 中断(RISING)
    IRQ->>WQ: schedule_work<br/>立即返回 IRQ_HANDLED
    Note over IRQ: 上半部不能睡眠<br/>不能调用 spi_sync
    WQ->>WQ: mutex_lock
    WQ->>HW: spi_write_then_read<br/>burst read 14 bytes
    HW-->>WQ: raw buf[0..13]
    WQ->>WQ: 解码 7 路 s16 存入 raw[]
    WQ->>WQ: mutex_unlock
    U->>R: cat in_accel_z_raw
    R->>R: mutex_lock
    R->>R: *val = raw[IDX_ACCEL_Z]
    R->>R: mutex_unlock
    R-->>U: "2022n"

5.2 为什么需要 Workqueue

中断上半部Workqueue 下半部
执行上下文中断上下文内核线程(进程上下文)
能否睡眠❌ 不能✅ 可以
spi_sync❌ 禁止(可能睡眠)✅ 允许
设计原则最短路径,只 schedule_work执行实际 I/O

5.3 SPI 读写实现

/* 单字节读:发1字节地址(bit7=1),读1字节数据 */
static int icm20608_reg_read(struct spi_device *spi, u8 reg, u8 *val)
{
    u8 cmd = reg | BIT(7);
    return spi_write_then_read(spi, &amp;cmd, 1, val, 1);
}

/* 14字节 burst read:同一 SPI 事务,CS 全程有效,保证数据同源 */
static int icm20608_bulk_read(struct spi_device *spi, u8 reg,
                               u8 *buf, int len)
{
    u8 cmd = reg | BIT(7);
    return spi_write_then_read(spi, &amp;cmd, 1, buf, len);
}

spi_write_then_read vs 手动 spi_message:

spi_write_then_read 内部会先发送,然后在 同一 CS 激活期间 接收数据,保证 14 字节来自同一采样周期,等效于 AP3216C 中的 ap3216c_read_reg16。

5.4 IIO 通道宏

#define ICM20608_ACCEL_CHAN(_mod, _idx) {
    .type                    = IIO_ACCEL,
    .modified                = 1,
    .channel2                = (_mod),       /* IIO_MOD_X/Y/Z */
    .address                 = (_idx),       /* 索引 raw[] 数组 */
    .info_mask_separate      = BIT(IIO_CHAN_INFO_RAW),
    .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), /* 三轴共享 */
}

address 字段是驱动自用字段,IIO 核心不关心其值,在 read_raw 里用它直接索引 raw[] 数组,避免在 switch/case 里枚举每个轴。


六、DTS 配置

/* imxplore.dts */

/* 1. 释放 UART2 引脚 */
&amp;uart2 {
    status = "disabled";
};

/* 2. ECSPI3 引脚复用 */
&amp;iomuxc {
    pinctrl_ecspi3: ecspi3grp {
        fsl,pins = &lt;
            MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20   0x10b0  /* CS (GPIO软件片选) */
            MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK  0x10b1
            MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI    0x10b1
            MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO    0x10b1
        &gt;;
    };
};

/* 3. ECSPI3 控制器 + ICM-20608 子节点 */
&amp;ecspi3 {
    fsl,spi-num-chipselects = &lt;1&gt;;
    cs-gpios = &lt;&amp;gpio1 20 GPIO_ACTIVE_LOW&gt;;
    pinctrl-names = "default";
    pinctrl-0 = &lt;&amp;pinctrl_ecspi3&gt;;
    status = "okay";

    icm20608@0 {
        compatible = "alientek,icm20608";
        reg = &lt;0&gt;;
        spi-max-frequency = &lt;8000000&gt;;
    };
};


七、调试过程记录

问题 1:probe 未执行(driver not bound)

项说明
现象insmod 成功,/sys/bus/spi/drivers/icm20608/ 为空
原因DTB 未更新,或 UART2 占用了 ECSPI3 的引脚导致 pinctrl 冲突
修复重新编译 DTB + 在 DTS 中添加 &uart2 { status = "disabled"; }

问题 2:WHO_AM_I 不匹配

项说明
现象unexpected WHO_AM_I=0xAE (expect 0xAF)
原因板载芯片是 ICM-20608(0xAE),代码写的是 ICM-20608G(0xAF)
修复同时接受 0xAE 和 0xAF

八、测试验证

8.1 加载与验证

insmod /lib/modules/4.14.13-xxx/icm20608.ko
dmesg | tail -5
# 期望:WHO_AM_I=0xAE OK → ICM-20608 probe ok

ls /sys/bus/iio/devices/iio:device0/
# 期望:in_accel_x_raw  in_anglvel_x_raw  in_temp_raw  等

8.2 测试 App

icm20608_test once       # 读一次原始值
icm20608_test            # 持续轮询
icm20608_test phys       # 换算物理量(m/s²、rad/s、°C)

8.3 典型数据(板子平放静置)

通道原始值物理量说明
accel_z~2048~9.8 m/s²Z 轴感受重力
accel_x/y~0~0 m/s²无水平加速度
gyro x/y/z~0~0 rad/s静止无角速度
temp~4343~38°C芯片结温,高于室温正常

九、与 AP3216C 驱动对比

维度AP3216C (I²C)ICM-20608 (SPI)
总线i2c_transferspi_write_then_read
批量读3×2字节独立读1次14字节 burst
中断/workqueue无INIT_WORK + schedule_work
IIO 通道3(ALS/PS/IR)7(加速度/陀螺仪/温度)
通道宏手写每个通道#define 宏批量生成
scale/offset无提供 SCALE + OFFSET

十、文件索引

drivers/icm20608/
├── icm20608.c     ← SPI IIO 驱动
└── Makefile

app/icm20608_test/
├── icm20608_test.c    ← 测试应用(once/poll/phys 三模式)
└── Makefile

kernel4.14/arch/arm/boot/dts/
└── imxplore.dts   ← 禁用 uart2 + ecspi3 + icm20608@0


参考

延伸阅读

ICM-20608 同时涉及 IIO 与 SPI,建议沿这两个子系统回看基础。

发表评论

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

滚动至顶部