完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-3-24 18:05 编辑
1)实验平台:正点原子Linux开发板 2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原子 在本章实验中,我们采用同步传输方式来完成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数据同步传输。 通过SPI进行n个字节的数据发送和接收的示例代码如下所示: 示例代码62.3.2.3 SPI数据读写操作 /* SPI多字节发送 */ staticint spi_send(struct spi_device *spi, u8 *buf,int len) { int ret; struct spi_message m; struct spi_transfer t ={ .tx_buf = buf, .len = len, }; spi_message_init(&m); /* 初始化spi_message */ spi_message_add_tail(t,&m);/* 将spi_transfer添加到spi_message队列 */ ret = spi_sync(spi,&m); /* 同步传输 */ return ret; } /* SPI多字节接收 */ staticint spi_receive(struct spi_device *spi, u8 *buf,int len) { int ret; struct spi_message m; struct spi_transfer t ={ .rx_buf = buf, .len = len, }; spi_message_init(&m); /* 初始化spi_message */ spi_message_add_tail(t,&m);/* 将spi_transfer添加到spi_message队列 */ ret = spi_sync(spi,&m); /* 同步传输 */ return ret; } 62.4 硬件原理图分析本章实验硬件原理图参考26.2小节即可。 62.5 试验程序编写本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->22_spi。 62.5.1 修改设备树1、添加ICM20608所使用的IO 首先在imx6ull-alientek-emmc.dts文件中添加ICM20608所使用的IO信息,在iomuxc节点中添加一个新的子节点来描述ICM20608所使用的SPI引脚,子节点名字为pinctrl_ecspi3,节点内容如下所示: 示例代码62.5.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}; UART2_TX_DATA这个IO是ICM20608的片选信号,这里我们并没有将其复用为ECSPI3的SS0信号,而是将其复用为了普通的GPIO。因为我们需要自己控制片选信号,所以将其复用为普通的GPIO。 2、在ecspi3节点追加icm20608子节点 在imx6ull-alientek-emmc.dts文件中并没有任何向ecspi3节点追加内容的代码,这是因为NXP官方的6ULL EVK开发板上没有连接SPI设备。在imx6ull-alientek-emmc.dts文件最后面加入如下所示内容: 示例代码62.5.1.2 向ecspi3节点加入icm20608信息 1&ecspi3 { 2 fsl,spi-num-chipselects =<1>; 3 cs-gpio =<&gpio1 20 GPIO_ACTIVE_LOW>;/* cant't use cs-gpios! */ 4 pinctrl-names ="default"; 5 pinctrl-0=<&pinctrl_ecspi3>; 6 status ="okay"; 7 8 spidev: icm20608@0 { 9 compatible ="alientek,icm20608"; 10 spi-max-frequency =<8000000>; 11 reg =<0>; 12}; 13}; 第2行,设置当前片选数量为1,因为就只接了一个ICM20608。 第3行,注意!这里并没有用到"cs-gpios"属性,而是用了一个自己定义的"cs-gpio"属性,因为我们要自己控制片选引脚。如果使用"cs-gpios"属性的话SPI主机驱动就会控制片选引脚。 第5行,设置IO要使用的pinctrl子节点,也就是我们在示例代码62.5.1.1中新建的pinctrl_ecspi3。 第6行,imx6ull.dtsi文件中默认将ecspi3节点状态(status)设置为"disable",这里我们要将其改为"okay"。 第8~12行,icm20608设备子节点,因为icm20608连接在ECSPI3的第0个通道上,因此@后面为0。第9行设置节点属性兼容值为"alientek,icm20608",第10行设置SPI最大时钟频率为8MHz,这是ICM20608的SPI接口所能支持的最大的时钟频率。第11行,icm20608连接在通道0上,因此reg为0。 imx6ull-alientek-emmc.dts文件修改完成以后重新编译一下,得到新的dtb文件,并使用新的dtb启动Linux系统。 62.5.2 编写ICM20608驱动新建名为"22_spi"的文件夹,然后在22_spi文件夹里面创建vscode工程,工作区命名为"spi"。工程创建好以后新建icm20608.c和icm20608reg.h这两个文件,icm20608.c为ICM20608的驱动代码,icm20608reg.h是ICM20608寄存器头文件。先在icm20608reg.h中定义好ICM20608的寄存器,输入如下内容(有省略,完成的内容请参考例程): 示例代码62.5.2.1 icm20608reg.h文件内容 1 #ifndef ICM20608_H 2 #define ICM20608_H 3/*************************************************************** 4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 5 文件名 : icm20608reg.h 6作者 : 左忠凯 7版本 : V1.0 8描述 : ICM20608寄存器地址描述头文件 9其他 : 无 10论坛 : www.openedv.com 11日志 : 初版V1.0 2019/9/2 左忠凯创建 12 ***************************************************************/ 13 #define ICM20608G_ID 0XAF/* ID值 */ 14 #define ICM20608D_ID 0XAE/* ID值 */ 15 16/* ICM20608寄存器 17 *复位后所有寄存器地址都为0,除了 18 *Register 107(0X6B) Power Management 1 = 0x40 19 *Register 117(0X75) WHO_AM_I = 0xAF或0xAE 20 */ 21/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */ 22 #define ICM20_SELF_TEST_X_GYRO 0x00 23 #define ICM20_SELF_TEST_Y_GYRO 0x01 24 #define ICM20_SELF_TEST_Z_GYRO 0x02 25 #define ICM20_SELF_TEST_X_ACCEL 0x0D 26 #define ICM20_SELF_TEST_Y_ACCEL 0x0E 27 #define ICM20_SELF_TEST_Z_ACCEL 0x0F ...... 80/* 加速度静态偏移 */ 81 #define ICM20_XA_OFFSET_H 0x77 82 #define ICM20_XA_OFFSET_L 0x78 83 #define ICM20_YA_OFFSET_H 0x7A 84 #define ICM20_YA_OFFSET_L 0x7B 85 #define ICM20_ZA_OFFSET_H 0x7D 86 #define ICM20_ZA_OFFSET_L 0x7E 87 88 #endif 接下来继续编写icm20608.c文件,因为icm20608.c文件内容比较长,因此这里就将其分开来讲解。 1、icm20608设备结构体创建 首先创建一个icm20608设备机构体,如下所示: 示例代码62.5.2.2 icm20608设备结构体创建 1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> ...... 22 #include <asm/io.h> 23 #include "icm20608reg.h" 24/*************************************************************** 25 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 26文件名 : icm20608.c 27作者 : 左忠凯 28版本 : V1.0 29描述 : ICM20608 SPI驱动程序 30其他 : 无 31论坛 : www.openedv.com 32日志 : 初版V1.0 2019/9/2 左忠凯创建 33 ***************************************************************/ 34 #define ICM20608_CNT 1 35 #define ICM20608_NAME "icm20608" 36 37struct icm20608_dev { 38 dev_t devid; /* 设备号 */ 39 struct cdev cdev; /* cdev */ 40 struct class *class; /* 类 */ 41 struct device *device; /* 设备 */ 42 struct device_node *nd; /* 设备节点 */ 43 int major; /* 主设备号 */ 44 void*private_data; /* 私有数据 */ 45 int cs_gpio; /* 片选所使用的GPIO编号*/ 46 signedint gyro_x_adc; /* 陀螺仪X轴原始值 */ 47 signedint gyro_y_adc; /* 陀螺仪Y轴原始值 */ 48 signedint gyro_z_adc; /* 陀螺仪Z轴原始值 */ 49 signedint accel_x_adc; /* 加速度计X轴原始值 */ 50 signedint accel_y_adc; /* 加速度计Y轴原始值 */ 51 signedint accel_z_adc; /* 加速度计Z轴原始值 */ 52 signedint temp_adc; /* 温度原始值 */ 53}; 54 55staticstruct icm20608_dev icm20608dev; icm20608的设备结构体icm20608_dev没什么好讲的,重点看一下第44行的private_data,对于SPI设备驱动来讲最核心的就是spi_device。probe函数会向驱动提供当前SPI设备对应的spi_device,因此在probe函数中设置private_data为probe函数传递进来的spi_device参数。 2、icm20608的spi_driver注册与注销 对于SPI设备驱动,首先就是要初始化并向系统注册spi_driver,icm20608的spi_driver初始化、注册与注销代码如下: 示例代码62.5.2.3 icm20608的spi_driver初始化、注册与注销 1 /* 传统匹配方式ID列表 */ 2staticconststruct spi_device_id icm20608_id[]={ 3 {"alientek,icm20608",0}, 4 {} 5}; 6 7 /* 设备树匹配列表 */ 8staticconststruct of_device_id icm20608_of_match[]={ 9 {.compatible ="alientek,icm20608"}, 10 {/* Sentinel */} 11}; 12 13 /* SPI驱动结构体 */ 14staticstruct spi_driver icm20608_driver ={ 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 */ 30staticint __init icm20608_init(void) 31{ 32 return spi_register_driver(&icm20608_driver); 33} 34 35/* 36 * @description : 驱动出口函数 37 * @param : 无 38 * @return : 无 39 */ 40staticvoid __exit icm20608_exit(void) 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"); 第2~5行,传统的设备和驱动匹配表。 第8~11行,设备树的设备与驱动匹配表,这里只有一个匹配项:"alientek,icm20608"。 第14~23行,icm20608的spi_driver结构体变量,当icm20608设备和此驱动匹配成功以后第15行的icm20608_probe函数就会执行。同样的,当注销此驱动的时候icm20608_remove函数会执行。 第30~33行,icm20608_init函数为icm20608的驱动入口函数,在此函数中使用spi_register_driver向Linux系统注册上面定义的icm20608_driver。 第40~43行,icm20608_exit函数为icm20608的驱动出口函数,在此函数中使用spi_unregister_driver注销掉前面注册的icm20608_driver。 3、probe/remove函数 icm20608_driver中的probe和remove函数内容如下所示: 示例代码62.5.2.4 probe和remove函数 1/* 2 * @description : spi驱动的probe函数,当驱动与 3 * 设备匹配以后此函数就会执行 4 * @param - client : spi设备 5 * @param - id : spi设备ID 6 * 7 */ 8staticint 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!rn"); 41 return-EINVAL; 42 } 43 44 /* 2、获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */ 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!rn"); 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 return0; 65} 66 67/* 68 * @description : spi驱动的remove函数,移除spi驱动的时候此函数会执行 69 * @param – client : spi设备 70 * @return : 0,成功;其他负值,失败 71 */ 72staticint 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 return0; 82} 第8~65行,probe函数,当设备与驱动匹配成功以后此函数就会执行,第13~55行都是标准的注册字符设备驱动。其中在第38~49行获取设备节点中的"cs-gpio"属性,也就是获取到设备的片选IO。 第58行,设置SPI为模式0,也就是CPOL=0,CPHA=0。 第59行,设置好spi_device以后需要使用spi_setup配置一下。 第60行,设置icm20608dev的private_data成员变量为spi_device。 第63行,调用icm20608_reginit函数初始化ICM20608,主要是初始化ICM20608指定寄存器。 第72~81行,icm20608_remove函数,注销驱动的时候此函数就会执行。 4、icm20608寄存器读写与初始化 SPI驱动的最终目的就是为了读写icm20608的寄存器,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对icm20608的初始化。icm20608的寄存器读写以及初始化代码如下: 示例代码62.5.2.5 icm20608寄存器读写以及出初始化 1/* 2 * @description : 从icm20608读取多个寄存器数据 3 * @param – dev : icm20608设备 4 * @param – reg : 要读取的寄存器首地址 5 * @param – val : 读取到的数据 6 * @param – len : 要读取的数据长度 7 * @return : 操作结果 8 */ 9staticint icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void*buf,int len) 10{ 11int ret; 12unsignedchar txdata[len]; 13struct spi_message m; 14struct spi_transfer *t; 15struct 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; /* 写数据的时候寄存器地址bit8要置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; /* 要读取的数据长度 */ 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 39return ret; 40} 41 42/* 43 * @description : 向icm20608多个寄存器写入数据 44 * @param – dev : icm20608设备 45 * @param – reg : 要写入的寄存器首地址 46 * @param – val : 要写入的数据缓冲区 47 * @param – len : 要写入的数据长度 48 * @return : 操作结果 49 */ 50static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len) 51{ 52int ret; 53 54unsignedchar txdata[len]; 55struct spi_message m; 56struct spi_transfer *t; 57struct 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 */ 79return ret; 80} 81 82/* 83 * @description : 读取icm20608指定寄存器值,读取一个寄存器 84 * @param – dev : icm20608设备 85 * @param – reg : 要读取的寄存器 86 * @return : 读取到的寄存器值 87 */ 88staticunsignedchar icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) 89{ 90 u8 data =0; 91 icm20608_read_regs(dev, reg,&data,1); 92return data; 93} 94 95/* 96 * @description : 向icm20608指定寄存器写入指定的值,写一个寄存器 97 * @param – dev : icm20608设备 98 * @param – reg : 要写的寄存器 99 * @param – data : 要写入的值 100 * @return : 无 101 */ 102 103staticvoid 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 */ 115void icm20608_readdata(struct icm20608_dev *dev) 116{ 117unsignedchar data[14]; 118 icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data,14); 119 120 dev->accel_x_adc =(signedshort)((data[0]<<8)| data[1]); 121 dev->accel_y_adc =(signedshort)((data[2]<<8)| data[3]); 122 dev->accel_z_adc =(signedshort)((data[4]<<8)| data[5]); 123 dev->temp_adc =(signedshort)((data[6]<<8)| data[7]); 124 dev->gyro_x_adc =(signedshort)((data[8]<<8)| data[9]); 125 dev->gyro_y_adc =(signedshort)((data[10]<<8)| data[11]); 126 dev->gyro_z_adc =(signedshort)((data[12]<<8)| data[13]); 127} 128/* 129 * ICM20608内部寄存器初始化函数 130 * @param : 无 131 * @return : 无 132 */ 133void 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 = %#Xrn", 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} 第9~40行,icm20608_read_regs函数,从icm20608中读取连续多个寄存器数据。 第50~80行,icm20608_write_regs函数,向icm20608连续写入多个寄存器数据。 第88~83行,icm20608_read_onereg函数,读取icm20608指定寄存器数据。 第103~107行,icm20608_write_onereg函数,向icm20608指定寄存器写入数据。 第115~126行,icm20608_readdata函数,读取icm20608六轴传感器和温度传感器原始数据值,应用程序读取icm20608的时候这些传感器原始数据就会上报给应用程序。 第133~153行,icm20608_reginit函数,初始化icm20608,和我们spi裸机实验里面的初始化过程一样。 5、字符设备驱动框架 icm20608的字符设备驱动框架如下: 示例代码62.5.2.6 icm20608字符设备驱动 1/* 2 * @description : 打开设备 3 * @param – inode : 传递给驱动的inode 4 * @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量 5 * 一般在open的时候将private_data似有向设备结构体。 6 * @return : 0 成功;其他失败 7 */ 8staticint icm20608_open(struct inode *inode,struct file *filp) 9{ 10 filp->private_data =&icm20608dev;/* 设置私有数据 */ 11 return0; 12} 13 14/* 15 * @description : 从设备读取数据 16 * @param - filp : 要打开的设备文件(文件描述符) 17 * @param - buf : 返回给用户空间的数据缓冲区 18 * @param - cnt : 要读取的数据长度 19 * @param - offt : 相对于文件首地址的偏移 20 * @return : 读取的字节数,如果为负值,表示读取失败 21 */ 22static ssize_t icm20608_read(struct file *filp,char __user *buf,size_t cnt, loff_t *off) 23{ 24 signedint data[7]; 25 long err =0; 26 struct icm20608_dev *dev =(struct icm20608_dev * )filp->private_data; 27 28 icm20608_readdata(dev); 29 data[0]= dev->gyro_x_adc; 30 data[1]= dev->gyro_y_adc; 31 data[2]= dev->gyro_z_adc; 32 data[3]= dev->accel_x_adc; 33 data[4]= dev->accel_y_adc; 34 data[5]= dev->accel_z_adc; 35 data[6]= dev->temp_adc; 36 err = copy_to_user(buf, data,sizeof(data)); 37 return0; 38} 39 40/* 41 * @description : 关闭/释放设备 42 * @param - filp : 要关闭的设备文件(文件描述符) 43 * @return : 0 成功;其他失败 44 */ 45staticint icm20608_release(struct inode *inode,struct file *filp) 46{ 47 return0; 48} 49 50/* icm20608操作函数 */ 51staticconststruct file_operations icm20608_ops ={ 52 .owner = THIS_MODULE, 53 .open = icm20608_open, 54 .read = icm20608_read, 55 .release = icm20608_release, 56}; 字符设备驱动框架没什么好说的,重点是第22~38行的icm20608_read函数,当应用程序调用read函数读取icm20608设备文件的时候此函数就会执行。此函数调用上面编写好的icm20608_readdata函数读取icm20608的原始数据并将其上报给应用程序。大家注意,在内核中尽量不要使用浮点运算,所以不要在驱动将icm20608的原始值转换为对应的实际值,因为会涉及到浮点计算。 62.5.3编写测试APP新建icm20608App.c文件,然后在里面输入如下所示内容: 示例代码62.5.3.1 icm20608App.c文件代码 1 #include "stdio.h" 2 #include "unistd.h" 3 #include "sys/types.h" 4 #include "sys/stat.h" 5 #include "sys/ioctl.h" 6 #include "fcntl.h" 7 #include "stdlib.h" 8 #include "string.h" 9 #include <poll.h> 10 #include <sys/select.h> 11 #include <sys/time.h> 12 #include <signal.h> 13 #include <fcntl.h> 14/*************************************************************** 15 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 16 文件名 : icm20608App.c 17作者 : 左忠凯 18版本 : V1.0 19描述 : icm20608设备测试APP。 20其他 : 无 21使用方法 :./icm20608App /dev/icm20608 22论坛 : www.openedv.com 23日志 : 初版V1.0 2019/9/20 左忠凯创建 24 ***************************************************************/ 25 26/* 27 * @description : main主程序 28 * @param - argc : argv数组元素个数 29 * @param - argv : 具体参数 30 * @return : 0 成功;其他失败 31 */ 32int main(int argc,char*argv[]) 33{ 34 int fd; 35 char*filename; 36 signedint databuf[7]; 37 unsignedchar data[14]; 38 signedint gyro_x_adc, gyro_y_adc, gyro_z_adc; 39 signedint accel_x_adc, accel_y_adc, accel_z_adc; 40 signedint 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!rn"); 50 return-1; 51 } 52 53 filename = argv[1]; 54 fd = open(filename, O_RDWR); 55 if(fd <0){ 56 printf("can't open file %srn", 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("rn原始值:rn"); 81 printf("gx = %d, gy = %d, gz = %drn", gyro_x_adc, gyro_y_adc, gyro_z_adc); 82 printf("ax = %d, ay = %d, az = %drn", accel_x_adc, accel_y_adc, accel_z_adc); 83 printf("temp = %drn", temp_adc); 84 printf("实际值:"); 85 printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/Srn", gyro_x_act, gyro_y_act, gyro_z_act); 86 printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fgrn", accel_x_act, accel_y_act, accel_z_act); 87 printf("act temp = %.2f°Crn", temp_act); 88 } 89 usleep(100000);/*100ms */ 90 } 91 close(fd);/* 关闭文件 */ 92 return0; 93} 第60~91行,在while循环中每隔100ms从icm20608中读取一次数据,读取到icm20608原始数据以后将其转换为实际值,比如陀螺仪就是角速度、加速度计就是g值。注意,我们在icm20608驱动中将陀螺仪和加速度计的测量范围全部设置到了最大,分别为±2000和±16g。因此,在计算实际值的时候陀螺仪使用16.4,加速度计使用2048。最终将传感器原始数据和得到的实际值显示在终端上。 62.6 运行测试62.6.1 编译驱动程序和测试APP1、编译驱动程序 编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为"icm20608.o",Makefile内容如下所示: 示例代码62.6.1.1 Makefile文件 1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_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 在icm20608App.c这个测试APP中我们用到了浮点计算,而I.MX6U是支持硬件浮点的,因此我们在编译icm20608App.c的时候就可以使能硬件浮点,这样可以加速浮点计算。使能硬件浮点很简单,在编译的时候加入如下参数即可: -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 编译成功以后就会生成icm20608App这个应用程序,那么究竟有没有使用硬件浮点呢?使用arm-linux-gnueabihf-readelf查看一下编译出来的icm20608App就知道了,输入如下命令: arm-linux-gnueabihf-readelf -A icm20608App 结果如图62.6.1.1所示: 图62.6.1.1 icm20608App文件信息 从图62.6.1.1可以看出FPU架构为VFPv3,SIMD使用了NEON,并且使用了SP和DP,说明icm20608App这个应用程序使用了硬件浮点。 62.6.2 运行测试将上一小节编译出来icm20608.ko和icm20608App这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中。输入如下命令加载icm20608.ko这个驱动模块。 depmod //第一次加载驱动的时候需要运行此命令 modprobe icm20608.ko //加载驱动模块 当驱动模块加载成功以后使用icm20608App来测试,输入如下命令: ./icm20608App /dev/icm20608 测试APP会不断的从ICM20608中读取数据,然后输出到终端上,如图62.6.2.1所示: 图62.6.2.1 获取到的ICM20608数据 可以看出,开发板静止状态下,Z轴方向的加速度在1g左右,这个就是重力加速度。对于陀螺仪来讲,静止状态下三轴的角速度应该在0°/S左右。ICM20608内温度传感器采集到的温度在30多度左右,大家可以晃动一下开发板,这个时候陀螺仪和加速度计的值就会有变化。 |
|
相关推荐
|
|
794 浏览 0 评论
4650 浏览 0 评论
如何使用python调起UDE STK5.2进行下载自动化下载呢?
2588 浏览 0 评论
开启全新AI时代 智能嵌入式系统快速发展——“第六届国产嵌入式操作系统技术与产业发展论坛”圆满结束
2945 浏览 0 评论
获奖公布!2024 RT-Thread全球巡回线下培训火热来袭!报名提问有奖!
31650 浏览 11 评论
73048 浏览 21 评论
浏览过的版块 |
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 00:13 , Processed in 0.566193 second(s), Total 64, Slave 46 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号