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驱动开发及运行测试的相关内容。


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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【广州华锐视点】海外制片人VR虚拟情景教学带来全新的学习体验

虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;是一种利用电脑模拟产生一个三维的虚拟世界&#xff0c;提供用户关于视觉、听觉、触觉等感官的模拟体验的技术。随着科技的进步&#xff0c;VR已经被广泛应用到许多领域&#xff0c;包括游戏、教育、医疗、房…

轻量封装WebGPU渲染系统示例<29>- 深度模糊DepthBlur(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/DepthBlur.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下: const blurRTTTex0 { diffuse: { uuid: "rtt0", …

【Linux】第十六站:进程地址空间

文章目录 一、程序地址空间1.内存的分布2.static修饰后为什么不会被释放3.一个奇怪的现象 二、进程地址空间1.前面现象的原因2.地址空间究竟是什么&#xff1f;3.为什么要有进程地址空间4.页表5.什么叫进程&#xff1f;6.进程具有独立性。为什么&#xff1f;怎么做到呢&#xf…

2022年09月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 已知字符串:s=“语文,数学,英语”,执行print(s.split(“,”))语句后结果是?( ) A: [‘语文’, ‘数学’, ‘英语’] B: [语文, 数学, 英语] C: [‘语文, 数学, 英语’] D: [‘语…

Web安全之PHP的伪协议漏洞利用,以及伪协议漏洞防护方法

一、背景 今天介绍一个比较冷门的知识&#xff0c;只有在PHP环境中存在的伪协议漏洞&#xff0c;那么什么是PHP伪协议呢&#xff1f;PHP伪协议事实上就是支持的协议与封装协议。可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。 除了这些封装协议…

【Vue】过滤器Filters

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如对您有用&#xff0c;可以点赞收藏哈 过滤器 filters过滤器已从Vue 3.0中删除&#xff0c;不再支持了&#xff0c;这里可以作为了解进行学习 vue3要精简代码&…

在 SQL 中,当复合主键成为外键时应该如何被其它表引用

文章目录 当研究一个问题慢慢深入时&#xff0c;一个看起来简单的问题也暗藏玄机。在 SQL 中&#xff0c;主键成为外键这是一个很平常的问题&#xff0c;乍一看没啥值得注意的。但如果这个主键是一种复合主键&#xff0c;而另一个表又引用这个键作为它的复合主键&#xff0c;问…

qnx log 系统

前言 本文主要介绍QNX 系统中的 log 打印相关接口和使用方法 软件环境:qnx7.1 一、QNX查看 log 的工具 slog2info 1. slog2info 的相关介绍 和linux 中查看 kernel log 信息的 dmesg 命令一样, qnx 里面也有一个查看 log 信息的命令,那就是 slog2info 命令, 如下图所示是…

【ArcGIS Pro微课1000例】0030:ArcGIS Pro中自带晕渲地貌工具的妙用

在ArcGIS中,制作地貌晕渲效果通常的做法是先制作山体阴影效果,然后叠加在DEM的下面,再改变DEM的透明度来实现。而在ArcGIS Pro中自带了效果显著的晕渲地貌工具。 文章目录 一、晕渲地貌工具1. 符号系统2. 栅格函数二、山体阴影效果1. 工具箱2. 栅格函数打开ArcGIS Pro3.0,加…

Ansible角色定制实例

目录 角色定制&#xff1a;roles 角色定制实例&#xff1a;利用角色部署wordpress 1.在roles目录下生成对应的目录结构 2.定义配置文件 ①nginx ②php ③mysql ④定义剧本文件 ⑤启动服务 角色定制&#xff1a;roles 对于普通的剧本&#xff08;playbook&#xff09;有…

全域全自主建设,亚信科技AntDB数据库助力广电5G业务上线运行

自2019年6月&#xff0c;中国广电成功获得5G牌照以来&#xff0c;迅速推进网络建设目标&#xff0c;成为5G网络覆盖广、应用场景多、用户体验出色的第四大运营商。其依托全球独有的700MHz频谱资源&#xff0c;具备覆盖能力强、容量足、速率高的优势。通过不断深化和中国移动的共…

HTTP服务器——tomcat的安装和使用

文章目录 前言下载tomcattomcat 文件bin 文件夹conf 文件lib 文件log 文件temp 文件webapps 文件work 目录 如何使用 tomcat 前言 前面我们已经学习了应用层协议 HTTP 协议和 HTTP 的改进版——HTTPS&#xff0c;这些协议是我们在写与服务器相关的代码的时候息息相关的&#x…

VSCode 好用的插件分享

文章目录 Introlistcode runner 【在文本编辑器中编辑好各类语言的源代码&#xff0c;然后一键运行】gitlens - 【git提交信息即时查看&#xff0c;类似IDEA中的 show annotation】还有更多&#xff0c;会日常补充。 Intro 大四毕业前&#xff0c;我只有一台dell latitude 455…

世微 降压恒流 12V 5A 一切一双灯 LED汽车大灯驱动方案 AP5191

AP5191是一款PWM工作模式,高效率、外围简 单、内置功率MOS管&#xff0c;适用于4.5-150V输入的高 精度降压LED恒流驱动芯片。输出功率150W&#xff0c; 电流6A。 AP5191可实现线性调光和PWM调光&#xff0c;线性调 光脚有效电压范围0.55-2.6V. AP5191 工作频率可以通过RT 外部…

微信小程序授权登陆 getUserProfile

目录 前言 步骤&#xff1a; 示例代码: 获取用户信息的接口变化历史: 注意事项&#xff1a; 前言 在微信小程序中&#xff0c;你可以使用 getUserProfile 接口来获取用户的个人信息&#xff0c;并进行授权登录。以下是使用 getUserProfile 的步骤&#xff1a; 小程序发了…

【系统安装】ubuntu20.04安装,正经教程,小白安装教程,百分百成功安装

1、安装的前提是有启动盘&#xff0c;这个比较好处理&#xff0c;清华源找到ubuntu20.04.iso镜像文件下载&#xff0c;然后用Rufus来制作启动盘就可以了&#xff0c;需要注意的是目标文件系统需要是UEFI&#xff0c;其他的话就没太多要求了&#xff0c;如果卡在这一步的话&…

Rust-使用dotenvy加载和使用环境变量

系统的开发&#xff0c;测试和部署离不开环境变量&#xff0c;今天分享在Rust的系统开发中&#xff0c;使用dotenvy来读取和使用环境变量。 安装 cargo add dotenvy dotenv_codegen 加载环境变量 use dotenvy::dotenv;fn main() {dotenv().expect(".env不存在");…

Maven 插件统一修改聚合工程项目版本号

目录 引言直接修改 pom.xml 的版本号的问题Maven 插件修改版本号开源项目微服务商城项目前后端分离项目 引言 在Maven项目中&#xff0c;我们通常有两种常见的方式来修改版本号&#xff1a;直接在pom.xml文件中手动编辑和利用Maven插件进行版本号调整。 本文将比较这两种修改…

说说你对Redux的理解?其工作原理?

一、是什么 React是用于构建用户界面的,帮助我们解决渲染DOM的过程 而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享 如果让每个组件都存储自身相关的状态,理论上来讲…

Flutter实践二:repository模式

1.repository 几乎所有的APP&#xff0c;从简单的到最复杂的&#xff0c;在它们的架构里几乎都包括状态管理和数据源这两部分。状态管理常见的有Bloc、Cubit、Provider、ViewModel等&#xff0c;数据源则是一些直接和数据库或者网络客户端进行交互&#xff0c;取得相应的数据&…