抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1Linux下SPI驱动框架

1.1 SPI驱动框架简介

SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。不管是什么 SPI 设备, SPI 控制器部分的驱动都是一样,我们的重点就落在了种类繁多的 SPI 设备驱动。

1.2 SPI主机驱动-spi_master

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动spi_master 是个结构体:

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
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num;
u16 num_chipselect;
u16 dma_alignment;
u16 mode_bits;
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);

int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
.........
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
.........
};
  • transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。

  • transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message, SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。

和 I2C 适配器驱动一样, SPI 主机驱动一般都是 SOC 厂商去编写的,所以我们作为 SOC 的使用者,这一部分的驱动就不用操心了,除非你是在 SOC 原厂工作,内容就是写 SPI 主机驱动。

SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master

  • spi_master 申请与释放

    • spi_alloc_master 函数用于申请 spi_master

      1
      2
      struct spi_master *spi_alloc_master(struct device *dev,
      unsigned size)
      • dev:设备,一般是 platform_device 中的 dev 成员变量。

      • size: 私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。

      • 返回值: 申请到的 spi_master

    • spi_master_put 函数用于释放spi_master

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

    • spi_master 注册函数为spi_register_master
    1
    int spi_register_master(struct spi_master *master)
    • master:要注册的 spi_master。
    • 返回值: 0,成功;负值,失败。
    • I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册, spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。
  • spi_master注销函数 spi_unregister_master

    1
    void spi_unregister_master(struct spi_master *master)
    • master:要注销的 spi_master
    • 返回值: 无。
    • 如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉spi_master

1.3 SPI设备驱动-spi_driver

Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,我们在编写 SPI 设备驱动的时候需要实 现 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 注册函数为spi_register_driver

    1
    int spi_register_driver(struct spi_driver *sdrv)
    • sdrv: 要注册的 spi_driver。
    • 返回值:0,注册成功;赋值,注册失败。
  • spi_driver 的注销函数为spi_unregister_driver

    1
    void spi_unregister_driver(struct spi_driver *sdrv)
    • sdrv: 要注销的 spi_driver。
    • 返回值: 无

1.4 设备和驱动匹配过程

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,
};

从上,可以知道SPI 设备和驱动的匹配函数为 spi_match_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);

/* 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;
}
  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。

  • acpi_driver_match_device 函数用于 ACPI 形式的匹配。

  • spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_idname 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。

  • 比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。

1.5收发函数

当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 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
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;
u32 speed_hz;

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

spi_transfer 需要组织成 spi_messagespi_message 也是一个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct spi_message {
struct list_head transfers;

struct spi_device *spi;

unsigned is_dma_mapped:1;
.......
/* 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_master controller driver.
*/
struct list_head queue;
void *state;
};

在使用spi_message之前需要对其进行初始化, spi_message初始化函数为spi_message_init,函数原型如下:

1
void spi_message_init(struct spi_message *m)
  • m: 要初始化的 spi_message

  • 返回值: 无。

spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用到 spi_message_add_tail 函数,此函数原型如下:

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 异步传输完成以后此函数就会被调用。 SPI 异步传输函数为 spi_async,函数原型如下:

1
int spi_async(struct spi_device *spi, struct spi_message *message)
  • spi: 要进行数据传输的 spi_device。
  • message:要传输的 spi_message。
  • 返回值: 无。

综上所述,SPI 数据传输步骤如下:

  1. 申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len成员变量,也就是要进行数据通信的长度。
  2. 使用spi_message_init 函数初始化 spi_message
  3. 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
  4. 使用spi_sync 函数完成 SPI 数据同步传输。

2 NXP官方驱动

打开IMX自己写的SPI,文件名为spi-imx.c,找到spi_imx_data结构体、

