SPI原理

SPI(Serial Peripheral Interface)是一种高速全双工同步的串行通信协议。它广泛用于连接微控制器与外部设备,如传感器、存储器等。

SPI特点

特性维度 SPI (Serial Peripheral Interface)
信号线数量 至少4根线:MOSI(主出从入)、MISO(主入从出)、SCLK(时钟)、CS(片选,每个从机独享一根)
拓扑结构 点对点型:主设备与每个从设备都有独立的片选线连接。
通信模式 全双工:发送和接收可以同时进行。
数据传输 无应答机制:主设备产生时钟,从设备被动跟随,无法确认数据是否被成功接收。
速度 速度非常高(通常50Mbps以上),几乎只受限于硬件性能。
软件复杂度 软件简单:本质上是简单的移位寄存器,主要靠硬件实现。
硬件复杂度 硬件复杂:线多,尤其是从设备多时,需要大量片选线,占用大量IO口。
主要应用场景 高速器件(如我的项目中的ICM20608陀螺仪)、Flash存储器显示屏ADC/DAC等。
四种工作模式 通过串行时钟极性(CPOL)和**相位(**CPHA)的搭配来得到四种工作模式
image-20250914162853670

SPI通信通常需要4根基础信号线:

  • SCLK(Serial Clock):时钟信号,由主设备产生,用于同步数据交换。
  • MOSI(Master Out Slave In):主设备数据输出,从设备数据输入。
  • MISO(Master In Slave Out):主设备数据输入,从设备数据输出。
  • CS/SS(Chip Select/Slave Select):片选信号,由主设备控制,低电平有效,用于选择特定从设备。

SPI通信流程

一次基本的SPI通信流程通常遵循以下步骤:

  1. 主设备将目标从设备的CS线拉低(激活)。
  2. 主设备产生时钟信号SCLK
  3. 数据在SCLK的每个时钟周期内通过MOSI和MISO线同时传输(全双工)。
  4. 数据传输完成后,主设备停止时钟并将CS线拉高(释放)。

数据的传输:

  1. 数据传输是基于移位寄存器的
  2. 主设备和从设备各有一个8位移位寄存器
  3. 每个时钟周期,两个寄存器同时移位一位
  4. 8个时钟周期后,主从设备完成一个字节的交换
  5. 通常MSB(最高有效位)先传输,但也有LSB先传输的设备

SPI工作模式

SPI有4种工作模式,由时钟极性(CPOL)时钟相位(CPHA) 两个参数组合决定

  1. CPOL (时钟极性) - 决定时钟空闲状态
    • CPOL = 0:时钟信号 SCLK 在空闲状态时为低电平
    • CPOL = 1:时钟信号 SCLK 在空闲状态时为高电平
  2. CPHA (时钟相位) - 决定数据采样时刻
    • CPHA = 0:数据在第一个时钟边沿(即从空闲状态跳变到有效状态的第一个边沿)被采样(读取)。数据必须在时钟边沿到来之前就保持稳定。
    • CPHA = 1:数据在第二个时钟边沿(即返回到空闲状态的边沿)被采样(读取)。数据可以在第一个边沿时发生变化。
模式 CPOL CPHA 时钟空闲状态 数据采样时刻
0 0 0 低电平 第一个边沿(上升沿)
1 0 1 低电平 第二个边沿(下降沿)
2 1 0 高电平 第一个边沿(下降沿)
3 1 1 高电平 第二个边沿(上升沿)
  • 根据从设备的要求选择,不同设备可能支持不同的模式
  • 查阅从设备的数据手册,确定其支持的SPI模式

SPI驱动开发

SPI控制器

Linux中使用spi_master结构体描述SPI控制器,里面最重要的成员就是transfer函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
struct spi_master {
struct device dev;

struct list_head list;

/* other than negative (== assign one dynamically), bus_num is fully
* board-specific. usually that simplifies to being SOC-specific.
* example: one SOC has three SPI controllers, numbered 0..2,
* and one board's schematics might show it using SPI-2. software
* would normally use bus_num=2 for that controller.
*/
s16 bus_num;

/* chipselects will be integral to many controllers; some others
* might use board-specific GPIOs.
*/
u16 num_chipselect;

/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;

/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;

/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))

/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;

/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */

/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;

/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;

/* Setup mode and clock, etc (spi driver may call many times).
*
* IMPORTANT: this may be called when transfers to another
* device are active. DO NOT UPDATE SHARED REGISTERS in ways
* which could break those transfers.
*/
int (*setup)(struct spi_device *spi);

/* bidirectional bulk transfers
*
* + The transfer() method may not sleep; its main role is
* just to add the message to the queue.
* + For now there's no remove-from-queue operation, or
* any other request management
* + To a given spi_device, message queueing is pure fifo
*
* + The master's main job is to process its message queue,
* selecting a chip then transferring data
* + If there are multiple spi_device children, the i/o queue
* arbitration algorithm is unspecified (round robin, fifo,
* priority, reservations, preemption, etc)
*
* + Chipselect stays active during the entire message
* (unless modified by spi_transfer.cs_change != 0).
* + The message transfers use clock and SPI mode parameters
* previously established by setup() for this device
*/
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);

/* called on release() to free memory provided by spi_master */
void (*cleanup)(struct spi_device *spi);

/*
* Used to enable core support for DMA handling, if can_dma()
* exists and returns true then the transfer will be mapped
* prior to transfer_one() being called. The driver should
* not modify or store xfer and dma_tx and dma_rx must be set
* while the device is prepared.
*/
bool (*can_dma)(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer);

