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定义的倍数
靠返回值告诉内核放大的倍数
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共同决定
应用
文件读取到的都是字符串