当前位置:   article > 正文

Linux SPI 驱动实验_linux spi的驱动开发流程

linux spi的驱动开发流程

目录

一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

2、SPI 设备驱动

SPI 设备数据收发处理流程

3、SPI 设备和驱动匹配过程

二、添加SPI 设备信息

1、添加 ICM20608 所使用的 IO

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

三、编写 ICM20608 驱动

1、修改makefile​编辑

2、icm20608reg.h

3、icm20608 设备结构体创建

4、probe 函数

5、icm20608 寄存器读写与初始化

①icm20608_read_regs函数

②icm20608_write_regs函数

③icm20608_read_onereg函数

④ icm20608_write_onereg函数

⑤ icm20608_readdata函数

⑥icm20608_reginit 函数

6、icm20608_read函数

代码如下

四、APP编写

编译验证


一、Linux 下 SPI 驱动框架简介

1、SPI 主机驱动

        SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核
使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,部分如下

  1. struct spi_master {
  2. struct device dev;
  3. struct list_head list;
  4. .....
  5. int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
  6. .....
  7. int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
  8. }

transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去

SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。和 I2C 适配器驱动一样, SPI 主机驱动一般都是 SOC 厂商去编写

SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master

2、SPI 设备驱动

spi 设备驱动也和 i2c 设备驱动也很类似, Linux 内核使用 spi_driver 结构体来表示 spi 设备
驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。 spi_driver 结构体定义在
include/linux/spi/spi.h 文件中,结构体内容如下:

 struct spi_driver {
          const struct spi_device_id *id_table;
          int (*probe)(struct spi_device *spi);
          int (*remove)(struct spi_device *spi);
          void (*shutdown)(struct spi_device *spi);
          struct device_driver driver;
 };

 spi_driver 和 i2c_driver、 platform_driver 基本一样,当 SPI 设备和驱动匹配成功以后 probe 函数就会执行。

SPI 设备数据收发处理流程

SPI 设备驱动的核心是 spi_driver,当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容部分如下:

  1. struct spi_transfer {
  2. 1 const void *tx_buf;
  3. 2 void *rx_buf;
  4. 3 unsigned len;
  5. ........
  6. 4 struct list_head transfer_list;
  7. };

第1行, tx_buf 保存着要发送的数据。
第2行, rx_buf 用于保存接收到的数据。
第3行, len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。

spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,在使用spi_message之前需要对其进行初始化,spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,异步传输不会阻塞的等到 SPI 数据传输完成,下面采用同步传输方式来完成 SPI 数据的传输工作

3、SPI 设备和驱动匹配过程

SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、 I2C 等驱动一样, SPI
总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:

 struct bus_type spi_bus_type = {
          .name = "spi",
          .dev_groups = spi_dev_groups,
        .match = spi_match_device,
         .uevent = spi_uevent,
 };

 SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:

  1. static int spi_match_device(struct device *dev,
  2. struct device_driver *drv)
  3. {
  4. 1 const struct spi_device *spi = to_spi_device(dev);
  5. 2 const struct spi_driver *sdrv = to_spi_driver(drv);
  6. 3
  7. 4 /* Attempt an OF style match */
  8. 5 if (of_driver_match_device(dev, drv))
  9. 6 return 1;
  10. 7
  11. 8 /* Then try ACPI */
  12. 9 if (acpi_driver_match_device(dev, drv))
  13. 10 return 1;
  14. 11
  15. 12 if (sdrv->id_table)
  16. 13 return !!spi_match_id(sdrv->id_table, spi);
  17. 14
  18. 15 return strcmp(spi->modalias, drv->name) == 0;
  19. }

 第5 行, of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配

第9 行, acpi_driver_match_device 函数用于 ACPI 形式的匹配

第13 行, spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配

第15 行,比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。

二、添加SPI 设备信息

1、添加 ICM20608 所使用的 IO

在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点
中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚

pinctrl_ecspi3: icm20608 {
            fsl,pins = < 
                MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x10b0    /* CS */
                MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK    0x10b1    /* SCLK */
                MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x10b1    /* MISO */
                MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x10b1    /* MOSI */
            >;
        };