/*
* These hooks are for drivers that want to use the generic
* master transfer queueing mechanism. If these are used, the
* transfer() function above must NOT be specified by the driver.
* Over time we expect SPI drivers to be phased over to this API.
*/
bool queued;
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work pump_messages;
spinlock_t queue_lock;
struct list_head queue;
struct spi_message *cur_msg;
bool idling;
bool busy;
bool running;
bool rt;
bool auto_runtime_pm;
bool cur_msg_prepared;
bool cur_msg_mapped;
struct completion xfer_completion;
size_t max_dma_len;

int (*prepare_transfer_hardware)(struct spi_master *master);
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master);
int (*prepare_message)(struct spi_master *master,
struct spi_message *message);
int (*unprepare_message)(struct spi_master *master,
struct spi_message *message);

/*
* These hooks are for drivers that use a generic implementation
* of transfer_one_message() provied by the core.
*/
void (*set_cs)(struct spi_device *spi, bool enable);
int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
struct spi_transfer *transfer);
void (*handle_err)(struct spi_master *master,
struct spi_message *message);

/* gpio chip select */
int *cs_gpios;

/* DMA channels for use with core dmaengine helpers */
struct dma_chan *dma_tx;
struct dma_chan *dma_rx;

/* dummy data for full duplex devices */
void *dummy_rx;
void *dummy_tx;
};
  • transfer函数,和i2c_algorithm中的master_xfer函数一样,控制器数据传输函数。

  • SPI主机端最终会通过transfer函数与SPI设备进行通信,因此对于SPI主机控制器的驱动编写者而言transfer函数是需要实现的

SPI的主机驱动的工作

申请spi_master,然后初始化spi_master,最后向Linux内核注册spi_master。

  • 申请与释放

    • struct spi_master *spi_alloc_master(struct device *dev,unsigned size)
      
      1
      2
      3
      4
      5
      6
      7

      **dev:**设备,一般是platform_device中的dev成员变量。
      **size:**私有数据大小,可以通过spi_master_get_devdata函数获取到这些私有数据。
      **返回值:**申请到的spi_master。

      - ```c
      void spi_master_put(struct spi_master *master)
      **master:**要释放的spi_master。 **返回值:**无。
  • 注册与注销

    • int spi_register_master(struct spi_master *master)
      
      1
      2
      3
      4
      5
      6

      **master:**要注册的spi_master。
      **返回值:**0,成功;负值,失败。

      - ```c
      void spi_unregister_master(struct spi_master *master)
      **master:**要注销的spi_master。 **返回值:**无。

SPI设备

struct spi_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct spi_device {
struct device dev;
struct spi_controller *controller;
struct spi_controller *master; /* compatibility layer */
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
bool rt;
u32 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
#define SPI_CS_WORD 0x1000 /* toggle cs after each word */
#define SPI_TX_OCTAL 0x2000 /* transmit with 8 wires */
#define SPI_RX_OCTAL 0x4000 /* receive with 8 wires */
#define SPI_3WIRE_HIZ 0x8000 /* high impedance turnaround */
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
const char *driver_override;
int cs_gpio; /* LEGACY: chip select gpio */
struct gpio_desc *cs_gpiod; /* chip select gpio desc */
struct spi_delay word_delay; /* inter-word delay */

/* the statistics */
struct spi_statistics statistics;

/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - chipselect delays
* - ...
*/
};
参数名 (Parameter Name) 说明 (Description) 备注 (Notes)
max_speed_hz 该设备能支持的 SPI 时钟频率最大值 主设备必须配置为不超过此值的频率。
chip_select 该设备在其 SPI 主机控制器(spi_master)下的片选索引号 此索引号用于从主控制的 cs_gpios[] 数组中获取具体的GPIO引脚号:cs_gpio = cs_gpios[spi_device.chip_select]
cs_gpio (可选) 直接指定该设备片选引脚所使用的 GPIO 编号 如果提供,通常会优先使用此值而非通过 chip_select 索引查找。
bits_per_word 每个基本的 SPI 传输单元包含的位数 默认通常是8位。可以设置为其他值(如16位),并且可以大于32位,以支持DMA进行大批量连续传输。

mode 参数是一个位掩码,用于设置SPI通信的各种模式和行为。以下是其支持的标志:

模式标志 (Mode Flag) 含义 (Meaning) 备注 (Notes)
SPI_CPOL 时钟极性 (Clock Polarity)。设置时钟信号在空闲时的状态。
  • 0: 空闲时 SCK 为低电平。
  • 1: 空闲时 SCK 为高电平。
SPI_CPHA 时钟相位 (Clock Phase)。设置数据采样的时刻。
  • 0: 在时钟的第一个边沿(从空闲状态跳变后的边沿)采样。
  • 1: 在时钟的第二个边沿采样。
