1. 概述
工业场合也有大量的模拟量和数字量之间的转换,这就是我们常说的 ADC
和 DAC
。此外,随着手机、物联网、工业物联网和可穿戴设备的爆发,对传感器的需求只会继续增加。例如,手机或手镯中的加速度计、光传感器、陀螺仪、气压计、磁,这些传感器本质上是ADC
,如果你注意这些传感器的手册,你会发现里面有一个 ADC
,外部提供传感器 IIC
或者 SPI
接口,SOC
可以通过 IIC
或者 SPI
获取传感器内部的接口 ADC
从而得到想要测量的结果。Linux
为了管理这些日益增多的内核 ADC
专门推出类传感器 IIO
子系统
2. iio设备驱动框架
3. iio分析设备驱动点
3.1 iio_dev 结构体
IIO
结构体用于子系统 iio_dev
来描述一个具体 IIO
该设备的结构体定义为include/linux/iio/iio.h
结构体内容如下(省略):
struct iio_dev {
int id; struct module *driver_module; int modes; int currentmode; struct device dev; struct iio_buffer *buffer; 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; bool trig_readonly; struct iio_poll_func *pollfunc; struct iio_poll_func *pollfunc_event; struct iio_chan_spec const *channels; int num_channels; const char *name; const char *label; 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;
#define IIO_MAX_GROUPS 6
const struct attribute_group *groups[IIO_MAX_GROUPS + 1];
int groupcounter;
unsigned long flags;
void *priv;
};
其中,有几个常用变量解析如下:
mode
:设备支持的模式
模式 | 描述 |
---|---|
INDIO_DIRECT_MODE | 提供 sysfs 接口 |
INDIO_BUFFER_TRIGGERED | 支持硬件缓冲触发 |
INDIO_BUFFER_SOFTWARE | 支持软件缓冲触发 |
INDIO_BUFFER_HARDWARE | 支持硬件缓冲区 |
INDIO_EVENT_TRIGGERED | 支持事件触发 |
INDIO_HARDWARE_TRIGGERED | 支持硬件触发 |
currentmode
为当前模式buffer
为缓冲区buffer_list
为当前匹配的缓冲区列表scan_bytes
为捕获到,并且提供给缓冲区的字节数。available_scan_masks
为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到IIO
缓冲区。active_scan_mask
为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。scan_timestamp
为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。trig
为IIO
设备当前触发器,当使用缓冲模式的时候。pollfunc
为一个函数,在接收到的触发器上运行。channels
为IIO
设备通道,为iio_chan_spec
结构体类型,稍后会详细讲解IIO
通道。num_channels
为IIO
设备的通道数。name
为IIO
设备名字。info
为iio_info
结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!我们从用户空间读取IIO
设备内部数据,最终调用的就是iio_info
里面的函数。setup_ops
为iio_buffer_setup_ops
结构体类型,内容如下: 可以看出iio_buffer_setup_ops
里面都是一些回调函数,在使能或禁用缓冲区的时候会调用,这些函数。如果未指定的话就默认使用iio_triggered_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);
};
chrdev
为字符设备,由IIO
内核创建。
3.2 iio_dev 申请与释放
在使用之前要先申请 iio_dev
,申请函数为 iio_device_alloc
,函数原型如下:
struct iio_dev *iio_device_alloc(int sizeof_priv)
函数参数和返回值含义如下:
sizeof_priv
:私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为iio_dev
的私有数据,这样可以直接通过iio_device_alloc
函数同时完成iio_dev
和设备结构体变量的内 存申请。申请成功以后使用iio_priv
函数来得到自定义的设备结构体变量首地址。返回值
:如果申请成功就返回iio_dev
首地址,如果失败就返回NULL
。 一般iio_device_alloc
和iio_priv
之间的配合使用如下所示:
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);
icm20608_dev
是自定义的设备结构体。indio_dev
是iio_dev
结构体变量指针。- 使用
iio_device_alloc
函数来申请iio_dev
,并且一起申请了icm2060_dev
的内存。 - 使用
iio_priv
函数从iio_dev
中提取出私有数据,也就是icm2608_dev
这个自定义结构体变量首地址。
如果要释放 iio_dev
,需要使用 iio_device_free
函数,函数原型如下:
void iio_device_free(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev
:需要释放的iio_dev
。返回值
:无。
也可以使用 devm_iio_device_alloc
来分配 iio_dev
, 这样就不需要我们手动调用iio_device_free
函数完成 iio_dev
的释放工作。
3.3 iio_dev 注册与注销
前面分配好 iio_dev
以后就要初始化各种成员变量,初始化完成以后就需要将 iio_dev 注册到内核中,需要用到 iio_device_register
函数,函数原型如下:
int iio_device_register(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev
:需要注册的iio_dev
。- 返回值:
0
,成功;其他值,失败。
如果要注销 iio_dev
使用 iio_device_unregister
函数,函数原型如下:
void iio_device_unregister(struct iio_dev *indio_dev)
函数参数和返回值含义如下:
indio_dev
:需要注销的iio_dev
。返回值
:0
,成功;其他值,失败。
3.4 iio_info结构体
iio_dev
有个成员变量:info
,为 iio_info
结构体指针变量,这个是我们在编写 IIO
驱动的时 候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info
里面。iio_info
结构体定义在 include/linux/iio/iio.h
中,结构体定义如下(有省略):
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 (*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);
};
attrs
是通用的设备属性。read_raw
和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
通道的时候详细讲解!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.5 iio_chan_spec结构体
IIO
的核心就是通道,一个传感器可能有多路数据,比如一个 ADC
芯片支持 8
路采集,那么这个 ADC
就有 8
个通道。我们本章实验用到的 ICM20608
,这是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z
)、三轴加速度计(X、Y、Z
)和一路温度,也就是一共有 7
路数据,因此就有 7
个通道。注意,三轴陀螺仪或加速度计的 X、Y、Z
这三个轴,每个轴都算一个通道。Linux
内核使用 iio_chan_spec
结构体来描述通道,定义在 include/linux/iio/iio.h
文件中,内容如下:
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_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;
unsigned indexed:1;
unsigned output:1;
unsigned differential:1;
};
iio_chan_spec
结构体中一些比较重要的成员变量:
- type 为通道类型,
iio_chan_type
是一个枚举类型,列举出了可以选择的通道类型,定义在include/uapi/linux/iio/types.h
文件里面,内容如下:
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, /* 速度类型 */
};
从示例代码可以看出,目前 Linux
内核支持的传感器类型非常丰富,而且支持类型也会不断的增加。如果是 ADC
,那就是 IIO_VOLTAGE
类型。如果是 ICM20608
这样的多轴传感器,那么就是复合类型了,陀螺仪部分是 IIO_ANGL_VEL
类型,加速度计部分是IIO_ACCEL
类型,温度部分就是 IIO_TEMP
。
indexed
为1
时候,channel
为通道索引modified
为1
的时候,channel2
为通道修饰符。Linux
内核给出了可用的通道修饰符,定义在include/uapi/linux/iio/types.h
文件里面,内容如下(有省略)
enum iio_modifier {
IIO_NO_MOD,
IIO_MOD_X, /* X 轴 */
IIO_MOD_Y, /* Y 轴 */
IIO_MOD_Z, /* Z 轴 */
};
比如 ICM20608
的加速度计部分,类型设置为 IIO_ACCEL
,X、Y、Z
这三个轴就用 channel2
的通道修饰符来区分。IIO_MOD_X
、IIO_MOD_Y
、IIO_MOD_Z
就分别对应 X
、Y
、Z
这三个轴。通道修饰符主要是影响 sysfs
下的通道文件名字,后面我们会讲解 sysfs
下通道文件名字组成形式。
-
address
成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。比如ICM20608
的加速度计X
轴这个通道,它的数据首地址就是0X3B
。address
也可以用作其他功能,自行选择,也可以不使用address
,一切以实际情况为准。 -
当使用触发缓冲区的时候,
scan_index
是扫描索引。 -
scan_type
是一个结构体,描述了扫描数据在缓冲区中的存储格式。我们依次来看一下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
标记某些属性专属于此通道,include/linux/iio/types.h
文件中的iio_chan_info_enum
枚举类型描述了可选的属性值,如下所示:
enum iio_chan_info_enum {
IIO_CHAN_INFO_RAW = 0,
IIO_CHAN_INFO_PROCESSED,
IIO_CHAN_INFO_SCALE,
IIO_CHAN_INFO_OFFSET,
IIO_CHAN_INFO_CALIBSCALE,
IIO_CHAN_INFO_CALIBBIAS,
IIO_CHAN_INFO_PEAK,
IIO_CHAN_INFO_PEAK_SCALE,
IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW,
IIO_CHAN_INFO_AVERAGE_RAW,
IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY,
IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY,
IIO_CHAN_INFO_SAMP_FREQ,
IIO_CHAN_INFO_FREQUENCY,
IIO_CHAN_INFO_PHASE,
IIO_CHAN_INFO_HARDWAREGAIN,
IIO_CHAN_INFO_HYSTERESIS,
IIO_CHAN_INFO_INT_TIME,
IIO_CHAN_INFO_ENABLE,
IIO_CHAN_INFO_CALIBHEIGHT,
IIO_CHAN_INFO_CALIBWEIGHT,
IIO_CHAN_INFO_DEBOUNCE_COUNT,
IIO_CHAN_INFO_DEBOUNCE_TIME,
IIO_CHAN_INFO_CALIBEMISSIVITY,
IIO_CHAN_INFO_OVERSAMPLING_RATIO,
IIO_CHAN_INFO_THERMOCOUPLE_TYPE,
IIO_CHAN_INFO_CALIBAMBIENT,
};
比如 ICM20608
加速度计的 X、Y、Z
这三个轴,在 sysfs
下这三个轴肯定是对应三个不同的文件,我们通过读取这三个文件就能得到每个轴的原始数据。IIO_CHAN_INFO_RAW
这个属性表示原始数据,当我们在配置 X、Y、Z
这三个通道的时候,在 info_mask_separate
中使能IIO_CHAN_INFO_RAW
这个属性,那么就表示在 sysfs
下生成三个不同的文件分别对应 X、Y、 Z
轴,这三个轴的 IIO_CHAN_INFO_RAW
属性是相互独立的。
info_mask_shared_by_type
标记导出的信息由相同类型的通道共享。也就是iio_chan_spec.type
成员变量相同的通道。比如ICM20608
加速度计的X、Y、Z
轴他们的type
都 是IIO_ACCEL
,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通道的时候就可以在info_mask_shared_by_type
中使能IIO_CHAN_INFO_SCALE
这个属性,表示这三个通道的分辨率是共用的,这样在sysfs
下就会只生成一个描述分辨率的文件,这三个通道都可以使用这一个分辨率文件。info_mask_shared_by_dir
标记某些导出的信息由相同方向的通道共享。info_mask_shared_by_all
表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。modified
为1
的时候,channel2
为通道修饰符。indexed
为 1 的时候,channel
为通道索引。output
表示为输出通道。differential
表示为差分通道。
返回总目录