注意的就是检查相应的 IO 有没有被其他的设备所使用,分别搜UART2_TX、UART2_RX、UART2_RTS和UART2_CTS,有复用的全部屏蔽,如下

 

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

 打开 imx6qdl-sabresd.dtsi 这个设备树头文件

  1. &ecspi3 {
  2. 1 fsl,spi-num-chipselects = <1>;
  3. 2 cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
  4. 3 pinctrl-names = "default";
  5. 4 pinctrl-0 = <&pinctrl_ecspi3>;
  6. 5 status = "okay";
  7. 6 spidev: icm20608@0 {
  8. 7 compatible = "my,icm20608";
  9. 8 spi-max-frequency = <8000000>;
  10. 9 reg = <0>;
  11. };
  12. };

第1行,设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备

第2行,设置“cs-gpios”属性,也就是片选信号,SPI 主机驱动就会控制片选引脚

第3行,设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字

第4行,设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点

第5行,“status”属性为“okay”

第6行,每一个 SPI 设备都采用一个子节点来描述其设备信息,icm20608 连接在 ECSPI3 的第 0 个通道上,因此@后面为 0

第7行,SPI 设备的 compatible 属性值,用于匹配设备驱动

第8行,“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据使用的SPI 设备来设置

第9行,icm20608 连接在通道 0 上,因此 reg 为 0。

三、编写 ICM20608 驱动

新建 icm20608.c 和 icm20608reg.h 这两个文件, icm20608.c 为 ICM20608的驱动代码, icm20608reg.h 是 ICM20608 寄存器头文件

1、修改makefile

2、icm20608reg.h

  1. #ifndef __IMC20608REG_H
  2. #define __IMC20608REG_H
  3. /* ID值 */
  4. #define ICM20608G_ID (0XAF)
  5. #define ICM20608D_ID (0XAE)
  6. /* 定义寄存器 */
  7. /* ICM20608寄存器
  8. *复位后所有寄存器地址都为0,除了
  9. *Register 107(0X6B) Power Management 1 = 0x40
  10. *Register 117(0X75) WHO_AM_I = 0xAF或0xAE
  11. */
  12. /* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
  13. #define ICM20_SELF_TEST_X_GYRO 0x00
  14. #define ICM20_SELF_TEST_Y_GYRO 0x01
  15. #define ICM20_SELF_TEST_Z_GYRO 0x02
  16. #define ICM20_SELF_TEST_X_ACCEL 0x0D
  17. #define ICM20_SELF_TEST_Y_ACCEL 0x0E
  18. #define ICM20_SELF_TEST_Z_ACCEL 0x0F
  19. /* 陀螺仪静态偏移 */
  20. #define ICM20_XG_OFFS_USRH 0x13
  21. #define ICM20_XG_OFFS_USRL 0x14
  22. #define ICM20_YG_OFFS_USRH 0x15
  23. #define ICM20_YG_OFFS_USRL 0x16
  24. #define ICM20_ZG_OFFS_USRH 0x17
  25. #define ICM20_ZG_OFFS_USRL 0x18
  26. #define ICM20_SMPLRT_DIV 0x19
  27. #define ICM20_CONFIG 0x1A
  28. #define ICM20_GYRO_CONFIG 0x1B
  29. #define ICM20_ACCEL_CONFIG 0x1C
  30. #define ICM20_ACCEL_CONFIG2 0x1D
  31. #define ICM20_LP_MODE_CFG 0x1E
  32. #define ICM20_ACCEL_WOM_THR 0x1F
  33. #define ICM20_FIFO_EN 0x23
  34. #define ICM20_FSYNC_INT 0x36
  35. #define ICM20_INT_PIN_CFG 0x37
  36. #define ICM20_INT_ENABLE 0x38
  37. #define ICM20_INT_STATUS 0x3A
  38. /* 加速度输出 */
  39. #define ICM20_ACCEL_XOUT_H 0x3B
  40. #define ICM20_ACCEL_XOUT_L 0x3C
  41. #define ICM20_ACCEL_YOUT_H 0x3D
  42. #define ICM20_ACCEL_YOUT_L 0x3E
  43. #define ICM20_ACCEL_ZOUT_H 0x3F
  44. #define ICM20_ACCEL_ZOUT_L 0x40
  45. /* 温度输出 */
  46. #define ICM20_TEMP_OUT_H 0x41
  47. #define ICM20_TEMP_OUT_L 0x42
  48. /* 陀螺仪输出 */
  49. #define ICM20_GYRO_XOUT_H 0x43
  50. #define ICM20_GYRO_XOUT_L 0x44
  51. #define ICM20_GYRO_YOUT_H 0x45
  52. #define ICM20_GYRO_YOUT_L 0x46
  53. #define ICM20_GYRO_ZOUT_H 0x47
  54. #define ICM20_GYRO_ZOUT_L 0x48
  55. #define ICM20_SIGNAL_PATH_RESET 0x68
  56. #define ICM20_ACCEL_INTEL_CTRL 0x69
  57. #define ICM20_USER_CTRL 0x6A
  58. #define ICM20_PWR_MGMT_1 0x6B
  59. #define ICM20_PWR_MGMT_2 0x6C
  60. #define ICM20_FIFO_COUNTH 0x72
  61. #define ICM20_FIFO_COUNTL 0x73
  62. #define ICM20_FIFO_R_W 0x74
  63. #define ICM20_WHO_AM_I 0x75
  64. /* 加速度静态偏移 */
  65. #define ICM20_XA_OFFSET_H 0x77
  66. #define ICM20_XA_OFFSET_L 0x78
  67. #define ICM20_YA_OFFSET_H 0x7A
  68. #define ICM20_YA_OFFSET_L 0x7B
  69. #define ICM20_ZA_OFFSET_H 0x7D
  70. #define ICM20_ZA_OFFSET_L 0x7E
  71. #endif

