54.Linux IIO驱动框架

54.Linux IIO驱动框架

这节我们继续修改icm20608程序,学习与应用程序交互的中间层 IIO驱动框架

之前我们编写adc类的外设icm20608或者ap3216c等,是自己规定了上报给应用程序的格式如:

c 复制代码
static ssize_t icm20608_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    signed int data[7];
	long err = 0;
    struct icm20608_dev *dev = file->private_data;

    //读取icm20608的数据
    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;
}

读取icm20608的7个寄存器的值,然后上报给应用程序

应用程序需要根据上报来的数据格式进行解析

c 复制代码
		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;

可以看到对于不同的外设有非常多的上报格式,应用程序编写人员有需要驱动开发人员很多时候规范上报的格式,产生多余的沟通,使用IIO的驱动框架来整合成规范形成标标准,有利于应用程序直接读取尤为重要!

理想的驱动应该是

1.驱动是一个黑匣子,提供人性化、标准化的接口

使用IIO子驱动需要内核支持

c 复制代码
-> Device Drivers                                                          -> Industrial I/O support (IIO [=y])                                       -> Enable buffer support within IIO (IIO_BUFFER [=y])
c 复制代码
[*]    Enable buffer support within IIO
[ ]    IIO callback buffer used for push in-kernel interfaces (NEW) 
<*>    Industrial I/O buffering based on kfifo

使用IIO驱动框架编写的驱动,在系统中的表现为:

会在根文件系统得到一堆标准命名的文件

c 复制代码
cd /sys/bus/iio/devices
ls
dev
in_anglvel_scale
in_temp_raw
in_accel_scale
in_anglvel_x_calibbias
in_temp_scale
in_accel_x_calibbias
in_anglvel_x_raw
name
in_anglvel_y_calibbias
of_node
in_accel_y_calibbias
in_accel_x_raw
in_accel_z_raw
in_anglvel_y_raw
power

z轴加速度:使用in_accel_z_raw(ADC原始值) * in_accel_scale (刻度值) = 1.00G 就可以获取实际的值。这就形成了一套标准

iio_dev

表示iio驱动框架设备结构体

iio_dev由于表示内核中的iio驱动

c 复制代码
struct iio_dev {
	int				id;

	int				modes;
	int				currentmode;
	struct device			dev;

	struct iio_event_interface	*event_interface;

	struct iio_buffer		*buffer;
	struct list_head		buffer_list;
	int				scan_bytes;
	struct mutex			mlock;

	const unsigned long		*available_scan_masks;
	unsigned			masklength;
	const unsigned long		*active_scan_mask;
	bool				scan_timestamp;
	unsigned			scan_index_timestamp;
	struct iio_trigger		*trig;
	struct iio_poll_func		*pollfunc;

	struct iio_chan_spec const	*channels;
	int				num_channels;

	struct list_head		channel_attr_list;
	struct attribute_group		chan_attr_group;
	const char			*name;
	const struct iio_info		*info;
	struct mutex			info_exist_lock;
	const struct iio_buffer_setup_ops	*setup_ops;
	struct cdev			chrdev;
#define IIO_MAX_GROUPS 6
	const struct attribute_group	*groups[IIO_MAX_GROUPS + 1];
	int				groupcounter;

	unsigned long			flags;
#if defined(CONFIG_DEBUG_FS)
	struct dentry			*debugfs_dentry;
	unsigned			cached_reg_addr;
#endif
};

其中比较重要的几个参数:

  1. 通道,表示传感器的每一个测量通道

    比如,表示温度的一个通道、加速度x、y、z的三个通道、陀螺仪x、y、z三个通道,总共7个通道,那么该设备就要注册包含7个通道的channels结构体

c 复制代码
struct iio_chan_spec const	*channels;
c 复制代码
struct iio_chan_spec {
	enum iio_chan_type	type;   //指定通道类型,是加速度类型还是角度类型...
	int			channel;	   //通道索引
	int			channel2;		//当modified为1的时候,channel2为通道修饰符,如x轴,y轴,z轴数据
	unsigned long		address;
	int			scan_index;
	struct {
		char	sign;
		u8	realbits;
		u8	storagebits;
		u8	shift;
		u8	repeat;
		enum iio_endian endianness;
	} scan_type;
	long			info_mask_separate;
	long			info_mask_shared_by_type;
	long			info_mask_shared_by_dir;
	long			info_mask_shared_by_all;
	const struct iio_event_spec *event_spec;
	unsigned int		num_event_specs;
	const struct iio_chan_spec_ext_info *ext_info;
	const char		*extend_name;
	const char		*datasheet_name;
	unsigned		modified:1;
	unsigned		indexed:1;
	unsigned		output:1;    		//默认输出为1
	unsigned		differential:1;
};

type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类型 ,后面有几个默认值为1

  1. iio_info结构体,最重要的结构体,里面有很多函数需要自己编写。这个是我们在编写 IIO 驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info 里面。
c 复制代码
	const struct iio_info		*info;
c 复制代码
struct iio_info {
	struct module			*driver_module;
	struct attribute_group		*event_attrs;
	const struct attribute_group	*attrs;

	int (*read_raw)(struct iio_dev *indio_dev,
			struct iio_chan_spec const *chan,
			int *val,
			int *val2,
			long mask);

	int (*read_raw_multi)(struct iio_dev *indio_dev,
			struct iio_chan_spec const *chan,
			int max_len,
			int *vals,
			int *val_len,
			long mask);

	int (*write_raw)(struct iio_dev *indio_dev,
			 struct iio_chan_spec const *chan,
			 int val,
			 int val2,
			 long mask);

	int (*write_raw_get_fmt)(struct iio_dev *indio_dev,
			 struct iio_chan_spec const *chan,
			 long mask);

	int (*read_event_config)(struct iio_dev *indio_dev,
				 const struct iio_chan_spec *chan,
				 enum iio_event_type type,
				 enum iio_event_direction dir);

	int (*write_event_config)(struct iio_dev *indio_dev,
				  const struct iio_chan_spec *chan,
				  enum iio_event_type type,
				  enum iio_event_direction dir,
				  int state);

	int (*read_event_value)(struct iio_dev *indio_dev,
				const struct iio_chan_spec *chan,
				enum iio_event_type type,
				enum iio_event_direction dir,
				enum iio_event_info info, int *val, int *val2);

	int (*write_event_value)(struct iio_dev *indio_dev,
				 const struct iio_chan_spec *chan,
				 enum iio_event_type type,
				 enum iio_event_direction dir,
				 enum iio_event_info info, int val, int val2);

	int (*validate_trigger)(struct iio_dev *indio_dev,
				struct iio_trigger *trig);
	int (*update_scan_mode)(struct iio_dev *indio_dev,
				const unsigned long *scan_mask);
	int (*debugfs_reg_access)(struct iio_dev *indio_dev,
				  unsigned reg, unsigned writeval,
				  unsigned *readval);
	int (*of_xlate)(struct iio_dev *indio_dev,
			const struct of_phandle_args *iiospec);
	int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned val);
	int (*hwfifo_flush_to_buffer)(struct iio_dev *indio_dev,
				      unsigned count);
};

核心:read_raw、read_raw

一般只要实现read_raw和read_raw以及规定写入原始数据的格式write_raw_get_fmt这三个即可

write_raw实现最为麻烦,需要根据不同情况做出不同实现方式

这两个函数就是最终读写设备内部数据的操作函数,需要程序编写人员去实现的。比如应用读取一个陀螺仪传感器的原始数据,那么最终完成工作的就是 read_raw 函数,我们需要在 read_raw 函数里面实现对陀螺仪芯片的读取操作。同理, write_raw

