Linux学习第41天:Linux SPI 驱动实验(二):乾坤大挪移

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


        本章的思维导图如下:

二、I.MX6U SPI主机驱动分析

       主机驱动一般都是由SOC厂商写好的。不作为重点需要掌握的内容。

三、SPI设备驱动编写流程

1、SPI设备信息描述

1)、IO pinctrl 子节点创建与修改

        根据所使用的 IO 来创建或修改 pinctrl 子节点,检查是否被占用。

2)、SPI 设备节点的创建与修改

308 &ecspi1 {
309 fsl,spi-num-chipselects = <1>;/*设置“ fsl,spi-num-chipselects”属性为 1,表示只有一个设备。*/
310 cs-gpios = <&gpio4 9 0>;/*设置“ cs-gpios”属性,也就是片选信号为 GPIO4_IO09。*/
311 pinctrl-names = "default";*设置“ pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。*/
312 pinctrl-0 = <&pinctrl_ecspi1>;/*设置“ pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。*/
313 status = "okay";/*将 ecspi1 节点的“ status”属性改为“ okay”。*/
314
315 flash: m25p80@0 {/*ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述
其设备信息。第 315 行的“ m25p80@0”后面的“ 0”表示 m25p80 的接到了 ECSPI 的通道 0
上。这个要根据自己的具体硬件来设置。*/
316 #address-cells = <1>;
317 #size-cells = <1>;
318 compatible = "st,m25p32";/*SPI 设备的 compatible 属性值,用于匹配设备驱动。*/
319 spi-max-frequency = <20000000>;/*“ spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的
SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz。*/
320 reg = <0>;/* reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道*/
321 };
322 };

        上述代码是 I.MX6Q 的一款板子上的一个 SPI 设备节点,在这个板子的 ECSPI 接口上接了一个 m25p80,这是一个 SPI 接口的设备。


2、SPI设备数据收发处理流程

        spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:

603 struct spi_transfer {
604 /* it's 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;/*tx_buf 保存着要发送的数据。*/
610 void *rx_buf;/*rx_buf 用于保存接收到的数据。*/
611 unsigned len;/*len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和
接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。*/
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 };

spi_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(), that's the spi_master controller driver.
688 */
689 struct list_head queue;
690 void *state;
691 };

spi_message初始化函数为 spi_message_init,函数原型如下:

void spi_message_init(struct spi_message *m)

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

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,函数原型如下:

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 数据同步传输。
 

四、硬件原理图分析

五、实验程序编写

1、修改设备树

1)、添加 ICM20608 所使用的 IO

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

2)、在 ecspi3 节点追加 icm20608 子节点
 

1 &ecspi3 {
2 fsl,spi-num-chipselects = <1>;/*当前片选数量为 1*/
3 cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* cant't use cs-gpios! *//*用了一个自己定义的“ cs-gpio”属
性*/
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_ecspi3>;/*设置 IO 要使用的 pinctrl 子节点*/
6 status = "okay";/* imx6ull.dtsi 文件中默认将 ecspi3 节点状态(status)设置为“ disable”,这里我们要将
其改为“ okay”。*/
7
8 spidev: icm20608@0 {/*icm20608 设备子节点,因为 icm20608 连接在 ECSPI3 的第 0 个通道上,因此
@后面为 0。第 9 行设置节点属性兼容值为“ alientek,icm20608”,第 10 行设置 SPI 最大时钟频
率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。第 11 行, icm20608 连接
在通道 0 上,因此 reg 为 0。*/
9 compatible = "alientek,icm20608";
10 spi-max-frequency = <8000000>;
11 reg = <0>;
12 };
13 };

2、编写ICM20608驱动

1)、icm20608 设备结构体创建

        需要注意在 probe 函数中设置 private_data 为 probe 函数传递进来的 spi_device 参数。

void *private_data; /* 私有数据 */

2)、icm20608 spi_driver 注册与注销