SPI_MODE_0 模式 0 SPI_CPOL = 0, SPI_CPHA = 0
SPI_MODE_1 模式 1 SPI_CPOL = 0, SPI_CPHA = 1
SPI_MODE_2 模式 2 SPI_CPOL = 1, SPI_CPHA = 0
SPI_MODE_3 模式 3 SPI_CPOL = 1, SPI_CPHA = 1
SPI_CS_HIGH 片选信号高有效 通常片选是低电平有效(CS引脚拉低表示选中)。设置此标志则表示片选为高电平有效
SPI_LSB_FIRST 数据传输从最低位 (LSB) 开始 标准SPI协议通常先传输最高位(MSB)。注意:很多SPI控制器硬件不支持此模式。
SPI_3WIRE 使用 3线制(半双工) 输入(MISO)和输出(MOSI)数据线合并为一条(SISO)。
SPI_LOOP 回环测试模式 控制器内部将输出(MOSI)与输入(MISO)短接,用于自检。
SPI_NO_CS 不使用片选信号 适用于总线上只有一个从设备的情况,无需片选操作。
SPI_READY 支持从设备就绪信号 从设备可以通过拉低某条信号线来通知主设备自己“未就绪”,主设备应等待。
SPI_TX_DUAL 发送时使用 2 条数据线
SPI_TX_QUAD 发送时使用 4 条数据线 这些是SPI的扩展模式(Dual/Quad SPI),常用于高速SPI Flash等器件,以提升数据传输带宽。
SPI_RX_DUAL 接收时使用 2 条数据线
SPI_RX_QUAD 接收时使用 4 条数据线

SPI设备树

  • SPI Master

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ecspi1: ecspi@02008000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
    reg = <0x02008000 0x4000>;
    interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_ECSPI1>,
    <&clks IMX6UL_CLK_ECSPI1>;
    clock-names = "ipg", "per";
    dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;
    dma-names = "rx", "tx";
    status = "disabled";
    };

    在设备树中,对于SPI Master,必须的属性如下:

    • #address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
    • #size-cells:必须设置为0
    • compatible:根据它找到SPI Master驱动

    可选的属性如下:

    • cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO
    • num-cs:片选引脚总数
  • SPI Device

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    &ecspi3 {
    fsl,spi-num-chipselects = <1>; /* 1个片选 */
    cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 片选引脚,软件片选! */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi3>;
    status = "okay";

    /* 对应的SPI芯片子节点 */
    spidev0: icm20608@0 { /* @后面的0表示次SPI芯片接到哪个硬件片选上 */
    reg = <0>;
    compatible = "kevin,icm20608";
    spi-max-frequency = <8000000>; /* SPI时钟频率8MHz*/
    };
    };

    在SPI Master对应的设备树节点下,每一个子节点都对应一个SPI设备,这个SPI设备连接在该SPI Master下面。

    这些子节点中,必选的属性如下:

    • **compatible:**根据它找到SPI Device驱动
    • **reg:**用来表示它使用哪个片选引脚
    • **spi-max-frequency:**必选,该SPI设备支持的最大SPI时钟

    可选的属性如下:

    • **spi-cpol:**这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平
    • **spi-cpha:**这是一个空属性(没有值),表示CPHA为1),即在时钟的第2个边沿采样数据
    • **spi-cs-high:**这是一个空属性(没有值),表示片选引脚高电平有效
    • **spi-3wire:**这是一个空属性(没有值),表示使用SPI 三线模式

SPI设备驱动

struct spi_driver

1
2
3
4
5
6
7
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_driver 和i2c_driver、platform_driver 基本一样,当SPI设备和驱动匹配成功以后probe 函数就会执行。

  • spi_driver 注册函数

    1
    int spi_register_driver(struct spi_driver *sdrv)

    **sdrv:**要注册的spi_driver。
    **返回值:**0,注册成功;赋值,注册失败。

  • spi_driver 注销函数

    1
    void spi_unregister_driver(struct spi_driver *sdrv)

    **sdrv:**要注销的spi_driver。

    **返回值:**无。

SPI设备与驱动匹配

SPI设备和驱动的匹配过程是由SPI总线来完成的,SPI总线为spi_bus_type

1
2
3
4
5
6
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
}

匹配的函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);

/* Check override first, and if set, only use the named driver */
if (spi->driver_override)
return strcmp(spi->driver_override, drv->name) == 0;

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;

if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);

return strcmp(spi->modalias, drv->name) == 0;
}

spi_match_device 函数和i2c_match_device 函数对于设备和驱动的匹配过程基本一样

  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较SPI设备节点的compatible 属性和of_device_id 中的compatible 属性是否相等,如果相当的话就表示SPI设备和驱动匹配。
  • spi_match_id函数用于传统的、无设备树的SPI 设备和驱动匹配过程。比较SPI设备名字和spi_device_id 的name 字段是否相等,相等的话就说明SPI 设备和驱动匹配。

SPI控制器驱动

spi-imx.c该驱动作为所有SPI设备驱动的基础,负责处理SPI总线的底层通信。SPI核心驱动提供了统一的接口,使得不同的SPI设备驱动可以方便地接入。

工作类别 具体描述 关键函数/操作
驱动初始化 向内核注册平台驱动,并在匹配到设备时执行探测函数。 platform_driver_register, spi_imx_probe
主机控制器注册 申请并设置 spi_master 结构体,包括片选数量、模式支持、回调函数等。 spi_alloc_master, 设置 master->bits_per_word_mask, master->num_chipselect, master->mode_bits
硬件资源申请 获取并映射 IO 内存空间、申请中断、配置 GPIO 片选引脚、使能时钟。 platform_get_resource, devm_ioremap_resource, platform_get_irq, devm_request_irq, of_get_named_gpio
回调函数设置 实现并设置一系列操作 SPI 控制器的回调函数,用于配置、数据传输等。 spi_imx_setup, spi_imx_transfer, spi_imx_prepare_message
传输队列管理 启动传输队列,处理上层提交的 SPI 消息。 spi_bitbang_start