是应用程序向陀螺仪芯片写数据,一般用于配置芯片,比如量程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下:

indio_dev: 需要读写的 IIO 设备。

chan:需要读取的通道。

val, val2:对于 read_raw 函数来说 val 和 val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。 val 和 val2 共同组成具体值, val 是整数部分, val2 是小数部分。但是val2 也是对具体的小数部分扩大 N 倍后的整数值,因为不能直接从内核向应用程序返回一个小数值。比如现在有个值为 1.00236,那么 val 就是 1, vla2 理论上来讲是 0.00236,但是我们需要对 0.00236 扩大 N 倍,使其变为整数,这里我们扩大 1000000 倍,那么 val2 就是 2360。因此val=1, val2=2360。

扩大的倍数我们不能随便设置,而是要使用 Linux 定义的倍数, Linux 内核里面定义的数据扩大倍数,或者说数据组合形式如下

组合宏 描述
IIO_VAL_INT 整数值,没有小数。比如 5000,那么就是 val=5000,不需要设置 val2
IIO_VAL_INT_PLUS_MICRO 小数部分扩大 1000000 倍,比如 1.00236,此时 val=1, val2=2360。
IIO_VAL_INT_PLUS_NANO 小数部分扩大 1000000000 倍,同样是 1.00236,此时val=1, val2=2360000。
IIO_VAL_INT_PLUS_MICRO_DB dB 数据,和 IIO_VAL_INT_PLUS_MICRO 数据形式一样,只是在后面添加 db。
IIO_VAL_INT_MULTIPLE 多个整数值,比如一次要传回 6 个整数值,那么 val 和val2就不够用了。此宏主要用于iio_info的read_raw_multi函数。
IIO_VAL_FRACTIONAL 分数值,也就是 val/val2。比如 val=1, val2=4,那么实际值就是 1/4。
IIO_VAL_FRACTIONAL_LOG2 值为 val>>val2,也就是 val 右移 val2 位。比如 val=25600, val2=4 , 那 么 真 正 的 值 就 是 25600 右 移 4 位 , 25600>>4=1600.

mask: 掩码,用于指定我们读取的是什么数据,比如 ICM20608 这样的传感器,他既有原始的测量数据,比如 X,Y,Z 轴的陀螺仪、加速度计等,也有测量范围值,或者分辨率。比如加速度计测量范围设置为±16g,那么分辨率就是 32/65536≈0.000488,我们只有读出原始值以及对应的分辨率(量程),才能计算出真实的重力加速度。此时就有两种数据值:传感器原始值、分辨率。 Linux 内核使用 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_SCALE 这两个宏来表示原始值以及分辨率,这两个宏就是掩码。

使用iio

  1. iio_dev的申请与释放

在使用之前要先申请 iio_dev,申请函数为 iio_device_alloc,函数原型如下:

c 复制代码
struct iio_dev *iio_device_alloc(int sizeof_priv);

sizeof_priv: 私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为 iio_dev的私有数据,这样可以直接通过 iio_device_alloc 函数同时完成 iio_dev 和设备结构体变量的内存申请。 申请成功以后使用 iio_privr函数来得到自定义的设备结构体变量首地址。

返回值:如果申请成功就返回

iio_dev 首地址,如果失败就返回 NULL。

一般 iio_device_alloc 和 iio_priv 之间的配合使用如下所示:

c 复制代码
struct icm20608_dev *dev;
struct iio_dev *indio_dev;

/* 1、申请 iio_dev 内存 */
indio_dev = iio_device_alloc(sizeof(*dev));
if (!indio_dev)
	return -ENOMEM;

/* 2、获取设备结构体变量地址 */
 dev = iio_priv(indio_dev);

释放 iio_dev,需要使用 iio_device_free 函数,函数原型如下:

c 复制代码
void iio_device_free(struct iio_dev *indio_dev)

indio_dev: 需要释放的 iio_dev。

也 可 以 使 用 devm_iio_device_alloc 来 分 配 iio_dev , 这 样 就 不 需 要 我 们 手 动 调 用iio_device_free 函数完成 iio_dev 的释放工作。

  1. 初始化iio_dev的成员变量

  2. iio_dev 注册与注销

前面分配好 iio_dev 以后就要初始化各种成员变量,初始化完成以后就需要将 iio_dev 注册到内核中,需要用到 iio_device_register 函数,函数原型如下

c 复制代码
int iio_device_register(struct iio_dev *indio_dev)

indio_dev: 需要注册的 iio_dev。

返回值: 0,成功;其他值,失败

如果要注销 iio_dev 使用 iio_device_unregister 函数,函数原型如下

c 复制代码
void iio_device_unregister(struct iio_dev *indio_dev)

indio_dev: 需要注销的 iio_dev。

返回值: 0,成功;其他值,失败

实战

1.添加头文件

c 复制代码
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/unaligned/be_byteshift.h>

基础框架:

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/unaligned/be_byteshift.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm_20608_reg.h"


#define icm20608_CNT      1
#define icm20608_NAME     "icm20608"


struct icm20608_dev
{
    struct spi_device *spi;
    struct iio_dev *indio_dev;
    struct mutex lock; //在读取数据的时候需要上锁操作,保证一次只有一个应用程序在读取该数据
    struct regmap *regmap;
    struct regmap_config regmap_config;
};



//读取单个寄存器
static unsigned char icm20608_read_Onereg(struct icm20608_dev *dev , u8 reg){
    unsigned int data = 0;
    int  ret = 0;
    
    ret = regmap_read(dev->regmap,reg,&data);
    return (u8)data;
}

//写入单个寄存器
static void icm20608_write_Onereg(struct icm20608_dev *dev , u8 reg , u8 value){
    u8 buf = value;
    int  ret = 0;
    ret = regmap_write(dev->regmap,reg,buf);
}