1 /* 传统匹配方式 ID 列表 */
2 static const struct spi_device_id icm20608_id[] = {/*第 2~5 行,传统的设备和驱动匹配表。*/
3 {"alientek,icm20608", 0},
4 {}
5 };
6
7 /* 设备树匹配列表 */
8 static const struct of_device_id icm20608_of_match[] = {/*第 8~11 行,设备树的设备与驱动匹配表,这里只有一个匹配项:“ alientek,icm20608”。*/
9 { .compatible = "alientek,icm20608" },
10 { /* Sentinel */ }
11 };
12
13 /* SPI 驱动结构体 */
14 static struct spi_driver icm20608_driver = {/*第 14~23 行, icm20608 的 spi_driver 结构体变量,当 icm20608 设备和此驱动匹配成功以后
第 15 行的 icm20608_probe 函数就会执行。同样的,当注销此驱动的时候 icm20608_remove 函
数会执行。*/
15 .probe = icm20608_probe,
16 .remove = icm20608_remove,
17 .driver = {
18 .owner = THIS_MODULE,
19 .name = "icm20608",
20 .of_match_table = icm20608_of_match,
21 },
22 .id_table = icm20608_id,
23 };
24
25 /*
26 * @description : 驱动入口函数
27 * @param : 无
28 * @return : 无
29 */
30 static int __init icm20608_init(void)/*第 30~33 行, icm20608_init 函数为 icm20608 的驱动入口函数,在此函数中使用
spi_register_driver 向 Linux 系统注册上面定义的 icm20608_driver。*/
31 {
32 return spi_register_driver(&icm20608_driver);
33 }
34
35 /*
36 * @description : 驱动出口函数
37 * @param : 无
38 * @return : 无
39 */
40 static void __exit icm20608_exit(void)/*第 40~43 行, icm20608_exit 函数为 icm20608 的驱动出口函数,在此函数中使用
spi_unregister_driver 注销掉前面注册的 icm20608_driver。*/
41 {
42 spi_unregister_driver(&icm20608_driver);
43 }
44
45 module_init(icm20608_init);
46 module_exit(icm20608_exit);
47 MODULE_LICENSE("GPL");
48 MODULE_AUTHOR("zuozhongkai");

3)、probe&remove 函数

8 static int icm20608_probe(struct spi_device *spi)
9 {
10 int ret = 0;
11
12 /* 1、构建设备号 */
13 if (icm20608dev.major) {
14 icm20608dev.devid = MKDEV(icm20608dev.major, 0);
15 register_chrdev_region(icm20608dev.devid, ICM20608_CNT,
ICM20608_NAME);
16 } else {
17 alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT,
ICM20608_NAME);
18 icm20608dev.major = MAJOR(icm20608dev.devid);
19 }
20
21 /* 2、注册设备 */
22 cdev_init(&icm20608dev.cdev, &icm20608_ops);
23 cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
24
25 /* 3、创建类 */
26 icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
27 if (IS_ERR(icm20608dev.class)) {
28 return PTR_ERR(icm20608dev.class);
29 }
30
31 /* 4、创建设备 */
32 icm20608dev.device = device_create(icm20608dev.class, NULL,
icm20608dev.devid, NULL, ICM20608_NAME);
33 if (IS_ERR(icm20608dev.device)) {
34 return PTR_ERR(icm20608dev.device);
35 }
36
37 /* 获取设备树中 cs 片选信号 */
38 icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/
spba-bus@02000000/ecspi@02010000");
39 if(icm20608dev.nd == NULL) {
40 printk("ecspi3 node not find!\r\n");
41 return -EINVAL;
42 }
43
44 /* 2、 获取设备树中的 gpio 属性,得到 CS 片选所使用的 GPIO 编号 */
45 icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd,
"cs-gpio", 0);
46 if(icm20608dev.cs_gpio < 0) {
47 printk("can't get cs-gpio");
48 return -EINVAL;
49 }
50
51 /* 3、设置 GPIO1_IO20 为输出,并且输出高电平 */
52 ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
53 if(ret < 0) {
54 printk("can't set gpio!\r\n");
55 }
56
57 /*初始化 spi_device */
58 spi->mode = SPI_MODE_0; /*MODE0, CPOL=0, CPHA=0 */
59 spi_setup(spi);
60 icm20608dev.private_data = spi; /* 设置私有数据 */
61
62 /* 初始化 ICM20608 内部寄存器 */
63 icm20608_reginit();
64 return 0;
65 }

        probe 函数,当设备与驱动匹配成功以后此函数就会执行,第 13~55 行都是标
准的注册字符设备驱动。其中在第 38~49 行获取设备节点中的“ cs-gpio”属性,也就是获取到
设备的片选 IO。

