当前位置: 首页 > news >正文

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


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

相关文章:

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

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章的思维导图如下&#xff1a; 二、I.MX6U SPI主机驱动分析 主机驱动一般都是由SOC厂商写好的。不作为重点需要掌握的内容。 三、SPI设备驱动编写流程 1、SP…...

黑客泄露 3500 万条 LinkedIn 用户记录

被抓取的 LinkedIn 数据库分为两部分泄露&#xff1a;一部分包含 500 万条用户记录&#xff0c;第二部分包含 3500 万条记录。 LinkedIn 数据库保存了超过 3500 万用户的个人信息&#xff0c;被化名 USDoD 的黑客泄露。 该数据库在臭名昭著的网络犯罪和黑客平台 Breach Forum…...

Flink SQL -- 反压

1、测试反压&#xff1a; 1、反压&#xff1a; 指的是下游消费数据的速度比上游产生数据的速度要小时会出现反压&#xff0c;下游导致上游的Task反压。 2、测试反压&#xff1a;使用的是DataGen CREATE TABLE words (word STRING ) WITH (connector datagen,rows-per-second…...

快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个…...

超详细介绍如何使用 OpenCV 和 BGS 库进行背景扣除

深入研究这些 CV 系统背后的想法,我们可以观察到,在大多数情况下,初始步骤包含背景减除 (BS),这有助于获得视频流中对象的相对粗略和快速的识别,以便对其进行进一步的精细处理。在当前的文章中,我们将介绍几种在准确性和处理时间 BS 方法方面值得注意的算法:SuBSENSE和基…...

STM32F4、GD32F4 内部硬件CRC使用方法和踩坑实录

背景 某项目用到了IC卡刷卡启动功能,程序中对读取IC卡的相关数据后要进行CRC校验,本文介绍如何在STM32F4 GD32F4 平台上使用标准库函数进行CRC硬件校验。 摘要 本文介绍如何在STM32F4、GD32F4 平台上使用标准库函数进行CRC硬件校验。包括容易出现的问题和解决方法。涉及STM3…...

【SpringBoot】序列化和反序列化介绍

一、认识序列化和反序列化 Serialization&#xff08;序列化&#xff09;是一种将对象以一连串的字节描述的过程&#xff1b;deserialization&#xff08;反序列化&#xff09;是一种将这些字节重建成一个对象的过程。将程序中的对象&#xff0c;放入文件中保存就是序列化&…...

Android 升级软件后清空工厂模式测试进度

Android 升级软件后清空工厂模式测试进度 最近收到项目需求反馈&#xff1a;升级软件后,进入工厂模式测试项,界面显示测试项保留了升级前的测试状态&#xff08;有成功及失败&#xff09;,需修改升级软件后默认清空测试项测试状态&#xff0c;具体修改参照如下&#xff1a; /…...

Promise原理、以及Promise.race、Promise.all、Promise.resolve、Promise.reject实现;

为了向那道光亮奔过去&#xff0c;他敢往深渊里跳&#xff1b; 于是今天朝着Promise的实现前进吧&#xff0c;写了四个小时&#xff0c;终于完结撒花&#xff1b; 我知道大家没有耐心&#xff0c;当然我也坐的腰疼&#xff0c;直接上代码&#xff0c;跟着我的注释一行行看过去…...

mysql---MHA(高可用)

MHA概述 magterhight availabulity :基于主库的高可用环境下&#xff0c;主故障切换基础要求&#xff1a;主从架构 &#xff08;一主两从&#xff09;解决mysql的单点故障问题&#xff0c;一旦数据库崩溃&#xff0c;MHA会在0-30s内这东东完成故障切换。复制方式&#xff1a;半…...

人工智能基础_机器学习032_多项式回归升维_原理理解---人工智能工作笔记0072

现在开始我们来看多项式回归,首先理解多维 原来我们学习的使用线性回归,其实就是一条直线对吧,那个是一维的,我们之前学的全部都是一维的对吧,是一维的,然后是多远的,因为有多个x1,x2,x3,x4... 但是比如我们有一个数据集,是上面这种,的如果用一条直线很难拟合,那么 这个时候,…...

C#截取范围

string[] strs new string[]{"1e2qe","23123e21","3ewqewq","4fewfew","5fsdfds"};var list strs[1..2];Range p 0..3;var list strs[Range];...

用 winget 在 Windows 上安装 kubectl

目录 kubectl 是什么&#xff1f; 安装 kubectl 以管理员身份打开 PowerShell 使用 winget 安装 kubectl 测试一下&#xff0c;确保安装的是最新版本 导航到你的 home 目录&#xff1a; 验证 kubectl 配置 kubectl 是什么&#xff1f; kubectl 是 Kubernetes 的命令行工…...

1 Supervised Machine Learning Regression and Classification

文章目录 Week1OverViewSupervised LearningUnsupervised LearningLinear Regression ModelCost functionGradient Descent Week2Muliple FeatureVectorizationGradient Descent for Multiple RegressionFeature ScalingGradient DescentFeature EngineeringPolynomial Regress…...

Antv/G2 折线图 DataSet 数据展开成指定格式

DataSet 文档 G2 3.2 DataSet 文档 Demo&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><m…...

物理问题中常见的分析问题----什么样的函数性质较好

物理问题中常见的积分符号位置交换问题 重极限与累次极限 高数下的定义 累次极限&#xff1a;求极限时需要遵循一定的顺序重极限&#xff1a;任意方向趋于的极限 两者之间的关系&#xff1a; 两者没啥关系存在累次极限存在而不相等的函数...... 求和符号与积分符号互换--逐项积…...

8 Go的函数

概述 在上一节的内容中&#xff0c;我们介绍了Go的指针&#xff0c;包括&#xff1a;使用指针、空指针、指针数组、指向指针的指针等。在本节中&#xff0c;我们将介绍Go的函数。函数允许开发者将相关的代码组织在一起&#xff0c;并将其命名&#xff0c;以便在其他地方进行调用…...

算法笔记-第九章-二叉树的遍历(待整理)

算法笔记-第九章-二叉树的遍历 二叉树的先序遍历二叉树的中序遍历二叉树的先序遍历 //二叉树的先序遍历 #include <cstdio> #include <vector> using namespace std;const int MAXN = 50;struct Node //用结构体表示左子树和右子树的数据 {int l, r; } nodes[MAXN]…...

C语言从入门到精通之【字符串】

C语言没有专门用于储存字符串的变量类型&#xff0c;字符串都被储存在char类型的数组中。数组由连续的存储单元组成&#xff0c;字符串中的字符被储存在相邻的存储单元中&#xff0c;每个单元储存一个字符&#xff0c;每个字符占1个字节。 数组末尾位置的字符\0。这是空字符&am…...

超详细!必看!!STM32--时钟树原理

一、什么是时钟&#xff1f; 时钟是单片机的脉搏&#xff0c;是系统工作的同步节拍。单片机上至CPU&#xff0c;下至总线外设&#xff0c;它们工作时序的配合&#xff0c;都需要一个同步的时钟信号来统一指挥。时钟信号是周期性的脉冲信号。 二、什么是时钟树&#xff1f; S…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究

摘要&#xff1a;在消费市场竞争日益激烈的当下&#xff0c;传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序&#xff0c;探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式&#xff0c;分析沉浸式体验的优势与价值…...