一、驱动概述
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 关键寄存器
地址 名称 说明 0x19 SMPLRT_DIV 采样率分频,200Hz = 1kHz/(1+4) 0x1A CONFIG DLPF 低通滤波配置 0x1B GYRO_CONFIG 陀螺仪量程,0x18=±2000dps 0x1C ACCEL_CONFIG 加速度量程,0x18=±16g 0x1D ACCEL_CONFIG2 加速度 DLPF 0x38 INT_ENABLE bit0=DATA_RDY_EN 0x3B ACCEL_XOUT_H 数据起始,连续 14 字节 0x6B PWR_MGMT_1 bit7=复位,bit6=睡眠 0x75 WHO_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 灵敏度 换算公式 加速度 ±16g 2048 LSB/g a(m/s²) = raw × 9.80665 / 2048陀螺仪 ±2000dps 16.4 LSB/dps ω(rad/s) = raw × π / (180 × 16.4)温度 — 326.8 LSB/°C T(°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 通道:
通道 type modifier address sysfs 属性 加速度 X IIO_ACCEL IIO_MOD_X 0 in_accel_x_raw加速度 Y IIO_ACCEL IIO_MOD_Y 1 in_accel_y_raw加速度 Z IIO_ACCEL IIO_MOD_Z 2 in_accel_z_raw温度 IIO_TEMP — 3 in_temp_raw陀螺仪 X IIO_ANGL_VEL IIO_MOD_X 4 in_anglvel_x_raw陀螺仪 Y IIO_ANGL_VEL IIO_MOD_Y 5 in_anglvel_y_raw陀螺仪 Z IIO_ANGL_VEL IIO_MOD_Z 6 in_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, &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, &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 引脚 */
&uart2 {
status = "disabled";
};
/* 2. ECSPI3 引脚复用 */
&iomuxc {
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
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
>;
};
};
/* 3. ECSPI3 控制器 + ICM-20608 子节点 */
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
icm20608@0 {
compatible = "alientek,icm20608";
reg = <0>;
spi-max-frequency = <8000000>;
};
};
七、调试过程记录
问题 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_workIIO 通道 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,建议沿这两个子系统回看基础。