3、icm20608 设备结构体创建

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/init.h>
  4. #include <linux/fs.h>
  5. #include <linux/uaccess.h>
  6. #include <linux/io.h>
  7. #include <linux/cdev.h>
  8. #include <linux/device.h>
  9. #include <linux/of.h>
  10. #include <linux/of_address.h>
  11. #include <linux/of_irq.h>
  12. #include <linux/slab.h>
  13. #include <linux/of_address.h>
  14. #include <linux/of_gpio.h>
  15. #include <linux/atomic.h>
  16. #include <linux/timer.h>
  17. #include <linux/jiffies.h>
  18. #include <linux/string.h>
  19. #include <linux/irq.h>
  20. #include <linux/interrupt.h>
  21. #include <linux/input.h>
  22. #include <linux/i2c.h>
  23. #include <linux/delay.h>
  24. #include <linux/spi/spi.h>
  25. #define ICM20608_CNT 1
  26. #define ICM20608_NAME "icm20680"
  27. /*设备结构体*/
  28. struct icm20608_dev{
  29. dev_t devid;/* 设备号 */
  30. int major;/* 主设备号 */
  31. int minor;/* 次设备号 */
  32. struct cdev cdev;/* cdev */
  33. struct class *class;/**/
  34. struct device *device;/* 设备 */
  35. void *private_data; /*私有数据 */
  36. };
  37. struct icm20608_dev icm20608dev;
  38. static int icm20608_open(struct inode *inode, struct file *filp)
  39. {
  40. filp->private_data = &icm20608dev;
  41. return 0;
  42. }
  43. static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
  44. {
  45. return 0;
  46. }
  47. static int icm20608_release(struct inode *inode, struct file *filp)
  48. {
  49. return 0;
  50. }
  51. /* icm20608操作函数 */
  52. struct file_operations icm20608_fops = {
  53. .owner = THIS_MODULE,
  54. .open = icm20608_open,
  55. .release = icm20608_release,
  56. .read = icm20608_read,
  57. };
  58. static int icm20608_probe(struct spi_device *spi)
  59. {
  60. int ret = 0;
  61. /* 1、构建设备号 */
  62. icm20608dev.major=0;
  63. if(icm20608dev.major)
  64. {
  65. icm20608dev.devid = MKDEV(icm20608dev.major,0);
  66. ret = register_chrdev_region(icm20608dev.devid,ICM20608_CNT,ICM20608_NAME);
  67. }else
  68. {
  69. ret = alloc_chrdev_region(&icm20608dev.devid,0,ICM20608_CNT,ICM20608_NAME);
  70. icm20608dev.major = MAJOR(icm20608dev.devid);
  71. icm20608dev.minor = MINOR(icm20608dev.devid);
  72. }
  73. if(ret < 0)
  74. {
  75. printk("icm20608 chrdev_region err!\r\n");
  76. goto fail_devid;
  77. }
  78. printk("icm20608dev major = %d, minor = %d\r\n",icm20608dev.major,icm20608dev.minor);
  79. /* 2、注册设备 */
  80. icm20608dev.cdev.owner = THIS_MODULE;
  81. cdev_init(&icm20608dev.cdev,&icm20608_fops);
  82. ret = cdev_add(&icm20608dev.cdev,icm20608dev.devid,ICM20608_CNT);
  83. if(ret <0)
  84. {
  85. printk("icm20608 cdev_add err!\r\n");
  86. goto fail_cdev;
  87. }
  88. /* 3、创建类 */
  89. icm20608dev.class = class_create(THIS_MODULE,ICM20608_NAME);
  90. if(IS_ERR(icm20608dev.class))
  91. {
  92. ret = PTR_ERR(icm20608dev.class);
  93. printk("icm20608 chrdev_class err!\r\n");
  94. goto fail_class;
  95. }
  96. /* 4、创建设备 */
  97. icm20608dev.device = device_create(icm20608dev.class,NULL,
  98. icm20608dev.devid,NULL,ICM20608_NAME);
  99. if(IS_ERR(icm20608dev.device))
  100. {
  101. ret = PTR_ERR(icm20608dev.device);
  102. printk("icm20608 chrdev_device err!\r\n");
  103. goto fail_device;
  104. }
  105. /*设置icm20608私有数据*/
  106. icm20608dev.private_data = spi;
  107. printk("icm20608_probe\r\n");
  108. return 0;
  109. fail_device:
  110. class_destroy(icm20608dev.class);
  111. fail_class:
  112. cdev_del(&icm20608dev.cdev);
  113. fail_cdev:
  114. unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);
  115. fail_devid:
  116. return ret;
  117. }
  118. static int icm20608_remove(struct spi_device *spi)
  119. {
  120. /* 删除字符设备 */
  121. cdev_del(&icm20608dev.cdev);
  122. /*注销设备号*/
  123. unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);
  124. /*摧毁设备*/
  125. device_destroy(icm20608dev.class,icm20608dev.devid);
  126. /*摧毁类*/
  127. class_destroy(icm20608dev.class);
  128. printk("icm20608_remove\r\n");
  129. return 0;
  130. }
  131. /*传统匹配*/
  132. struct spi_device_id icm20608_id[] ={
  133. {"my,icm20608",0},
  134. {}
  135. };
  136. /*设备树匹配*/
  137. static const struct of_device_id icm20608_of_match[] = {
  138. {.compatible = "my,icm20608"},
  139. {}
  140. };
  141. /* SPI驱动结构体 */
  142. static struct spi_driver icm20608_driver = {
  143. .probe = icm20608_probe,
  144. .remove = icm20608_remove,
  145. .driver = {
  146. .owner = THIS_MODULE,
  147. .name = "icm20608",
  148. .of_match_table = icm20608_of_match,
  149. },
  150. .id_table = icm20608_id,
  151. };
  152. /*驱动入口*/
  153. static int __init icm20608_init(void)
  154. {
  155. return spi_register_driver(&icm20608_driver);
  156. }
  157. /*驱动出口*/
  158. static void __exit icm20608_exit(void)
  159. {
  160. spi_unregister_driver(&icm20608_driver);
  161. }
  162. module_init(icm20608_init);
  163. module_exit(icm20608_exit);
  164. MODULE_LICENSE("GPL");
  165. MODULE_AUTHOR("ba che kai qi lai");