//初始化icm20608
void driver_icm20608_init(struct icm20608_dev *dev){
    u8 value = 0;

    //从睡眠模式唤醒
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x80); //复位,睡眠模式
    mdelay(50);
    icm20608_write_Onereg(dev,ICM20_PWR_MGMT_1,0x01); //关闭睡眠模式
    mdelay(50);

    value = icm20608_read_Onereg(dev,ICM20_WHO_AM_I);
    printk("ICM20_WHO_AM_I reg = %#x\r\n",value);

    value = icm20608_read_Onereg(dev,ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1 reg = %#x\r\n",value);


    icm20608_write_Onereg(dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/
	icm20608_write_Onereg(dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/
	icm20608_write_Onereg(dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/
	icm20608_write_Onereg(dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/
	icm20608_write_Onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
	icm20608_write_Onereg(dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/
	icm20608_write_Onereg(dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/
	icm20608_write_Onereg(dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/
}






static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask){
    printk("icm20608_read_raw\r\n");

    return 0;
}

static int icm20608_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val,int val2,long mask){
    printk("icm20608_write_raw\r\n");
    return 0;
}

static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,long mask){
    printk("icm20608_write_raw_get_fmt\r\n");
    return 0;

}
static const struct iio_info icm20608_info = {
    .driver_module = THIS_MODULE,
    .read_raw = icm20608_read_raw,
    .write_raw = icm20608_write_raw,
    .write_raw_get_fmt = icm20608_write_raw_get_fmt,
};

struct iio_chan_spec xxx_channels[] = {
    
};
static int icm20608_probe(struct spi_device *spi){

    int ret = 0;
    printk("icm20608_probe\r\n");


    //搭建iio驱动框架
    struct icm20608_dev *dev;
    struct iio_dev *indio_dev;
    
    indio_dev = devm_iio_device_alloc(&spi->dev,sizeof(*dev));
    if (!indio_dev)
    {
        ret =  -ENOMEM;
        goto failed_indio;
    }
    
    //初始化设备结构体
    dev = iio_priv(indio_dev);  //获取设备首地址
    dev->spi = spi;             //将spi赋给设备结构体
    spi_set_drvdata(spi,indio_dev);  //将私有数据与spi进行绑定,方便后面获取iio_dev

    //初始化锁
    mutex_init(&dev->lock);

    //初始化iio_dev成员变量
    indio_dev->dev.parent = &spi->dev;
    indio_dev->info = &xxx_info;
    indio_dev->name = "xxx";
    indio_dev->modes = INDIO_DIRECT_MODE;   //直接模式
    indio_dev->channels = xxx_channels;
    indio_dev->num_channels = ARRAY_SIZE(xxx_channels);

    ret = iio_device_register(indio_dev);
    if (ret<0)
    {
        dev_err(&spi->dev,"unable to register iio device \n");
        goto failed_iio_regiest;
    }
    
    

    //regmap_config初始化
    dev->regmap_config.reg_bits = 8;
    dev->regmap_config.val_bits = 8;
    dev->regmap_config.read_flag_mask = 0x80;
    //regmap初始化
    dev->regmap = regmap_init_spi(spi,&dev->regmap_config);
    if (IS_ERR(dev->regmap))
    {
        ret = PTR_ERR(dev->regmap);
        goto fail_regmap_init;
    }
    
    //初始化spi,设置spi的模式
    spi->mode = SPI_MODE_0;         /*MODE0, CPOL=0, CPHA=0*/
    spi_setup(spi);   

    //初始化icm20608芯片
    driver_icm20608_init(dev);

    return 0;

fail_regmap_init:
    iio_device_unregister(indio_dev);
failed_iio_regiest:
failed_indio:
    return ret;
}

static int icm20608_remove(struct spi_device *spi){

    

    struct iio_dev *indio_dev = spi_get_drvdata(spi);
    struct icm20608_dev *dev;

    dev = iio_priv(indio_dev);

    regmap_exit(dev->regmap);

    iio_device_unregister(indio_dev); //使用devm_XX函数不用手动取消
    return 0;

}
//设备树匹配
const struct of_device_id icm20608_of_match[]= {
    { .compatible = "alientek,icm20608"},
    {},
};

//传统匹配表
const struct spi_device_id icm20608_id[] = {
    {"alientek,icm20608",0},
    {},
};
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,

};
static int __init icm20608_init(void){
    int ret = 0;
    ret  =  spi_register_driver(&icm20608_driver);
    return ret;

}
static void __exit icm20608_exit(void){
    spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

上去框架可以编译成ko文件并modprobe

成功加载后

在linux中表现为:

c 复制代码
ls /sys/bus/iio/devices/
iio:device0
//可以查看我们定义的名字
cat /sys/bus/iio/devices/iio\:device0/name
xxx

完善驱动框架

  1. 编写iio的通道
c 复制代码
#define ICM20608_CHAN(_type, _channel2, _index)                    \
	{                                                             \
		.type = _type,                                        \
		.modified = 1,                                        \
		.channel2 = _channel2,                                \
		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	      \
				      BIT(IIO_CHAN_INFO_CALIBBIAS),   \
		.scan_index = _index,                                 \
		.scan_type = {                                        \
				.sign = 's',                          \
				.realbits = 16,                       \
				.storagebits = 16,                    \
				.shift = 0,                           \
				.endianness = IIO_BE,                 \
			     },                                       \
	}


/* 
 * ICM20608的扫描元素,3轴加速度计、
 * 3轴陀螺仪、1路温度传感器,1路时间戳 
 */
enum inv_icm20608_scan {
	INV_ICM20608_SCAN_ACCL_X,
	INV_ICM20608_SCAN_ACCL_Y,
	INV_ICM20608_SCAN_ACCL_Z,
	INV_ICM20608_SCAN_TEMP,
	INV_ICM20608_SCAN_GYRO_X,
	INV_ICM20608_SCAN_GYRO_Y,
	INV_ICM20608_SCAN_GYRO_Z,
	INV_ICM20608_SCAN_TIMESTAMP,
};

//配置7个通道 重点
struct iio_chan_spec icm20608_channels[] = {
    //温度通道
    {
        .type = IIO_TEMP,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)|BIT(IIO_CHAN_INFO_OFFSET)|BIT(IIO_CHAN_INFO_SCALE),
        .scan_index = INV_ICM20608_SCAN_TEMP,
        .scan_type = {
            .sign = 's',
            .realbits = 16,
            .storagebits = 16,
            .shift = 0,
            .endianness = IIO_BE,
        },
    },
    //使用宏定义来定义其他通道,避免重复性代码
    //加速度X,Y,Z三个通道
    ICM20608_CHAN(IIO_ACCEL,IIO_MOD_X,INV_ICM20608_SCAN_ACCL_X),  //加速度x轴
    ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_ICM20608_SCAN_ACCL_Y),  //加速度y轴
    ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_ICM20608_SCAN_ACCL_Z), //加速度z轴

    //陀螺仪
    ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_X,INV_ICM20608_SCAN_GYRO_X),
    ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Y,INV_ICM20608_SCAN_GYRO_Y),
    ICM20608_CHAN(IIO_ANGL_VEL,IIO_MOD_Z,INV_ICM20608_SCAN_GYRO_Z),
};

重新加载后

c 复制代码
ls /sys/bus/iio/devices/iio\:device0/
dev                     in_anglvel_scale        in_temp_raw
in_accel_scale          in_anglvel_x_calibbias  in_temp_scale
in_accel_x_calibbias    in_anglvel_x_raw        name
in_accel_x_raw          in_anglvel_y_calibbias  of_node
in_accel_y_calibbias    in_anglvel_y_raw        power
in_accel_y_raw          in_anglvel_z_calibbias  subsystem
in_accel_z_calibbias    in_anglvel_z_raw        uevent
in_accel_z_raw          in_temp_offset

所有定义的通道,对通道的描述都已经通过文件呈现出来了

如果屏蔽掉对于的通道

c 复制代码
#define ICM20608_CHAN(_type, _channel2, _index)                    \
	{                                                             \
		.type = _type,                                        \
		.modified = 1,                                        \
		.channel2 = _channel2,                                \
		/* .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), */ \
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	      \
				      BIT(IIO_CHAN_INFO_CALIBBIAS),   \
		.scan_index = _index,                                 \
		.scan_type = {                                        \
				.sign = 's',                          \
				.realbits = 16,                       \
				.storagebits = 16,                    \
				.shift = 0,                           \
				.endianness = IIO_BE,                 \
			     },                                       \
	}

则会表现为对应的文件

c 复制代码
ls /sys/bus/iio/devices/iio\:device0/
dev                     in_anglvel_x_calibbias  in_temp_raw
in_accel_x_calibbias    in_anglvel_x_raw        in_temp_scale
in_accel_x_raw          in_anglvel_y_calibbias  name
in_accel_y_calibbias    in_anglvel_y_raw        of_node
in_accel_y_raw          in_anglvel_z_calibbias  power
in_accel_z_calibbias    in_anglvel_z_raw        subsystem
in_accel_z_raw          in_temp_offset          uevent

没有了in_accel_scale 、in_anglvel_scale、in_temp_scale

如果将范围作为每个单独的文件则

