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

imx6ull-驱动开发篇47——Linux SPI 驱动实验

目录

实验程序编写

修改设备树

添加 ICM20608 所使用的 IO

追加 icm20608 子节点

编写 ICM20608 驱动

icm20608reg.h

icm20608.c

icm20608App.c

Makefile文件

运行测试


在之前的文章里:

Linux 下 SPI 驱动框架简介

I.MX6U SPI 主机驱动分析

我们已经学习了linux下SPI驱动相关函数、编写步骤,本讲实验学习如何在 Linux 下编写 SPI 设备驱动,驱动正点原子I.MX6U-ALPHA 开发板上的 ICM-20608 这个 SPI 接口的六轴传感器,在应用程序中读取 ICM-20608的原始传感器数据。

实验程序编写

硬件原理图参考:裸机学习实验17——SPI 实验。

修改设备树

添加 ICM20608 所使用的 IO

在设备树文件 imx6ull-alientek-emmc.dts 文件中,添加 ICM20608 所使用的 IO 信息。

iomuxc 节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,节点内容如下所示:

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 主机输出从机输入 */>;
};

UART2_TX_DATA 这个 IO 是 ICM20608 的片选信号,将其复用为了普通的 GPIO,方便我们控制片选信号。

追加 icm20608 子节点

imx6ull-alientek-emmc.dts 文件,ecspi3 节点中追加 icm20608 子节点,如下内容:

&ecspi3 {fsl,spi-num-chipselects = <1>;                // 使用1个片选信号cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;        // 片选使用GPIO1_IO20,低电平有效pinctrl-names = "default";                     // 引脚控制状态名称pinctrl-0 = <&pinctrl_ecspi3>;                // 使用pinctrl_ecspi3节点定义的引脚配置status = "okay";                               // 启用ECSPI3控制器spidev: icm20608@0 {                           // SPI设备节点(ICM20608陀螺仪,接在通道0)compatible = "alientek,icm20608";          // 兼容性字符串,用于匹配驱动spi-max-frequency = <8000000>;             // 最大SPI时钟频率8MHzreg = <0>;                                // 使用SPI通道0(片选线0)};
};

设置 SPI 最大时钟频率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。

设备树文件修改完成以后,使用命令 “make dtbs” 重新编译一下,得到新的 dtb 文件,并使用新的 dtb 启动 Linux 系统。

编写 ICM20608 驱动

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

icm20608reg.h

先在 icm20608reg.h 中定义好 ICM20608的寄存器,输入如下内容

#ifndef ICM20608_H
#define ICM20608_H#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1 	= 0x40*Register 117(0X75) WHO_AM_I 				= 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F/* 陀螺仪静态偏移 */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A/* 加速度输出 */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40/* 温度输出 */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42/* 陀螺仪输出 */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75/* 加速度静态偏移 */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_ZA_OFFSET_L 			0x7E#endif

icm20608.c