当 icm20608 设备和此驱动匹配成功以后 icm20608_probe 函数就会执行。同样的,当注销此驱动的时候 icm20608_remove 函数会执行。

4、probe 函数

在probe函数里面,添加初始化spi_device和icm20608

 315行,设置 SPI 为模式 0,也就是 CPOL=0, CPHA=0

316行,设置好 spi_device 以后需要使用 spi_setup 配置一下

318行,设置 icm20608dev 的 private_data 成员变量为 spi_device

321行,调用 icm20608_reginit 函数初始化 ICM20608,主要是初始化 ICM20608 指定寄存器

5、icm20608 寄存器读写与初始化

SPI 驱动最终是通过读写 icm20608 的寄存器来实现的,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对 icm20608 的初始化

①icm20608_read_regs函数

 从 icm20608 中读取连续多个寄存器数据;注意:在这实验中, SPI 为全双工通讯没有所谓的发送和接收长度之分。要读取或者发送 N 个字节就要封装 N+1 个字节,第 1 个字节是告诉设备我们要进行读还是写,后面的 N 个字节才是我们要读或者发送的数据。 因为是读操作,因此在第 77行设置第一个数据 bit7 位 1,表示读操作

67和71行,用kzalloc申请内存的时候, 效果等同于先是用 kmalloc() 申请空间 , 然用 memset() 来初始化 ,所有申请的元素都被初始化为 0,GFP_KERNEL,内核内存的正常分配. 可能睡眠.

