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 品人间百味 思文短情长 本章的思维导图如下: 二、I.MX6U SPI主机驱动分析 主机驱动一般都是由SOC厂商写好的。不作为重点需要掌握的内容。 三、SPI设备驱动编写流程 1、SP…...

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

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

快速入门安装及使用git与svn的区别常用命令
一、导言 1、什么是svn? SVN是Subversion的简称,是一个集中式版本控制系统。与Git不同,SVN没有分布式的特性。在SVN中,项目的代码仓库位于服务器上,团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个…...
超详细介绍如何使用 OpenCV 和 BGS 库进行背景扣除
深入研究这些 CV 系统背后的想法,我们可以观察到,在大多数情况下,初始步骤包含背景减除 (BS),这有助于获得视频流中对象的相对粗略和快速的识别,以便对其进行进一步的精细处理。在当前的文章中,我们将介绍几种在准确性和处理时间 BS 方法方面值得注意的算法:SuBSENSE和基…...

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

【SpringBoot】序列化和反序列化介绍
一、认识序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程;deserialization(反序列化)是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化&…...
Android 升级软件后清空工厂模式测试进度
Android 升级软件后清空工厂模式测试进度 最近收到项目需求反馈:升级软件后,进入工厂模式测试项,界面显示测试项保留了升级前的测试状态(有成功及失败),需修改升级软件后默认清空测试项测试状态,具体修改参照如下: /…...
Promise原理、以及Promise.race、Promise.all、Promise.resolve、Promise.reject实现;
为了向那道光亮奔过去,他敢往深渊里跳; 于是今天朝着Promise的实现前进吧,写了四个小时,终于完结撒花; 我知道大家没有耐心,当然我也坐的腰疼,直接上代码,跟着我的注释一行行看过去…...
mysql---MHA(高可用)
MHA概述 magterhight availabulity :基于主库的高可用环境下,主故障切换基础要求:主从架构 (一主两从)解决mysql的单点故障问题,一旦数据库崩溃,MHA会在0-30s内这东东完成故障切换。复制方式:半…...

人工智能基础_机器学习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 是什么? 安装 kubectl 以管理员身份打开 PowerShell 使用 winget 安装 kubectl 测试一下,确保安装的是最新版本 导航到你的 home 目录: 验证 kubectl 配置 kubectl 是什么? 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: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><m…...
物理问题中常见的分析问题----什么样的函数性质较好
物理问题中常见的积分符号位置交换问题 重极限与累次极限 高数下的定义 累次极限:求极限时需要遵循一定的顺序重极限:任意方向趋于的极限 两者之间的关系: 两者没啥关系存在累次极限存在而不相等的函数...... 求和符号与积分符号互换--逐项积…...
8 Go的函数
概述 在上一节的内容中,我们介绍了Go的指针,包括:使用指针、空指针、指针数组、指向指针的指针等。在本节中,我们将介绍Go的函数。函数允许开发者将相关的代码组织在一起,并将其命名,以便在其他地方进行调用…...

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

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

超详细!必看!!STM32--时钟树原理
一、什么是时钟? 时钟是单片机的脉搏,是系统工作的同步节拍。单片机上至CPU,下至总线外设,它们工作时序的配合,都需要一个同步的时钟信号来统一指挥。时钟信号是周期性的脉冲信号。 二、什么是时钟树? S…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...