SPI主机驱动器采用了platfom 驱动框架。当设备和驱动匹配成功以后spi_imx_probe 函数就会执行。

SPI设备数据收发处理流程

  1. 设 Transfer:初始化一个spi_transfer结构体,填入发送缓冲区tx_buf)、接收缓冲区rx_buf)和传输长度len)。
  2. 初始 Message:使用spi_message_init()初始化一个spi_message
  3. 挂载 Transfer:使用spi_message_add_tail()将设置好的spi_transfer添加到spi_message的队列中。
  4. 同步传输:调用spi_sync()函数。此函数会阻塞直到整个传输完成,此时数据已存在于rx_buf中。

transfer是单元,message是序列transfer描述“做什么”(发什么,收什么),message描述“做的顺序和时机”(一系列动作在一个片选周期内完成)。

  • struct spi_transfer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    struct spi_transfer {
    /* it's ok if tx_buf == rx_buf (right?)
    * for MicroWire, one buffer must be null
    * buffers must work with dma_*map_single() calls, unless
    * spi_message.is_dma_mapped reports a pre-existing mapping
    */
    const void *tx_buf;
    void *rx_buf;
    unsigned len;

    dma_addr_t tx_dma;
    dma_addr_t rx_dma;
    struct sg_table tx_sg;
    struct sg_table rx_sg;

    unsigned cs_change:1;
    unsigned tx_nbits:3;
    unsigned rx_nbits:3;
    #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
    #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
    #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
    u8 bits_per_word;
    u16 delay_usecs;
    struct spi_delay delay;
    struct spi_delay cs_change_delay;
    struct spi_delay word_delay;
    u32 speed_hz;

    u32 effective_speed_hz;

    unsigned int ptp_sts_word_pre;
    unsigned int ptp_sts_word_post;

    struct ptp_system_timestamp *ptp_sts;

    bool timestamped;

    struct list_head transfer_list;

    #define SPI_TRANS_FAIL_NO_START BIT(0)
    u16 error;
    };
    • tx_buf:保存着要发送的数据。
    • rx_buf:用于保存接收到的数据。
    • len:是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中也就没有发送长度和接收长度之分。
  • struct spi_message

    spi_transfer 需要组织成spi_message

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    struct spi_message {
    struct list_head transfers;

    struct spi_device *spi;

    unsigned is_dma_mapped:1;

    /* REVISIT: we might want a flag affecting the behavior of the
    * last transfer ... allowing things like "read 16 bit length L"
    * immediately followed by "read L bytes". Basically imposing
    * a specific message scheduling algorithm.
    *
    * Some controller drivers (message-at-a-time queue processing)
    * could provide that as their default scheduling algorithm. But
    * others (with multi-message pipelines) could need a flag to
    * tell them about such special cases.
    */

    /* completion is reported through a callback */
    void (*complete)(void *context);
    void *context;
    unsigned frame_length;
    unsigned actual_length;
    int status;

    /* for optional use by whatever driver currently owns the
    * spi_message ... between calls to spi_async and then later
    * complete(), that's the spi_controller controller driver.
    */
    struct list_head queue;
    void *state;

    /* list of spi_res reources when the spi message is processed */
    struct list_head resources;
    };
    • 在使用spi_message之前需要对其进行初始化

      1
      void spi_message_init(struct spi_message *m)

      **m:**要初始化的spi_message。
      **返回值:**无。

    • spi_message 初始化完成以后需要将spi_transfer添加到spi_message 队列中

      1
      void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

      **t:**要添加到队列中的spi_transfer。
      **m:**spi_transfer 要加入的spi_message。
      **返回值:**无。

    • spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输异步传输

      • 同步传输会阻塞的等待SPI 数据传输完成,同步传输函数为spi_sync

        1
        int spi_sync(struct spi_device *spi, struct spi_message *message)

        **spi:**要进行数据传输的spi_device。
        **message:**要传输的spi_message。
        **返回值:**无。

      • 异步传输不会阻塞的等到SPI 数据传输完成,异步传输需要设置spi_message 中的complete成员变量,complete 是一个回调函数,当SPI 异步传输完成以后此函数就会被调用

        1
        int spi_async(struct spi_device *spi, struct spi_message *message)

        **spi:**要进行数据传输的spi_device。
        **message:**要传输的spi_message。
        **返回值:**无。

因此SPI的收发为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 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 */
spi_message_add_tail(t, &m);/* 将spi_transfer 添加到spi_message 队列 */
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 */
spi_message_add_tail(t, &m);/* 将spi_transfer 添加到spi_message 队列 */
ret = spi_sync(spi, &m); /* 同步传输 */
return ret;
}

集成之后就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*
* @description : 向icm20608寄存器读写数据
* @param - dev: icm20608设备
* @param - reg: 寄存器地址
* @param -buf: 数据缓冲区
* @param - len: 数据长度
* @param - is_read: 是否为读操作,true为读,false为写
* @return : 操作结果
*/
static int icm20608_transfer(struct icm20608_dev *dev, u8 reg, void *buf, int len, bool is_read)
{
int ret = -1;
unsigned char *txdata;
unsigned char *rxdata = NULL;
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;
}

/* 如果是读操作,还需要为接收数据分配内存 */
if (is_read) {
rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);
if(!rxdata) {
goto out2;
}
}

/* 设置寄存器地址,读操作时bit8置1,写操作时bit8清零 */
txdata[0] = is_read ? (reg | 0x80) : (reg & ~0x80);