1
2
3
4
5
6
7
8
9
10
11
12
13
struct spi_imx_data {
struct spi_bitbang bitbang;

struct completion xfer_done;
void __iomem *base;
struct clk *clk_per;
struct clk *clk_ipg;
unsigned long spi_clk;
.........
const struct spi_imx_devtype_data *devtype_data;
int chipselect[0];
};

分析:主要部分

image-20241224224307007

从probe函数开始,

注冊spi_master

image-20241225192643903

初始化,最后一句吧master赋予了spi_max,

image-20241225192834122

for循环是获取spi片选引脚,支持4个硬件片选

image-20241225193139723

现在只需要对spi_imx初始化就行了

image-20241225193044200

跳转到spi_imx_setupxfer函数,函数主要是这段,

image-20241225193351665

1
2
spi_imx->rx = spi_imx_buf_rx_u8;   //最终的SPI接收函数
spi_imx->tx = spi_imx_buf_tx_u8; //发送函数
1
spi_imx->devtype_data->config(spi_imx, &config);//对应的就是以下结构体的mx51_ecspi_config   配置6ULL的SPI控制寄存器

image-20241225193803387

mx51_ecspi_config这个函数就是配置SPI的

bitbang下的spi_imx_transfer调用spi_imx_pio_transfer调用spi_imx_push调用spi_imx->tx(spi_imx);

image-20241225194436493

接下来是中断处理接收

image-20241225194934603

最终进入spi_bitbang_start

image-20241225195030136

最终会使用spi_register_master向系统注册SPI。

3 驱动编写

3.1 修改设备树

设备树内添加以下

1
2
3
4
5
6
7
8
pinctrl_ecspio3: icm20602 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
>;
};
  • 注意屏蔽掉其他占用的引脚,不光UART2_RX这些要屏蔽,GPIO1_IO20也要屏蔽。

  • 这里片选信号不作为硬件片选,而是作为普通的GPIO,我们在程序里面自行控制片选引脚。

然后在ECSPI3节点下创建icm20602子节点。

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

//对应的SPI芯片子节点
spidev0: icm20602@0 { //@后面的0表示SPI芯片接到哪个硬件片选上 6ull硬件接口支持4个硬件片选
reg = <0>;
compatible = "cbus,icm20602";
spi-max-frequency = <8000000>; //SPI时钟频率
};
};

可以看到设备已经添加成功。

image-20241226205039704

3.2 驱动程序

具体细节都在代码快内部有注释。

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
#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include "linux/spi/spi.h"
#include "linux/miscdevice.h"
#include "icm20602reg.h"
//#include "math.h"

#define MISC_ICM20602_NAME "icm20602"
#define MISC_ICM20602_MINOR 255 //255就是自动分配

struct icm20602_dev {
void *private;
int cs_gpio;
struct device_node *nd;

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; /* 温度原始值 */
};
struct icm20602_dev icm20602dev;

#if 0
//spi读寄存器
static int icm20602_read_regs(struct icm20602_dev *dev,u8 reg,void *buf,int len)
{
int ret = 0;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private;

//片选拉低
gpio_set_value(dev->cs_gpio,0);

//构建spi_transfer
t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL); //申请内存

//第一步:发送要读取的寄存器地址
txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1;

spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t,&m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi,&m); /* 同步发送 */

//第二步:读取数据
txdata[0] = 0xff; //无效的 全双工
t->rx_buf = buf;
t->len = len;

spi_message_init(&m);
spi_message_add_tail(t,&m);
ret = spi_sync(spi,&m);

kfree(t);

//片选拉高
gpio_set_value(dev->cs_gpio,1);

return ret;
}


//spi写寄存器
static int icm20602_write_regs(struct icm20602_dev *dev,u8 reg,u8 *buf,int len)
{
int ret = 0;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private;

//片选拉低
gpio_set_value(dev->cs_gpio,0);

//构建spi_transfer
t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);

//第一步:发送要写的寄存器地址
txdata[0] = reg & ~0x80;
t->tx_buf = txdata;
t->len = 1;

