--- title: SPI驱动开发 tags: [Linux驱动, SPI, 嵌入式, I.MX6ULL, 总线驱动] created: 2026-09-16 updated: 2026-09-17 pdf_ref: "68_第六十二章 Linux SPI驱动实验" --- # SPI驱动开发 > **关联知识**: [[01-I2C驱动开发]] | [[03-Linux驱动开发核心/07-platform驱动模型]] ## 一、SPI协议概述 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第六十二章内容(SPI 物理协议在裸机篇第二十七章讲解),为扩展知识。 ### 1.1 SPI总线原理 SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的通信总线,由摩托罗拉公司提出。SPI采用主从架构,一个主设备可以挂载多个从设备,通过独立的片选信号(CS)选择通信对象。 SPI总线由四根信号线组成: | 信号线 | 全称 | 方向 | 说明 | | --------- | ------------------- | ----- | ---------------------- | | **SCLK** | Serial Clock | 主→从 | 时钟信号,由主设备产生 | | **MOSI** | Master Out Slave In | 主→从 | 主设备发送,从设备接收 | | **MISO** | Master In Slave Out | 从→主 | 从设备发送,主设备接收 | | **CS/SS** | Chip Select | 主→从 | 片选信号,低电平有效 | > **SPI vs I2C**: SPI使用4根线(I2C只需2根),但SPI速度更快(可达数十MHz),且为全双工通信。I2C支持多主多从(通过地址寻址),SPI则通过硬件CS线选择从设备。 ### 1.2 SPI工作模式 SPI有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)决定: | 模式 | CPOL | CPHA | 空闲时SCLK | 数据采样时刻 | | ------ | ---- | ---- | ---------- | ------------ | | Mode 0 | 0 | 0 | 低电平 | 上升沿采样 | | Mode 1 | 0 | 1 | 低电平 | 下降沿采样 | | Mode 2 | 1 | 0 | 高电平 | 下降沿采样 | | Mode 3 | 1 | 1 | 高电平 | 上升沿采样 | - **CPOL=0**: SCLK空闲时为低电平;**CPOL=1**: SCLK空闲时为高电平 - **CPHA=0**: 在SCLK第一个跳变沿采样数据;**CPHA=1**: 在SCLK第二个跳变沿采样数据 大多数SPI从设备支持多种模式,具体使用哪种模式需查阅设备数据手册。ICM20608支持Mode 0和Mode 3。 ### 1.3 SPI传输格式 SPI是全双工通信,每次传输时主从设备同时发送和接收数据。发送和接收的字节数相同,由SCLK时钟数决定。 ``` 时序示意图 (Mode 0: CPOL=0, CPHA=0): SCLK: _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_ MOSI: D7 D6 D5 D4 D3 D2 D1 D0 MISO: D7 D6 D5 D4 D3 D2 D1 D0 CS: ‾‾‾|___________________________|‾‾‾ ``` SPI传输数据时通常MSB(最高有效位)在前。SPI协议本身没有定义设备寻址机制,通过硬件CS线选择从设备,因此每增加一个从设备就需要一根CS线。 ## 二、Linux SPI架构 ### 2.1 SPI子系统架构 ```mermaid flowchart TD subgraph user_space["用户空间"] app["应用程序\nread/write/ioctl"] end subgraph kernel_space["Linux内核"] subgraph device_driver["SPI设备驱动层"] spi_drv["spi_driver\n(设备驱动程序)"] end subgraph core["SPI核心层"] core_api["SPI核心API\n(spi_sync/spi_write/spi_read)"] end subgraph controller["SPI控制器驱动层"] spi_master["spi_master\n(硬件控制器驱动)"] end end subgraph hardware["硬件层"] spi_bus["SPI总线\n(SCLK+MOSI+MISO+CS)"] spi_dev["SPI设备\n(ICM20608/W25Qxx等)"] end app -->|"read()/write()"| spi_drv spi_drv -->|"spi_transfer/spi_message"| core_api core_api -->|"transfer()"| spi_master spi_master -->|"寄存器操作"| spi_bus spi_bus <-->|"全双工通信"| spi_dev ``` ### 2.2 SPI控制器驱动 vs SPI设备驱动 Linux SPI子系统分为两层驱动: | 层次 | 说明 | 开发者 | 核心结构体 | | ----------------- | ----------------------------------------------- | -------- | ------------ | | **SPI控制器驱动** | 控制SOC内部SPI硬件控制器,负责实际的数据收发 | SOC厂商 | `spi_master` | | **SPI设备驱动** | 挂载在SPI总线上的具体设备驱动,实现设备特定逻辑 | 用户编写 | `spi_driver` | SPI控制器驱动一般由SOC厂商编写好(如I.MX6ULL的`spi-imx.c`),用户只需编写SPI设备驱动。这与I2C子系统中适配器驱动和设备驱动的关系完全一致。 ### 2.3 核心数据结构 **spi_master(SPI控制器)** ```c struct spi_master { struct device dev; struct list_head list; s16 bus_num; /* SPI总线编号 */ u16 num_chipselect; /* 片选信号数量 */ u16 dma_alignment; /* DMA对齐要求 */ u16 mode_bits; /* 支持的SPI模式位 */ u32 bits_per_word_mask; /* 支持的位宽掩码 */ u32 min_speed_hz; /* 最低传输速率 */ u32 max_speed_hz; /* 最高传输速率 */ u16 flags; spinlock_t bus_lock_spinlock; struct mutex bus_lock_mutex; bool bus_lock_flag; int (*setup)(struct spi_device *spi); /* 配置SPI设备 */ int (*transfer)(struct spi_device *spi, struct spi_message *mesg); /* 数据传输函数 */ int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); /* 单消息传输 */ }; ``` **spi_driver(SPI设备驱动)** ```c struct spi_driver { const struct spi_device_id *id_table; /* 传统匹配表 */ int (*probe)(struct spi_device *spi); /* 匹配成功后执行 */ int (*remove)(struct spi_device *spi); /* 驱动卸载时执行 */ void (*shutdown)(struct spi_device *spi); struct device_driver driver; }; ``` **spi_device(SPI设备)** ```c struct spi_device { struct spi_master *master; /* 所属的SPI控制器 */ u32 max_speed_hz; /* 最大时钟频率 */ u8 chip_select; /* 片选号 */ u8 mode; /* SPI模式 */ /* ... */ }; ``` **spi_transfer(SPI传输描述)** ```c struct spi_transfer { const void *tx_buf; /* 发送缓冲区 */ void *rx_buf; /* 接收缓冲区 */ unsigned len; /* 数据长度(全双工,收发长度相同) */ dma_addr_t tx_dma; /* DMA发送地址 */ dma_addr_t rx_dma; /* DMA接收地址 */ struct sg_table tx_sg; /* 发送散列表 */ struct sg_table rx_sg; /* 接收散列表 */ unsigned cs_change:1; /* 传输完成后是否改变CS状态 */ unsigned tx_nbits:3; /* 发送位宽(1/2/4线) */ unsigned rx_nbits:3; /* 接收位宽(1/2/4线) */ #define SPI_NBITS_SINGLE 0x01 /* 1bit传输 */ #define SPI_NBITS_DUAL 0x02 /* 2bits传输 */ #define SPI_NBITS_QUAD 0x04 /* 4bits传输 */ u8 bits_per_word; /* 每字传输位数 */ u16 delay_usecs; /* 传输间延时(微秒) */ u32 speed_hz; /* 传输时钟频率 */ struct list_head transfer_list; }; ``` **spi_message(SPI消息)** ```c struct spi_message { struct list_head transfers; /* spi_transfer链表 */ struct spi_device *spi; /* 目标SPI设备 */ unsigned is_dma_mapped:1; void (*complete)(void *context); /* 异步完成回调 */ void *context; unsigned frame_length; /* 帧长度 */ unsigned actual_length; /* 实际传输长度 */ int status; /* 传输状态 */ struct list_head queue; void *state; }; ``` **数据结构关系**: `spi_transfer` 通过链表组织成 `spi_message`,`spi_message` 绑定到 `spi_device`,而 `spi_device` 属于某个 `spi_master`。 ## 三、SPI设备树配置 ### 3.1 节点格式 SPI设备信息通过设备树节点描述。SPI设备节点挂在对应的SPI控制器节点下。 ```mermaid flowchart LR subgraph dts["设备树结构"] ecspi3["ecspi3: ecspi@02010000\n(SPI控制器节点)"] icm["icm20608@0\n(子节点: 设备信息)"] end ecspi3 --> icm ``` ### 3.2 关键属性说明 | 属性 | 说明 | 示例 | | ------------------------- | -------------------- | ----------------------------- | | `compatible` | 匹配驱动的兼容字符串 | `"alientek,icm20608"` | | `reg` | 片选通道号(CS号) | `<0>` 表示CS0 | | `spi-max-frequency` | SPI最大时钟频率 | `<8000000>` 即8MHz | | `fsl,spi-num-chipselects` | 片选信号数量 | `<1>` | | `cs-gpios` | 片选GPIO定义 | `<&gpio1 20 GPIO_ACTIVE_LOW>` | | `pinctrl-names` | IO配置名称 | `"default"` | | `pinctrl-0` | 对应pinctrl节点 | `<&pinctrl_ecspi3>` | ### 3.3 完整设备树示例(I.MX6ULL + ICM20608) ```dts /* I.MX6ULL设备树头文件中的ecspi3控制器节点 */ ecspi3: ecspi@02010000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi"; reg = <0x02010000 0x4000>; interrupts = ; clocks = <&clks IMX6UL_CLK_ECSPI3>, <&clks IMX6UL_CLK_ECSPI3>; clock-names = "ipg", "per"; dmas = <&sdma 7 7 1>, <&sdma 8 7 2>; dma-names = "rx", "tx"; status = "disabled"; }; /* 在板级dts文件中追加ICM20608设备节点 */ &ecspi3 { fsl,spi-num-chipselects = <1>; /* 片选数量: 1 */ cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* CS: GPIO1_IO20 */ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; /* IO配置 */ status = "okay"; /* 使能ecspi3 */ spidev: icm20608@0 { /* @0: 挂在CS0 */ compatible = "alientek,icm20608"; /* 匹配驱动 */ spi-max-frequency = <8000000>; /* 最大8MHz */ reg = <0>; /* CS通道号 */ }; }; /* IO引脚配置 */ &iomuxc { pinctrl_ecspi3: icm20608 { fsl,pins = < MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ >; }; }; ``` > **注意**: 片选信号UART2_TX_DATA被复用为普通GPIO1_IO20,而非ECSPI3的SS0,因为需要软件控制片选信号。 ## 四、SPI驱动API ### 4.1 驱动注册与注销 ```c #include /* 注册SPI设备驱动 - 成功返回0 */ int spi_register_driver(struct spi_driver *sdrv); /* 注销SPI设备驱动 - 无返回值 */ void spi_unregister_driver(struct spi_driver *sdrv); /* 注册SPI控制器 - 成功返回0 */ int spi_register_master(struct spi_master *master); /* 注销SPI控制器 - 无返回值 */ void spi_unregister_master(struct spi_master *master); /* 申请spi_master */ struct spi_master *spi_alloc_master(struct device *dev, unsigned size); /* 释放spi_master */ void spi_master_put(struct spi_master *master); /* spi_bitbang:通用bitbang框架的注册/注销,内部会调用 spi_register_master */ int spi_bitbang_start(struct spi_bitbang *bitbang); /* I.MX6U SPI 主机驱动使用此API注册 */ void spi_bitbang_stop(struct spi_bitbang *bitbang); /* 对应注销 */ ``` > 原书 62.1.1 节说明:`spi_bitbang_start()` 内部通过调用 `spi_register_master()` 完成 `spi_master` 注册;使用它注册时,注销需用 `spi_bitbang_stop()`。 ### 4.2 数据传输API | 函数 | 说明 | 阻塞 | 适用场景 | | ----------------------- | ------------------ | ---- | ---------------- | | `spi_sync()` | 同步传输,等待完成 | 是 | 通用,最常用 | | `spi_write()` | 发送数据 | 是 | 只需发送 | | `spi_read()` | 接收数据 | 是 | 只需接收 | | `spi_write_then_read()` | 先发后收 | 是 | 典型的寄存器读写 | | `spi_async()` | 异步传输 | 否 | 高性能场景 | ### 4.3 核心API详解 ```c /* 初始化spi_message */ void spi_message_init(struct spi_message *m); /* 将spi_transfer添加到spi_message队列尾部 */ void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m); /* 同步传输 - 阻塞等待SPI传输完成 */ int spi_sync(struct spi_device *spi, struct spi_message *message); /* 简单发送 - 发送len个字节 */ int spi_write(struct spi_device *spi, const u8 *buf, size_t len); /* 简单接收 - 接收len个字节 */ int spi_read(struct spi_device *spi, u8 *buf, size_t len); /* 先发后收 - 典型寄存器读操作 */ int spi_write_then_read(struct spi_device *spi, const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx); /* 配置SPI设备 - 设置mode、speed等 */ int spi_setup(struct spi_device *spi); ``` ### 4.4 SPI数据传输步骤 1. 申请并初始化 `spi_transfer`,设置 `tx_buf`(发送数据)、`rx_buf`(接收缓冲区)、`len`(数据长度) 2. 使用 `spi_message_init()` 初始化 `spi_message` 3. 使用 `spi_message_add_tail()` 将 `spi_transfer` 添加到 `spi_message` 队列 4. 使用 `spi_sync()` 完成SPI数据同步传输 ### 4.5 常用数据读写封装 ```c /* SPI多字节发送 */ static int spi_send(struct spi_device *spi, u8 *buf, int len) { int ret; struct spi_message m; struct spi_transfer t = { .tx_buf = buf, .len = len, }; spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(spi, &m); return ret; } /* SPI多字节接收 */ static int spi_receive(struct spi_device *spi, u8 *buf, int len) { int ret; struct spi_message m; struct spi_transfer t = { .rx_buf = buf, .len = len, }; spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(spi, &m); return ret; } ``` ## 五、完整源码分析 ### 5.1 ICM20608寄存器定义头文件 ```c /* icm20608reg.h - ICM20608寄存器地址 */ #ifndef ICM20608_H #define ICM20608_H #define ICM20608G_ID 0xAF /* ICM20608G ID值 */ #define ICM20608D_ID 0xAE /* ICM20608D ID值 */ /* 电源管理 */ #define ICM20_PWR_MGMT_1 0x6B #define ICM20_PWR_MGMT_2 0x6C /* 采样率分频 */ #define ICM20_SMPLRT_DIV 0x19 /* 配置寄存器 */ #define ICM20_CONFIG 0x1A /* 陀螺仪配置 */ #define ICM20_GYRO_CONFIG 0x1B /* 加速度计配置 */ #define ICM20_ACCEL_CONFIG 0x1C #define ICM20_ACCEL_CONFIG2 0x1D /* 低功耗模式 */ #define ICM20_LP_MODE_CFG 0x1E /* FIFO使能 */ #define ICM20_FIFO_EN 0x23 /* 设备ID */ #define ICM20_WHO_AM_I 0x75 /* 加速度数据寄存器(连续14字节: ACCEL_XOUT_H~GYRO_ZOUT_L) */ #define ICM20_ACCEL_XOUT_H 0x3B /* ... 陀螺仪和温度数据紧随其后 ... */ #endif ``` ### 5.2 ICM20608设备结构体 ```c #include #include #include #include #include #include #include #include #include #include #include #include #include "icm20608reg.h" #define ICM20608_CNT 1 #define ICM20608_NAME "icm20608" struct icm20608_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ struct device_node *nd; /* 设备节点 */ int major; /* 主设备号 */ void *private_data; /* 私有数据,存放spi_device */ int cs_gpio; /* 片选GPIO编号 */ signed int gyro_x_adc; /* 陀螺仪X轴原始值 */ signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */ signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */ signed int accel_x_adc; /* 加速度计X轴原始值 */ signed int accel_y_adc; /* 加速度计Y轴原始值 */ signed int accel_z_adc; /* 加速度计Z轴原始值 */ signed int temp_adc; /* 温度原始值 */ }; static struct icm20608_dev icm20608dev; ``` ### 5.3 SPI设备驱动注册与注销 ```c /* 传统匹配方式ID列表 */ static const struct spi_device_id icm20608_id[] = { {"alientek,icm20608", 0}, {} }; /* 设备树匹配列表 */ static const struct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { /* Sentinel */ } }; /* SPI驱动结构体 */ static struct spi_driver icm20608_driver = { .probe = icm20608_probe, .remove = icm20608_remove, .driver = { .owner = THIS_MODULE, .name = "icm20608", .of_match_table = icm20608_of_match, }, .id_table = icm20608_id, }; /* 驱动入口函数 */ static int __init icm20608_init(void) { return spi_register_driver(&icm20608_driver); } /* 驱动出口函数 */ static void __exit icm20608_exit(void) { spi_unregister_driver(&icm20608_driver); } module_init(icm20608_init); module_exit(icm20608_exit); MODULE_LICENSE("GPL"); ``` ### 5.4 probe和remove函数 ```c static int icm20608_probe(struct spi_device *spi) { /* 1、构建设备号 */ if (icm20608dev.major) { icm20608dev.devid = MKDEV(icm20608dev.major, 0); register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME); } else { alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME); icm20608dev.major = MAJOR(icm20608dev.devid); } /* 2、注册设备 */ cdev_init(&icm20608dev.cdev, &icm20608_ops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT); /* 3、创建类 */ icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME); if (IS_ERR(icm20608dev.class)) { return PTR_ERR(icm20608dev.class); } /* 4、创建设备 */ icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME); if (IS_ERR(icm20608dev.device)) { return PTR_ERR(icm20608dev.device); } /* 5、初始化SPI设备 */ spi->mode = SPI_MODE_0; /* MODE0: CPOL=0, CPHA=0 */ spi_setup(spi); icm20608dev.private_data = spi; /* 保存spi_device */ /* 6、初始化ICM20608内部寄存器 */ icm20608_reginit(); return 0; } static int icm20608_remove(struct spi_device *spi) { cdev_del(&icm20608dev.cdev); unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT); device_destroy(icm20608dev.class, icm20608dev.devid); class_destroy(icm20608dev.class); return 0; } ``` ### 5.5 寄存器读写函数(核心) ```c /* 从ICM20608读取多个寄存器数据 */ static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) { int ret = -1; unsigned char txdata[1]; unsigned char *rxdata; struct spi_message m; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); if (!t) return -ENOMEM; rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); if (!rxdata) goto out1; /* SPI全双工: 发送len+1字节, 第一个字节为寄存器地址(bit7=1表示读) */ txdata[0] = reg | 0x80; /* 读操作: 寄存器地址bit7置1 */ t->tx_buf = txdata; /* 发送缓冲区 */ t->rx_buf = rxdata; /* 接收缓冲区 */ t->len = len + 1; /* 总长度 = 地址 + 数据 */ spi_message_init(&m); /* 初始化spi_message */ spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); /* 同步传输 */ if (ret) goto out2; memcpy(buf, rxdata + 1, len); /* 跳过第一个字节(地址),拷贝数据 */ out2: kfree(rxdata); out1: kfree(t); return ret; } /* 向ICM20608多个寄存器写入数据 */ static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len) { int ret = -1; unsigned char *txdata; struct spi_message m; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); if (!t) return -ENOMEM; txdata = kzalloc(sizeof(char) + len, GFP_KERNEL); if (!txdata) goto out1; /* 发送len+1字节: 第一个字节为寄存器地址(bit7=0表示写) */ *txdata = reg & ~0x80; /* 写操作: 寄存器地址bit7清零 */ memcpy(txdata + 1, buf, len); /* 拷贝要写入的数据 */ t->tx_buf = txdata; t->len = len + 1; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); if (ret) goto out2; out2: kfree(txdata); out1: kfree(t); return ret; } /* 读取单个寄存器 */ static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) { u8 data = 0; icm20608_read_regs(dev, reg, &data, 1); return data; } /* 写入单个寄存器 */ static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) { u8 buf = value; icm20608_write_regs(dev, reg, &buf, 1); } ``` ### 5.6 数据读取与初始化 ```c /* 读取ICM20608原始数据(六轴+温度,共14字节) */ void icm20608_readdata(struct icm20608_dev *dev) { unsigned char data[14] = { 0 }; icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14); dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]); dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]); } /* ICM20608寄存器初始化 */ void icm20608_reginit(void) { u8 value = 0; /* 复位ICM20608 */ icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80); mdelay(50); /* 选择时钟源: PLL with X axis gyro reference */ icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01); mdelay(50); /* 读取并打印设备ID */ value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I); printk("ICM20608 ID = %#X\r\n", value); /* 配置采样率分频: 0 (不分频, Fs=1kHz) */ icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 陀螺仪配置: ±2000°/s */ icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 加速度计配置: ±16g */ icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 低通滤波配置 */ icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 加速度计低通滤波 */ icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 使能所有轴 */ icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 关闭低功耗模式 */ icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭FIFO */ icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); } ``` ### 5.7 字符设备驱动框架 ```c static int icm20608_open(struct inode *inode, struct file *filp) { filp->private_data = &icm20608dev; /* 设置私有数据 */ return 0; } static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) { signed int data[7]; long err = 0; struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data; icm20608_readdata(dev); data[0] = dev->gyro_x_adc; data[1] = dev->gyro_y_adc; data[2] = dev->gyro_z_adc; data[3] = dev->accel_x_adc; data[4] = dev->accel_y_adc; data[5] = dev->accel_z_adc; data[6] = dev->temp_adc; err = copy_to_user(buf, data, sizeof(data)); return 0; } static int icm20608_release(struct inode *inode, struct file *filp) { return 0; } static const struct file_operations icm20608_ops = { .owner = THIS_MODULE, .open = icm20608_open, .read = icm20608_read, .release = icm20608_release, }; ``` ### 5.8 I.MX6U SPI控制器驱动分析 I.MX6ULL的SPI控制器驱动位于 `drivers/spi/spi-imx.c`,采用platform驱动框架: ```c /* 设备树匹配表 */ static const struct of_device_id spi_imx_dt_ids[] = { { .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, }, { .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, }, { .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, }, { .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, }, { /* sentinel */ } }; static struct platform_driver spi_imx_driver = { .driver = { .name = "spi_imx", .of_match_table = spi_imx_dt_ids, }, .probe = spi_imx_probe, .remove = spi_imx_remove, }; module_platform_driver(spi_imx_driver); ``` 数据发送调用链: ``` spi_imx_transfer → spi_imx_pio_transfer → spi_imx_push → spi_imx->tx (函数指针,根据位宽选择u8/u16/u32发送函数) ``` 发送函数通过宏定义实现: ```c #define MXC_SPI_BUF_TX(type) \ static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \ { \ type val = 0; \ \ if (spi_imx->tx_buf) { \ val = *(type *)spi_imx->tx_buf; \ spi_imx->tx_buf += sizeof(type); \ } \ \ spi_imx->count -= sizeof(type); \ \ writel(val, spi_imx->base + MXC_CSPITXDATA); \ } MXC_SPI_BUF_TX(u8) /* 展开为spi_imx_buf_tx_u8 */ MXC_SPI_BUF_TX(u16) /* 展开为spi_imx_buf_tx_u16 */ MXC_SPI_BUF_TX(u32) /* 展开为spi_imx_buf_tx_u32 */ ``` ## 六、实验验证 ### 6.1 spidev工具测试 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第六十二章内容,为扩展知识(本章原书使用 icm20608 字符设备测试 APP,未涉及 spidev)。 Linux内核提供了spidev驱动,可以在用户空间直接测试SPI设备: ```bash # 加载spidev模块(如内核未编译进) modprobe spidev # 检查SPI设备节点 ls /dev/spidev* # 编译内核源码树中的 tools/spi/spidev_test.c 得到 spidev_test 工具 arm-linux-gnueabihf-gcc tools/spi/spidev_test.c -o spidev_test # 基本收发测试(默认发送 0x00...,打印收发结果) ./spidev_test -D /dev/spidev0.0 -s 1000000 -b 8 -v # 发送自定义数据(-p 后为十六进制字符串,此例为 W25Q 读ID命令 0x90 0x00 0x00 0x00) ./spidev_test -D /dev/spidev0.0 -p 90000000 -v ``` ### 6.2 ICM20608驱动测试步骤 ```bash # 1. 编译驱动模块 make -j32 # 生成 icm20608.ko # 2. 编译测试APP(使能硬件浮点) arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard \ icm20608App.c -o icm20608App # 3. 拷贝到开发板rootfs cp icm20608.ko icm20608App /rootfs/lib/modules/4.1.15/ # 4. 开发板上加载驱动 depmod # 首次加载需执行 modprobe icm20608 # 加载驱动模块 # 5. 运行测试 ./icm20608App /dev/icm20608 ``` ### 6.3 测试输出示例 ``` 原始值: gx = 12, gy = -8, gz = 5 ax = 2045, ay = 102, az = 16892 temp = 9856 实际值: act gx = 0.73°/S, act gy = -0.49°/S, act gz = 0.30°/S act ax = 1.00g, act ay = 0.05g, act az = 8.25g act temp = 31.24°C ``` 静止状态下Z轴加速度约1g(重力加速度),三轴角速度约0°/S,温度约30°C。 ## 七、跨平台对比 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 | 特性 | Linux SPI | I2C | STM32 HAL SPI | RK3568 SPI | | ------------ | ------------ | -------------- | ------------------------- | --------------- | | **总线类型** | 全双工4线 | 半双工2线 | 全双工4线 | 全双工4线 | | **速度** | 10~80MHz | 100K/400K/1MHz | 最高50MHz | 最高50MHz | | **设备寻址** | 硬件CS线 | 7bit地址 | 硬件CS线 | 硬件CS线 | | **多主支持** | 不支持 | 支持 | 不支持 | 不支持 | | **DMA支持** | 内核框架支持 | 内核框架支持 | HAL库支持 | 内核框架支持 | | **设备描述** | 设备树 | 设备树 | CubeMX配置 | 设备树 | | **驱动框架** | spi_driver | i2c_driver | HAL_SPI_Transmit | platform_driver | | **API示例** | spi_sync() | i2c_transfer() | HAL_SPI_TransmitReceive() | spi_sync() | **I.MX6ULL vs STM32 vs RK3568 SPI差异**: | 平台 | SPI控制器驱动 | 特点 | | -------- | ---------------- | ----------------------------------------------- | | I.MX6ULL | `spi-imx.c` | ECSPI控制器,支持32位传输,使用platform驱动框架 | | STM32F4 | `spi-stm32.c` | SPI/I2S控制器,支持SPI全双工/半双工模式 | | RK3568 | `spi-rockchip.c` | SPI控制器,支持DMA传输,兼容多种SPI Flash | ## 八、面试精选 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 题目1:SPI和I2C有什么区别?各适用于什么场景? **考察点**: 总线对比理解 | 对比项 | SPI | I2C | | -------- | ----------------------------- | ------------------------- | | 线数 | 4根(SCLK/MOSI/MISO/CS) | 2根(SCL/SDA) | | 速度 | 高(10~80MHz) | 低(100K/400K/1MHz) | | 通信方式 | 全双工 | 半双工 | | 寻址方式 | 硬件CS线 | 7bit/10bit软件地址 | | 多主支持 | 不支持 | 支持 | | 适用场景 | 高速数据传输(Flash、传感器) | 低速多设备(EEPROM、RTC) | **详细答案**: SPI适合对速度要求高的场景,如SPI Flash(W25Qxx可达80MHz)、高速ADC/DAC、显示屏等。I2C适合对引脚资源敏感、设备较多的场景,如EEPROM、温湿度传感器、RTC时钟等。SPI每增加一个从设备需要一根CS线,而I2C通过地址区分,理论上可挂128个设备。 --- ### 题目2:Linux SPI子系统架构是怎样的?与I2C有何异同? **考察点**: 驱动架构理解 **详细答案**: Linux SPI子系统分为三层:SPI设备驱动层(`spi_driver`)、SPI核心层(API函数)、SPI控制器驱动层(`spi_master`)。与I2C子系统高度相似:I2C使用`i2c_driver`→`i2c_core`→`i2c_adapter`,SPI使用`spi_driver`→`spi_core`→`spi_master`。 核心区别:I2C的`i2c_adapter`对应SPI的`spi_master`,I2C的`i2c_client`对应SPI的`spi_device`。两者都采用总线匹配机制,匹配函数分别为`i2c_match_device`和`spi_match_device`,都支持设备树匹配(compatible)、传统ID匹配(id_table)和名称匹配。 --- ### 题目3:SPI驱动中如何实现寄存器读写?tx_buf和rx_buf的作用是什么? **考察点**: SPI传输机制 **详细答案**: SPI是全双工通信,每次传输同时发送和接收数据。寄存器读写通常通过以下步骤: 1. **发送**: 构造发送缓冲区`tx_buf`,第一个字节为寄存器地址(读操作bit7置1,写操作bit7清零),后续字节为写入数据 2. **接收**: 构造接收缓冲区`rx_buf`,长度与发送相同 3. **全双工传输**: 调用`spi_sync()`,硬件同时发送`tx_buf`并接收数据到`rx_buf` 4. **解析**: 跳过`rx_buf`第一个字节(对应地址字节的响应),后续字节即为读取的寄存器值 `tx_buf`和`rx_buf`可以指向同一缓冲区(原地读写),但通常分开使用以提高可读性。长度由`len`统一指定(全双工收发长度相同)。 --- ### 题目4:SPI的四种模式(Mode 0~3)有什么区别?如何选择? **考察点**: SPI时序理解 **详细答案**: 四种模式由CPOL(时钟极性)和CPHA(时钟相位)组合决定: - **Mode 0 (CPOL=0, CPHA=0)**: SCLK空闲低电平,第一个边沿采样。最常用,多数SPI设备默认模式。 - **Mode 1 (CPOL=0, CPHA=1)**: SCLK空闲低电平,第二个边沿采样。 - **Mode 2 (CPOL=1, CPHA=0)**: SCLK空闲高电平,第一个边沿采样。 - **Mode 3 (CPOL=1, CPHA=1)**: SCLK空闲高电平,第二个边沿采样。 选择依据:必须查阅从设备数据手册。ICM20608支持Mode 0和Mode 3。如果主从设备模式不匹配,数据会出错。在Linux驱动中通过`spi->mode = SPI_MODE_X`设置,然后调用`spi_setup()`生效。 --- ### 题目5:I.MX6ULL的SPI控制器驱动是如何工作的?数据最终如何发送到硬件? **考察点**: 平台驱动理解 **详细答案**: I.MX6ULL的SPI控制器驱动`spi-imx.c`采用platform驱动框架。匹配成功后`spi_imx_probe()`执行,从设备树读取属性值,申请并初始化`spi_master`,调用`spi_bitbang_start()`注册到内核。 数据发送调用链:`spi_imx_transfer` → `spi_imx_pio_transfer` → `spi_imx_push` → `spi_imx->tx`(函数指针)。`spi_imx`结构体中维护了`tx`和`rx`函数指针,根据数据位宽(8/16/32位)选择对应的发送函数(如`spi_imx_buf_tx_u8`),最终通过`writel()`将数据写入ECSPI的TXDATA寄存器。这与裸机操作SPI寄存器的方式完全一致,只是被封装在Linux驱动框架中。 --- **代码来源**: Linux驱动例程 22_spi(基于正点原子I.MX6ULL开发板) **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第六十二章 Linux SPI驱动实验 **最后更新**: 2026-09-17