【Linux驱动开发】SPI 驱动实验(ICM20608驱动)

论坛 期权论坛 期权     
已经选择匿名的用户   2021-10-25 13:35   5925   0

一、I.MX6U ECSPI 简介

ECSPI 64*32 个接收 FIFO(RXFIFO)和 64*32 个发送 FIFO(TXFIFO)
I.MX6U 4 个ECSPI,每个 ECSPI 支持四个片选信号,即使用 硬件片选信号的话, 一个 ECSPI 可以支持 4 个外设。
ECSPI 特性:
  • ①、全双工同步串行接口。
  • ②、可配置的主/从模式。
  • ③、四个片选信号,支持多从机。
  • ④、发送和接收都有一个 32x64 FIFO
  • ⑤、片选信号 SS/CS,时钟信号 SCLK 极性可配置。
  • ⑥、支持 DMA

二、ICM-20608 简介

  • ICM-20608 InvenSense 出品的一款 6 MEMS 传感器,包括 3 轴加速度和 3 轴陀螺仪。
  • ICM-20608 尺寸非常小,只有 3x3x0.75mm,采用 16P LGA 封装。
  • ICM-20608 内部有一个 512 字节的 FIFO
  • 陀螺仪的量程范围可以编程设置,可选择±250,±500,±1000 和±2000°/s,加速度的量程范围也可以编程设置,可选择±2g,±4g,±4g,±8g 和±16g
  • 陀螺仪和加速度计都是 16 位的 ADC,并且支持 I2C SPI 两种协议,使用 I2C 接口的话通信速度最高可以达到400KHz,使用 SPI 接口的话通信速度最高可达到 8MHz
ICM-20608 特性:
  • ①、陀螺仪支持 X,Y Z 三轴输出,内部集成 16 ADC,测量范围可设置:±250,±500,±1000 和±2000°/s
  • ②、加速度计支持 X,Y Z 轴输出,内部集成 16 ADC,测量范围可设置:±2g,±4g,±4g,±8g 和±16g
  • ③、用户可编程中断。
  • ④、内部包含 512 字节的 FIFO
  • ⑤、内部包含一个数字温度传感器。
  • ⑥、耐 10000g 的冲击。
  • ⑦、支持快速 I2C,速度可达 400KHz
  • ⑧、支持 SPI,速度可达 8MHz
ICM-20608 3 轴方向如图 所示:
ICM-20608 检测轴方向和极性
ICM-20608 框图
  • 使用 IIC 接口的话 ICM-20608 AD0 引脚决定 I2C 设备从地址的最后一位。
  • AD0 = 0 ICM-20608 从设备地址是 0X68。
  • AD0 = 1 ICM-20608 从设备地址是 0X69

三、SPI 驱动框架简介

1、SPI 主机驱动

  • SPI 主机驱动就是 SOC SPI 控制器驱动Linux 内核使用 spi_master 表示 SPI 主机驱动,定义在 include/linux/spi/spi.h
  • SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master
申请 spi_master
struct spi_master * spi_alloc_master (struct device *dev, unsigned size)
dev :设备,一般是 platform_device 中的 dev 成员变量。
size 私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。
返回值: 申请到的 spi_master
释放 spi_master
void spi_master_put (struct spi_master *master)
master :要释放的 spi_master
返回值: 无。
注册 spi_master
int spi_register_master (struct spi_master *master)
master :要注册的 spi_master
返回值: 0 ,成功;负值,失败。
I.MX6U SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数注册 spi_master
注销 spi_master
void spi_unregister_master (struct spi_master *master)
master :要注销的 spi_master
返回值: 无。
如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销 spi_master

2、SPI 设备驱动

  • Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,spi_driver 结构体定义在 include/linux/spi/spi.h 文件。
  • spi_driver 初始化完成以后需要向 Linux 内核注册。
注册 spi_driver
int spi_register_driver (struct spi_driver *sdrv)
sdrv 要注册的 spi_driver
返回值: 0 ,注册成功;赋值,注册失败。
注销 spi_driver
void spi_unregister_driver (struct spi_driver *sdrv)
sdrv 要注销的 spi_driver
返回值: 无。

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

  • SPI 设备和驱动的匹配过程是由 SPI 总线来完成。
  • SPI 总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件。
  • SPI 设备和驱动的匹配函数为 spi_match_device,包含以下函数和过程:
of_driver_match_device 函数

用于完成设备树设备和驱动匹配。

比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等。

acpi_driver_match_device 函数用于 ACPI 形式的匹配。
spi_match_id 函数

用于传统的、无设备树的 SPI 设备和驱动匹配过程。

比较 SPI 设备名字和 spi_device_id 的 name 字段是否相等。

4、I.MX6U SPI 主机驱动分析

I.MX6U ECSPI 主机驱动文件为 drivers/spi/spi-imx.c
I.MX6U的 SPI 主机的最终数据收发函数为 spi_imx_transfer
函数通关系 spi_imx_transfer -> spi_imx_pio_transfer -> spi_imx_push -> spi_imx->tx
发送函数
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32
接收函数
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32

四、实验程序编写

1、修改设备树

(1)添加 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 */
  >;
};

(2)在设备树最后面向 ecspi3 节点追加 icm20608 子节点

&ecspi3 { 
 fsl,spi-num-chipselects = <1>;    /*片选数量*/
 cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 自定义片选引脚 而不是cs-gpios属性 */
 pinctrl-names = "default"; 
 pinctrl-0 = <&pinctrl_ecspi3>;
 status = "okay"; 

 spidev: icm20608@0 { 
  compatible = "iot,icm20608";
  spi-max-frequency = <8000000>;
  reg = <0>;    /*SPI通道 0*/
 };
};

2、ICM20608设备驱动程序编写

驱动程序流程

1、定义设备名
2、定义 icm20608_dev 设备结构体
3、从icm20608读取寄存器数据(icm20608_read_regs)
4、向icm20608寄存器写入数据(icm20608_write_regs)
5、读取ICM20608数据函数(icm20608_readdata)
6、打开设备函数 icm20608_open
7、从设备读取数据函数(icm20608_read)
8、关闭/释放设备函数(icm20608_release)
9、icm20608操作函数(file_operations 结构体类型)
10、ICM20608内部寄存器初始化函数(icm20608_reginit)
11、spi驱动的probe函数,当驱动与设备匹配以后此函数就会执行
    (1)构建设备号(register_chrdev_region、alloc_chrdev_region)
    (2)注册设备(cdev_init、cdev_add)
    (3)创建类(class_create)
    (4)创建设备(device_create)
    (5)获取设备树中cs片选信号(of_find_node_by_path)
    (6)获取设备树中的gpio属性,得到BEEP所使用的BEEP编号(of_get_named_gpio)
    (7)设置GPIO1_IO20为输出,并且输出高电平(gpio_direction_output)
    (8)初始化spi_device(spi_setup)
    (9)初始化ICM20608内部寄存器(icm20608_reginit)
12、spi驱动的remove函数,移除spi驱动的时候此函数会执行
    (1)删除cdev(cdev_del)
    (2)注销设备号(unregister_chrdev_region)
    (3)删除设备(device_destroy)
    (4)删除类(class_destroy)