c 复制代码
#define ICM20608_CHAN(_type, _channel2, _index)                    \
	{                                                             \
		.type = _type,                                        \
		.modified = 1,                                        \
		.channel2 = _channel2,                                \
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)|BIT(IIO_CHAN_INFO_CALIBBIAS)|BIT(IIO_CHAN_INFO_SCALE), \
		.scan_index = _index,                                 \
		.scan_type = {                                        \
				.sign = 's',                          \
				.realbits = 16,                       \
				.storagebits = 16,                    \
				.shift = 0,                           \
				.endianness = IIO_BE,                 \
			     },                                       \
	}

BIT(IIO_CHAN_INFO_SCALE)加入到了info_mask_separate中则表现为每一个通道都有一个范围文件

c 复制代码
ls /sys/bus/iio/devices/iio\:device0/*_scale
/sys/bus/iio/devices/iio:device0/in_accel_x_scale
/sys/bus/iio/devices/iio:device0/in_accel_y_scale
/sys/bus/iio/devices/iio:device0/in_accel_z_scale
/sys/bus/iio/devices/iio:device0/in_anglvel_x_scale
/sys/bus/iio/devices/iio:device0/in_anglvel_y_scale
/sys/bus/iio/devices/iio:device0/in_anglvel_z_scale
/sys/bus/iio/devices/iio:device0/in_temp_scale

因此一个通道中对其他通道生效就设备,需要理清楚那些是单独的info_mask_shared_by_type,那些是分开的info_mask_separate

那这些文件的命令规范是指通道文件命名方式是由系统自己生成的

是根据ICM20608_CHAN()传入前两个参数生产对应的名字

c 复制代码
static const char * const iio_direction[] = {
	[0] = "in",
	[1] = "out",
};

static const char * const iio_chan_type_name_spec[] = {
	[IIO_VOLTAGE] = "voltage",
	[IIO_CURRENT] = "current",
	[IIO_POWER] = "power",
	[IIO_ACCEL] = "accel",
	[IIO_ANGL_VEL] = "anglvel",
	[IIO_MAGN] = "magn",
	[IIO_LIGHT] = "illuminance",
	[IIO_INTENSITY] = "intensity",
	[IIO_PROXIMITY] = "proximity",
	[IIO_TEMP] = "temp",
	[IIO_INCLI] = "incli",
	[IIO_ROT] = "rot",
	[IIO_ANGL] = "angl",
	[IIO_TIMESTAMP] = "timestamp",
	[IIO_CAPACITANCE] = "capacitance",
	[IIO_ALTVOLTAGE] = "altvoltage",
	[IIO_CCT] = "cct",
	[IIO_PRESSURE] = "pressure",
	[IIO_HUMIDITYRELATIVE] = "humidityrelative",
	[IIO_ACTIVITY] = "activity",
	[IIO_STEPS] = "steps",
	[IIO_ENERGY] = "energy",
	[IIO_DISTANCE] = "distance",
	[IIO_VELOCITY] = "velocity",
};

static const char * const iio_modifier_names[] = {
	[IIO_MOD_X] = "x",
	[IIO_MOD_Y] = "y",
	[IIO_MOD_Z] = "z",
	[IIO_MOD_ROOT_SUM_SQUARED_X_Y] = "sqrt(x^2+y^2)",
	[IIO_MOD_SUM_SQUARED_X_Y_Z] = "x^2+y^2+z^2",
	[IIO_MOD_LIGHT_BOTH] = "both",
	[IIO_MOD_LIGHT_IR] = "ir",
	[IIO_MOD_LIGHT_CLEAR] = "clear",
	[IIO_MOD_LIGHT_RED] = "red",
	[IIO_MOD_LIGHT_GREEN] = "green",
	[IIO_MOD_LIGHT_BLUE] = "blue",
	[IIO_MOD_QUATERNION] = "quaternion",
	[IIO_MOD_TEMP_AMBIENT] = "ambient",
	[IIO_MOD_TEMP_OBJECT] = "object",
	[IIO_MOD_NORTH_MAGN] = "from_north_magnetic",
	[IIO_MOD_NORTH_TRUE] = "from_north_true",
	[IIO_MOD_NORTH_MAGN_TILT_COMP] = "from_north_magnetic_tilt_comp",
	[IIO_MOD_NORTH_TRUE_TILT_COMP] = "from_north_true_tilt_comp",
	[IIO_MOD_RUNNING] = "running",
	[IIO_MOD_JOGGING] = "jogging",
	[IIO_MOD_WALKING] = "walking",
	[IIO_MOD_STILL] = "still",
	[IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)",
};

/* relies on pairs of these shared then separate */
static const char * const iio_chan_info_postfix[] = {
	[IIO_CHAN_INFO_RAW] = "raw",
	[IIO_CHAN_INFO_PROCESSED] = "input",
	[IIO_CHAN_INFO_SCALE] = "scale",
	[IIO_CHAN_INFO_OFFSET] = "offset",
	[IIO_CHAN_INFO_CALIBSCALE] = "calibscale",
	[IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
	[IIO_CHAN_INFO_PEAK] = "peak_raw",
	[IIO_CHAN_INFO_PEAK_SCALE] = "peak_scale",
	[IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW] = "quadrature_correction_raw",
	[IIO_CHAN_INFO_AVERAGE_RAW] = "mean_raw",
	[IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY]
	= "filter_low_pass_3db_frequency",
	[IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency",
	[IIO_CHAN_INFO_FREQUENCY] = "frequency",
	[IIO_CHAN_INFO_PHASE] = "phase",
	[IIO_CHAN_INFO_HARDWAREGAIN] = "hardwaregain",
	[IIO_CHAN_INFO_HYSTERESIS] = "hysteresis",
	[IIO_CHAN_INFO_INT_TIME] = "integration_time",
	[IIO_CHAN_INFO_ENABLE] = "en",
	[IIO_CHAN_INFO_CALIBHEIGHT] = "calibheight",
	[IIO_CHAN_INFO_CALIBWEIGHT] = "calibweight",
	[IIO_CHAN_INFO_DEBOUNCE_COUNT] = "debounce_count",
	[IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time",
};

我们虽然没有指定iio_chan_spec的output为1,但是前面分析了,初始化结构体iio_chan_spec时他的output默认为1,调用如下函数去命名:

c 复制代码
static
int __iio_device_attr_init(struct device_attribute *dev_attr,
			   const char *postfix,
			   struct iio_chan_spec const *chan,
			   ssize_t (*readfunc)(struct device *dev,
					       struct device_attribute *attr,
					       char *buf),
			   ssize_t (*writefunc)(struct device *dev,
						struct device_attribute *attr,
						const char *buf,
						size_t len),
			   enum iio_shared_by shared_by)
{
	int ret = 0;
	char *name = NULL;
	char *full_postfix;
	sysfs_attr_init(&dev_attr->attr);

	/* Build up postfix of <extend_name>_<modifier>_postfix */
	if (chan->modified && (shared_by == IIO_SEPARATE)) {
		if (chan->extend_name)
			full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
						 iio_modifier_names[chan
								    ->channel2],
						 chan->extend_name,
						 postfix);
		else
			full_postfix = kasprintf(GFP_KERNEL, "%s_%s",
						 iio_modifier_names[chan
								    ->channel2],
						 postfix);
	} else {
		if (chan->extend_name == NULL || shared_by != IIO_SEPARATE)
			full_postfix = kstrdup(postfix, GFP_KERNEL);
		else
			full_postfix = kasprintf(GFP_KERNEL,
						 "%s_%s",
						 chan->extend_name,
						 postfix);
	}
	if (full_postfix == NULL)
		return -ENOMEM;

	if (chan->differential) { /* Differential can not have modifier */
		switch (shared_by) {
		case IIO_SHARED_BY_ALL:
			name = kasprintf(GFP_KERNEL, "%s", full_postfix);
			break;
		case IIO_SHARED_BY_DIR:
			name = kasprintf(GFP_KERNEL, "%s_%s",
						iio_direction[chan->output],
						full_postfix);
			break;
		case IIO_SHARED_BY_TYPE:
			name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s",
					    iio_direction[chan->output],
					    iio_chan_type_name_spec[chan->type],
					    iio_chan_type_name_spec[chan->type],
					    full_postfix);
			break;
		case IIO_SEPARATE:
			if (!chan->indexed) {
				WARN_ON("Differential channels must be indexed\n");
				ret = -EINVAL;
				goto error_free_full_postfix;
			}
			name = kasprintf(GFP_KERNEL,
					    "%s_%s%d-%s%d_%s",
					    iio_direction[chan->output],
					    iio_chan_type_name_spec[chan->type],
					    chan->channel,
					    iio_chan_type_name_spec[chan->type],
					    chan->channel2,
					    full_postfix);
			break;
		}
	} else { /* Single ended */
		switch (shared_by) {
		case IIO_SHARED_BY_ALL:
			name = kasprintf(GFP_KERNEL, "%s", full_postfix);
			break;
		case IIO_SHARED_BY_DIR:
			name = kasprintf(GFP_KERNEL, "%s_%s",
						iio_direction[chan->output],
						full_postfix);
			break;
		case IIO_SHARED_BY_TYPE:
			name = kasprintf(GFP_KERNEL, "%s_%s_%s",
					    iio_direction[chan->output],
					    iio_chan_type_name_spec[chan->type],
					    full_postfix);
			break;

		case IIO_SEPARATE:
			if (chan->indexed)
				name = kasprintf(GFP_KERNEL, "%s_%s%d_%s",
						    iio_direction[chan->output],
						    iio_chan_type_name_spec[chan->type],
						    chan->channel,
						    full_postfix);
			else
				name = kasprintf(GFP_KERNEL, "%s_%s_%s",
						    iio_direction[chan->output],
						    iio_chan_type_name_spec[chan->type],
						    full_postfix);
			break;
		}
	}
	if (name == NULL) {
		ret = -ENOMEM;
		goto error_free_full_postfix;
	}
	dev_attr->attr.name = name;

	if (readfunc) {
		dev_attr->attr.mode |= S_IRUGO;
		dev_attr->show = readfunc;
	}

	if (writefunc) {
		dev_attr->attr.mode |= S_IWUSR;
		dev_attr->store = writefunc;
	}

error_free_full_postfix:
	kfree(full_postfix);

	return ret;
}

以温度为例

c 复制代码
{
        .type = IIO_TEMP,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)|BIT(IIO_CHAN_INFO_OFFSET)|BIT(IIO_CHAN_INFO_SCALE),
        .scan_index = INV_ICM20608_SCAN_TEMP,
        .scan_type = {
            .sign = 's',
            .realbits = 16,
            .storagebits = 16,
            .shift = 0,
            .endianness = IIO_BE,
        },
        
    },