57 /*初始化 spi_device */
58 spi->mode = SPI_MODE_0; /*MODE0, CPOL=0, CPHA=0 *//*设置 SPI 为模式 0,也就是 CPOL=0, CPHA=0。*/
59 spi_setup(spi);/*设置好 spi_device 以后需要使用 spi_setup 配置一下。*/
60 icm20608dev.private_data = spi; /* 设置私有数据 *//*设置 icm20608dev 的 private_data 成员变量为 spi_device。*/
61
62 /* 初始化 ICM20608 内部寄存器 */
63 icm20608_reginit();/*调用 icm20608_reginit 函数初始化 ICM20608,主要是初始化 ICM20608 指定寄
存器。*/
64 return 0;
65 }
66
67 /*
68 * @description : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行
69 * @param – client : spi 设备
70 * @return : 0,成功;其他负值,失败
71 */

/*icm20608_remove 函数,注销驱动的时候此函数就会执行。*/
72 static int icm20608_remove(struct spi_device *spi)
73 {
74 /* 删除设备 */
75 cdev_del(&icm20608dev.cdev);
76 unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
77
78 /* 注销掉类和设备 */
79 device_destroy(icm20608dev.class, icm20608dev.devid);
80 class_destroy(icm20608dev.class);
81 return 0;
82 }

4)、icm20608 寄存器读写与初始化

1 /*
2 * @description : 从 icm20608 读取多个寄存器数据
3 * @param – dev : icm20608 设备
4 * @param – reg : 要读取的寄存器首地址
5 * @param – val : 读取到的数据
6 * @param – len : 要读取的数据长度
7 * @return : 操作结果
8 */
9 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg,
void *buf, int len)
10 {
11 int ret;
12 unsigned char txdata[len];
13 struct spi_message m;
14 struct spi_transfer *t;
15 struct spi_device *spi = (struct spi_device *)dev->private_data;
16
17 gpio_set_value(dev->cs_gpio, 0); /* 片选拉低,选中 ICM20608 */
18 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
19
20 /* 第 1 次,发送要读取的寄存地址 */
21 txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址 bit7 要置 1 */
22 t->tx_buf = txdata; /* 要发送的数据 */
23 t->len = 1; /* 1 个字节 */
24 spi_message_init(&m); /* 初始化 spi_message */
25 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message */
26 ret = spi_sync(spi, &m); /* 同步发送 */
27
28 /* 第 2 次,读取数据 */
29 txdata[0] = 0xff; /* 随便一个值,此处无意义 */
30 t->rx_buf = buf; /* 读取到的数据 */
31 t->len = len; /* 要读取的数据长度 */
原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
1467
I.MX6U 嵌入式 Linux 驱动开发指南
32 spi_message_init(&m); /* 初始化 spi_message */
33 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message*/
34 ret = spi_sync(spi, &m); /* 同步发送 */
35
36 kfree(t); /* 释放内存 */
37 gpio_set_value(dev->cs_gpio, 1); /* 片选拉高,释放 ICM20608 */
38
39 return ret;
40 }
41
42 /*
43 * @description : 向 icm20608 多个寄存器写入数据
44 * @param – dev : icm20608 设备
45 * @param – reg : 要写入的寄存器首地址
46 * @param – val : 要写入的数据缓冲区
47 * @param – len : 要写入的数据长度
48 * @return : 操作结果
49 */
50 static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg,
u8 *buf, u8 len)
51 {
52 int ret;
53
54 unsigned char txdata[len];
55 struct spi_message m;
56 struct spi_transfer *t;
57 struct spi_device *spi = (struct spi_device *)dev->private_data;
58
59 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
60 gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */
61
62 /* 第 1 次,发送要读取的寄存地址 */
63 txdata[0] = reg & ~0x80; /* 写数据的时候寄存器地址 bit8 要清零 */
64 t->tx_buf = txdata; /* 要发送的数据 */
65 t->len = 1; /* 1 个字节 */
66 spi_message_init(&m); /* 初始化 spi_message */
67 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message */
68 ret = spi_sync(spi, &m); /* 同步发送 */
69
70 /* 第 2 次,发送要写入的数据 */
71 t->tx_buf = buf; /* 要写入的数据 */
72 t->len = len; /* 写入的字节数 */
73 spi_message_init(&m); /* 初始化 spi_message */
74 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message*/
75 ret = spi_sync(spi, &m); /* 同步发送 */
76
77 kfree(t); /* 释放内存 */
78 gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放 ICM20608 */
79 return ret;
80 }
81
82 /*
83 * @description : 读取 icm20608 指定寄存器值,读取一个寄存器
84 * @param – dev : icm20608 设备
85 * @param – reg : 要读取的寄存器
86 * @return : 读取到的寄存器值
87 */
88 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
u8 reg)
89 {
90 u8 data = 0;
91 icm20608_read_regs(dev, reg, &data, 1);
92 return data;
93 }
94
95 /*
96 * @description : 向 icm20608 指定寄存器写入指定的值,写一个寄存器
97 * @param – dev : icm20608 设备
98 * @param – reg : 要写的寄存器
99 * @param – data : 要写入的值
100 * @return : 无
101 */
102
103 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg,
u8 value)
104 {
105 u8 buf = value;
106 icm20608_write_regs(dev, reg, &buf, 1);
107 }
108
109 /*
110 * @description : 读取 ICM20608 的数据,读取原始数据,包括三轴陀螺仪、
111 * : 三轴加速度计和内部温度。
112 * @param - dev : ICM20608 设备
113 * @return : 无。
114 */
115 void icm20608_readdata(struct icm20608_dev *dev)
116 {
117 unsigned char data[14];
118 icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
119
120 dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
121 dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
122 dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
123 dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
124 dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
125 dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
126 dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
127 }
128 /*
129 * ICM20608 内部寄存器初始化函数
130 * @param : 无
131 * @return : 无
132 */
133 void icm20608_reginit(void)
134 {
135 u8 value = 0;
136
137 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
138 mdelay(50);
139 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
140 mdelay(50);
141
142 value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
143 printk("ICM20608 ID = %#X\r\n", value);
144
145 icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
146 icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
147 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
148 icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
149 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
150 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);
151 icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);
152 icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);
153 }

