fuzidage
专注嵌入式、linux驱动 、arm裸机研究

导航

 

1 引入IIO 子系统

随着手机、物联网、工业物联网和可穿戴设备的爆发,传感器的需求越来越多。比如手机或者手环里面的加速度计、光传感器、陀螺仪、气压计、磁力计等,这些传感器本质上都是 ADC。这些传感器对外通过 IIC 或者 SPI 接口来发送ADC转换后的原始数据。

Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统。

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的 模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、SPI 等传输给 SOC。

因此,当使用的传感器本质是 ADC器件的时候,可以优先考虑使用 IIO 驱动框架。

2 IIO 子系统驱动框架

image

  • IIO sysfs对用户空间提供IIO设备访问和配置。
  • IIO Core提供IIO设备、IIO Trigger、IIO Buffer分配、初始化、注册等工作。
    • industrialio-core.c
    • industrialio-buffer.c
    • industrialio-event.c
  • IIO Driver不同IIO设备的驱动程序。

3 数据结构

3.1 iio_dev-iio设备

struct iio_dev {
      int id;
      struct module                   *driver_module;
      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;//扫描位掩码,使用触发缓冲区的时候可以通过
//设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到 IIO 缓冲区
      unsigned masklength;
      const unsigned long             *active_scan_mask;//缓冲区已经开启的通道掩码
      bool scan_timestamp; 
      unsigned scan_index_timestamp;//扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面
      struct iio_trigger              *trig;// IIO 设备当前触发器
      bool trig_readonly;
      struct iio_poll_func            *pollfunc;
      struct iio_poll_func            *pollfunc_event;
      struct iio_chan_spec const      *channels;//IIO设备通道列表
      int num_channels;//IIO设备通道数
      struct list_head                channel_attr_list;
      struct attribute_group          chan_attr_group;
      const char                      *name;
      const struct iio_info           *info;
      clockid_t clock_id;
      struct mutex                    info_exist_lock;
      const struct iio_buffer_setup_ops       *setup_ops;
      struct cdev                     chrdev;
};//iio_dev用于描述一个具体IIO设备, include/linux/iio/iio.h
/* Device operating modes */
#define INDIO_DIRECT_MODE		0x01
#define INDIO_BUFFER_TRIGGERED		0x02
#define INDIO_BUFFER_SOFTWARE		0x04
#define INDIO_BUFFER_HARDWARE		0x08

#define INDIO_ALL_BUFFER_MODES					\
	(INDIO_BUFFER_TRIGGERED | INDIO_BUFFER_HARDWARE | INDIO_BUFFER_SOFTWARE)
模式 描述
INDIO_DIRECT_MODE 提供 sysfs 接口
INDIO_BUFFER_TRIGGERED 支持硬件缓冲触发
INDIO_BUFFER_SOFTWARE 支持软件缓冲触发
INDIO_BUFFER_HARDWARE 支持硬件缓冲区

3.1.1 iio_buffer_setup_ops

struct iio_buffer_setup_ops {
	int (*preenable)(struct iio_dev *); /* 缓冲区使能之前调用 */
	int (*postenable)(struct iio_dev *); /* 缓冲区使能之后调用 */
	int (*predisable)(struct iio_dev *); /* 缓冲区禁用之前调用 */
	int (*postdisable)(struct iio_dev *); /* 缓冲区禁用之后调用 */
	bool (*validate_scan_mask)(struct iio_dev *indio_dev, const unsigned long *scan_mask); /* 检查扫描掩码是否有效 */
};

在使能或禁用缓冲区的时候会调用这些函数。

3.2 iio_info-属性和函数实现

iio_info包含每个iio设备的属性和具体实现函数。

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

indio_dev:需要读写的 IIO 设备。

chan:需要读取的通道。

val,val2:对于 read_raw 来说 val 和 val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw 来说就是应用程序向设备写入的数据。val 和 val2 共同组成具体值,val 是整数部分,val2 是小数部分。Linux内核无法支持浮点运算,因此val2是放大后的值。扩大的倍数我们不能随便设置,而是要使用 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_inforead_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 这两个宏来表示原 始值以及分辨率,这两个宏就是掩码。

write_raw_get_fmt 用于设置用户空间向内核空间写入的数据格式,write_raw_get_fmt函数决定了 wtite_raw 函数中 val 和 val2 的意义,也就是表中的组合 形式。比如我们需要在应用程序中设置 ICM20608 加速度计的量程为±8g,那么分辨率就是16/65536≈0.000244,我们在 write_raw_get_fmt 函数里面设置加速度计的数据格式为 IIO_VAL_INT_PLUS_MICRO。那么我们在应用程序里面向指定的文件写入 0.000244 以后,最 终传递给内核驱动的就是 0.000244*1000000=244。也就是 write_raw 函数的 val 参数为 0,val2 参数为 244。

3.3 iio_chan_spec-通道属性

一个IIO设备可能有多个通道,每个通道由struct iio_chan_spec表示。比如一个 ADC 芯片支持 8 路采集,那 么这个 ADC 就有 8 个通道。的 ICM20608六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,共7路数据。

struct iio_chan_spec {
    enum iio_chan_type    type;//表示channel类型,电压、电流、加速度、电磁等等。
    int            channel;
    int            channel2;
    unsigned long        address;//该通道对应的芯片数据寄存器地址
    int            scan_index;//当使用触发缓冲区的时候,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_separate_available;
    long            info_mask_shared_by_type;//标记导出的信息由相同类型的通道共享,比如
    long            info_mask_shared_by_type_available;
    long            info_mask_shared_by_dir;
    long            info_mask_shared_by_dir_available;
    long            info_mask_shared_by_all;
    long            info_mask_shared_by_all_available;
    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;//modified 为 1 的时候,channel2 为通道修饰符
    unsigned        indexed:1;// indexed 为 1时候,channel 为通道索引
    unsigned        output:1;
    unsigned        differential:1;
};

enum iio_chan_type {
	IIO_VOLTAGE, /* 电压类型 */
	IIO_CURRENT, /* 电流类型 */
	IIO_POWER, /* 功率类型 */
	IIO_ACCEL, /* 加速度类型 */
	IIO_ANGL_VEL, /* 角度类型(陀螺仪) */
	IIO_MAGN, /* 电磁类型(磁力计) */
	IIO_LIGHT, /* 灯光类型 */
	IIO_INTENSITY, /* 强度类型(光强传感器) */
	IIO_PROXIMITY, /* 接近类型(接近传感器) */
	IIO_TEMP, /* 温度类型 */
	IIO_INCLI, /* 倾角类型(倾角测量传感器) */
	IIO_ROT, /* 旋转角度类型 */
	IIO_ANGL, /* 转动角度类型(电机旋转角度测量传感器) */
	IIO_TIMESTAMP, /* 时间戳类型 */
	IIO_CAPACITANCE, /* 电容类型 */
	IIO_ALTVOLTAGE, /* 频率类型 */
	IIO_CCT, /* 笔者暂时未知的类型 */
	IIO_PRESSURE, /* 压力类型 */
	IIO_HUMIDITYRELATIVE, /* 湿度类型 */
	IIO_ACTIVITY, /* 活动类型(计步传感器) */
	IIO_STEPS, /* 步数类型 */
	IIO_ENERGY, /* 能量类型(卡路里) */
	IIO_DISTANCE, /* 距离类型 */
	IIO_VELOCITY, /* 速度类型 */
};

如果是 ICM20608 这样的多轴传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是IIO_ACCEL,温度部分就是 IIO_TEMP

当成员变量 modified 为 1 的时候,channel2 为通道修饰符。Linux 内核给出了 可用的通道修饰符:

enum iio_modifier {
	IIO_NO_MOD,
	IIO_MOD_X, /* X 轴 */
	IIO_MOD_Y, /* Y 轴 */
	IIO_MOD_Z, /* Z 轴 */
......
};

比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCELX、Y、Z 这三个轴就用 channel2通道修饰符来区分.

scan_type 各个成员变量:

scan_type.sign:如果为‘u’表示数据为无符号类型,为‘s’的话为有符号类型。

scan_type.realbits:数据真实的有效位数,比如很多传感器说的 10 位 ADC,其真实有效数
据就是 10 位。

scan_type.storagebits:存储位数,有效位数+填充位。比如有些传感器 ADC 是 12 位的,
那么我们存储的话肯定要用到 2 个字节,也就是 16 位,这 16 位就是存储位数。

scan_type.shift:右移位数,也就是存储位数和有效位数不一致的时候,需要右移的位数,这
个参数不总是需要,一切以实际芯片的数据手册位数。

scan_type.repeat:实际或存储位的重复数量。
scan_type.endianness:数据的大小端模式,可设置为 IIO_CPU、IIO_BE(大端)或 IIO_LE(小
端)。

info_mask_separate 将属性标记为特定于此通

info_mask_shared_by_type 标记导出的信息由相同类型的通道共享。X、Y、Z 轴他们的 type 都 是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么这3个通道info_mask_shared_by_type 中使能IO_CHAN_INFO_SCALE 这个属性,表示 这三个通道的分辨率是共用的,这样在sysfs下就会只生成一个描述分辨率的文件,这三个通道 都可以使用这一个分辨率文件。

info_mask_shared_by_dir 标记某些导出的信息由相同方向的通道共享。

info_mask_shared_by_all 表设计某些信息所有的通道共享,无论这些通道的类 型、方向如何,全部共享。

output 表示为输出通道。

differential 表示为差分通道。

3.4 iio_trigger-触发数据采集

触发器是基于某种信号来触发数据采集,比如:数据就绪中断;周期性中断;用户空间sysfs读写。

struct iio_trigger {
    const struct iio_trigger_ops    *ops;
    struct module            *owner;
    int                id;
    const char            *name;
    struct device            dev;
    struct list_head        list;
    struct list_head        alloc_list;
    atomic_t            use_count;
    struct irq_chip            subirq_chip;
    int                subirq_base;
    struct iio_subirq subirqs[CONFIG_IIO_CONSUMERS_PER_TRIGGER];
    unsigned long pool[BITS_TO_LONGS(CONFIG_IIO_CONSUMERS_PER_TRIGGER)];
    struct mutex            pool_lock;
    bool                attached_own_device;
};

3.4.1 iio_trigger_ops

struct iio_trigger_ops {
    int (*set_trigger_state)(struct iio_trigger *trig, bool state);--设置触发器状态,打开或关闭。
    int (*try_reenable)(struct iio_trigger *trig);--当用户计数为0时,重新使能接口。
    int (*validate_device)(struct iio_trigger *trig,
                   struct iio_dev *indio_dev);
};

3.5 iio_buffer-保存采集到的数据

struct iio_buffer {
	int					length;
	int					bytes_per_datum;
	struct attribute_group			*scan_el_attrs;
	long					*scan_mask;
	bool					scan_timestamp;
	const struct iio_buffer_access_funcs	*access;
	struct list_head			scan_el_dev_attr_list;
	struct attribute_group			buffer_group;
	struct attribute_group			scan_el_group;
	wait_queue_head_t			pollq;
	bool					stufftoread;
	const struct attribute			**attrs;
	struct list_head			demux_list;
	void					*demux_bounce;
	struct list_head			buffer_list;
	struct kref				ref;
	unsigned int				watermark;
};

4 API

4.1 iio_dev 注册与注销

struct iio_dev *iio_device_alloc(int sizeof_priv)//申请 iio_dev
struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)//申请 iio_dev
void iio_device_free(struct iio_dev *indio_dev);
void devm_iio_device_free(struct device *dev, struct iio_dev *indio_dev);

int iio_device_register(struct iio_dev *indio_dev)//注册iio设备
void iio_device_unregister(struct iio_dev *indio_dev)//注销iio设备
      
#define iio_device_register(indio_dev) \
    __iio_device_register((indio_dev), THIS_MODULE)
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod);
void iio_device_unregister(struct iio_dev *indio_dev);
#define devm_iio_device_register(dev, indio_dev) \
    __devm_iio_device_register((dev), (indio_dev), THIS_MODULE);
int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev,
                   struct module *this_mod);
void devm_iio_device_unregister(struct device *dev, struct iio_dev *indio_dev);

image

devm_iio_device_register/iio_device_register
  ->__iio_device_register
    ->iio_check_unique_scan_index
    ->iio_device_register_debugfs
      ->debugfs_create_file//创建direct_reg_access调试节点,通过此节点可以直接读写寄存器。iio_debugfs_reg_fops调用IIO设备struct iio_dev->info->debugfs_reg_access()函数。

4.1.1 iio_device_register_sysfs过程

devm_iio_device_register过程中会调用iio_device_register_sysfs:

 ->iio_buffer_alloc_sysfs_and_mask
    ->iio_device_register_sysfs//创建adc属性和每个channel属性。
    ->cdev_init//初始化cdev设备,操作函数集为iio_buffer_fileops。
    ->cdev_device_add//创建cdev设备/dev/iio:deviceX。

展开iio_device_register_sysfs如下:

iio_device_register_sysfs
        --->  iio_device_add_channel_sysfs
                --->  iio_device_add_info_mask_type
                        --->  __iio_add_chan_devattr
                                --->  __iio_device_attr_init

其中在iio_device_add_info_mask_type中设置了读写回调函数,比如cat in_voltage0_input就会调用回调函数iio_read_channel_info.

int __iio_device_attr_init()
{...
		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);
    //in_voltage0_input
    //
      ..........
}

格式为%s_%s%d_%s", 对应了in_voltage0_input,其中iio_direction、iio_chan_type_name_spec、iio_chan_info_postfix对应的值为 "in" 、"voltage" 、"input"

如下代码片段:

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",
	// 以下省略
};
 
 
/* 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",
    // 以下省略
};

4.2 iio_init-子系统的初始化

IIO子系统初始化包括:

  • iio总线注册
  • 分配IIO字符设备号
  • 创建IIO设备debugfs
iio_init
  ->bus_register//注册iio bus。
  ->alloc_chrdev_region//创建iio cdev设备编号iio_devt。
  ->debugfs_create_dir//创建/sys/kernel/debug/iio调试目录iio_debugfs_dentry。

5 iio实验-操作ICM20608

5.1 使能内核 IIO

-> Device Drivers
 -> Industrial I/O support (IIO [=y]) 
 	-> [*]Enable buffer support within IIO //选中
 	-> <*>Industrial I/O buffering based on kfifo //选中

image

5.2 驱动代码分析

5.2.0 源码

#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include "icm20608reg.h"
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.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>

#define ICM20608_NAME	"icm20608"
#define ICM20608_TEMP_OFFSET	     0
#define ICM20608_TEMP_SCALE		     326800000

#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,
};

struct icm20608_dev {
	struct spi_device *spi;		/* spi设备 */
	struct regmap *regmap;				/* regmap */
	struct regmap_config regmap_config;	
	struct mutex lock;
};

/*
 * 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通道,1路温度通道,3路陀螺仪,3路加速度计
 */
static const 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,
			     },
	},

	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X),	/* 陀螺仪X轴 */
	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),	/* 陀螺仪Y轴 */
	ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),	/* 陀螺仪Z轴 */

	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),	/* 加速度X轴 */
	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),	/* 加速度Y轴 */
	ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),	/* 加速度Z轴 */
};

static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 ret;
	unsigned int data;

	ret = regmap_read(dev->regmap, reg, &data);
	return (u8)data;
}

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	regmap_write(dev->regmap,  reg, value);
}

void icm20608_reginit(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("ICM20608 ID = %#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_INT_ENABLE, 0x01);		/* 使能FIFO溢出以及数据就绪中断	*/
}

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

/*
  * @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) {
			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是小数部分。
  * @param - mask   	: 掩码。
  * @return				: 0,成功;其他值,错误
  */
static int icm20608_read_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;
	unsigned char regdata = 0;

	switch (mask) {
	case IIO_CHAN_INFO_RAW:								/* 读取ICM20608加速度计、陀螺仪、温度传感器原始值 */
		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_OFFSET:		/* ICM20608温度传感器offset值 */
		switch (chan->type) {
		case IIO_TEMP:
			*val = ICM20608_TEMP_OFFSET;
			return IIO_VAL_INT;
		default:
			return -EINVAL;
		}
		return ret;
	case IIO_CHAN_INFO_CALIBBIAS:	/* ICM20608加速度计和陀螺仪校准值 */
		switch (chan->type) {
		case IIO_ANGL_VEL:		/* 陀螺仪的校准值 */
			mutex_lock(&dev->lock);
			ret = icm20608_sensor_show(dev, ICM20_XG_OFFS_USRH, chan->channel2, val);
			mutex_unlock(&dev->lock);
			return ret;
		case IIO_ACCEL:			/* 加速度计的校准值 */
			mutex_lock(&dev->lock);	
			ret = icm20608_sensor_show(dev, ICM20_XA_OFFSET_H, chan->channel2, val);
			mutex_unlock(&dev->lock);
			return ret;
		default:
			return -EINVAL;
		}
		
	default:
		return ret -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;

	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;
}

static const struct iio_info icm20608_info = {
	.read_raw		= icm20608_read_raw,
	.write_raw		= icm20608_write_raw,
	.write_raw_get_fmt = &icm20608_write_raw_get_fmt,	/* 用户空间写数据格式 */
};
	
static int icm20608_probe(struct spi_device *spi)
{
	int ret;
	struct icm20608_dev *dev;
	struct iio_dev *indio_dev;

	/*  1、申请iio_dev内存 */
	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));
	if (!indio_dev)
		return -ENOMEM;

	/* 2、获取icm20608_dev结构体地址 */
	dev = iio_priv(indio_dev); 
	dev->spi = spi;
	spi_set_drvdata(spi, indio_dev);    		/* 将indio_de设置为spi->dev的driver_data */
	mutex_init(&dev->lock);

	/* 3、iio_dev的其他成员变量 */
	indio_dev->dev.parent = &spi->dev;
	indio_dev->info = &icm20608_info;
	indio_dev->name = ICM20608_NAME;	
	indio_dev->modes = INDIO_DIRECT_MODE;	/* 直接模式,提供sysfs接口 */
	indio_dev->channels = icm20608_channels;
	indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);

	/* 4、注册iio_dev */
	ret = iio_device_register(indio_dev);
	if (ret < 0) {
		dev_err(&spi->dev, "iio_device_register failed\n");
		goto err_iio_register;
	}

	/* 5、初始化regmap_config设置 */
	dev->regmap_config.reg_bits = 8;			/* 寄存器长度8bit */
	dev->regmap_config.val_bits = 8;			/* 值长度8bit */
	dev->regmap_config.read_flag_mask = 0x80;  /* 读掩码设置为0X80,ICM20608使用SPI接口读的时候寄存器最高位应该为1 */

	/* 6、初始化SPI接口的regmap */
	dev->regmap = regmap_init_spi(spi, &dev->regmap_config);
	if (IS_ERR(dev->regmap)) {
		ret = PTR_ERR(dev->regmap);
		goto err_regmap_init;
	}

	/* 7、初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	
	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit(dev);	
	return 0;

err_regmap_init:
    iio_device_unregister(indio_dev);
err_iio_register:
	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);

	/* 1、删除regmap */ 
	regmap_exit(dev->regmap);

	/* 2、注销IIO */
	iio_device_unregister(indio_dev);
	return 0;
}

static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},
	{}
};

static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

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,
};

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

static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

5.2.1 probe

image

还是按照spi子系统框架probe时利用iio子系统,初始化iio_dev

iio_info属性赋值:

image

iio_channels属性赋值:

image

image

温度通道

  1. info_mask_separate设置为IIO_CHAN_INFO_RAW,IIO_CHAN_INFO_SCALE, IIO_CHAN_INFO_OFFSET此通。

    IIO_CHAN_INFO_RAW 为温度通道的原始值,IIO_CHAN_INFO_OFFSETICM20608 温度 offset 值,这个要查阅数 据手册。IIO_CHAN_INFO_SCALE ICM20608 的比例,也就是一个单位的原始值为多少℃, 这个也要查阅 ICM20608 的数据手册。

  2. 扫描元素设置成SCAN_TEMP

  3. 扫描类型:有符号数据,数据位数16位,存储位数16位,右移位数0, 大端传输(一般MSB传输)

陀螺仪通道

  1. modified 成员变量为 1,所以 channel2 就是通道修饰符,用来指定 X、Y、Z 轴。

  2. 扫描元素设置SCAN_GYRO_X, Y,Z

  3. info_mask_separate设置为IIO_CHAN_INFO_RAW, IIO_CHAN_INFO_CALIBBIAS此通。“scale”是比例的意思,在这里就是量程(分辨率),因为 ICM20608 的陀螺仪和加速度计的量程是可以调整的,量程不同分辨率也就不同。设置每个通道的IIO_CHAN_INFO_RAWIIO_CHAN_INFO_CALIBBIAS这两个属性都是独立的,IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,这个肯定 是每个通道独立的。IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是 ICM20608 的特性,不是所有的传感器都有校准值,一切都要以实际所使用的传感器为准。

  4. info_mask_shared_by_type设置为IIO_CHAN_INFO_SCALE此通。表示量程分辨率共享对陀螺仪通道。

  5. 扫描类型:有符号数据,数据位数16位,存储位数16位,右移位数0, 大端传输(一般MSB传输)

加速度通道:同理与陀螺仪通道.

INDIO_DIRECT_MODE 直接模式,提供sysfs接口。

5.2.2 icm20608_read_raw

image

5.2.2.1 读raw数据

image

icm20608_read_raw会传入具体通道和mask, 比如温度通道的raw,传入ICM20_TEMP_OUT_H寄存器,IIO_MOD_X那么ind就等于0,那么regmap_bulk_read出来就是一个16位的温度值。

同理,读加速度通道,传入ICM20_ACCEL_XOUT_H寄存器,ind = (IIO_MOD_X - IIO_MOD_X )*2 = 0;,因此读出16bit的x数据。再次传入ind = (IIO_MOD_Y - IIO_MOD_X)*2 = 2,得到ICM20_ACCEL_YOUT_H寄存器,因此读取出16bit的y数据。再次传入ind = (IIO_MOD_Z - IIO_MOD_X)*2 = 4,得到ICM20_ACCEL_ZOUT_H寄存器,因此读取出16bit的z数据。

同理,读陀螺仪通道。

5.2.2.2 读量程

image

温度通道: 直接用预设量程即可或者val1,val2。分别表示整数,小数。

加速度通道: 获取量程参考数据手册:icm20608控制寄存器

icm20608控制寄存器

根据读到的寄存器值:regdata配置量程如下:

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

陀螺仪通道:同理

陀螺仪量程计算:

可选的量程有±250、±500、±1000 和±2000°/s。以± 250 这个量程为例,每个数值对应的度数就是 500/2^10≈0.007629°/s。同理,±500 量程对应 的度数为 0.015258±1000 量程对应的度数为 0.030517±2000 量程为0.061035。假设现在设 置量程为±2000,读取到的原始值为 12540,那么对应的度数就是 12540*0.061035≈765.37°/s。 注意,这里扩大了 1000000 倍。

加速度量程计算:

计算方法和陀螺仪一样。这里扩大了 1000000000 倍。

5.2.2.3 读offset

image

只有温度有offset

5.2.3 icm20608_write_raw

image

通过写寄存器来配置量程,校准值。

5.2.3.1 配置量程

image

根据设置的val量程值,匹配到i量程等级,d<<3然后设置量程等级。

5.2.3.2 配置校准值

image

image

image

根据静态偏移寄存器,进行校准,写入校准值。

5.2.4 icm20608_write_raw_get_fmt

image

用户空间设置数据格式。

陀螺仪通道:规定用户空间写的陀螺仪分辨率数据要乘以1000000。

加速度通道:规定用户空间写的陀螺仪分辨率数据要乘以1000000000。

比如我们在用户空间要设置加速度计量程为±4g,只需要向 in_accel_scale 写 入 0.000122070 , 那 么 最 终 传 入 到 驱 动 里 面 的 就 是 0.000122070*1000000000=122070

5.3 测试

进入“/sys/bus/iio/devices/”目录:可以看到,此时有两个 IIO 设备“iio:device0”,iio:device0 是 I.MX6ULL 内 部 ADC,iio:device1 才是 ICM20608。

image

image

可以看出,iio:device1 对应spi2.0 上的设备,也就是 ICM20608

此目录下有 很多文件,比如in_accel_scale、in_accel_x_calibias、in_accel_x_raw等,这些就是我们设置的通道。in_accel_scale 就是加速度计的比例,也就是分辨率(量程),in_accel_x_calibias 就是加速度 计 X 轴的校准值,in_accel_x_raw 就是加速度计的 X 轴原始值。我们在配置通道的时候,设置 了类型相同的所有通道共用 SCALE,所以这里只有一个 in_accel_scale,而 X、Y、Z 轴的原始值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

5.3.1 通道文件命名方式

源码见4.1.1 iio_device_register_sysfs过程。

IIO_CHAN_INFO_RAW IIO_CHAN_INFO_CALIBBIAS这两个专属属性,对应in_accel_x_raw in_accel_x_calibias 这两个文件。

通道的命名:[direction]_[type]_[index]_[modifier]_[info_mask]

direction:为属性对应的方向

static const char * const iio_direction[] = {
	[0] = "in",
	[1] = "out",
};

type:也就是配置通道的时候 type 值,type 对应的字符可以参考 iio_chan_type_name_spec:

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",
...
}

index:索引,如果配置通道的时候设置了 indexed=1,那么就会使用通道的 channel 成员变 量来替代此部分命名。比如,有个 ADC 芯片支持 8 个通道,那么就可以使用 channel 来表示对 应的通道,最终在用户空间呈现的每个通道文件名的 index 部分就是通道号。

modifier:当通道的 modified 成员变量为 1 的时候,channel2 就是修饰符, iio_modifier_names:

static const char * const iio_modifier_names[] = {
	[IIO_MOD_X] = "x",
	[IIO_MOD_Y] = "y",
	[IIO_MOD_Z] = "z",
......
	[IIO_MOD_PM4] = "pm4",
	[IIO_MOD_PM10] = "pm10",
};

info_mask:属性掩码,也就是属性:

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_DEBOUNCE_TIME] = "debounce_time",
	[IIO_CHAN_INFO_CALIBEMISSIVITY] = "calibemissivity",
	[IIO_CHAN_INFO_OVERSAMPLING_RATIO] = "oversampling_ratio",
};

综上所述,in_accel_x_raw 组成形式如图:

image

5.3.2 读取raw数据

进入“/sys/bus/iio/devices/”目录,cat iio:device1/in_ccel_x,这时驱动中的icm20608_read_raw执行。

读取一下 in_accel_scale 这个文件,这是加速度计的分辨率,我们默认设置了加速度计 量程为±16g,因此分辨率为 0.000488281

image

这时候有朋友可能会疑问,我们设置加速度计±16g 的分辨率为 488281,也就是扩大了1000000000 倍,为啥这里读出来的是 0.000488281 这个原始值?

驱动返回的是IIO_VAL_INT_PLUS_NANO, 因 此 用 户 空 间 得 到 分 辨 率 以 后 会 除 以 1000000000 , 得 到 真 实 的 分 辨 率 , 488281/1000000000=0.000488281

再读取一下 in_accel_z_raw,加速度计的 Z 轴原始值。静态情况 下 Z 轴应该是 1g 的重力加速度计。

image

2074×0.000488281≈1.01g,此时 Z 轴重力为 1g,结果正确。

5.3.3 编写应用程序测试

#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:device1/in_accel_scale",
	"/sys/bus/iio/devices/iio:device1/in_accel_x_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_accel_x_raw",
	"/sys/bus/iio/devices/iio:device1/in_accel_y_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_accel_y_raw",
	"/sys/bus/iio/devices/iio:device1/in_accel_z_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_accel_z_raw",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_scale",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_x_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_x_raw",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_y_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_y_raw",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_z_calibbias",
	"/sys/bus/iio/devices/iio:device1/in_anglvel_z_raw",
	"/sys/bus/iio/devices/iio:device1/in_temp_offset",
	"/sys/bus/iio/devices/iio:device1/in_temp_raw",
	"/sys/bus/iio/devices/iio:device1/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;
}

image

6 IIO实验-操作vf610_adc.c

以imx6ull芯片为例子,drivers/iio/adc/vf610_adc.c

6.1 dts描述

dtsi描述如下:默认是关闭的

adc1: adc@02198000 {
	compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
	reg = <0x02198000 0x4000>;
	interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_ADC1>;
	num-channels = <2>;
	clock-names = "adc";
	status = "disabled";
};

我们最后在 imx6ull-alientek-emmc.dts添加节点内容:

pinctrl_adc1: adc1grp {
	fsl,pins = <MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0>;
};
reg_vref_adc: regulator@2 {
	compatible = "regulator-fixed";
	regulator-name = "VREF_3V3";
	regulator-min-microvolt = <3300000>;
	regulator-max-microvolt = <3300000>;
};
&adc1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_adc1>;
	num-channels = <2>;
	vref-supply = <&reg_vref_adc>;
	status = "okay";
};
  1. 添加 ADC 使用的GPIO1_IO01引脚配置信息
  2. regulators 节点下添加参考电源子节点,最后status设置成okay

6.2 使能 ADC 驱动

-> Device Drivers
	-> Industrial I/O support
		-> Analog to digital converters
			-> <*> Freescale vf610 ADC driver //选中

image

drivers/iio/adc/下的makefile和Kconfig:

image

6.3 驱动代码分析

image

6.3.1 probe

image

申请初始化iio设备,alloc iio设备有多分一块内存给info结构体。

从dts获取寄存器地址信息,进行ioremap。获取中断号,注册中断服务程序。

获取时钟资源,电源资源,使能电源输出。并且获取电源参考电压vref_uv.

image

获取adc采样周期,num-channels =2.

中间vf610_adc_cfg_init和vf610_adc_hw_init是ADC controller的寄存器配置不展开介绍。可以参考裸机实验IMX6ULL ADC控制器

然后设置iio_dev结构体,最后注册iio设备。

6.3.2 vf610_read_raw

image

读取 ADC 原始数据值,type 值为 IIO_VOLTAGE,也就是读取电压值。这里info->value是怎么来的呢?

当然是中断服务程序进行ADC采样啊,如下:

image

6.4 用户态测试

进入/sys/bus/iio/devices/iio:device0 目录下,该iio就是对应vf610这个ADC:

image

in_voltage1_raw:ADC1 通道 1 原始值文件。

in_voltage_scale:ADC1 比例文件(分辨率),单位为 mV。实际电压值(mV)=in_voltage1_raw* in_voltage_scale

我的开发板此时 in_voltage1_rawin_voltage_scale 这两个文件内容如下:

image

经过计算,实际电压:991*0.805664062≈798.4mV,也就是 0.7984V

#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);\

static char *file_path[] = {
	"/sys/bus/iio/devices/iio:device0/in_voltage_scale",
	"/sys/bus/iio/devices/iio:device0/in_voltage1_raw",
};
enum path_index {
	IN_VOLTAGE_SCALE = 0,
	IN_VOLTAGE_RAW,
};

struct adc_dev{
	int raw;
	float scale;
	float act;
};
struct adc_dev imx6ulladc;

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;
}

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

	SENSOR_FLOAT_DATA_GET(ret, IN_VOLTAGE_SCALE, str, scale);
	SENSOR_INT_DATA_GET(ret, IN_VOLTAGE_RAW, str, raw);
	/* 转换得到实际电压值mV */
	dev->act = (dev->scale * dev->raw)/1000.f;
	return ret;
}
int main(int argc, char *argv[]) {
	int ret = 0;

	if (argc != 1) {
		printf("Error Usage!\r\n");
		return -1;
	}
	while (1) {
		ret = adc_read(&imx6ulladc);
		if(ret == 0) { 			/* 数据读取成功 */
			printf("ADC原始值:%d,电压值:%.3fV\r\n", imx6ulladc.raw, imx6ulladc.act);
		}
		usleep(100000); /*100ms */
	}
	return 0;
}

注意应用程序用到了浮点运算,因此:

arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard adcApp.c -o adcApp

image

posted on 2024-07-12 18:01  fuzidage  阅读(538)  评论(0编辑  收藏  举报