驱动文件icm20608.c,代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"#define ICM20608_CNT	1
#define ICM20608_NAME	"icm20608"struct icm20608_dev {dev_t devid;				/* 设备号 	 */struct cdev cdev;			/* cdev 	*/struct class *class;		/* 类 		*/struct device *device;		/* 设备 	 */struct device_node	*nd; 	/* 设备节点 */int major;					/* 主设备号 */void *private_data;			/* 私有数据 		*/signed int gyro_x_adc;		/* 陀螺仪X轴原始值 	 */signed int gyro_y_adc;		/* 陀螺仪Y轴原始值		*/signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 		*/signed int accel_x_adc;		/* 加速度计X轴原始值 	*/signed int accel_y_adc;		/* 加速度计Y轴原始值	*/signed int accel_z_adc;		/* 加速度计Z轴原始值 	*/signed int temp_adc;		/* 温度原始值 			*/
};static struct icm20608_dev icm20608dev;/** @description	: 从icm20608读取多个寄存器数据* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器首地址* @param - val:  读取到的数据* @param - len:  要读取的数据长度* @return 		: 操作结果*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret = -1;unsigned char txdata[1];unsigned char * rxdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */if(!t) {return -ENOMEM;}rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,一共要读取len个字节长度的数据,*/txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			t->tx_buf = txdata;			/* 要发送的数据 */t->rx_buf = rxdata;			/* 要读取的数据 */t->len = len+1;				/* t->len=发送的长度+读取的长度 */spi_message_init(&m);		/* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);	/* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */out2:kfree(rxdata);					/* 释放内存 */
out1:	kfree(t);						/* 释放内存 */return ret;
}/** @description	: 向icm20608多个寄存器写入数据* @param - dev:  icm20608设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return 	  :   操作结果*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{int ret = -1;unsigned char *txdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */if(!t) {return -ENOMEM;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata;			/* 要发送的数据 */t->len = len+1;				/* t->len=发送的长度+读取的长度 */spi_message_init(&m);		/* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);	/* 同步发送 */if(ret) {goto out2;}out2:kfree(txdata);				/* 释放内存 */
out1:kfree(t);					/* 释放内存 */return ret;
}/** @description	: 读取icm20608指定寄存器值,读取一个寄存器* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器* @return 	  :   读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data = 0;icm20608_read_regs(dev, reg, &data, 1);return data;
}/** @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器* @param - dev:  icm20608设备* @param - reg:  要写的寄存器* @param - data: 要写入的值* @return   :    无*/	static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{u8 buf = value;icm20608_write_regs(dev, reg, &buf, 1);
}/** @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、* 				: 三轴加速度计和内部温度。* @param - dev	: ICM20608设备* @return 		: 无。*/
void icm20608_readdata(struct icm20608_dev *dev)
{unsigned char data[14] = { 0 };icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量* 					  一般在open的时候将private_data似有向设备结构体。* @return 			: 0 成功;其他 失败*/
static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev; /* 设置私有数据 */return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;icm20608_readdata(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {.owner = THIS_MODULE,.open = icm20608_open,.read = icm20608_read,.release = icm20608_release,
};/** ICM20608内部寄存器初始化函数 * @param  	: 无* @return 	: 无*/
void icm20608_reginit(void)
{u8 value = 0;icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value);	icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/
}/** @description     : spi驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - client  : i2c设备* @param - id      : i2c设备ID* */	
static int icm20608_probe(struct spi_device *spi)
{/* 1、构建设备号 */if (icm20608dev.major) {icm20608dev.devid = MKDEV(icm20608dev.major, 0);register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);} else {alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);}/* 2、注册设备 */cdev_init(&icm20608dev.cdev, &icm20608_ops);cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);/* 3、创建类 */icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);if (IS_ERR(icm20608dev.class)) {return PTR_ERR(icm20608dev.class);}/* 4、创建设备 */icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);if (IS_ERR(icm20608dev.device)) {return PTR_ERR(icm20608dev.device);}/*初始化spi_device */spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);icm20608dev.private_data = spi; /* 设置私有数据 *//* 初始化ICM20608内部寄存器 */icm20608_reginit();		return 0;
}/** @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client 	: i2c设备* @return          : 0,成功;其他负值,失败*/
static int icm20608_remove(struct spi_device *spi)
{/* 删除设备 */cdev_del(&icm20608dev.cdev);unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);/* 注销掉类和设备 */device_destroy(icm20608dev.class, icm20608dev.devid);class_destroy(icm20608dev.class);return 0;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},  {}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI驱动结构体 */	
static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = "icm20608",.of_match_table = icm20608_of_match, },.id_table = icm20608_id,
};/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

关键代码分析如下:

icm20608 设备结构体icm20608_dev,其中有private_data变量,对于 SPI 设备驱动来讲最核心的就是 spi_device。

probe 函数会向驱动提供当前 SPI 设备对应的spi_device,因此在 probe 函数中设置 private_data 为 probe 函数传递进来的 spi_device 参数。

struct icm20608_dev {dev_t devid;				/* 设备号 	 */struct cdev cdev;			/* cdev 	*/struct class *class;		/* 类 		*/struct device *device;		/* 设备 	 */struct device_node	*nd; 	/* 设备节点 */int major;					/* 主设备号 */void *private_data;			/* 私有数据 		*/signed int gyro_x_adc;		/* 陀螺仪X轴原始值 	 */signed int gyro_y_adc;		/* 陀螺仪Y轴原始值		*/signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 		*/signed int accel_x_adc;		/* 加速度计X轴原始值 	*/signed int accel_y_adc;		/* 加速度计Y轴原始值	*/signed int accel_z_adc;		/* 加速度计Z轴原始值 	*/signed int temp_adc;		/* 温度原始值 			*/
};

spi_driver 注册与注销函数:

icm20608_init函数里,使用spi_register_driver 向 Linux 系统注册上面定义的 icm20608_driver。
icm20608_exit函数里,使用spi_unregister_driver 注销掉前面注册的 icm20608_driver。

static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}

icm20608_probe函数:当设备与驱动匹配成功以后此函数就会执行,标准的注册字符设备驱动。

  • 设置 SPI 为模式 0,也就是 CPOL=0, CPHA=0。
  • 设置好 spi_device 以后需要使用 spi_setup 配置一下。
  • 设置 icm20608dev 的 private_data 成员变量为 spi_device。
  • 调用 icm20608_reginit 函数初始化 ICM20608,主要是初始化 ICM20608 指定寄存器。
	/*初始化spi_device */spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);icm20608dev.private_data = spi; /* 设置私有数据 *//* 初始化ICM20608内部寄存器 */icm20608_reginit();		