5)、字符设备驱动框架

        重点是第 22~38 行的 icm20608_read 函数,当应用程序调用 read 函数读取 icm20608 设备文件的时候此函数就会执行。此函数调用上面编写好的icm20608_readdata 函数读取 icm20608 的原始数据并将其上报给应用程序。

3、编写测试APP

32 int main(int argc, char *argv[])
33 {
34 int fd;
35 char *filename;
36 signed int databuf[7];
37 unsigned char data[14];
38 signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
39 signed int accel_x_adc, accel_y_adc, accel_z_adc;
40 signed int temp_adc;
41
42 float gyro_x_act, gyro_y_act, gyro_z_act;
43 float accel_x_act, accel_y_act, accel_z_act;
44 float temp_act;
45
46 int ret = 0;
47
48 if (argc != 2) {
49 printf("Error Usage!\r\n");
50 return -1;
51 }
52
53 filename = argv[1];
54 fd = open(filename, O_RDWR);
55 if(fd < 0) {
原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
1473
I.MX6U 嵌入式 Linux 驱动开发指南
56 printf("can't open file %s\r\n", filename);
57 return -1;
58 }
59
60 while (1) {
61 ret = read(fd, databuf, sizeof(databuf));
62 if(ret == 0) { /* 数据读取成功 */
63 gyro_x_adc = databuf[0];
64 gyro_y_adc = databuf[1];
65 gyro_z_adc = databuf[2];
66 accel_x_adc = databuf[3];
67 accel_y_adc = databuf[4];
68 accel_z_adc = databuf[5];
69 temp_adc = databuf[6];
70
71 /* 计算实际值 */
72 gyro_x_act = (float)(gyro_x_adc) / 16.4;
73 gyro_y_act = (float)(gyro_y_adc) / 16.4;
74 gyro_z_act = (float)(gyro_z_adc) / 16.4;
75 accel_x_act = (float)(accel_x_adc) / 2048;
76 accel_y_act = (float)(accel_y_adc) / 2048;
77 accel_z_act = (float)(accel_z_adc) / 2048;
78 temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
79
80 printf("\r\n 原始值:\r\n");
81 printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc,
gyro_y_adc, gyro_z_adc);
82 printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc,
accel_y_adc, accel_z_adc);
83 printf("temp = %d\r\n", temp_adc);
84 printf("实际值:");
85 printf("act gx = %.2f°/S, act gy = %.2f°/S,
act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act,
gyro_z_act);
86 printf("act ax = %.2fg, act ay = %.2fg,
act az = %.2fg\r\n", accel_x_act, accel_y_act,
accel_z_act);
87 printf("act temp = %.2f°C\r\n", temp_act);
88 }
89 usleep(100000); /*100ms */
90 }
91 close(fd); /* 关闭文件 */
92 return 0;
93 }