匹配表中

c 复制代码
					full_postfix = kasprintf(GFP_KERNEL, "x_%s_%s",
						 iio_modifier_names[chan
								    ->channel2],
						 chan->extend_name,
						 postfix);

其中

full_postfix为

shell 复制代码
chan->output =  1,      in_%s%d-%s%d_%s
chan->type = IIO_TEMP,	%s_%s%d-%s%d_%s
chan->channel = 1
chan->type = IIO_TEMP,
chan->channel2 = 0
full_postfix 

使用拼接即可得到对应的命名

  1. 进行完善iio_info框架
c 复制代码
static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask){
    printk("icm20608_read_raw\r\n");

    return 0;
}

static int icm20608_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int val,int val2,long mask){
    printk("icm20608_write_raw\r\n");
    return 0;
}

static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,long mask){
    printk("icm20608_write_raw_get_fmt\r\n");
    return 0;

}
static const struct iio_info icm20608_info = {
    .driver_module = THIS_MODULE,
    .read_raw = icm20608_read_raw,
    .write_raw = icm20608_write_raw,
    .write_raw_get_fmt = icm20608_write_raw_get_fmt,
};

使用cat命令对驱动进行读取的时候会调用前面写好的info框架,这时我们就需要根据读取不同的文件返回不同的结果

c 复制代码
cat in_accel_x_raw
	icm20608_read_raw
echo 000 > in_accel_x_scale
    icm20608_write_raw_get_fmt
    sh: write error: Invalid argument

需要我们继续完善

c 复制代码
static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask){
    int ret = 0;
    //获取设备句柄
    struct icm20608_dev *dev  = iio_priv(indio_dev);
    
    //区分读取的数据是raw,scale还是offset,使用到mask
    switch (mask)
    {
    case IIO_CHAN_INFO_RAW:
        printk("IIO_CHAN_INFO_RAW  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_SCALE:
        printk("iIIO_CHAN_INFO_SCALE  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_CALIBBIAS:
        printk("IIO_CHAN_INFO_CALIBBIAS  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_OFFSET:
        printk("IIO_CHAN_INFO_OFFSET  %d\r\n",mask);
        break;
    default:
        return -EINVAL;
    }

    return 0;
}

目前

c 复制代码
 cat in_accel_y_raw
IIO_CHAN_INFO_RAW  0 

读取raw格式的数据(加速度的x、y、z以及陀螺仪的x、y、z还有温度)使用mask为一个long类型的值 0

读取scale格式的数据(加速度的x、y、z以及陀螺仪的x、y、z还有温度)时,传入的mask为long类型的值2

读取calibbias格式的数据(加速度的x、y、z以及陀螺仪的x、y、z还有温度)时,传入的mask为long类型的值5

不会区分具体是哪个通道,而是区分具体的数据格式

我们继续完善

添加一个区分不同数据类型 的函数

c 复制代码
static int icm20608_read_channel_data(struct iio_dev *indio_dev,struct iio_chan_spec *chan,int *val){
    int ret = 0;
    struct icm20608_dev *dev = iio_priv(indio_dev);

    switch (chan->type)  //根据类型来区分不同数据类型
    {
    case IIO_ACCEL:
        printk("IIO_ACCEL  \r\n");
        break;
    case IIO_ANGL_VEL:
        printk("IIO_ANGL_VEL  \r\n");
        break;
    case IIO_TEMP:
        printk("IIO_TEMP  \r\n");
        break;
    
    default:
        ret  = -EINVAL;
    }
    return ret;
}

在icm20608_read_raw中调用

c 复制代码
    case IIO_CHAN_INFO_RAW:
        printk("IIO_CHAN_INFO_RAW   %d \r\n",mask);
        icm20608_read_channel_data(indio_dev,chan,val);
        break;

读取raw格式 的数据时就能准确的知道这个要读取的到底是什么了

c 复制代码
cat in_anglvel_x_raw
IIO_CHAN_INFO_RAW   0
IIO_ANGL_VEL

cat in_anglvel_x_raw和IIO_ANGL_VEL可以看到读取的为陀螺仪类型的原始数据或是加速度类型的原始数据或是温度的原始数据

shell 复制代码
cat in_anglvel_x_raw
IIO_CHAN_INFO_RAW   0
IIO_ANGL_VEL

cat in_anglvel_y_raw
IIO_CHAN_INFO_RAW   0
IIO_ANGL_VEL
    
cat in_accel_x_raw
IIO_CHAN_INFO_RAW   0
IIO_ACCEL

cat in_temp_raw
IIO_CHAN_INFO_RAW   0
IIO_TEMP

但是还是区分不了具体的x、y、z的数据

下一步区分具体的通道channel2参数---》规定了 x、y、z

c 复制代码
cat in_anglvel_x_raw
IIO_CHAN_INFO_RAW   0
IIO_ANGL_VEL
IIO_MOD_X
    
    
cat in_anglvel_x_raw
IIO_CHAN_INFO_RAW   0
IIO_ANGL_VEL
IIO_MOD_X

    
cat in_accel_y_raw
IIO_CHAN_INFO_RAW   0
IIO_ACCEL
IIO_MOD_Y

    
cat in_accel_z_raw
IIO_CHAN_INFO_RAW   0
IIO_ACCEL
IIO_MOD_Z

至此就已经区分了具体的数据相当于三维空间的坐标唯一确定了一个元素,我们只需要将值与这个元素进行绑定即可。

当然还有高级的玩法来对寄存器进行映射,就不需要三级定位,两级定位后,最后一个坐标值直接与要读/写的进行映射,最后

c 复制代码
icm20608_read_channel_data(indio_dev,chan,val);
c 复制代码
static int icm20608_read_channel_data(struct iio_dev *indio_dev,
					 struct iio_chan_spec const *chan,
					 int *val)
{
	struct icm20608_dev *dev = iio_priv(indio_dev);
	int ret = 0;

	switch (chan->type) {
	case IIO_ANGL_VEL:	/* 读取陀螺仪数据 */
		ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);  /* channel2为X、Y、Z轴 */
		break;
	case IIO_ACCEL:		/* 读取加速度计数据 */
		ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */
		break;
	case IIO_TEMP:		/* 读取温度 */
		ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);  
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}
c 复制代码
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,
				   int axis, int *val)
{
	int ind, result;
	__be16 d;

	ind = (axis - IIO_MOD_X) * 2;
	result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);
	if (result)
		return -EINVAL;
	*val = (short)be16_to_cpup(&d); //将高16为和低16位进行整理成具体的x\y\z的数据

	return IIO_VAL_INT;
}