81行,在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init, 函数原型如下:

void spi_message_init(struct spi_message *m)

m:要初始化的 spi_message。 返回值:无。

 82行,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。 返回值:无

83行,spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步 传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

int spi_sync(struct spi_device *spi, struct spi_message *message)

spi:要进行数据传输的 spi_device。 message:要传输的 spi_message。

返回值:错误返回负值。

89-92行,用kfree函数释放内存

②icm20608_write_regs函数

向 icm20608 连续写入多个寄存器数据。此函数和icm20608_read_regs 函数区别不大。

 123行,清零表示写操作

③icm20608_read_onereg函数

读取 icm20608 指定寄存器数据

④ icm20608_write_onereg函数

向 icm20608 指定寄存器写入数据

⑤ icm20608_readdata函数

读取 icm20608 六轴传感器和温度传感器原始数据值,应用程序读取 icm20608 的时候这些传感器原始数据就会上报给应用程序,先在设备结构体中添加对应数据类型

⑥icm20608_reginit 函数

 初始化 icm20608

6、icm20608_read函数

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

代码如下

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/init.h>
  4. #include <linux/fs.h>
  5. #include <linux/uaccess.h>
  6. #include <linux/io.h>
  7. #include <linux/cdev.h>
  8. #include <linux/device.h>
  9. #include <linux/of.h>
  10. #include <linux/of_address.h>
  11. #include <linux/of_irq.h>
  12. #include <linux/slab.h>
  13. #include <linux/of_address.h>
  14. #include <linux/of_gpio.h>
  15. #include <linux/atomic.h>
  16. #include <linux/timer.h>
  17. #include <linux/jiffies.h>
  18. #include <linux/string.h>
  19. #include <linux/irq.h>
  20. #include <linux/interrupt.h>
  21. #include <linux/input.h>
  22. #include <linux/i2c.h>
  23. #include <linux/delay.h>
  24. #include <linux/spi/spi.h>
  25. #include "icm20608reg.h"
  26. #define ICM20608_CNT 1
  27. #define ICM20608_NAME "icm20680"
  28. /*设备结构体*/
  29. struct icm20608_dev
  30. {
  31. dev_t devid; /* 设备号 */
  32. int major; /* 主设备号 */
  33. int minor; /* 次设备号 */
  34. struct cdev cdev; /* cdev */
  35. struct class *class; /**/
  36. struct device *device; /* 设备 */
  37. void *private_data; /*私有数据 */
  38. signed int gyro_x_adc; /* 陀螺仪X轴原始值 */
  39. signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */
  40. signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */
  41. signed int accel_x_adc; /* 加速度计X轴原始值 */
  42. signed int accel_y_adc; /* 加速度计Y轴原始值 */
  43. signed int accel_z_adc; /* 加速度计Z轴原始值 */
  44. signed int temp_adc; /* 温度原始值 */
  45. };
  46. struct icm20608_dev icm20608dev;
  47. /*
  48. * @description : 从 icm20608 读取多个寄存器数据
  49. * @param – dev : icm20608 设备
  50. * @param – reg : 要读取的寄存器首地址
  51. * @param – val : 读取到的数据
  52. * @param – len : 要读取的数据长度
  53. * @return : 操作结果
  54. */
  55. static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
  56. {
  57. int ret = -1;
  58. unsigned char txdata[1];
  59. unsigned char * rxdata;
  60. struct spi_message m;
  61. struct spi_transfer *t;
  62. struct spi_device *spi = (struct spi_device *)dev->private_data;
  63. t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
  64. if(!t) {
  65. return -ENOMEM;
  66. }
  67. rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存 */
  68. if(!rxdata) {
  69. goto out1;
  70. }
  71. /* 一共发送len+1个字节的数据,第一个字节为
  72. 寄存器首地址,一共要读取len个字节长度的数据,*/
  73. txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 表示读*/
  74. t->tx_buf = txdata; /* 要发送的数据 */
  75. t->rx_buf = rxdata; /* 要读取的数据 */
  76. t->len = len+1; /* t->len=发送的长度+读取的长度 */
  77. spi_message_init(&m); /* 初始化spi_message */
  78. spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
  79. ret = spi_sync(spi, &m); /* 同步发送 */
  80. if(ret) {
  81. goto out2;
  82. }
  83. memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */
  84. out2:
  85. kfree(rxdata); /* 释放内存 */
  86. out1:
  87. kfree(t); /* 释放内存 */
  88. return ret;
  89. }
  90. /*
  91. * @description : 向 icm20608 多个寄存器写入数据
  92. * @param – dev : icm20608 设备
  93. * @param – reg : 要写入的寄存器首地址
  94. * @param – val : 要写入的数据缓冲区
  95. * @param – len : 要写入的数据长度
  96. * @return : 操作结果
  97. * */
  98. static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len)
  99. {
  100. int ret = -1;
  101. unsigned char *txdata;
  102. struct spi_message m;
  103. struct spi_transfer *t;
  104. struct spi_device *spi = (struct spi_device *)dev->private_data;
  105. t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
  106. if(!t) {
  107. return -ENOMEM;
  108. }
  109. txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
  110. if(!txdata) {
  111. goto out1;
  112. }
  113. /* 一共发送len+1个字节的数据,第一个字节为
  114. 寄存器首地址,len为要写入的寄存器的集合,*/
  115. *txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */
  116. memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */
  117. t->tx_buf = txdata; /* 要发送的数据 */
  118. t->len = len+1; /* t->len=发送的长度+读取的长度 */
  119. spi_message_init(&m); /* 初始化spi_message */
  120. spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
  121. ret = spi_sync(spi, &m); /* 同步发送 */
  122. if(ret) {
  123. goto out2;
  124. }
  125. out2:
  126. kfree(txdata); /* 释放内存 */
  127. out1:
  128. kfree(t); /* 释放内存 */
  129. return ret;
  130. }
  131. /*
  132. * @description : 读取icm20608指定寄存器值,读取一个寄存器
  133. * @param - dev: icm20608设备
  134. * @param - reg: 要读取的寄存器
  135. * @return : 读取到的寄存器值
  136. */
  137. static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
  138. {
  139. u8 data = 0;
  140. icm20608_read_regs(dev, reg, &data, 1);
  141. return data;
  142. }
  143. /*icm20608写一个寄存器*/
  144. static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
  145. {
  146. u8 buf = value;
  147. icm20608_write_regs(dev, reg, &buf, 1);
  148. }
  149. /*
  150. * @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
  151. * : 三轴加速度计和内部温度。
  152. * @param - dev : ICM20608设备
  153. * @return : 无。
  154. */
  155. void icm20608_readdata(struct icm20608_dev *dev)
  156. {
  157. unsigned char data[14] = {0};
  158. icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
  159. dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
  160. dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
  161. dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
  162. dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
  163. dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
  164. dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
  165. dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
  166. }
  167. /*
  168. * @description : 打开设备
  169. * @param - inode : 传递给驱动的inode
  170. * @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
  171. * 一般在open的时候将private_data似有向设备结构体。
  172. * @return : 0 成功;其他 失败
  173. */
  174. static int icm20608_open(struct inode *inode, struct file *filp)
  175. {
  176. filp->private_data = &icm20608dev;
  177. return 0;
  178. }
  179. /*
  180. * @description : 从设备读取数据
  181. * @param - filp : 要打开的设备文件(文件描述符)
  182. * @param - buf : 返回给用户空间的数据缓冲区
  183. * @param - cnt : 要读取的数据长度
  184. * @param - offt : 相对于文件首地址的偏移
  185. * @return : 读取的字节数,如果为负值,表示读取失败
  186. */
  187. static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
  188. {
  189. signed int data[7];
  190. long err = 0;
  191. struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
  192. icm20608_readdata(dev);
  193. data[0] = dev->gyro_x_adc;
  194. data[1] = dev->gyro_y_adc;
  195. data[2] = dev->gyro_z_adc;
  196. data[3] = dev->accel_x_adc;
  197. data[4] = dev->accel_y_adc;
  198. data[5] = dev->accel_z_adc;
  199. data[6] = dev->temp_adc;
  200. err = copy_to_user(buf, data, sizeof(data));
  201. return 0;
  202. }
  203. static int icm20608_release(struct inode *inode, struct file *filp)
  204. {
  205. return 0;
  206. }
  207. /* icm20608操作函数 */
  208. struct file_operations icm20608_fops = {
  209. .owner = THIS_MODULE,
  210. .open = icm20608_open,
  211. .release = icm20608_release,
  212. .read = icm20608_read,
  213. };
  214. /*
  215. * ICM20608内部寄存器初始化函数
  216. * @param : 无
  217. * @return : 无
  218. */
  219. void icm20608_reginit(void)
  220. {
  221. u8 value = 0;
  222. icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80); /* 复位,复位后为0x40,睡眠模式 */
  223. mdelay(50);
  224. icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠,自动选择时钟 */
  225. mdelay(50);
  226. value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
  227. printk("ICM20608 ID = %#X\r\n", value);
  228. value = icm20608_read_onereg(&icm20608dev, ICM20_PWR_MGMT_1);
  229. printk("ICM20_PWR_MGMT_1 = %#X\r\n", value);
  230. icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
  231. icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
  232. icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
  233. icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
  234. icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2,0x04); /* 加速度计低通滤波BW=21.2Hz */
  235. icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
  236. icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
  237. icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
  238. }
  239. static int icm20608_probe(struct spi_device *spi)
  240. {
  241. int ret = 0;
  242. /* 1、构建设备号 */
  243. icm20608dev.major = 0;
  244. if (icm20608dev.major)
  245. {
  246. icm20608dev.devid = MKDEV(icm20608dev.major, 0);
  247. ret = register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
  248. }
  249. else
  250. {
  251. ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
  252. icm20608dev.major = MAJOR(icm20608dev.devid);
  253. icm20608dev.minor = MINOR(icm20608dev.devid);
  254. }
  255. if (ret < 0)
  256. {
  257. printk("icm20608 chrdev_region err!\r\n");
  258. goto fail_devid;
  259. }
  260. printk("icm20608dev major = %d, minor = %d\r\n", icm20608dev.major, icm20608dev.minor);
  261. /* 2、注册设备 */
  262. icm20608dev.cdev.owner = THIS_MODULE;
  263. cdev_init(&icm20608dev.cdev, &icm20608_fops);
  264. ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
  265. if (ret < 0)
  266. {
  267. printk("icm20608 cdev_add err!\r\n");
  268. goto fail_cdev;
  269. }
  270. /* 3、创建类 */
  271. icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
  272. if (IS_ERR(icm20608dev.class))
  273. {
  274. ret = PTR_ERR(icm20608dev.class);
  275. printk("icm20608 chrdev_class err!\r\n");
  276. goto fail_class;
  277. }
  278. /* 4、创建设备 */
  279. icm20608dev.device = device_create(icm20608dev.class, NULL,
  280. icm20608dev.devid, NULL, ICM20608_NAME);
  281. if (IS_ERR(icm20608dev.device))
  282. {
  283. ret = PTR_ERR(icm20608dev.device);
  284. printk("icm20608 chrdev_device err!\r\n");
  285. goto fail_device;
  286. }
  287. /*初始化spi_device*/
  288. spi->mode = SPI_MODE_0;
  289. spi_setup(spi);
  290. /*设置icm20608私有数据*/
  291. icm20608dev.private_data = spi;
  292. /*初始化icm20608*/
  293. icm20608_reginit();
  294. printk("icm20608_probe\r\n");
  295. return 0;
  296. fail_device:
  297. class_destroy(icm20608dev.class);
  298. fail_class:
  299. cdev_del(&icm20608dev.cdev);
  300. fail_cdev:
  301. unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
  302. fail_devid:
  303. return ret;
  304. }
  305. static int icm20608_remove(struct spi_device *spi)
  306. {
  307. /* 删除字符设备 */
  308. cdev_del(&icm20608dev.cdev);
  309. /*注销设备号*/
  310. unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
  311. /*摧毁设备*/
  312. device_destroy(icm20608dev.class, icm20608dev.devid);
  313. /*摧毁类*/
  314. class_destroy(icm20608dev.class);
  315. printk("icm20608_remove\r\n");
  316. return 0;
  317. }
  318. /*传统匹配*/
  319. struct spi_device_id icm20608_id[] = {
  320. {"my,icm20608", 0},
  321. {}
  322. };
  323. /*设备树匹配*/
  324. static const struct of_device_id icm20608_of_match[] = {
  325. {.compatible = "my,icm20608"},
  326. {}
  327. };
  328. /* SPI驱动结构体 */
  329. static struct spi_driver icm20608_driver = {
  330. .probe = icm20608_probe,
  331. .remove = icm20608_remove,
  332. .driver = {
  333. .owner = THIS_MODULE,
  334. .name = "icm20608",
  335. .of_match_table = icm20608_of_match,
  336. },
  337. .id_table = icm20608_id,
  338. };
  339. /*驱动入口*/
  340. static int __init icm20608_init(void)
  341. {
  342. return spi_register_driver(&icm20608_driver);
  343. }
  344. /*驱动出口*/
  345. static void __exit icm20608_exit(void)
  346. {
  347. spi_unregister_driver(&icm20608_driver);
  348. }
  349. module_init(icm20608_init);
  350. module_exit(icm20608_exit);
  351. MODULE_LICENSE("GPL");
  352. MODULE_AUTHOR("ba che kai qi lai");