/* 写操作需要将要写入的数据拷贝到发送缓冲区 */
if (!is_read) {
memcpy(txdata + 1, buf, len);
}

/* 配置SPI传输 */
t->tx_buf = txdata;
t->rx_buf = is_read ? rxdata : NULL;
t->len = len + 1; /* 包含寄存器地址的总长度 */

spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
if(ret) {
goto out3;
}

/* 读操作需要将接收的数据拷贝到输出缓冲区 */
if (is_read) {
memcpy(buf, rxdata, len);
}

out3:
if (is_read) {
kfree(rxdata); /* 释放接收缓冲区 */
}
out2:
kfree(txdata); /* 释放发送缓冲区 */
out1:
kfree(t); /* 释放spi_transfer结构体 */

return ret;
}

/* 读寄存器的封装函数 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
return icm20608_transfer(dev, reg, buf, len, true);
}

/* 写寄存器的封装函数 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
return icm20608_transfer(dev, reg, buf, len, false);
}

spidev

spidev接口是一个在用户空间与内核空间之间提供SPI通信能力的桥梁。它通过一组文件操作接口(如open、read、write、ioctl等)暴露给用户空间程序。当用户空间程序打开spidev设备文件时,内核会为该程序创建一个表示SPI设备的文件描述符。随后,程序可以使用write和read系统调用来发送和接收数据,使用ioctl系统调用来配置SPI通信的参数(如时钟频率、时钟极性、时钟相位等)。spidev接口简化了SPI通信的复杂性,使得开发者无需深入了解内核驱动开发即可实现SPI通信。

image-20250914212501998

使用方法:

设备树里你需要的spi设备节点的compatible属性等于下列值,就会跟spidev驱动匹配:

  • “rohm,dh2228fv”
  • “lineartechnology,ltc2488”
  • “spidev”

匹配之后,spidev.c的spidev_probe会被调用,它会:

  • 分配一个spidev_data结构体,用来记录对于的spi_device
  • spidev_data会被记录在一个链表里
  • 分配一个次设备号,以后可以根据这个次设备号在链表里找到spidev_data
  • device_create:这会生产一个设备节点/dev/spidevB.D,B表示总线号,D表示它是这个SPI Master下第几个设备

以后,我们就可以通过/dev/spidevB.D来访问spidev驱动程序。

驱动

内核提供的测试程序:tools\spi\spidev_fdx.c

1
spidev_fdx [-h] [-m N] [-r N] /dev/spidevB.D
  • -h: 打印用法
  • -m N:先写1个字节0xaa,再读N个字节,**注意:**不是同时写同时读
  • -r N:读N个字节

spidev.c通过file_operations向应用层提供接口:

  • 单独的读/写函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    /* Read-only message with current device setup */
    static ssize_t
    spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
    {
    struct spidev_data *spidev;
    ssize_t status;

    /* chipselect only toggles at start or end of operation */
    if (count > bufsiz)
    return -EMSGSIZE;

    spidev = filp->private_data;

    mutex_lock(&spidev->buf_lock);
    status = spidev_sync_read(spidev, count);
    if (status > 0) {
    unsigned long missing;

    missing = copy_to_user(buf, spidev->rx_buffer, status);
    if (missing == status)
    status = -EFAULT;
    else
    status = status - missing;
    }
    mutex_unlock(&spidev->buf_lock);

    return status;
    }

    /* Write-only message with current device setup */
    static ssize_t
    spidev_write(struct file *filp, const char __user *buf,
    size_t count, loff_t *f_pos)
    {
    struct spidev_data *spidev;
    ssize_t status;
    unsigned long missing;

    /* chipselect only toggles at start or end of operation */
    if (count > bufsiz)
    return -EMSGSIZE;

    spidev = filp->private_data;

    mutex_lock(&spidev->buf_lock);
    missing = copy_from_user(spidev->tx_buffer, buf, count);
    if (missing == 0)
    status = spidev_sync_write(spidev, count);
    else
    status = -EFAULT;
    mutex_unlock(&spidev->buf_lock);

    return status;
    }
  • 双工传输

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    static long
    spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    int retval = 0;
    struct spidev_data *spidev;
    struct spi_device *spi;
    u32 tmp;
    unsigned n_ioc;
    struct spi_ioc_transfer *ioc;

    /* Check type and command number */
    if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
    return -ENOTTY;

    /* guard against device removal before, or while,
    * we issue this ioctl.
    */
    spidev = filp->private_data;
    spin_lock_irq(&spidev->spi_lock);
    spi = spi_dev_get(spidev->spi);
    spin_unlock_irq(&spidev->spi_lock);

    if (spi == NULL)
    return -ESHUTDOWN;

    /* use the buffer lock here for triple duty:
    * - prevent I/O (from us) so calling spi_setup() is safe;
    * - prevent concurrent SPI_IOC_WR_* from morphing
    * data fields while SPI_IOC_RD_* reads them;
    * - SPI_IOC_MESSAGE needs the buffer locked "normally".
    */
    mutex_lock(&spidev->buf_lock);

    switch (cmd) {
    /* read requests */
    case SPI_IOC_RD_MODE:
    retval = put_user(spi->mode & SPI_MODE_MASK,
    (__u8 __user *)arg);
    break;
    case SPI_IOC_RD_MODE32:
    retval = put_user(spi->mode & SPI_MODE_MASK,
    (__u32 __user *)arg);
    break;
    case SPI_IOC_RD_LSB_FIRST:
    retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
    (__u8 __user *)arg);
    break;
    case SPI_IOC_RD_BITS_PER_WORD:
    retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
    break;
    case SPI_IOC_RD_MAX_SPEED_HZ:
    retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
    break;

    /* write requests */
    case SPI_IOC_WR_MODE:
    case SPI_IOC_WR_MODE32:
    if (cmd == SPI_IOC_WR_MODE)
    retval = get_user(tmp, (u8 __user *)arg);
    else
    retval = get_user(tmp, (u32 __user *)arg);
    if (retval == 0) {
    struct spi_controller *ctlr = spi->controller;
    u32 save = spi->mode;

    if (tmp & ~SPI_MODE_MASK) {
    retval = -EINVAL;
    break;
    }

    if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
    ctlr->cs_gpiods[spi->chip_select])
    tmp |= SPI_CS_HIGH;

    tmp |= spi->mode & ~SPI_MODE_MASK;
    spi->mode = (u16)tmp;
    retval = spi_setup(spi);
    if (retval < 0)
    spi->mode = save;
    else
    dev_dbg(&spi->dev, "spi mode %x\n", tmp);
    }
    break;
    case SPI_IOC_WR_LSB_FIRST:
    retval = get_user(tmp, (__u8 __user *)arg);
    if (retval == 0) {
    u32 save = spi->mode;

    if (tmp)
    spi->mode |= SPI_LSB_FIRST;
    else
    spi->mode &= ~SPI_LSB_FIRST;
    retval = spi_setup(spi);
    if (retval < 0)
    spi->mode = save;
    else
    dev_dbg(&spi->dev, "%csb first\n",
    tmp ? 'l' : 'm');
    }
    break;
    case SPI_IOC_WR_BITS_PER_WORD:
    retval = get_user(tmp, (__u8 __user *)arg);
    if (retval == 0) {
    u8 save = spi->bits_per_word;

    spi->bits_per_word = tmp;
    retval = spi_setup(spi);
    if (retval < 0)
    spi->bits_per_word = save;
    else
    dev_dbg(&spi->dev, "%d bits per word\n", tmp);
    }
    break;
    case SPI_IOC_WR_MAX_SPEED_HZ:
    retval = get_user(tmp, (__u32 __user *)arg);
    if (retval == 0) {
    u32 save = spi->max_speed_hz;

    spi->max_speed_hz = tmp;
    retval = spi_setup(spi);
    if (retval == 0) {
    spidev->speed_hz = tmp;
    dev_dbg(&spi->dev, "%d Hz (max)\n",
    spidev->speed_hz);
    }
    spi->max_speed_hz = save;
    }
    break;

    default:
    /* segmented and/or full-duplex I/O request */
    /* Check message and copy into scratch area */
    ioc = spidev_get_ioc_message(cmd,
    (struct spi_ioc_transfer __user *)arg, &n_ioc);
    if (IS_ERR(ioc)) {
    retval = PTR_ERR(ioc);
    break;
    }
    if (!ioc)
    break; /* n_ioc is also 0 */

    /* translate to spi_message, execute */
    retval = spidev_message(spidev, ioc, n_ioc);
    kfree(ioc);
    break;
    }

    mutex_unlock(&spidev->buf_lock);
    spi_dev_put(spi);
    return retval;
    }

icm20608实验

ICM-20608

ICM-20608 是InvenSense 出品的一款6 轴MEMS 传感器,包括3 轴加速度和3 轴陀螺仪。

使用SPI 接口读写寄存器需要16 个时钟或者更多(如果读写操作包括多个字节的话),第一个字节包含要读写的寄存器地址,寄存器地址最高位是读写标志位,如果是读的话寄存器地址最高位要为1,如果是写的话寄存器地址最高位要为0,剩下的7 位才是实际的寄存器地址,寄存器地址后面跟着的就是读写的数据。

image-20250827212420473 image-20250827212517570 image-20250827212538389 image-20250827212628763

在icm20608 驱动中将陀螺仪和加速度计的测量范围全部设置到了最大,分别为±2000 和±16g。因此,在计算实际值的时候陀螺仪使用16.4加速度计使用2048

设备树编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
&ecspi3 {
fsl,spi-num-chipselects = <1>; /* 1个片选 */
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 片选引脚,软件片选! */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";

/* 对应的SPI芯片子节点 */
spidev0: icm20608@0 { /* @后面的0表示次SPI芯片接到哪个硬件片选上 */
reg = <0>;
compatible = "kevin,icm20608";
spi-max-frequency = <8000000>; /* SPI时钟频率8MHz*/
};
};
1
2
3
4
5
6
7
8
pinctrl_ecspi3: ecspi3grp{
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0/* CS */
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0X10b1/* SCLK */
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1/* MISO */
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1/* MOSI */
>;
};
  • 设置当前片选数量为1,因为就只接了一个ICM20608。
  • 一定要使用 “cs-gpios”属性来描述片选引脚,SPI 主机驱动就会控制片选引脚
  • 设置IO 要使用的pinctrl 子节点,也就是我们在示例代码62.5.1.1 中新建的pinctrl_ecspi3
  • icm20608 设备子节点,因为icm20608 连接在ECSPI3 的第0 个通道上,因此@后面为0。
  • 设置节点属性兼容值为“kevin,icm20608”
  • 设置SPI 最大时钟频率为8MHz,这是ICM20608 的SPI 接口所能支持的最大的时钟频率。
  • icm20608连接在通道0上,因此reg 为0。