icm20608_sensor_show传入的传入的ICM20_GYRO_XOUT_H为寄存器的基地址,chan->channel2加以利用变成axis位偏移,与实际的寄存器(多个)相对应写出优秀的代码

同理其他

都可以套用该函数,读取温度时传递的数据稍作改变即可(无需偏移)

c 复制代码
case IIO_TEMP:		/* 读取温度 */
		ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);  
		break;

因此可以将read函数改写为

c 复制代码
/*
  * @description  	: 读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取
  * @param - dev	: icm20608设备 
  * @param - reg  	: 要读取的通道寄存器首地址。
  * @param - anix  	: 需要读取的通道,比如X,Y,Z。
  * @param - val  	: 保存读取到的值。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_sensor_show(struct icm20608_dev *dev, int reg,
				   int axis, int *val)
{
	int ind, result;
	__be16 d;

	ind = (axis - IIO_MOD_X) * 2;
	result = regmap_bulk_read(dev->regmap, reg + ind, (u8 *)&d, 2);
	if (result)
		return -EINVAL;
	*val = (short)be16_to_cpup(&d);

	return IIO_VAL_INT;
}

/*
  * @description  		: 读取ICM20608陀螺仪、加速度计、温度通道值
  * @param - indio_dev	: iio设备 
  * @param - chan  		: 通道。
  * @param - val  		: 保存读取到的通道值。
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_read_channel_data(struct iio_dev *indio_dev,
					 struct iio_chan_spec const *chan,
					 int *val)
{
	struct icm20608_dev *dev = iio_priv(indio_dev);
	int ret = 0;

	switch (chan->type) {
	case IIO_ANGL_VEL:	/* 读取陀螺仪数据 */
		ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);  /* channel2为X、Y、Z轴 */
		break;
	case IIO_ACCEL:		/* 读取加速度计数据 */
		ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */
		break;
	case IIO_TEMP:		/* 读取温度 */
		ret = icm20608_sensor_show(dev, ICM20_TEMP_OUT_H, IIO_MOD_X, val);  
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}


static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask){
    int ret = 0;
    //获取设备句柄
    struct icm20608_dev *dev  = iio_priv(indio_dev);
    
    //区分读取的数据是raw,scale还是offset,使用到mask
    switch (mask)
    {
    case IIO_CHAN_INFO_RAW:
        mutex_lock(&dev->lock);
        ret = icm20608_read_channel_data(indio_dev,chan,val);
        mutex_unlock(&dev->lock);
        return ret;
    case IIO_CHAN_INFO_SCALE:
        printk("IIO_CHAN_INFO_SCALE  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_CALIBBIAS:
        printk("IIO_CHAN_INFO_CALIBBIAS  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_OFFSET:
        printk("IIO_CHAN_INFO_OFFSET  %d\r\n",mask);
        break;
    default:
        return -EINVAL;
    }

    return 0;
}

那么使用cat in_accel_x_raw就会输出具体的值了,这个值就是一直传递的int *val,读取到的值就放在 了val中,cat后会自动打印val的值,我们无需纠结

c 复制代码
cat in_accel_z_raw
2037

这个值为原始 的ad值,需要乘以 32(量程)/ 2^32 (ADC精度)= 0.9975G,即Z轴的加速度为0.9G

那么读函数就实现完毕

量程设置

c 复制代码
/*
 * icm20608陀螺仪分辨率,对应250、500、1000、2000,计算方法:
 * 以正负250度量程为例,500/2^16=0.007629,扩大1000000倍,就是7629
 */
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};

/* 
 * icm20608加速度计分辨率,对应2、4、8、16 计算方法:
 * 以正负2g量程为例,4/2^16=0.000061035,扩大1000000000倍,就是61035
 */
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};

icm20608陀螺仪测量有正负250,即满量程为500 ,除以精度2^32 ,得到一个精度代表的陀螺仪度数,即分辨率

需要扩大1000000倍方便计算得到7629

以此类推

c 复制代码
#define ICM20608_TEMP_OFFSET	     0
#define ICM20608_TEMP_SCALE		     326800000


static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,int *val,int *val2,long mask){
    int ret = 0;
    unsigned char regdata;
    //获取设备句柄
    struct icm20608_dev *dev  = iio_priv(indio_dev);
    
    //区分读取的数据是raw,scale还是offset,使用到mask
    switch (mask)
    {
    case IIO_CHAN_INFO_RAW:
        mutex_lock(&dev->lock);
        ret = icm20608_read_channel_data(indio_dev,chan,val);
        mutex_unlock(&dev->lock);
        return ret;
    case IIO_CHAN_INFO_SCALE:
        switch (chan->type) {
		case IIO_ANGL_VEL:
			mutex_lock(&dev->lock);
			regdata = (icm20608_read_Onereg(dev, ICM20_GYRO_CONFIG) & 0X18) >> 3;
			*val  = 0; //整数部分
			*val2 = gyro_scale_icm20608[regdata]; //小数部分
			mutex_unlock(&dev->lock);
			return IIO_VAL_INT_PLUS_MICRO;	/* 值为val+val2/1000000 */
		case IIO_ACCEL:
			mutex_lock(&dev->lock);
			regdata = (icm20608_read_Onereg(dev, ICM20_ACCEL_CONFIG) & 0X18) >> 3;
			*val = 0;
			*val2 = accel_scale_icm20608[regdata];;
			mutex_unlock(&dev->lock);
			return IIO_VAL_INT_PLUS_NANO;/* 值为val+val2/1000000000 */
		case IIO_TEMP:					
			*val = ICM20608_TEMP_SCALE/ 1000000;
			*val2 = ICM20608_TEMP_SCALE % 1000000;
			return IIO_VAL_INT_PLUS_MICRO;	/* 值为val+val2/1000000 */
		default:
			return -EINVAL;
		}
		return ret;
    case IIO_CHAN_INFO_CALIBBIAS:
        printk("IIO_CHAN_INFO_CALIBBIAS  %d \r\n",mask);
        break;
    case IIO_CHAN_INFO_OFFSET:
        printk("IIO_CHAN_INFO_OFFSET  %d\r\n",mask);
        break;
    default:
        return -EINVAL;
    }

    return 0;
}

