字符设备驱动-IIO子系统

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设备#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
1
2
3
4
5
6
7
8
/* 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#

1
2
3
4
5
6
7
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设备的属性和具体实现函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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路数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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 内核给出了 可用的通道修饰符:

1
2
3
4
5
6
7
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 各个成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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#

1
2
3
4
5
6
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-保存采集到的数据#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 注册与注销#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
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:

1
2
3
4
 ->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如下:

1
2
3
4
5
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
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"

如下代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
1
2
3
4
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#

1
2
3
4
-> 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 源码#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
#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: /* 读取陀螺仪数据, channel2为X、Y、Z轴*/
ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H, chan->channel2, val);
break;
case IIO_ACCEL: /* 读取加速度计数据,channel2为X、Y、Z轴 */
ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val);
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配置量程如下:

1
2
3
4
5
6
7
8
9
10
11
/*
* 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:为属性对应的方向

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

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

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
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:属性掩码,也就是属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 编写应用程序测试#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#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描述如下:默认是关闭的

1
2
3
4
5
6
7
8
9
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添加节点内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 驱动#

1
2
3
4
-> 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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