驱动编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define ICM20_SELF_TEST_X_GYRO 0x00
#define ICM20_SELF_TEST_Y_GYRO 0x01
#define ICM20_SELF_TEST_Z_GYRO 0x02
#define ICM20_SELF_TEST_X_ACCEL 0x0D
#define ICM20_SELF_TEST_Y_ACCEL 0x0E
#define ICM20_SELF_TEST_Z_ACCEL 0x0F

/* 陀螺仪静态偏移 */
#define ICM20_XG_OFFS_USRH 0x13
#define ICM20_XG_OFFS_USRL 0x14
#define ICM20_YG_OFFS_USRH 0x15
#define ICM20_YG_OFFS_USRL 0x16
#define ICM20_ZG_OFFS_USRH 0x17
#define ICM20_ZG_OFFS_USRL 0x18

#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
#define ICM20_ACCEL_WOM_THR 0x1F
#define ICM20_FIFO_EN 0x23
#define ICM20_FSYNC_INT 0x36
#define ICM20_INT_PIN_CFG 0x37
#define ICM20_INT_ENABLE 0x38
#define ICM20_INT_STATUS 0x3A

/* 加速度输出 */
#define ICM20_ACCEL_XOUT_H 0x3B
#define ICM20_ACCEL_XOUT_L 0x3C
#define ICM20_ACCEL_YOUT_H 0x3D
#define ICM20_ACCEL_YOUT_L 0x3E
#define ICM20_ACCEL_ZOUT_H 0x3F
#define ICM20_ACCEL_ZOUT_L 0x40

/* 温度输出 */
#define ICM20_TEMP_OUT_H 0x41
#define ICM20_TEMP_OUT_L 0x42

/* 陀螺仪输出 */
#define ICM20_GYRO_XOUT_H 0x43
#define ICM20_GYRO_XOUT_L 0x44
#define ICM20_GYRO_YOUT_H 0x45
#define ICM20_GYRO_YOUT_L 0x46
#define ICM20_GYRO_ZOUT_H 0x47
#define ICM20_GYRO_ZOUT_L 0x48

#define ICM20_SIGNAL_PATH_RESET 0x68
#define ICM20_ACCEL_INTEL_CTRL 0x69
#define ICM20_USER_CTRL 0x6A
#define ICM20_PWR_MGMT_1 0x6B
#define ICM20_PWR_MGMT_2 0x6C
#define ICM20_FIFO_COUNTH 0x72
#define ICM20_FIFO_COUNTL 0x73
#define ICM20_FIFO_R_W 0x74
#define ICM20_WHO_AM_I 0x75

/* 加速度静态偏移 */
#define ICM20_XA_OFFSET_H 0x77
#define ICM20_XA_OFFSET_L 0x78
#define ICM20_YA_OFFSET_H 0x7A
#define ICM20_YA_OFFSET_L 0x7B
#define ICM20_ZA_OFFSET_H 0x7D
#define ICM20_ZA_OFFSET_L 0x7E
#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; /* 私有数据 */
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;

/*
* @description : 从icm20608读取多个寄存器数据
* @param - dev: icm20608设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
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;
}

/* 一共发送len+1个字节的数据,第一个字节为
寄存器首地址,一共要读取len个字节长度的数据,*/
txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->rx_buf = rxdata; /* 要读取的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
if(ret) {
goto out2;
}

memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */

out2:
kfree(rxdata); /* 释放内存 */
out1:
kfree(t); /* 释放内存 */

return ret;
}

/*
* @description : 向icm20608多个寄存器写入数据
* @param - dev: icm20608设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
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个字节的数据,第一个字节为
寄存器首地址,len为要写入的寄存器的集合,*/
*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */
memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
if(ret) {
goto out2;
}

out2:
kfree(txdata); /* 释放内存 */
out1:
kfree(t); /* 释放内存 */
return ret;
}

/*
* @description : 读取icm20608指定寄存器值,读取一个寄存器
* @param - dev: icm20608设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}

/*
* @description : 向icm20608指定寄存器写入指定的值,写一个寄存器
* @param - dev: icm20608设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 1);
}

/*
* @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
* : 三轴加速度计和内部温度。
* @param - dev : ICM20608设备
* @return : 无。
*/
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]);
}

/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
* 一般在open的时候将private_data似有向设备结构体。
* @return : 0 成功;其他 失败
*/
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev; /* 设置私有数据 */
return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
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;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int icm20608_release(struct inode *inode, struct file *filp)
{
return 0;
}

/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};

/*
* ICM20608内部寄存器初始化函数
* @param : 无
* @return : 无
*/
void icm20608_reginit(void)
{
u8 value = 0;

icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);

value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\r\n", value);

icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}

/*
* @description : spi驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
*
*/
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);
}

/*初始化spi_device */
spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
icm20608dev.private_data = spi; /* 设置私有数据 */

/* 初始化ICM20608内部寄存器 */
icm20608_reginit();
return 0;
}

/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @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;
}