其余代码就是 icm20608 寄存器读写与初始化函数

// 从icm20608读取多个寄存器数据
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)// 向icm20608多个寄存器写入数据
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)// 读取icm20608指定寄存器值,读取一个寄存器
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)// 向icm20608指定寄存器写入指定的值,写一个寄存器
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)// 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、三轴加速度计和内部温度
void icm20608_readdata(struct icm20608_dev *dev)

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

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;icm20608_readdata(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));return 0;
}

icm20608App.c

测试app,icm20608App.c,代码如下:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;char *filename;signed int databuf[7];unsigned char data[14];signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;signed int accel_x_adc, accel_y_adc, accel_z_adc;signed int temp_adc;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;int ret = 0;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, databuf, sizeof(databuf));if(ret == 0) { 			/* 数据读取成功 */gyro_x_adc = databuf[0];gyro_y_adc = databuf[1];gyro_z_adc = databuf[2];accel_x_adc = databuf[3];accel_y_adc = databuf[4];accel_z_adc = databuf[5];temp_adc = databuf[6];/* 计算实际值 */gyro_x_act = (float)(gyro_x_adc)  / 16.4;gyro_y_act = (float)(gyro_y_adc)  / 16.4;gyro_z_act = (float)(gyro_z_adc)  / 16.4;accel_x_act = (float)(accel_x_adc) / 2048;accel_y_act = (float)(accel_y_adc) / 2048;accel_z_act = (float)(accel_z_adc) / 2048;temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);printf("temp = %d\r\n", temp_adc);printf("实际值:");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);printf("act temp = %.2f°C\r\n", temp_act);}usleep(100000); /*100ms */}close(fd);	/* 关闭文件 */	return 0;
}

在 while 循环中每隔 100ms 从 icm20608 中读取一次数据,读取到 icm20608原始数据以后,将其转换为实际值,比如陀螺仪就是角速度、加速度计就是 g 值,最终将传感器原始数据和得到的实际值显示在终端上。

Makefile文件

makefile文件只需要修改 obj-m 变量的值,改为icm20608.o。

代码如下:

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := icm20608.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行测试

编译出驱动模块文件:

make -j32

编译 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

编译成功以后,就会生成一个名为“icm20608.ko”的驱动模块文件,和icm20608App 这个应用程序。

使用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App:

可以看出, FPU 架构为 VFPv3, SIMD 使用了 NEON,并且使用了 SP 和 DP,说明 icm20608App 这个应用程序使用了硬件浮点。

将编译出来的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 中读取数据,打印出来,如图:

大家可以试着晃动一下开发板,陀螺仪和加速度计的值就会有变化。

http://www.xdnf.cn/news/18991.html

相关文章:

  • Electron解压缩文件
  • hive on tez为什么写表时,要写临时文件到hdfs目录
  • docker 1分钟 快速搭建 redis 哨兵集群
  • 配置nginx.conf (增加21001端口实例操作)
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(三)
  • [灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU之比较器
  • jQuery 从入门到实践:基础语法、事件与元素操作全解析
  • mac电脑双屏显示时程序坞跑到副屏的解决方法
  • 机器视觉学习-day10-图像添加水印
  • Mybatis 与 Springboot 集成过程详解
  • Kubernetes一EFK日志架构
  • Ovis2.5技术解密:原生分辨率与“反思模式”如何铸就新一代MLLM王者
  • 嵌入式学习日志————实验:串口发送串口发送+接收
  • 2025年渗透测试面试题总结-37(题目+回答)
  • 2024年06月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 零基础-力扣100题从易到难详解(持续更新1-10题)
  • 【链表 - LeetCode】25. K 个一组翻转链表
  • DAY 58 经典时序预测模型2
  • Kubernetes 的20 个核心命令分类详解
  • Linex系统网络管理(二)
  • 数据结构第8章 排序(竟成)
  • SqlHelper类库的使用方法
  • .NET周刊【8月第3期 2025-08-17】
  • 鸿蒙ArkUI 基础篇-06-组件基础语法-Column/Row/Text
  • 车载诊断架构 --- 基于整车功能的正向诊断需求开发
  • Dubbo加标签方式
  • Vue3 + 高德地图实现车辆电子围栏监控与报警
  • banner这个文件是怎么请求到后端数据的
  • codeforces(1045)(div2)D. Sliding Tree
  • zookeeper-集群扩缩容