六、运行测试

1、编译驱动程序和测试APP

1)、编译驱动程序

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := icm20608.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第 4 行,设置 obj-m 变量的值为“ icm20608.o”。
输入如下命令编译出驱动模块文件:
make -j32
编译成功以后就会生成一个名为“ icm20608.ko”的驱动模块文件。


2)、编译测试APP

在编译的时候加入如下参数即可:
-march-armv7-a -mfpu-neon -mfloat=hard
输入如下命令使能硬件浮点编译 icm20608App.c 这个测试程序:
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o
icm20608App
 

2、运行测试

        输入如下命令加载 icm20608.ko 这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko //加载驱动模块
        当驱动模块加载成功以后使用 icm20608App 来测试,输入如下命令:
./icm20608App /dev/icm20608
        测试 APP 会不断的从 ICM20608 中读取数据,然后输出到终端上

七、总结

        本节的内容较多,可以分成两天进行学习。主要学习了SPI驱动开发及运行测试的相关内容。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/141595.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件测试面试,一份八股文足矣(含文档)

前言 在我认为&#xff0c;对于测试面试以及进阶的最佳学习方法莫过于刷题博客书籍视频总结&#xff0c;前几者我将淋漓尽致地挥毫于这篇博客文章中&#xff0c;至于总结在于个人&#xff0c;实际上越到后面你会发现面试并不难&#xff0c;其次就是在刷题的过程中有没有去思考…

Topaz Photo AI for Mac/win(人工智能降噪软件) 完美兼容激活版

Topaz Photo AI是一款基于人工智能的照片编辑软件&#xff0c;具有革命性的功能。它提供了强大的工具和技术&#xff0c;让用户能够编辑照片而不降低质量。该软件具备高清晰度效果、降噪和自动照片润色工具&#xff0c;能够帮助用户制作令人惊叹的照片。 它包括复杂的锐化算法…

DNS域名解析

目录 1.概述 1.1产生原因 1.2作用 1.3连接方式 1.4因特网的域名结构 1.4.1拓扑 1.4.2分类 1.4.3域名服务器类型划分 2. DNS域名解析过程 2.1分类 2.2解析图 2.2.2过程分析 3.搭建DNS域名解析服务器 3.1.概述 3.2安装软件 3.3bind服务中三个关键文件 3.4主配置…

Matplotlib的使用方法

Matplotlib是Python最著名的绘图库&#xff0c;它提供了一整套和Matlab相似的命令API&#xff0c;十分适合交互式地进行制图。而且也可以方便地将它作为绘图控件&#xff0c;嵌入到GUI应用程序中。Matplotlib能够创建多数类型的图表&#xff0c;如条形图、散点图、条形图、饼图…

mysql之正则表达式匹配

题目&#xff1a; 今天在牛客网看到一道关于数据库正则表达式匹配的问题&#xff0c;发现自己一点不会做。 正则表达式&#xff1a; 一、正则表达式 MySQL 正则表达式通常是在检索数据库记录的时候&#xff0c;根据指定的匹配模式匹配记录中 符合要求的特殊字符串。MySQL 的…

JavaScript事件处理

在IE 3.0和Netscape 2.0浏览器中开始出现事件。DOM 2规范开始标准化DOM事件&#xff0c;直到2004年发布DOM 3.0时&#xff0c;W3C才完善事件模型。目前&#xff0c;所有主流浏览器都支持DOM 2事件模块。IE8及其早期版本还继续使用IE事件模块。 1、事件基础 1.1、事件模型 在…

deepstream生成pipeline拓扑图的方法

deepstream生成pipeline拓扑图的方法 1、前期工作1.1 安装dot 2、使用命令行生成2.1、添加环境变量2.2 、运行管道2.3 、使用dot 生成png图片 3、在c中使用3.1、添加代码3.2、运行代码3.3 、使用dot 生成png图片 4、在python中使用4.1、添加代码4.2 、使用dot 生成png图片 1、前…

【机器学习基础】机器学习入门(2)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a;【机器学习基础】机器学习入门&#xff08;1&#xff09; &#x1f4a1;…

设计模式之工厂模式 ( Factory Pattern )(1)