四、APP编写

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include <sys/ioctl.h>
  9. #include <linux/input.h>
  10. /*
  11. argc:应用程序参数个数(argv数组元素个数)
  12. argv:具体参数,也可以写作char **argv
  13. ./icm20608APP <filename>
  14. ./icm20608APP /dev/icm20608
  15. */
  16. int main(int argc, char *argv[])
  17. {
  18. int fd;
  19. char *filename;
  20. signed int databuf[7];
  21. unsigned char data[14];
  22. signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
  23. signed int accel_x_adc, accel_y_adc, accel_z_adc;
  24. signed int temp_adc;
  25. float gyro_x_act, gyro_y_act, gyro_z_act;
  26. float accel_x_act, accel_y_act, accel_z_act;
  27. float temp_act;
  28. int ret = 0;
  29. /*判断命令行输入参数是否正确*/
  30. if(argc != 2){
  31. printf("error usage!\r\n");
  32. return -1;
  33. }
  34. /*用指针指向文件*/
  35. filename = argv[1];
  36. /*打开文件*/
  37. fd = open(filename , O_RDWR);
  38. if(fd < 0){
  39. printf("file open failed\r\n",filename);
  40. return -1;
  41. }
  42. while(1)
  43. {
  44. ret = read(fd, databuf, sizeof(databuf));
  45. if(ret == 0) { /* 数据读取成功 */
  46. gyro_x_adc = databuf[0];
  47. gyro_y_adc = databuf[1];
  48. gyro_z_adc = databuf[2];
  49. accel_x_adc = databuf[3];
  50. accel_y_adc = databuf[4];
  51. accel_z_adc = databuf[5];
  52. temp_adc = databuf[6];
  53. /* 计算实际值 */
  54. gyro_x_act = (float)(gyro_x_adc) / 16.4;
  55. gyro_y_act = (float)(gyro_y_adc) / 16.4;
  56. gyro_z_act = (float)(gyro_z_adc) / 16.4;
  57. accel_x_act = (float)(accel_x_adc) / 2048;
  58. accel_y_act = (float)(accel_y_adc) / 2048;
  59. accel_z_act = (float)(accel_z_adc) / 2048;
  60. temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
  61. printf("\r\n原始值:\r\n");
  62. printf("gx = %d, gy = %d, gz = %d\r\n",
  63. gyro_x_adc, gyro_y_adc, gyro_z_adc);
  64. printf("ax = %d, ay = %d, az = %d\r\n",
  65. accel_x_adc,accel_y_adc, accel_z_adc);
  66. printf("temp = %d\r\n", temp_adc);
  67. printf("实际值:");
  68. printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n",
  69. gyro_x_act, gyro_y_act, gyro_z_act);
  70. printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n",
  71. accel_x_act, accel_y_act, accel_z_act);
  72. printf("act temp = %.2f°C\r\n", temp_act);
  73. }
  74. usleep(100000); /*100ms */
  75. }
  76. /*关闭文件*/
  77. close(fd);
  78. return 0;
  79. }

最终将传感器原始数据和得到的实际值显示在终端上。

编译验证

在编译APP的时候,编译命令加入如下参数即可使能硬件浮点编译

-march-armv7-a -mfpu-neon -mfloat=hard

编译之后有没有使用硬件浮点呢?使用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了,输入如下命令:

arm-linux-gnueabihf-readelf -A icm20608App

 使用APP测试如下

 

 这是会一直打印的,部分截图如上图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/712490
推荐阅读
相关标签
  

闽ICP备14008679号