设置量程需要读icm20608的控制寄存器ICM20_ACCEL_CONFIG的【3:4】

处理时需要按照设置整数val、小数val2,最后返回扩大倍数即可

c 复制代码
cat in_anglvel_scale
0.061035

读取会返回 val+val2/1000000 =( 0+61035)/ 1000000 = 0.061035

那么这时候就可以读取z轴加速度和加速度量程,两个一乘就得到了实际值

c 复制代码
cat in_accel_z_raw
2040

cat in_accel_scale
0.000488281

实际值z轴加速度 = 2040 * 0.000488281 = 1.000487769 G

继续完善

c 复制代码
/*
  * @description  	: 设置ICM20608传感器,可以用于陀螺仪、加速度计设置
  * @param - dev	: icm20608设备 
  * @param - reg  	: 要设置的通道寄存器首地址。
  * @param - anix  	: 要设置的通道,比如X,Y,Z。
  * @param - val  	: 要设置的值。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_sensor_set(struct icm20608_dev *dev, int reg,
				int axis, int val)
{
	int ind, result;
	__be16 d = cpu_to_be16(val);

	ind = (axis - IIO_MOD_X) * 2;
	result = regmap_bulk_write(dev->regmap, reg + ind, (u8 *)&d, 2);
	if (result)
		return -EINVAL;

	return 0;
}

/*
  * @description  	: 设置ICM20608的陀螺仪计量程(分辨率)
  * @param - dev	: icm20608设备
  * @param - val   	: 量程(分辨率值)。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_write_gyro_scale(struct icm20608_dev *dev, int val)
{
	int result, i;
	u8 d;

	for (i = 0; i < ARRAY_SIZE(gyro_scale_icm20608); ++i) {
		if (gyro_scale_icm20608[i] == val) {
			d = (i << 3);
			result = regmap_write(dev->regmap, ICM20_GYRO_CONFIG, d);
			if (result)
				return result;
			return 0;
		}
	}
	return -EINVAL;
}

 /*
  * @description  	: 设置ICM20608的加速度计量程(分辨率)
  * @param - dev	: icm20608设备
  * @param - val   	: 量程(分辨率值)。
  * @return			: 0,成功;其他值,错误
  */
static int icm20608_write_accel_scale(struct icm20608_dev *dev, int val)
{
	int result, i;
	u8 d;

	for (i = 0; i < ARRAY_SIZE(accel_scale_icm20608); ++i) {
		if (accel_scale_icm20608[i] == val) {  //使用for循环匹配设置的值是否在我的匹配表里面,不在说明量程设置错误,在里面就修改寄存器的值来设置量程
			d = (i << 3);
			result = regmap_write(dev->regmap, ICM20_ACCEL_CONFIG, d);
			if (result)
				return result;
			return 0;
		}
	}
	return -EINVAL;
}
/*
  * @description     	: 写函数,当向sysfs中的文件写数据的时候最终此函数会执行,一般在此函数
  * 					:里面设置传感器,比如量程等。
  * @param - indio_dev	: iio_dev
  * @param - chan   	: 通道
  * @param - val   		: 应用程序写入的值,如果是小数值的话,val是整数部分。
  * @param - val2   	: 应用程序写入的值,如果是小数值的话,val2是小数部分。
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_write_raw(struct iio_dev *indio_dev,
			    struct iio_chan_spec const *chan,
			    int val, int val2, long mask)
{
	struct icm20608_dev *dev = iio_priv(indio_dev);
	int ret = 0;
    printk("val2 = %d \r\n",val2);
	switch (mask) {
	case IIO_CHAN_INFO_SCALE:	/* 设置陀螺仪和加速度计的分辨率 */
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 设置陀螺仪 */
			mutex_lock(&dev->lock);
			ret = icm20608_write_gyro_scale(dev, val2);
			mutex_unlock(&dev->lock);
			break;
		case IIO_ACCEL:			/* 设置加速度计 */
			mutex_lock(&dev->lock);
			ret = icm20608_write_accel_scale(dev, val2);
			mutex_unlock(&dev->lock);
			break;
		default:
			ret = -EINVAL;
			break;
		}
		break;
	case IIO_CHAN_INFO_CALIBBIAS:	/* 设置陀螺仪和加速度计的校准值*/
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 设置陀螺仪校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_set(dev, ICM20_XG_OFFS_USRH,
									    chan->channel2, val);
			mutex_unlock(&dev->lock);
			break;
		case IIO_ACCEL:			/* 加速度计校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_set(dev, ICM20_XA_OFFSET_H,
							             chan->channel2, val);
			mutex_unlock(&dev->lock);
			break;
		default:
			ret = -EINVAL;
			break;
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}
/*
  * @description     	: 用户空间写数据格式,比如我们在用户空间操作sysfs来设置传感器的分辨率,
  * 					:如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是
  *						: 用来设置这个的。
  * @param - indio_dev	: iio_dev
  * @param - chan   	: 通道
  * @param - mask   	: 掩码
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,
				 struct iio_chan_spec const *chan, long mask)
{
	switch (mask) {
	case IIO_CHAN_INFO_SCALE:
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 用户空间写的陀螺仪分辨率数据要乘以1000000 */
			return IIO_VAL_INT_PLUS_MICRO;
		default:				/* 用户空间写的加速度计分辨率数据要乘以1000000000 */
			return IIO_VAL_INT_PLUS_NANO;
		}
	default:
		return IIO_VAL_INT_PLUS_MICRO;
	}
	return -EINVAL;
}
c 复制代码
echo 0.007629 > in_anglvel_scale
val2 = 7629
d = 0
    
echo 0.015258 > in_anglvel_scale
val2 = 15258
d = 8

echo 0.030517 > in_anglvel_scale
val2 = 30517
d = 16

对应了加速度计的

寄存器27的【4:3】位用于设置陀螺仪的量程

寄存器28的【4:3】位用于设置加速度计的量程

匹配表

c 复制代码
/*
 * icm20608陀螺仪分辨率,对应250、500、1000、2000,计算方法:
 * 以正负250度量程为例,500/2^16=0.007629,扩大1000000倍,就是7629
 */
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};

/* 
 * icm20608加速度计分辨率,对应2、4、8、16 计算方法:
 * 以正负2g量程为例,4/2^16=0.000061035,扩大1000000000倍,就是61035
 */
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};

那么就可以修改量程和读取adc原始值了

c 复制代码
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_scale
0.000488281
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_z_raw
2047

echo 0.000244140 > in_accel_scale


/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_scale
0.000244140
/sys/devices/platform/soc/2000000.aips-bus/2000000.spba-bus/2010000.ecspi/spi_master/spi2/spi2.0/iio:device0 # cat in_accel_z_raw
4104
    
cat in_temp_raw
3271
 
cat in_temp_scale
326.800000

cat in_temp_offset
0