其他设计模式也会后续更新… 设计模式其实需要有一定开发经验才好理解&#xff0c;对代码有一定的设计要求&#xff0c;工作中融入才是最好的 工厂模式 ( Factory Pattern ) 工厂模式&#xff08;Factory Pattern&#xff09;提供了一种创建对象的最佳方式 工厂模式在创建对…

“ChatGPT 之父”暗讽马斯克;传安卓版本与鸿蒙将不再兼容;PICO 裁撤游戏工作室团队丨 RTE 开发者日报 Vol.83

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

ubuntu开机系统出错且无法恢复。请联系系统管理员。

背景&#xff1a; ubuntu22.04.2命令行&#xff0c;执行自动安装系统推荐显卡驱动命令&#xff0c;字体变大&#xff0c;重启后出现如下图错误&#xff0c;无法进入系统&#xff0c;无法通过CTRLALTF1-F3进入TTY模式。 解决办法&#xff1a; 1.首先要想办法进入系统&#xff…

VMware 虚拟机开启后黑屏问题的解决方式

很好&#xff0c;现在是vm 虚拟机节目的连续剧了 首先&#xff0c;我们安装好了&#xff0c;vm软件。 其次&#xff0c;我们在vm中创建了虚拟机。 再其次&#xff0c;我们解决了&#xff0c;开启虚拟机计算机自动重启的问题。 最后我们遇到了这个问题&#xff1a;虚拟机开启后整…

CSDN的规范、检测文章质量、博客等级好处等等(我也是意外发现的,我相信很多人还不知道,使用分享给大家!)

前言 都是整理官方的文档&#xff0c;方便自己查看和检查使用&#xff0c;以前我也不知道。后来巧合下发现的&#xff0c;所以分享给大家&#xff01; 下面都有官方的链接&#xff0c;详情去看官方的文档。 大家严格按照官方的规范去记录自己工作生活中的文章&#xff0c;很快…

AP/PF PLASMA电源维修等离子变频电源PF23V-A1-138

维修包括&#xff1a;PECVD、MOCVD、IONIMP,PLASMA的设备电源,包括直流、高压、脉冲、射频、微波、匹配器、RPSC、CHILLER等。电源维修的时候&#xff0c;需要检测一下各功率器件是否存在击穿短路&#xff0c;例如电源整流桥堆、开关管、高频大功率整流管、浪涌电流的大功率电阻…

东莞松山湖数据中心|莞服务器托管的优势

东莞位于珠江三角洲经济圈&#xff0c;交通便利&#xff0c;与广州、深圳等大城市相邻&#xff0c;而且东莞是中国重要的制造业基地&#xff0c;有众多的制造业和科技企业集聚于此&#xff0c;随着互联网和数字化时代的到来&#xff0c;企业都向数字化转型&#xff0c;对于信息…

将请求映射到servlet的规则

参考资源 详情可以参考&#xff1a;https://jakarta.ee/specifications/servlet/6.0/jakarta-servlet-spec-6.0.html#mapping-requests-to-servlets URL路径的使用 web容器接收到客户端的请求&#xff0c;决定转发给哪个web应用。被选中的web 应用必须具有最长的上下文&…

【LittleXi】C程序预处理、编译、汇编、链接步骤

【LittleXi】C程序预处理、编译、汇编、链接步骤 C程序 #include<stdio.h> int main(){int x1,y1;printf("xy%d",xy); }1、预处理 将头文件引入进来、除去注释、宏定义下放 执行指令 g -E esc.c -o esc.i 2、编译 将处理好的代码编译为汇编代码.s 执行…

测试行业爬了7年,从功能测试到高级测试,工资也翻了好几倍

我在测试行业爬了7年。从功能测试到现在成为高级测试&#xff0c;我的工资也翻了好几倍。 入门阶段&#xff08;功能测试&#xff09; 个人认为&#xff0c;测试的前景还不错&#xff0c;只要你肯努力&#xff1b;刚出来的时候在鹅厂做外包功能测试。每天都很悠闲。点了两年&a…

Cesium深入浅出之自定义材质

引子 做为一名技术宅却没有能拿得出手的技术无疑是最可悲的事情。三年前&#xff0c;当我第一次接触Cesium的时候就被它强大和炫丽所折服&#xff0c;最关键的是它还是开源的。以前我一直是机械地敲着业务代码&#xff0c;好像计算机程序就只能干这点事情一样&#xff0c;而 C…

基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码

基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于缎蓝园丁鸟算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于缎蓝园丁鸟优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针…