/* 传统匹配方式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,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KEVIN");
寄存器 设置值 解释
ICM20_SMPLRT_DIV (0x19) 0x00 采样率分频器 计算公式:采样率 = 内部采样率 / (1 + SMPLRT_DIV)。 设置 0x00 意味着 SMPLRT_DIV = 0,因此采样率等于内部采样率(通常为1kHz)。这是为了获得最高的输出数据率(ODR)。
ICM20_GYRO_CONFIG (0x1B) 0x18 陀螺仪配置 查看寄存器位定义: - Bit 4:3 FS_SEL[1:0]: 陀螺仪量程选择。 - 00 = ±250 dps - 01 = ±500 dps - 10 = ±1000 dps - 11 = ±2000 dps - Bit 1:0 FCHOICE_B[1:0]: 用于选择是否 bypass 数字低通滤波器 (DLPF)。 - 00 = 使用 DLPF (由 CONFIG 寄存器配置) - 其他值 = bypass DLPF (带宽更宽,噪声更大) 0x18 的二进制是 0001 1000。 - FS_SEL[1:0] (Bit4:3) = 11±2000 dps - FCHOICE_B[1:0] (Bit1:0) = 00使用 DLPF 此设置选择了陀螺仪的最大量程并启用了抗混叠滤波。
ICM20_ACCEL_CONFIG (0x1C) 0x18 加速度计配置 位定义类似陀螺仪: - Bit 4:3 ACCEL_FS_SEL[1:0]: 加速度计量程选择。 - 00 = ±2g - 01 = ±4g - 10 = ±8g - 11 = ±16g 0x18 的二进制是 0001 1000。 - ACCEL_FS_SEL[1:0] (Bit4:3) = 11±16g 此设置选择了加速度计的最大量程。
ICM20_CONFIG (0x1A) 0x04 全局配置 (主要配置陀螺仪DLPF) - Bit 2:0 DLPF_CFG[2:0]: 数字低通滤波器配置。 根据手册中的表格,DLPF_CFG = 4 (即 0x04 的二进制 100) 对应的陀螺仪带宽为 20Hz (噪声带宽 30.5Hz)。 此设置将陀螺仪的通路限制在20Hz,有效抑制高频噪声,适用于大多数运动检测场景。
ICM20_ACCEL_CONFIG2 (0x1D) 0x04 加速度计配置2 - Bit 3 ACCEL_FCHOICE_B: 加速度计DLPF选择位。 - 1 = bypass DLPF (带宽 ~1046Hz) - 0 = 使用 DLPF - Bit 2:0 A_DLPF_CFG[2:0]: 加速度计DLPF配置。 根据手册表格,A_DLPF_CFG = 4 (即 0x04 的二进制 100) 对应的加速度计带宽为 21.2Hz (噪声带宽 31.0Hz)。 此设置将加速度计的通路也限制在约21Hz,与陀螺仪设置匹配,同样是为了滤除高频噪声。
ICM20_PWR_MGMT_2 (0x6C) 0x00 电源管理2 这个寄存器的低6位用于控制各轴的待机模式: - Bit 5: STBY_XA (X加速度计待机) - Bit 4: STBY_YA (Y加速度计待机) - Bit 3: STBY_ZA (Z加速度计待机) - Bit 2: STBY_XG (X陀螺仪待机) - Bit 1: STBY_YG (Y陀螺仪待机) - Bit 0: STBY_ZG (Z陀螺仪待机) 1 = 关闭该轴, 0 = 开启该轴。 0x00 的二进制是 0000 0000,意味着所有低6位都是0此设置开启了加速度计和陀螺仪的所有轴,使它们全部进入工作状态。
ICM20_LP_MODE_CFG (0x1E) 0x00 低功耗模式配置 设置 0x00 会关闭低功耗模式下的各种循环采样功能。 此设置确保了传感器工作在正常的“低噪声”全功耗模式,而不是为了省电而间歇工作的“标准”模式,从而获得最佳性能。
ICM20_FIFO_EN (0x23) 0x00 FIFO使能 这个寄存器的每一位控制是否将一种传感器数据(温度、各轴陀螺仪、加速度计)写入FIFO缓冲区。 0x00 表示禁止所有数据写入FIFO此设置意味着我们选择直接读取传感器数据寄存器,而不是使用FIFO功能。这简化了数据读取流程,适合常规应用。

应用层代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
signed int databuf[7];
unsigned char data[14];
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
signed int accel_x_adc, accel_y_adc, accel_z_adc;
signed int temp_adc;

float gyro_x_act, gyro_y_act, gyro_z_act;
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;

int ret = 0;

if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}

filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}

while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* 数据读取成功 */
gyro_x_adc = databuf[0];
gyro_y_adc = databuf[1];
gyro_z_adc = databuf[2];
accel_x_adc = databuf[3];
accel_y_adc = databuf[4];
accel_z_adc = databuf[5];
temp_adc = databuf[6];

/* 计算实际值 */
gyro_x_act = (float)(gyro_x_adc) / 16.4;
gyro_y_act = (float)(gyro_y_adc) / 16.4;
gyro_z_act = (float)(gyro_z_adc) / 16.4;
accel_x_act = (float)(accel_x_adc) / 2048;
accel_y_act = (float)(accel_y_adc) / 2048;
accel_z_act = (float)(accel_z_adc) / 2048;
temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;

printf("\r\n原始值:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
printf("temp = %d\r\n", temp_adc);
printf("实际值:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
printf("act temp = %.2f°C\r\n", temp_act);
}
usleep(100000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}

运行结果:

1
2
3
4
5
6
7
原始值:
gx = -4, gy = 2, gz = -1
ax = -7, ay = 339, az = 2007
temp = 649
实际值:act gx = -0.24°/S, act gy = 0.12°/S, act gz = -0.06°/S
act ax = -0.00g, act ay = 0.17g, act az = 0.98g
act temp = 26.91°C