徐州做网站的公司有几家,深圳seo招聘,网站建设平台ui确认书,5944免费空间上搭建网站跟上一章一样#xff0c;其实这些设备驱动#xff0c;无非就是传感器对应寄存器的读写。而这个读写是建立在各种通信协议上的#xff0c;比如上一章的i2c#xff0c;我们做了什么呢#xff0c;就是把设备注册成一个i2c平台驱动#xff0c;这个i2c驱动怎么搞的呢#xff…跟上一章一样其实这些设备驱动无非就是传感器对应寄存器的读写。而这个读写是建立在各种通信协议上的比如上一章的i2c我们做了什么呢就是把设备注册成一个i2c平台驱动这个i2c驱动怎么搞的呢有个i2c_client 从机的地址放在这个结构体内还有个适配器和算法两个结构体。其他的就是正常的i2c的操作。i2c通信的驱动已经被SOC厂商写好了我们做的就是调用其API函数去实现对设备的驱动撰写。等于驱动分层假如第一层是i2c寄存器的物理属性那么它在设备树内。第二层是通信协议被写好了我们写的是基于这个通信协议的设备驱动。并不直接跟寄存器打交道
上一章我们讲解了如何编写 Linux 下的 I2C 设备驱动SPI 也是很常用的串行通信协议 本章我们就来学习如何在 Linux 下编写 SPI 设备驱动。本章实验的最终目的就是驱动 I.MX6UALPHA 开发板上的 ICM-20608 这个 SPI 接口的六轴传感器可以在应用程序中读取 ICM-20608 的原始传感器数据
1Linux 下 SPI 驱动框架简介
SPI 驱动框架和 I2C 很类似都分为主机控制器驱动和设备驱动主机控制器也就是 SOC 的 SPI 控制器接口。比如在裸机篇中的《第二十七章 SPI 实验》我们编写了 bsp_spi.c 和 bsp_spi.h 这两个文件这两个文件是 I.MX6U 的 SPI 控制器驱动我们编写好 SPI 控制器驱动以后就可 以直接使用了不管是什么 SPI 设备SPI 控制器部分的驱动都是一样我们的重点就落在了 种类繁多的 SPI 设备驱动。
1.1SPI 主机驱动
SPI 主机驱动就是 SOC 的 SPI 控制器驱动类似 I2C 驱动里面的适配器驱动。Linux 内核 使用 spi_master 表示 SPI 主机驱动spi_master 是个结构体定义在 include/linux/spi/spi.h 文件 中内容如下(有缩减)
315 struct spi_master {
316 struct device dev;
317
318 struct list_head list;
......
326 s16 bus_num;
327
328 /* chipselects will be integral to many controllers; some others
329 * might use board-specific GPIOs.
330 */
331 u16 num_chipselect;
332
333 /* some SPI controllers pose alignment requirements on DMAable
334 * buffers; let protocol drivers know about these requirements.
335 */
336 u16 dma_alignment;
337
338 /* spi_device.mode flags understood by this controller driver */
339 u16 mode_bits;
340
341 /* bitmask of supported bits_per_word for transfers */
342 u32 bits_per_word_mask;
......
347 /* limits on transfer speed */
348 u32 min_speed_hz;
349 u32 max_speed_hz;
350
351 /* other constraints relevant to this driver */
352 u16 flags;
......
359 /* lock and mutex for SPI bus locking */
360 spinlock_t bus_lock_spinlock;
361 struct mutex bus_lock_mutex;
362
363 /* flag indicating that the SPI bus is locked for exclusive use */
364 bool bus_lock_flag;
......
372 int (*setup)(struct spi_device *spi);
373
......
393 int (*transfer)(struct spi_device *spi,
394 struct spi_message *mesg);
......
434 int (*transfer_one_message)(struct spi_master *master,
435 struct spi_message *mesg);
......
462 };
第 393 行transfer 函数和 i2c_algorithm 中的 master_xfer 函数一样控制器数据传输函 数。 第 434 行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。
1.1.1spi_master 申请与释放
spi_alloc_master 函数用于申请 spi_master函数原型如下
struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)函数参数和返回值含义如下
dev设备一般是 platform_device 中的 dev 成员变量。
size私有数据大小可以通过 spi_master_get_devdata 函数获取到这些私有数据。
返回值申请到的 spi_master。 spi_master 的释放通过 spi_master_put 函数来完成
当我们删除一个 SPI 主机驱动的时候就 需要释放掉前面申请的 spi_masterspi_master_put 函数原型如下
void spi_master_put(struct spi_master *master)
函数参数和返回值含义如下
master要释放的 spi_master。
返回值无。
1.1.2spi_master 的注册与注销
当 spi_master 初始化完成以后就需要将其注册到 Linux 内核spi_master 注册函数为 spi_register_master函数原型如下
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 函数此函数原型为
void spi_unregister_master(struct spi_master *master)
函数参数和返回值含义如下
master要注销的 spi_master。
返回值无。
如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉 spi_master。
1.2SPI 设备驱动
spi 设备驱动也和 i2c 设备驱动也很类似Linux 内核使用 spi_driver 结构体来表示 spi 设备 驱动我们在编写 SPI 设备驱动的时候需要实现 spi_driver。spi_driver 结构体定义在 include/linux/spi/spi.h 文件中结构体内容如下
180 struct spi_driver {
181 const struct spi_device_id *id_table;
182 int (*probe)(struct spi_device *spi);
183 int (*remove)(struct spi_device *spi);
184 void (*shutdown)(struct spi_device *spi);
185 struct device_driver driver;
186 };可以看出spi_driver 和 i2c_driver、platform_driver 基本一样当 SPI 设备和驱动匹配成功 以后 probe 函数就会执行。 同样的spi_driver 初始化完成以后需要向 Linux 内核注册spi_driver 注册函数为 spi_register_driver函数原型如下
int spi_register_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下
sdrv要注册的 spi_driver。
返回值0注册成功赋值注册失败。
注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver使用 spi_unregister_driver 函 数完成 spi_driver 的注销函数原型如下
void spi_unregister_driver(struct spi_driver *sdrv)函数参数和返回值含义如下
sdrv要注销的 spi_driver。
返回值无。 spi_driver 注册示例程序如下
1 /* probe 函数 */
2 static int xxx_probe(struct spi_device *spi)
3 {
4 /* 具体函数内容 */
5 return 0;
6 }
7
8 /* remove 函数 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具体函数内容 */
12 return 0;
13 }
14 /* 传统匹配方式 ID 列表 */
15 static const struct spi_device_id xxx_id[] {
16 {xxx, 0},
17 {}
18 };
19
20 /* 设备树匹配列表 */
21 static const struct of_device_id xxx_of_match[] {
22 { .compatible xxx },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驱动结构体 */
27 static struct spi_driver xxx_driver {
28 .probe xxx_probe,
29 .remove xxx_remove,
30 .driver {
31 .owner THIS_MODULE,
32 .name xxx,
33 .of_match_table xxx_of_match,
34 },
35 .id_table xxx_id,
36 };
37
38 /* 驱动入口函数 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(xxx_driver);
42 }
43
44 /* 驱动出口函数 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);
第 1~36 行spi_driver 结构体需要 SPI 设备驱动人员编写包括匹配表、probe 函数等。 和 i2c_driver、platform_driver 一样就不详细讲解了。
第 39~42 行在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。
第 45~48 行在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。
1.3SPI 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的这点和 platform、I2C 等驱动一样SPI 总线为 spi_bus_type定义在 drivers/spi/spi.c 文件中内容如下
131 struct bus_type spi_bus_type {
132 .name spi,
133 .dev_groups spi_dev_groups,
134 .match spi_match_device,
135 .uevent spi_uevent,
136 };
可以看出SPI 设备和驱动的匹配函数为 spi_match_device函数内容如下
99 static int spi_match_device(struct device *dev,
struct device_driver *drv)
100 {
101 const struct spi_device *spi to_spi_device(dev);
102 const struct spi_driver *sdrv to_spi_driver(drv);
103
104 /* Attempt an OF style match */
105 if (of_driver_match_device(dev, drv))
106 return 1;
107
108 /* Then try ACPI */
109 if (acpi_driver_match_device(dev, drv))
110 return 1;
111
112 if (sdrv-id_table)
113 return !!spi_match_id(sdrv-id_table, spi);
114
115 return strcmp(spi-modalias, drv-name) 0;
116 }
spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。
第 105 行of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节 点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等如果相当的话就表示 SPI 设 备和驱动匹配。
第 109 行acpi_driver_match_device 函数用于 ACPI 形式的匹配。
第 113 行spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI 设备名字和 spi_device_id 的 name 字段是否相等相等的话就说明 SPI 设备和驱动匹配。
第 115 行比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否 相等。
2I.MX6U SPI 主机驱动分析
和 I2C 的适配器驱动一样SPI 主机驱动一般都由 SOC 厂商编写好了打开 imx6ull.dtsi 文件找到如下所示内容
1 ecspi3: ecspi02010000 {
2 #address-cells 1;
3 #size-cells 0;
4 compatible fsl,imx6ul-ecspi, fsl,imx51-ecspi;
5 reg 0x02010000 0x4000;
6 interrupts GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH;
7 clocks clks IMX6UL_CLK_ECSPI3,
8 clks IMX6UL_CLK_ECSPI3;
9 clock-names ipg, per;
10 dmas sdma 7 7 1, sdma 8 7 2;
11 dma-names rx, tx;
12 status disabled;
13 };重点来看一下第 4 行的 compatible 属性值compatible 属性有两个值“fsl,imx6ul-ecspi”和 “fsl,imx51-ecspi”在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI) 主机驱动。I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c在此文件中找到如下内容
694 static struct platform_device_id spi_imx_devtype[] {
695 {
696 .name imx1-cspi,
697 .driver_data (kernel_ulong_t) imx1_cspi_devtype_data,
698 }, {
699 .name imx21-cspi,
700 .driver_data (kernel_ulong_t) imx21_cspi_devtype_data,
......
713 }, {
714 .name imx6ul-ecspi,
715 .driver_data (kernel_ulong_t) imx6ul_ecspi_devtype_data,
716 }, {
717 /* sentinel */
718 }
719 };
720
721 static const struct of_device_id spi_imx_dt_ids[] {
722 { .compatible fsl,imx1-cspi, .data
imx1_cspi_devtype_data, },
......
728 { .compatible fsl,imx6ul-ecspi, .data
imx6ul_ecspi_devtype_data, },
729 { /* sentinel */ }
730 };
731 MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
......
1338 static struct platform_driver spi_imx_driver {
1339 .driver {
1340 .name DRIVER_NAME,
1341 .of_match_table spi_imx_dt_ids,
1342 .pm IMX_SPI_PM,
1343 },
1344 .id_table spi_imx_devtype,
1345 .probe spi_imx_probe,
1346 .remove spi_imx_remove,
1347 };
1348 module_platform_driver(spi_imx_driver);
第 714 行spi_imx_devtype 为 SPI 无设备树匹配表。
第 721 行spi_imx_dt_ids 为 SPI 设备树匹配表。
第 728 行“fsl,imx6ul-ecspi”匹配项因此可知 I.MX6U 的 ECSPI 驱动就是 spi-imx.c 这个 文件。
第 1338~1347 行platform_driver 驱动框架和 I2C 的适配器驱动一样SPI 主机驱动器采 用了 platfom 驱动框架。当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行。 spi_imx_probe 函数会从设备树中读取相应的节点属性值申请并初始化 spi_master最后 调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册 spi_master。 对于 I.MX6U 来讲SPI 主机的最终数据收发函数为 spi_imx_transfer此函数通过如下层 层调用最终实现 SPI 数据发送
spi_imx_transfer- spi_imx_pio_transfer- spi_imx_push- spi_imx-txspi_imx 是个 spi_imx_data 类型的机构指针变量其中 tx 和 rx 这两个成员变量分别为 SPI 数据发送和接收函数。I.MX6U SPI 主机驱动会维护一个 spi_imx_data 类型的变量 spi_imx并 且使用 spi_imx_setupxfer 函数来设置 spi_imx 的 tx 和 rx 函数。根据要发送的数据数据位宽的不 同分别有 8 位、16 位和 32 位的发送函数如下所示
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32同理也有 8 位、16 位和 32 位的数据接收函数如下所示
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32
我们就以 spi_imx_buf_tx_u8 这个函数为例看看一个自己的数据发送是怎么完成的在 spi-imx.c 文件中找到如下所示内容
152 #define MXC_SPI_BUF_TX(type) \
153 static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
154 { \
155 type val 0; \
156 \
157 if (spi_imx-tx_buf) { \
158 val *(type *)spi_imx-tx_buf; \
159 spi_imx-tx_buf sizeof(type); \
160 } \
161 \
162 spi_imx-count - sizeof(type); \
163 \
164 writel(val, spi_imx-base MXC_CSPITXDATA); \
165 }
166
167 MXC_SPI_BUF_RX(u8)
168 MXC_SPI_BUF_TX(u8)从示例代码 62.2.3 可以看出spi_imx_buf_tx_u8 函数是通过 MXC_SPI_BUF_TX 宏来实现 的。第 164 行就是将要发送的数据值写入到 ECSPI 的 TXDATA 寄存器里面去这和我们 SPI 裸 机实验的方法一样。将第 168 行的 MXC_SPI_BUF_TX(u8)展开就是 spi_imx_buf_tx_u8 函数。 其他的 tx 和 rx 函数都是这样实现的这里就不做介绍了。关于 I.MX6U 的主机驱动程序就讲 解到这里基本套路和 I2C 的适配器驱动程序类似。
3SPI 设备驱动编写流程
3.1:SPI 设备信息描述
3.1.1:IO 的 pinctrl 子节点创建与修改
首先肯定是根据所使用的 IO 来创建或修改 pinctrl 子节点这个没什么好说的唯独要注意的就是检查相应的 IO 有没有被其他的设备所使用如果有的话需要将其删除掉
3.1.2:SPI 设备节点的创建与修改
采用设备树的情况下SPI 设备信息描述就通过创建相应的设备子节点来完成我们可以 打开 imx6qdl-sabresd.dtsi 这个设备树头文件在此文件里面找到如下所示内容
308 ecspi1 {
309 fsl,spi-num-chipselects 1;
310 cs-gpios gpio4 9 0;
311 pinctrl-names default;
312 pinctrl-0 pinctrl_ecspi1;
313 status okay;
314
315 flash: m25p800 {
316 #address-cells 1;
317 #size-cells 1;
318 compatible st,m25p32;
319 spi-max-frequency 20000000;
320 reg 0;
321 };
322 };示例代码 62.3.1.1 是 I.MX6Q 的一款板子上的一个 SPI 设备节点在这个板子的 ECSPI 接 口上接了一个 m25p80这是一个 SPI 接口的设备。
第 309 行设置“fsl,spi-num-chipselects”属性为 1表示只有一个设备。
第 310 行设置“cs-gpios”属性也就是片选信号为 GPIO4_IO09。
第 311 行设置“pinctrl-names”属性也就是 SPI 设备所使用的 IO 名字。
第 312 行设置“pinctrl-0”属性也就是所使用的 IO 对应的 pinctrl 节点。
第 313 行将 ecspi1 节点的“status”属性改为“okay”。
第 315~320 行ecspi1 下的 m25p80 设备信息每一个 SPI 设备都采用一个子节点来描述 其设备信息。
第 315 行的“m25p800”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0 上。这个要根据自己的具体硬件来设置。
第 318 行SPI 设备的 compatible 属性值用于匹配设备驱动。
第 319 行“spi-max-frequency”属性设置 SPI 控制器的最高频率这个要根据所使用的 SPI 设备来设置比如在这里将 SPI 控制器最高频率设置为 20MHz。
第 320 行reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道和“m25p800”后面的 “0”一样。 我们一会在编写 ICM20608 的设备树节点信息的时候就参考示例代码 62.3.1.1 中的内容即 可。
3.2:SPI 设备数据收发处理流程
SPI 设备驱动的核心是 spi_driver这个我们已经在 62.1.2 小节讲过了。当我们向 Linux 内 核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。 首先是 spi_transfer 结构体此结构体用于描述 SPI 传输信息结构体内容如下
603 struct spi_transfer {
604 /* its ok if tx_buf rx_buf (right?)
605 * for MicroWire, one buffer must be null
606 * buffers must work with dma_*map_single() calls, unless
607 * spi_message.is_dma_mapped reports a pre-existing mapping
608 */
609 const void *tx_buf;
610 void *rx_buf;
611 unsigned len;
612
613 dma_addr_t tx_dma;
614 dma_addr_t rx_dma;
615 struct sg_table tx_sg;
616 struct sg_table rx_sg;
617
618 unsigned cs_change:1;
619 unsigned tx_nbits:3;
620 unsigned rx_nbits:3;
621 #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
622 #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
623 #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
624 u8 bits_per_word;
625 u16 delay_usecs;
626 u32 speed_hz;
627
628 struct list_head transfer_list;
629 };
第 609 行tx_buf 保存着要发送的数据。指针前面加了一个const表示指针指向一个常量这个值不能改。也就是固定指向
第 610 行rx_buf 用于保存接收到的数据。
第 611 行len 是要进行传输的数据长度SPI 是全双工通信因此在一次通信中发送和 接收的字节数都是一样的所以 spi_transfer 中也就没有发送长度和接收长度之分。
spi_transfer 需要组织成 spi_messagespi_message 也是一个结构体内容如下
660 struct spi_message {
661 struct list_head transfers;
662
663 struct spi_device *spi;
664
665 unsigned is_dma_mapped:1;
......
678 /* completion is reported through a callback */
679 void (*complete)(void *context);
680 void *context;
681 unsigned frame_length;
682 unsigned actual_length;
683 int status;
684
685 /* for optional use by whatever driver currently owns the
686 * spi_message ... between calls to spi_async and then later
687 * complete(), thats the spi_master controller driver.
688 */
689 struct list_head queue;
690 void *state;
691 };
在使用spi_message之前需要对其进行初始化spi_message初始化函数为spi_message_init 函数原型如下
void spi_message_init(struct spi_message *m)
函数参数和返回值含义如下
m要初始化的 spi_message。
返回值无。
spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中这里我们要用 到 spi_message_add_tail 函数此函数原型如下
跟i2c的msg很像是吧i2c要指定读还是写从机地址发送长度读取或发送的地址
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
函数参数和返回值含义如下 t要添加到队列中的 spi_transfer。
mspi_transfer 要加入的 spi_message。
返回值无。
spi_message 准备好以后既可以进行数据传输了数据传输分为同步传输和异步传输同步 传输会阻塞的等待 SPI 数据传输完成同步传输函数为 spi_sync函数原型如下
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函数原型如下
int spi_async(struct spi_device *spi, struct spi_message *message)函数参数和返回值含义如下
spi要进行数据传输的 spi_device。
message要传输的 spi_message。
返回值无。
在本章实验中我们采用同步传输方式来完成 SPI 数据的传输工作也就是 spi_sync 函数。 综上所述SPI 数据传输步骤如下
①、申请并初始化 spi_transfer设置 spi_transfer 的 tx_buf 成员变量tx_buf 为要发送的数 据。然后设置 rx_buf 成员变量rx_buf 保存着接收到的数据。最后设置 len 成员变量也就是 要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message。
③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。 ④、使用 spi_sync 函数完成 SPI 数据同步传输。 通过 SPI 进行 n 个字节的数据发送和接收的示例代码如下所示
/* 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;
}
4试验程序编写
其实mini也没有这个传感器可以自己用杜邦线外扩一个当然要在设备树内保证所用IO没被其他外设占用。片选的化就默认接地然后就是那两根数据线和一个VCC了。
原来学stm32的w25q64模块就可以接。i2c是那个ATc02好像一个是flash一个是eeprom
4.1修改设备树
4.1.1添加 ICM20608 所使用的 IO
首先在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息在 iomuxc 节点 中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚子节点名字为 pinctrl_ecspi3节 点内容如下所示
1 pinctrl_ecspi3: icm20608 {
2 fsl,pins
3 MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */
4 MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
5 MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
6 MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
7 ;
8 };UART2_TX_DATA 这个 IO 是 ICM20608 的片选信号这里我们并没有将其复用为 ECSPI3 的 SS0 信号而是将其复用为了普通的 GPIO。因为我们需要自己控制片选信号所以将其复 用为普通的 GPIO。
4.1.2在 ecspi3 节点追加 icm20608 子节点
在 imx6ull-alientek-emmc.dts 文件中并没有任何向 ecspi3 节点追加内容的代码这是因为 NXP 官方的 6ULL EVK 开发板上没有连接 SPI 设备。在 imx6ull-alientek-emmc.dts 文件最后面 加入如下所示内容
1 ecspi3 {
2 fsl,spi-num-chipselects 1;
3 cs-gpios gpio1 20 GPIO_ACTIVE_LOW;
4 pinctrl-names default;
5 pinctrl-0 pinctrl_ecspi3;
6 status okay;
7
8 spidev: icm206080 {
9 compatible alientek,icm20608;
10 spi-max-frequency 8000000;
11 reg 0;
12 };
13 };第 2 行设置当前片选数量为 1因为就只接了一个 ICM20608。
第 3 行一定要使用 “cs-gpios”属性来描述片选引脚SPI 主机驱动就会控制片选引脚。
第 5 行设置 IO 要使用的 pinctrl 子节点也就是我们在示例代码 62.5.1.1 中新建的 pinctrl_ecspi3。
第 6 行imx6ull.dtsi 文件中默认将 ecspi3 节点状态(status)设置为“disable”这里我们要将 其改为“okay”。
第 8~12 行icm20608 设备子节点因为 icm20608 连接在 ECSPI3 的第 0 个通道上因此 后面为 0。第 9 行设置节点属性兼容值为“alientek,icm20608”
第 10 行设置 SPI 最大时钟频 率为 8MHz这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。
第 11 行icm20608 连接 在通道 0 上因此 reg 为 0。 imx6ull-alientek-emmc.dts 文件修改完成以后重新编译一下得到新的 dtb 文件并使用新 的 dtb 启动 Linux 系统。
4.2编写 ICM20608 驱动
新建名为“22_spi”的文件夹然后在 22_spi 文件夹里面创建 vscode 工程工作区命名为 “spi”。工程创建好以后新建 icm20608.c 和 icm20608reg.h 这两个文件icm20608.c 为 ICM20608 的驱动代码icm20608reg.h 是 ICM20608 寄存器头文件。先在 icm20608reg.h 中定义好 ICM20608 的寄存器输入如下内容(有省略完整的内容请参考例程)
#ifndef ICM20608_H
#define ICM20608_H
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608reg.h
作者 : 左忠凯
版本 : V1.0
描述 : ICM20608寄存器地址描述头文件
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define ICM20608G_ID 0XAF /* ID值 */
#define ICM20608D_ID 0XAE /* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0除了*Register 107(0X6B) Power Management 1 0x40*Register 117(0X75) WHO_AM_I 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置用于与用户的自检输出值比较 */
#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#endif
接下来继续编写 icm20608.c 文件因为 icm20608.c 文件内容比较长因此这里就将其分开 来讲解。
4.2.1icm20608 设备结构体创建
首先创建一个 icm20608 设备结构体如下所示
#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
#include icm20608reg.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608.c
作者 : 左忠凯
版本 : V1.0
描述 : ICM20608 SPI驱动程序
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define ICM20608_CNT 1
#define ICM20608_NAME icm20608struct 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;
icm20608 的设备结构体 icm20608_dev 没什么好讲的重点看一下第 44 行的 private_data 对于 SPI 设备驱动来讲最核心的就是 spi_device。probe 函数会向驱动提供当前 SPI 设备对应的 spi_device因此在 probe 函数中设置 private_data 为 probe 函数传递进来的 spi_device 参数。
4.2.2icm20608 的 spi_driver 注册与注销
对于 SPI 设备驱动首先就是要初始化并向系统注册 spi_drivericm20608 的 spi_driver 初 始化、注册与注销代码如下 /* 传统匹配方式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(zuozhongkai);
第 2~5 行传统的设备和驱动匹配表。
第 8~11 行设备树的设备与驱动匹配表这里只有一个匹配项“alientek,icm20608”。
第 14~23 行icm20608 的 spi_driver 结构体变量当 icm20608 设备和此驱动匹配成功以后 第 15 行的 icm20608_probe 函数就会执行。同样的当注销此驱动的时候 icm20608_remove 函 数会执行。
第 30~33 行icm20608_init 函数为 icm20608 的驱动入口函数在此函数中使用 spi_register_driver 向 Linux 系统注册上面定义的 icm20608_driver。
第 40~43 行icm20608_exit 函数为 icm20608 的驱动出口函数在此函数中使用 spi_unregister_driver 注销掉前面注册的 icm20608_driver。
4.2.3proberemove 函数
icm20608_driver 中的 probe 和 remove 函数内容如下所示
/** 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; /*MODE0CPOL0CPHA0*/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;
}
第 8~43 行probe 函数当设备与驱动匹配成功以后此函数就会执行第 10~33 行都是标 准的注册字符设备驱动。
第 36 行设置 SPI 为模式 0也就是 CPOL0CPHA0。空闲状态低电平第一个前沿采样也就是上升沿 第 37 行设置好 spi_device 以后需要使用 spi_setup 配置一下。 第 38 行设置 icm20608dev 的 private_data 成员变量为 spi_device。
第 41 行调用 icm20608_reginit 函数初始化 ICM20608主要是初始化 ICM20608 指定寄 存器。
第 50~60 行icm20608_remove 函数注销驱动的时候此函数就会执行。
4.2.4icm20608 寄存器读写与初始化
/** 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;}/* 一共发送len1个字节的数据第一个字节为寄存器首地址一共要读取len个字节长度的数据*/txdata[0] reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t-tx_buf txdata; /* 要发送的数据 */t-rx_buf rxdata; /* 要读取的数据 */t-len len1; /* 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 , rxdata1, 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;}/* 一共发送len1个字节的数据第一个字节为寄存器首地址len为要写入的寄存器的集合*/*txdata reg ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata1, buf, len); /* 把len个寄存器拷贝到txdata里等待发送 */t-tx_buf txdata; /* 要发送的数据 */t-len len1; /* 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]);
}
第 9~50 行icm20608_read_regs 函数从 icm20608 中读取连续多个寄存器数据注意 在本实验中SPI 为全双工通讯没有所谓的发送和接收长度之分。要读取或者发送 N 个字节就 要封装 N1 个字节第 1 个字节是告诉设备我们要进行读还是写后面的 N 个字节才是我们 要读或者发送的数据。因为是读操作因此在第 31 行设置第一个数据 bit7 位 1表示读操作。
第 60~96 行icm20608_write_regs 函数向 icm20608 连续写入多个寄存器数据。此函数和 icm20608_read_regs 函数区别不大。
第 104~109 行icm20608_read_onereg 函数读取 icm20608 指定寄存器数据。
第 119~123 行icm20608_write_onereg 函数向 icm20608 指定寄存器写入数据。
第 131~143 行icm20608_readdata 函数读取 icm20608 六轴传感器和温度传感器原始数 据值应用程序读取 icm20608 的时候这些传感器原始数据就会上报给应用程序。
第 150~170 行icm20608_reginit 函数初始化 icm20608和我们 spi 裸机实验里面的初始 化过程一样。
4.2.5字符设备驱动框架
icm20608 的字符设备驱动框架如下
/** 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,
};字符设备驱动框架没什么好说的重点是第 22~38 行的 icm20608_read 函数当应用程序 调用 read 函数读取 icm20608 设备文件的时候此函数就会执行。此函数调用上面编写好的 icm20608_readdata 函数读取 icm20608 的原始数据并将其上报给应用程序。大家注意在内核 中尽量不要使用浮点运算所以不要在驱动将 icm20608 的原始值转换为对应的实际值因为会 涉及到浮点计算。
4.3编写测试 APP
新建 icm20608App.c 文件然后在里面输入如下所示内容
#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
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608App.c
作者 : 左忠凯
版本 : V1.0
描述 : icm20608设备测试APP。
其他 : 无
使用方法 ./icm20608App /dev/icm20608
论坛 : www.openedv.com
日志 : 初版V1.0 2019/9/20 左忠凯创建
***************************************************************//** 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(cant 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;
}
第 60~91 行在 while 循环中每隔 100ms 从 icm20608 中读取一次数据读取到 icm20608 原始数据以后将其转换为实际值比如陀螺仪就是角速度、加速度计就是 g 值。注意我们在 icm20608 驱动中将陀螺仪和加速度计的测量范围全部设置到了最大分别为±2000 和±16g。 因此在计算实际值的时候陀螺仪使用 16.4加速度计使用 2048。最终将传感器原始数据和得 到的实际值显示在终端上。
5运行测试
5.1编写 Makefile 文件
编写 Makefile 文件本章实验的 Makefile 文件和第四十章实验基本一样只是将 obj-m 变 量的值改为“icm20608.o”Makefile 内容如下所示
KERNELDIR : /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH : $(shell pwd)obj-m : icm20608.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean
第 4 行设置 obj-m 变量的值为“icm20608.o”。
5.2编译测试 APP
在 icm20608App.c 这个测试 APP 中我们用到了浮点计算而 I.MX6U 是支持硬件浮点的 因此我们在编译 icm20608App.c 的时候就可以使能硬件浮点这样可以加速浮点计算。使能硬 件浮点很简单在编译的时候加入如下参数即可
-march-armv7-a -mfpu-neon -mfloathard
输入如下命令使能硬件浮点编译 icm20608App.c 这个测试程序
arm-linux-gnueabihf-gcc -marcharmv7-a -mfpuneon -mfloat-abihard icm20608App.c -o
icm20608App
编译成功以后就会生成 icm20608App 这个应用程序那么究竟有没有使用硬件浮点呢使 用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了输入如下命令
arm-linux-gnueabihf-readelf -A icm20608App
结果如图 62.6.1.1 所示 从图 62.6.1.1 可以看出 FPU 架构为 VFPv3SIMD 使用了 NEON并且使用了 SP 和 DP 说明 icm20608App 这个应用程序使用了硬件浮点。
6运行测试
将上一小节编译出来 icm20608.ko 和 icm20608App 这 两 个 文 件 拷 贝 到 rootfs/lib/modules/4.1.15 目录中重启开发板进入到目录 lib/modules/4.1.15 中。输入如下命令 加载 icm20608.ko 这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块
当驱动模块加载成功以后使用 icm20608App 来测试输入如下命令
./icm20608App /dev/icm20608 可以看出开发板静止状态下Z 轴方向的加速度在 1g 左右这个就是重力加速度。对于 陀螺仪来讲静止状态下三轴的角速度应该在 0°/S 左右。ICM20608 内温度传感器采集到的温 度在 30 多度左右大家可以晃动一下开发板这个时候陀螺仪和加速度计的值就会有变化。
本文仅在记录学习正点原子imx6ull-mini开发板的过程不做他用。