temperture = (in_temp_raw - in_temp_offset)/in_temp_scale + 25

应用编程

  1. 编写应用程序的时候读取驱动的值全部是str类型的字符串
c 复制代码
cat in_accel_scale
0.000488281

0.000488281实际上是"0.000488281"

需要使用atof(str)转换成整形的数据

APP:

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>
#include <errno.h>


/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */

#define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\
	ret = file_data_read(file_path[index], str);\
	dev->member = atof(str);\

/* 字符串转数字,将整数字符串转换为整数数值 */
#define SENSOR_INT_DATA_GET(ret, index, str, member)\
	ret = file_data_read(file_path[index], str);\
	dev->member = atoi(str);\


/* icm20608 iio框架对应的文件路径 */
static char *file_path[] = {
	"/sys/bus/iio/devices/iio:device0/in_accel_scale",
	"/sys/bus/iio/devices/iio:device0/in_accel_x_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_x_raw",
	"/sys/bus/iio/devices/iio:device0/in_accel_y_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_y_raw",
	"/sys/bus/iio/devices/iio:device0/in_accel_z_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_accel_z_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_scale",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_x_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_y_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_z_calibbias",
	"/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw",
	"/sys/bus/iio/devices/iio:device0/in_temp_offset",
	"/sys/bus/iio/devices/iio:device0/in_temp_raw",
	"/sys/bus/iio/devices/iio:device0/in_temp_scale",
};



/* 文件路径索引,要和file_path里面的文件顺序对应 */

enum path_index {
	IN_ACCEL_SCALE = 0,
	IN_ACCEL_X_CALIBBIAS,
	IN_ACCEL_X_RAW,
	IN_ACCEL_Y_CALIBBIAS,
	IN_ACCEL_Y_RAW,
	IN_ACCEL_Z_CALIBBIAS,
	IN_ACCEL_Z_RAW,
	IN_ANGLVEL_SCALE,
	IN_ANGLVEL_X_CALIBBIAS,
	IN_ANGLVEL_X_RAW,
	IN_ANGLVEL_Y_CALIBBIAS,
	IN_ANGLVEL_Y_RAW,
	IN_ANGLVEL_Z_CALIBBIAS,
	IN_ANGLVEL_Z_RAW,
	IN_TEMP_OFFSET,
	IN_TEMP_RAW,
	IN_TEMP_SCALE,
};

/*
 * icm20608数据设备结构体
 */
struct icm20608_dev{
	int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;
	int accel_x_raw, accel_y_raw, accel_z_raw;

	int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;
	int gyro_x_raw, gyro_y_raw, gyro_z_raw;
	int temp_offset, temp_raw;
	float accel_scale, gyro_scale, temp_scale;

	float gyro_x_act, gyro_y_act, gyro_z_act;
	float accel_x_act, accel_y_act, accel_z_act;
	float temp_act;
};

struct icm20608_dev icm20608;

 /*
 * @description			: 读取指定文件内容
 * @param - filename 	: 要读取的文件路径
 * @param - str 		: 读取到的文件字符串
 * @return 				: 0 成功;其他 失败
 */
static int file_data_read(char *filename, char *str)
{
	int ret = 0;
	FILE *data_stream;

    data_stream = fopen(filename, "r"); /* 只读打开 */
    if(data_stream == NULL) {
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	ret = fscanf(data_stream, "%s", str);
    if(!ret) {
        printf("file read error!\r\n");
    } else if(ret == EOF) {
        /* 读到文件末尾的话将文件指针重新调整到文件头 */
        fseek(data_stream, 0, SEEK_SET);  
    }
	fclose(data_stream);	/* 关闭文件 */	
	return 0;
}



 /*

 * @description	: 获取ICM20608数据

 * @param - dev : 设备结构体

 * @return 		: 0 成功;其他 失败

 */

static int sensor_read(struct icm20608_dev *dev)
{
	int ret = 0;
	char str[50];

	/* 1、获取陀螺仪原始数据 */
	SENSOR_FLOAT_DATA_GET(ret, IN_ANGLVEL_SCALE, str, gyro_scale);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_X_RAW, str, gyro_x_raw);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Y_RAW, str, gyro_y_raw);
	SENSOR_INT_DATA_GET(ret, IN_ANGLVEL_Z_RAW, str, gyro_z_raw);


	/* 2、获取加速度计原始数据 */
	SENSOR_FLOAT_DATA_GET(ret, IN_ACCEL_SCALE, str, accel_scale);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_X_RAW, str, accel_x_raw);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_Y_RAW, str, accel_y_raw);
	SENSOR_INT_DATA_GET(ret, IN_ACCEL_Z_RAW, str, accel_z_raw);


	/* 3、获取温度值 */
	SENSOR_FLOAT_DATA_GET(ret, IN_TEMP_SCALE, str, temp_scale);
	SENSOR_INT_DATA_GET(ret, IN_TEMP_OFFSET, str, temp_offset);
	SENSOR_INT_DATA_GET(ret, IN_TEMP_RAW, str, temp_raw);


	/* 3、转换为实际数值 */
	dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;
	dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;
	dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;
	dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;
	dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;
	dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;

	dev->temp_act = ((dev->temp_raw - dev->temp_offset) / dev->temp_scale) + 25;
	return ret;
}


/*

 * @description		: main主程序

 * @param - argc 	: argv数组元素个数

 * @param - argv 	: 具体参数

 * @return 			: 0 成功;其他 失败

 */
int main(int argc, char *argv[])
{
	int ret = 0;

	if (argc != 1) {
		printf("Error Usage!\r\n");
		return -1;
	}

	while (1) {
		ret = sensor_read(&icm20608);
		if(ret == 0) { 			/* 数据读取成功 */
			printf("\r\n原始值:\r\n");
			printf("gx = %d, gy = %d, gz = %d\r\n", icm20608.gyro_x_raw, icm20608.gyro_y_raw, icm20608.gyro_z_raw);
			printf("ax = %d, ay = %d, az = %d\r\n", icm20608.accel_x_raw, icm20608.accel_y_raw, icm20608.accel_z_raw);
			printf("temp = %d\r\n", icm20608.temp_raw);
			printf("实际值:");
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", icm20608.gyro_x_act, icm20608.gyro_y_act, icm20608.gyro_z_act);
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", icm20608.accel_x_act, icm20608.accel_y_act, icm20608.accel_z_act);
			printf("act temp = %.2f°C\r\n", icm20608.temp_act);
		}
		usleep(100000); /*100ms */
	}

	return 0;
}

根据具体的驱动名称修改路径,然后直接运行即可./app_icm20608

相关推荐
纷飞梦雪1 小时前
ubuntu22开启root
linux·运维·ubuntu
Konwledging1 小时前
linux debug工具集合
linux
星哥说事1 小时前
恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击
linux·服务器
Evan芙1 小时前
shell编程求10个随机数的最大值与最小值
java·linux·前端·javascript·网络
再睡一夏就好1 小时前
进程调度毫秒之争:详解Linux O(1)调度与进程切换
linux·运维·服务器·c++·算法·哈希算法
咬_咬1 小时前
C++仿muduo库高并发服务器项目:EventLoop模块
服务器·c++·muduo·eventloop
BS_Li1 小时前
【Linux系统编程】库制作与原理
linux·运维·服务器
茶杯6751 小时前
“舒欣双免“方案助力MSI-H/dMMR结肠癌治疗新突破
java·服务器·前端
我真会写代码1 小时前
从入门到精通:Java Socket 网络编程实战(含线程池优化)
java·linux·服务器·socket·tcp/ip协议