spi_message_init(&m);
spi_message_add_tail(t,&m);
ret = spi_sync(spi,&m);

//第二步:读取数据
t->tx_buf = buf;
t->len = len;

spi_message_init(&m);
spi_message_add_tail(t,&m);
ret = spi_sync(spi,&m);

kfree(t);

//片选拉高
gpio_set_value(dev->cs_gpio,1);

return ret;
}

#endif


static void icm20602_read_regs(struct icm20602_dev *dev,u8 reg,void *buf,int len)
{
u8 data = 0;
struct spi_device *spi = (struct spi_device *)dev->private;

//片选拉低
gpio_set_value(dev->cs_gpio,0);

data = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */

spi_write(spi,&data,1); //发送要读取的寄存器地址
spi_read(spi,buf,len);

//片选拉高
gpio_set_value(dev->cs_gpio,1);
}

static void icm20602_write_regs(struct icm20602_dev *dev,u8 reg,u8 *buf,int len)
{
u8 data = 0;
struct spi_device *spi = (struct spi_device *)dev->private;

//片选拉低
gpio_set_value(dev->cs_gpio,0);

data = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要置1 */

spi_write(spi,&data,1); //发送要读取的寄存器地址
spi_write(spi,buf,len); //发送要读取的寄存器地址

//片选拉高
gpio_set_value(dev->cs_gpio,1);
}
//读取单个寄存器
static unsigned char icm20602_read_reg(struct icm20602_dev *dev,u8 reg)
{
u8 data = 0;
icm20602_read_regs(dev,reg,&data,1);
return data;
}

//写入单个寄存器
static void icm20602_write_reg(struct icm20602_dev *dev,u8 reg,u8 data)
{
u8 buf = data;
icm20602_write_regs(dev,reg,&buf,1);
}

void icm20602_reginit(struct icm20602_dev *dev)
{
u8 val = 0;
icm20602_write_reg(dev,ICM20602_PWR_MGMT_1,0x80); //复位
mdelay(50);


val = icm20602_read_reg(dev,ICM20602_WHO_AM_I);
printk("icm20602 ID = %#X\r\n",val);

icm20602_write_reg(dev,ICM20602_PWR_MGMT_1,0x01); //自动选择时钟
icm20602_write_reg(dev,ICM20602_PWR_MGMT_2, 0x00); //开启陀螺仪和加速度计
icm20602_write_reg(dev,ICM20602_CONFIG, 0x01); //176HZ 1KHZ
icm20602_write_reg(dev,ICM20602_SMPLRT_DIV, 0x07); //采样速率 SAMPLE_RATE = INTERNAL_SAMPLE_RATE / (1 + SMPLRT_DIV)
icm20602_write_reg(dev,ICM20602_GYRO_CONFIG, 0x18); //±2000 dps
icm20602_write_reg(dev,ICM20602_ACCEL_CONFIG, 0x10); //±8g
icm20602_write_reg(dev,ICM20602_ACCEL_CONFIG_2, 0x03); //Average 8 samples 44.8HZ
icm20602_write_reg(dev,ICM20602_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
icm20602_write_reg(dev,ICM20602_FIFO_EN, 0x00); /* 关闭FIFO */
}

static void icm20602_getdata(struct icm20602_dev *dev)
{
unsigned char data[14] = {0};

icm20602_read_regs(dev,ICM20602_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]);
}

static int icm20602_open(struct inode *inode, struct file *file)
{
file->private_data = &icm20602dev;

printk("icm20602_open!\r\n");

return 0;
}

static ssize_t icm20602_read(struct file *file, char __user *buf, size_t cnt, loff_t *offt)
{
signed int data[7];
long err = 0;
struct icm20602_dev *dev = file->private_data;
icm20602_getdata(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 ssize_t icm20602_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt)
{
//struct icm20602_dev *dev = file->private_data;
printk("icm20602_write!\r\n");
return 0;
}

static int icm20602_release(struct inode *inode, struct file *file)
{
printk("icm20602_release!\r\n");
return 0;
}

//字符设备操作集
struct file_operations icm20602_fops = {
.owner = THIS_MODULE,
.open = icm20602_open,
.read = icm20602_read,
.write = icm20602_write,
.release = icm20602_release,
};

struct miscdevice icm20602_miscdev = {
.minor = MISC_ICM20602_MINOR,
.name = MISC_ICM20602_NAME,
.fops = &icm20602_fops,
};

static int icm20602_probe(struct spi_device *spi)
{
int ret;

//搭建字符设备驱动框架 使用MISC注册驱动
ret = misc_register(&icm20602_miscdev);
if(ret < 0){
printk("icm20602 misc device register failed!\r\n");
ret = -EFAULT;
}

//获取片选引脚
icm20602dev.nd = of_get_parent(spi->dev.of_node);
icm20602dev.cs_gpio = of_get_named_gpio(icm20602dev.nd,"cs-gpio",0);
if (icm20602dev.cs_gpio < 0)
{
printk("can not get cs-gpio!\n");
goto fail_gpio;
}
ret = gpio_request(icm20602dev.cs_gpio,"cs");
if (ret < 0)
{
printk("cs-gpio request failed!\n");
}
ret = gpio_direction_output(icm20602dev.cs_gpio,1); //默认高电平

//初始化spi_device
spi->mode = SPI_MODE_0;
spi_setup(spi);

//设置私有数据
icm20602dev.private = spi;
printk("icm20602_misc_probe!\n");

//初始化ICM20602
icm20602_reginit(&icm20602dev);

return ret;
fail_gpio:
return 0;
}

static int icm20602_remove(struct spi_device *spi)
{
gpio_free(icm20602dev.cs_gpio);
misc_deregister(&icm20602_miscdev); //misc驱动卸载
printk("icm20602_spi_remove\n");
return 0;
}

//设备树匹配
static const struct of_device_id icm20602_of_match[] = {
{ .compatible = "cbus,icm20602"},
{ }
};

//传统匹配
static const struct spi_device_id icm20602_id[] = {
{ "cbus,icm20602", 0 },
{ }
};

struct spi_driver icm20602_driver = {
.driver = {
.name = "icm20602",
.owner = THIS_MODULE,
.of_match_table = icm20602_of_match,
},
.probe = icm20602_probe,
.remove = icm20602_remove,
.id_table = icm20602_id,
};

static int __init icm20602_init(void)
{
int ret = 0;

ret = spi_register_driver(&icm20602_driver);
printk("icm20602_init\n");
return ret;

}

static void __exit icm20602_exit(void)
{
spi_unregister_driver(&icm20602_driver);
}

module_init(icm20602_init);
module_exit(icm20602_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cbus");

3.3 应用程序

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
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
#include <linux/input.h>

int main(int argc, char *argv[]){
int fd,ret;
char *filename;
signed short data[7];
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;

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

filename = argv[1];

fd = open(filename,O_RDWR);
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}

while (1)
{
ret = read(fd,&data,sizeof(data));
if (ret == 0)
{
gyro_x_adc = data[0];
gyro_y_adc = data[1];
gyro_z_adc = data[2];
accel_x_adc = data[3];
accel_y_adc = data[4];
accel_z_adc = data[5];
temp_adc = data[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);
}
else
{
printf("read data failed!!!\r\n");
return -1;
}
usleep(200000); //200ms
}
close(fd);
return 0;
}

3.4 ICM20602寄存器

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
#ifndef ICM20602_H
#define ICM20602_H

#define ICM20602_XG_OFFS_TC_H 0x04
#define ICM20602_XG_OFFS_TC_L 0x05
#define ICM20602_YG_OFFS_TC_H 0x07
#define ICM20602_YG_OFFS_TC_L 0x08
#define ICM20602_ZG_OFFS_TC_H 0x0A
#define ICM20602_ZG_OFFS_TC_L 0x0B
#define ICM20602_SELF_TEST_X_ACCEL 0x0D
#define ICM20602_SELF_TEST_Y_ACCEL 0x0E
#define ICM20602_SELF_TEST_Z_ACCEL 0x0F
#define ICM20602_XG_OFFS_USRH 0x13
#define ICM20602_XG_OFFS_USRL 0x14
#define ICM20602_YG_OFFS_USRH 0x15
#define ICM20602_YG_OFFS_USRL 0x16
#define ICM20602_ZG_OFFS_USRH 0x17
#define ICM20602_ZG_OFFS_USRL 0x18
#define ICM20602_SMPLRT_DIV 0x19
#define ICM20602_CONFIG 0x1A
#define ICM20602_GYRO_CONFIG 0x1B
#define ICM20602_ACCEL_CONFIG 0x1C
#define ICM20602_ACCEL_CONFIG_2 0x1D
#define ICM20602_LP_MODE_CFG 0x1E
#define ICM20602_ACCEL_WOM_X_THR 0x20
#define ICM20602_ACCEL_WOM_Y_THR 0x21
#define ICM20602_ACCEL_WOM_Z_THR 0x22
#define ICM20602_FIFO_EN 0x23
#define ICM20602_FSYNC_INT 0x36
#define ICM20602_INT_PIN_CFG 0x37
#define ICM20602_INT_ENABLE 0x38
#define ICM20602_FIFO_WM_INT_STATUS 0x39
#define ICM20602_INT_STATUS 0x3A
#define ICM20602_ACCEL_XOUT_H 0x3B
#define ICM20602_ACCEL_XOUT_L 0x3C
#define ICM20602_ACCEL_YOUT_H 0x3D
#define ICM20602_ACCEL_YOUT_L 0x3E
#define ICM20602_ACCEL_ZOUT_H 0x3F
#define ICM20602_ACCEL_ZOUT_L 0x40
#define ICM20602_TEMP_OUT_H 0x41
#define ICM20602_TEMP_OUT_L 0x42
#define ICM20602_GYRO_XOUT_H 0x43
#define ICM20602_GYRO_XOUT_L 0x44
#define ICM20602_GYRO_YOUT_H 0x45
#define ICM20602_GYRO_YOUT_L 0x46
#define ICM20602_GYRO_ZOUT_H 0x47
#define ICM20602_GYRO_ZOUT_L 0x48
#define ICM20602_SELF_TEST_X_GYRO 0x50
#define ICM20602_SELF_TEST_Y_GYRO 0x51
#define ICM20602_SELF_TEST_Z_GYRO 0x52
#define ICM20602_FIFO_WM_TH1 0x60
#define ICM20602_FIFO_WM_TH2 0x61
#define ICM20602_SIGNAL_PATH_RESET 0x68
#define ICM20602_ACCEL_INTEL_CTRL 0x69
#define ICM20602_USER_CTRL 0x6A
#define ICM20602_PWR_MGMT_1 0x6B
#define ICM20602_PWR_MGMT_2 0x6C
#define ICM20602_I2C_IF 0x70
#define ICM20602_FIFO_COUNTH 0x72
#define ICM20602_FIFO_COUNTL 0x73
#define ICM20602_FIFO_R_W 0x74
#define ICM20602_WHO_AM_I 0x75
#define ICM20602_XA_OFFSET_H 0x77
#define ICM20602_XA_OFFSET_L 0x78
#define ICM20602_YA_OFFSET_H 0x7A
#define ICM20602_YA_OFFSET_L 0x7B
#define ICM20602_ZA_OFFSET_H 0x7D
#define ICM20602_ZA_OFFSET_L 0x7E

#endif

3.5接线图

image-20250101132815299

4 实验

输入以下命令:

1
./icm20602App  /dev/icm20602

image-20250101162111800

image-20250101162151375

评论