13、传统匹配方式ID列表(spi_device_id)和 设备树匹配列表(of_device_id)
14、SPI驱动结构体(传入probe函数、remove函数、匹配列表等)
15、驱动入口函数(icm20608_init)—— 注册设备驱动
16、驱动出口函数(icm20608_exit)—— 注销设备驱动
17、module_init 和 module_exit 指定驱动入口和出口函数
18、LICENSE 和作者信息

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"
/***************************************************************
文件名  : icm20608.c
描述     : ICM20608 SPI驱动程序
***************************************************************/
#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;   /* 私有数据   */
 int cs_gpio;    /* 片选所使用的GPIO编号  */
 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;
 unsigned char txdata[len];
 struct spi_message m;
 struct spi_transfer *t;
 struct spi_device *spi = (struct spi_device *)dev->private_data;

 gpio_set_value(dev->cs_gpio, 0);    /* 片选拉低,选中ICM20608 */
 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */

 /* 第1次,发送要读取的寄存地址 */
 txdata[0] = reg | 0x80;  /* 写数据的时候寄存器地址bit8要置1 */
 t->tx_buf = txdata;   /* 要发送的数据 */
 t->len = 1;     /* 1个字节 */
 spi_message_init(&m);  /* 初始化spi_message */
 spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
 ret = spi_sync(spi, &m); /* 同步发送 */

 /* 第2次,读取数据 */
 txdata[0] = 0xff;   /* 随便一个值,此处无意义 */
 t->rx_buf = buf;   /* 读取到的数据 */
 t->len = len;    /* 要读取的数据长度 */
 spi_message_init(&m);  /* 初始化spi_message */
 spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
 ret = spi_sync(spi, &m); /* 同步发送 */

 kfree(t);         /* 释放内存 */
 gpio_set_value(dev->cs_gpio, 1);   /* 片选拉高,释放ICM20608 */

 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;

 unsigned char txdata[len];
 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); /* 申请内存 */
 gpio_set_value(dev->cs_gpio, 0);   /* 片选拉低 */

 /* 第1次,发送要读取的寄存地址 */
 txdata[0] = reg & ~0x80; /* 写数据的时候寄存器地址bit8要清零 */
 t->tx_buf = txdata;   /* 要发送的数据 */
 t->len = 1;     /* 1个字节 */
 spi_message_init(&m);  /* 初始化spi_message */
 spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
 ret = spi_sync(spi, &m); /* 同步发送 */

 /* 第2次,发送要写入的数据 */
 t->tx_buf = buf;   /* 要写入的数据 */
 t->len = len;    /* 写入的字节数 */
 spi_message_init(&m);  /* 初始化spi_message */
 spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
 ret = spi_sync(spi, &m); /* 同步发送 */

 kfree(t);     /* 释放内存 */
 gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */
 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];
 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  : spi设备
  * @param - id      : spi设备ID
  * 
  */ 
static int icm20608_probe(struct spi_device *spi)
{
 int ret = 0;

 /* 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);
 }

 /* 获取设备树中cs片选信号 */
 icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
 if(icm20608dev.nd == NULL) {
  printk("ecspi3 node not find!\r\n");
  return -EINVAL;
 } 

 /* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
 icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
 if(icm20608dev.cs_gpio < 0) {
  printk("can't get cs-gpio");
  return -EINVAL;
 }

 /* 3、设置GPIO1_IO20为输出,并且输出高电平 */
 ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
 if(ret < 0) {
  printk("can't set gpio!\r\n");
 }

 /*初始化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     : spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param - client  : spi设备
 * @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[] = {
 {"iot,icm20608", 0},  
 {}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
 { .compatible = "iot,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");
MODULE_AUTHOR("pjw");

icm20608reg.h

#ifndef ICM20608_H
#define ICM20608_H
/***************************************************************
文件名  : icm20608reg.h
描述     : ICM20608寄存器地址描述头文件
***************************************************************/
#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

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>
/***************************************************************
文件名  : icm20608App.c
描述     : icm20608设备测试APP。
使用方法  :./icm20608App /dev/icm20608
***************************************************************/

/*
 * @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;
}

Makefile

KERNELDIR := /home/pjw/linux/kernel/iot_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
 
CURRENT_PATH := $(shell pwd)
 
obj-m := dtsled.o
 
build: kernel_modules
 
kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
 
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

五、测试:

1、将 icm20608.ko 和 icm20608App 这两个文件拷贝到 rootfs/lib/modules 目录
2、主板终端进入到目录 lib/modules/4.1.15 中,输入如下命令加载 icm20608.ko 驱动模块:

depmod                 //第一次加载驱动的时候需要运行此命令
modprobe icm20608.ko     //加载驱动

3、查看模块是否加载成功

使用“lsmod”命令查看当前系统中存在的模块

4、驱动模块加载成功以后使用 icm20608App 来测试:

./icm20608App /dev/icm20608

从 icm20608 中读取数据:

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:86
帖子:240
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP