XuSenfeng

个人站

复读了,更新随缘,有的文件不全或者图片缺失具体看我的笔记库(https://github.com/XuSenfeng/note)


IIO

目录

IIO

就是为了ADC或DAC之类的传感器准备的, 电压, 电流, 光传感器等都可以使用IIO驱动框架

之前的写法就是驱动编写人员自己上传取得的数据, 在编写应用的时候不灵活, 上传的数据结构不统一, 最好是应用可以直接人性化的读取到传感器的数据

实现方法

需要使能内核对应的配置

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

构建

使用结构体iio_dev进行初始化

  • 重要的参数

modes: 设备支持的模式

模式 描述
INDIO_DIRECT_MODE 提供sysfs接口。
INDIO_BUFFER_TRIGGERED 支持硬件缓冲触发。
INDIO_BUFFER_SOFTWARE 支持软件缓冲触发。
INDIO_BUFFER_HARDWARE 支持硬件缓冲区。

一般使用第一种

channels: 设置为通道, 为iio_chan_spec结构体类型, 实际上就是表示传感器的每一个测量通道

num_channels: IIO设备的通道数

info为iio_info: 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要

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

在进行读写的时候最终会调用的函数,

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定义的倍数

QQ图片20220911204316

靠返回值告诉内核放大的倍数

mask: 实际读取的数据的种类, 就是设置通道的时候设置的拥有的参数

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

根据写入的数据判断放大的倍数, write_raw_get_fmt函数决定了wtite_raw函数中val和val2的意义

  • 通道
struct iio_chan_spec {
	enum iio_chan_type	type;		//类型
	int			channel;
	int			channel2;
	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;
	unsigned		differential:1;
};

type, 枚举类型, 定义了采集到的数据的的类型, 里面有很多种类型

当成员变量indexed为1时候,channel为通道索引.

当成员变量modified为1的时候,channel2为通道修饰符。用来在种类相同的时候进行区分, Linux内核给出了可用的通道修饰符,定义在include/uapi/linux/iio/types.h文件里面

address: 读取的起始地址

scan_type是一个结构体,描述了扫描数据在缓冲区中的存储格式。

  struct {
      char	sign;
      u8	realbits;
      u8	storagebits;
      u8	shift;
      u8	repeat;
      enum iio_endian endianness;
  } 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: 标记某些属性专属于此通道,include/linux/iio/types.h文件中的iio_chan_info_enum枚举类型描述了可选的属性值

  • 使用
struct iio_dev *iio_device_alloc(int sizeof_priv)

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

申请成功以后使用iio_priv函数来得到自定义的设备结构体变量首地址。返回值:如果申请成功就返回iio_dev首地址,如果失败就返回NULL。

void iio_device_free(struct iio_dev *indio_dev)

释放

int iio_device_register(struct iio_dev *indio_dev)

注册

void iio_device_unregister(struct iio_dev *indio_dev)

注销

编写

  • 初始化
    //申请iio_dev和icm20608_dev
    indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dev));
    if(!indio_dev)
    {
        ret = -ENOMEM;
        goto fail_iio_dev;
    }

    dev = iio_priv(indio_dev);
    dev->spi = spi;
    spi_set_drvdata(spi, indio_dev);        //设置设备的私有数据
    mutex_init(&dev->lock);     //初始化互斥锁

    //初始化iio_dev
    indio_dev->dev.parent = &spi->dev;      //设置父设备
    indio_dev->channels = icm20608_channel;           //设置通道
    indio_dev->num_channels = ARRAY_SIZE(icm20608_channel);     //通道的数量
    indio_dev->name = ICM20608_NAME;
    indio_dev->modes = INDIO_DIRECT_MODE; //直接接口,提供sysfs
    indio_dev->info = &icm20608_info;       //对应的函数


    //将iio_dev注册到内核
	ret = iio_device_register(indio_dev);
	if (ret < 0) {
		dev_err(&spi->dev, "unable to register iio device\n");
		goto fail_iio_register;
	}
  • 初始化通道
#define ICM20608_CHANNEL(_type, _channel2, _index)              \
    {                                                           \
        .type = _type,                                          \
        /*设置为1的时候channel12为通道修饰符*/                    \
        .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_CHANNEL(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),
ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),
ICM20608_CHANNEL(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),

//陀螺仪
ICM20608_CHANNEL(IIO_ANGL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X),
ICM20608_CHANNEL(IIO_ANGL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),
ICM20608_CHANNEL(IIO_ANGL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),
  • 实际的判断

首先采用的mask是在设计通道的时候设置的拥有的参数种类比如原始值, 偏移, 参照之类的, chan->type是区分相同种类中的不同参数比如加速度计, 温度, chan->channel2可以提取其他的相关数字比如xyz轴

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


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:	/* 读取陀螺仪数据 */
		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);
    unsigned char regdata = 0;
    //区分读取的文件类型
    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_CALIBBIAS:
            //读取的是数据文件
            switch (chan->type) {
                case IIO_ANGL:
                    mutex_lock(&dev->lock);
                    regdata = (icm20608_read_reg(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_reg(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:
            //读取的是温度参照文件
            switch (chan->type) {
                case IIO_TEMP:
                    *val = ICM20608_TEMP_OFFSET;
                    return IIO_VAL_INT;
                default:
                    return -EINVAL;
                }
            return ret;     
        case IIO_CHAN_INFO_SCALE:
            //读取的是数据文件
            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:
            ret = -EINVAL;
            return ret;
    }

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

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  	: 设置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;
}
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:		/* 设置陀螺仪 */
			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:		/* 设置陀螺仪校准值 */
			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;
}
//在写入数据的时候进行判断, 根据返回值对写入的数值进行放大
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:		/* 用户空间写的陀螺仪分辨率数据要乘以1000000 */
			return IIO_VAL_INT_PLUS_MICRO;
		default:				/* 用户空间写的加速度计分辨率数据要乘以1000000000 */
			return IIO_VAL_INT_PLUS_NANO;
		}
	default:
		return IIO_VAL_INT_PLUS_MICRO;
	}
	return -EINVAL;
}

使用

IIO驱动框架提供了sysfs接口,因此加载成功以后我们可以在用户空间访问对应的sysfs目录项,进入目录“/sys/bus/iio/devices/”目录里面

iio:device0是I.MX6ULL内部ADC,iio:device1才是ICM20608。大家进入到对应的设备目录就可以看出对应的IIO设备

此目录下有很多文件,比如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轴的原始值和校准值每个轴都有一个文件,陀螺仪和温度计同理。

in_accel_z_calibbias  in_angl_y_raw
in_accel_scale   in_accel_z_raw  in_angl_z_calibbias

产生的部分文件

文件的名字是.type, .channel2 = _channel2, .info_mask_shared_by_type或.info_mask_separate共同决定

应用

文件读取到的都是字符串