title: ADC与IIO驱动 tags: [Linux驱动, ADC, IIO, 传感器, 嵌入式, I.MX6ULL] created: 2026-09-17 updated: 2026-09-17 pdf_ref:
💡 关联知识: [[05-Linux外设驱动实战/02-按键输入驱动]] | [[03-Linux驱动开发核心/08-misc与input子系统]]
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》第七十六章内容,为扩展知识。原书指出 ADC 原理已在“裸机篇”《ADC 实验》章节讲解,本章直接分析内核自带的
vf610_adc.c驱动。
ADC(Analog-to-Digital Converter,模数转换器)是将连续的模拟信号转换为离散的数字信号的器件。其工作过程包括:
| 参数 | 说明 | 示例 |
|---|---|---|
| 分辨率 | ADC输出数字位数,决定精度 | 10位、12位、16位 |
| 采样率 | 每秒采样次数 | 100KSPS、1MSPS |
| 参考电压 | 满量程对应的模拟电压 | 3.3V、5V |
| INL/DNL | 积分/微分非线性误差 | ±1 LSB |
对于N位ADC,参考电压为Vref:
数字输出值 = (Vin / Vref) × 2^N
实际电压 = 数字输出值 × (Vref / 2^N)
I.MX6ULL ADC参数:
vf610_adc.c 中 res_mode 决定,scale 返回 IIO_VAL_FRACTIONAL_LOG2)vref-supply 指定的 regulator 提供)3300mV / 2^12 = 0.805664062 mV(与原书图 76.4.3.3 中 in_voltage_scale=0.805664062 一致)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";
};
| 属性 | 说明 |
|---|---|
compatible |
兼容性属性,必须设置为"fsl,imx6ul-adc" |
reg |
ADC控制器寄存器基地址和长度 |
interrupts |
中断属性,ADC1和ADC2各对应一个中断 |
clocks |
时钟属性 |
clock-names |
时钟名字,可选"adc" |
num-channels |
ADC通道数量 |
vref-supply |
参考电压句柄(绑定文档要求) |
在imx6ull-alientek-emmc.dts中添加:
/* 引脚配置 */
pinctrl_adc1: adc1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
>;
};
/* 参考电源节点(在regulators节点下添加) */
reg_vref_adc: regulator@2 {
compatible = "regulator-fixed";
regulator-name = "VREF_3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
/* 使能ADC1 */
&adc1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_adc1>;
num-channels = <2>;
vref-supply = <®_vref_adc>;
status = "okay";
};
I.MX6ULL的ADC驱动文件为drivers/iio/adc/vf610_adc.c,主体框架是platform + IIO。
struct vf610_adc {
struct device *dev;
void __iomem *regs;
struct clk *clk;
u32 vref_uv; // 参考电压(微伏)
u32 value; // ADC转换结果
struct regulator *vref;
struct vf610_adc_feature adc_feature;
u32 sample_freq_avail[5]; // 可用采样频率
struct completion completion; // 完成量
};
static int vf610_adc_probe(struct platform_device *pdev)
{
struct vf610_adc *info;
struct iio_dev *indio_dev;
struct resource *mem;
int irq, ret;
u32 channels;
/* 1. 申请iio_dev内存(同时分配vf610_adc) */
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct vf610_adc));
if (!indio_dev) {
dev_err(&pdev->dev, "Failed allocating iio device\n");
return -ENOMEM;
}
/* 2. 获取vf610_adc结构体地址 */
info = iio_priv(indio_dev);
info->dev = &pdev->dev;
/* 3. 获取并映射寄存器 */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(info->regs))
return PTR_ERR(info->regs);
/* 4. 获取中断号并注册中断 */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return irq;
}
ret = devm_request_irq(info->dev, irq,
vf610_adc_isr, 0,
dev_name(&pdev->dev), info);
if (ret < 0) {
dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq);
return ret;
}
/* 5. 获取时钟 */
info->clk = devm_clk_get(&pdev->dev, "adc");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
PTR_ERR(info->clk));
return PTR_ERR(info->clk);
}
/* 6. 获取参考电压 */
info->vref = devm_regulator_get(&pdev->dev, "vref");
if (IS_ERR(info->vref))
return PTR_ERR(info->vref);
ret = regulator_enable(info->vref);
if (ret)
return ret;
info->vref_uv = regulator_get_voltage(info->vref);
platform_set_drvdata(pdev, indio_dev);
init_completion(&info->completion);
/* 7. 读取通道数量 */
ret = of_property_read_u32(pdev->dev.of_node,
"num-channels", &channels);
if (ret)
channels = ARRAY_SIZE(vf610_adc_iio_channels);
/* 8. 初始化iio_dev */
indio_dev->name = dev_name(&pdev->dev);
indio_dev->dev.parent = &pdev->dev;
indio_dev->dev.of_node = pdev->dev.of_node;
indio_dev->info = &vf610_adc_iio_info; // 关键:用户空间读取ADC数据的入口
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = vf610_adc_iio_channels;
indio_dev->num_channels = (int)channels;
/* 9. 使能时钟 */
ret = clk_prepare_enable(info->clk);
if (ret) {
dev_err(&pdev->dev,
"Could not prepare or enable the clock.\n");
goto error_adc_clk_enable;
}
/* 10. 初始化ADC配置和硬件 */
vf610_adc_cfg_init(info);
vf610_adc_hw_init(info);
/* 11. 注册iio_dev到内核 */
ret = iio_device_register(indio_dev);
if (ret) {
dev_err(&pdev->dev, "Couldn't register the device.\n");
goto error_iio_device_register;
}
return 0;
// ... 错误处理
}
关键流程解读:
| 步骤 | 函数 | 说明 |
|---|---|---|
| 1 | devm_iio_device_alloc |
申请iio_dev + 私有数据内存 |
| 2 | iio_priv |
从iio_dev获取私有数据地址 |
| 4 | devm_request_irq |
注册中断,服务函数为vf610_adc_isr |
| 8 | 初始化iio_dev | 设置info、channels、modes等成员 |
| 11 | iio_device_register |
向内核注册IIO设备 |
static const struct iio_info vf610_adc_iio_info = {
.driver_module = THIS_MODULE,
.read_raw = &vf610_read_raw,
.write_raw = &vf610_write_raw,
.debugfs_reg_access = &vf610_adc_reg_access,
.attrs = &vf610_attribute_group,
};
这是用户空间读取ADC原始数据的核心函数:
static int vf610_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct vf610_adc *info = iio_priv(indio_dev);
unsigned int hc_cfg;
long ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
case IIO_CHAN_INFO_PROCESSED:
mutex_lock(&indio_dev->mlock);
reinit_completion(&info->completion);
/* 配置通道并启动转换 */
hc_cfg = VF610_ADC_ADCHC(chan->channel);
hc_cfg |= VF610_ADC_AIEN;
writel(hc_cfg, info->regs + VF610_REG_ADC_HC0);
/* 等待转换完成(中断触发completion) */
ret = wait_for_completion_interruptible_timeout
(&info->completion, VF610_ADC_TIMEOUT);
if (ret == 0) {
mutex_unlock(&indio_dev->mlock);
return -ETIMEDOUT;
}
if (ret < 0) {
mutex_unlock(&indio_dev->mlock);
return ret;
}
switch (chan->type) {
case IIO_VOLTAGE:
*val = info->value; // 读取ADC转换结果
break;
case IIO_TEMP:
*val = 25000 - ((int)info->value - 864) * 1000000 / 1840;
break;
default:
mutex_unlock(&indio_dev->mlock);
return -EINVAL;
}
mutex_unlock(&indio_dev->mlock);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = info->vref_uv / 1000; // 分辨率(mV)
*val2 = info->adc_feature.res_mode;
return IIO_VAL_FRACTIONAL_LOG2;
case IIO_CHAN_INFO_SAMP_FREQ:
*val = info->sample_freq_avail[info->adc_feature.sample_rate];
*val2 = 0;
return IIO_VAL_INT;
default:
break;
}
return -EINVAL;
}
返回值编码说明:
| 返回值 | 含义 |
|---|---|
IIO_VAL_INT |
整数值,*val即为结果 |
IIO_VAL_INT_PLUS_MICRO |
*val + *val2/1000000 |
IIO_VAL_INT_PLUS_NANO |
*val + *val2/1000000000 |
IIO_VAL_FRACTIONAL_LOG2 |
*val >> *val2 |
static irqreturn_t vf610_adc_isr(int irq, void *dev_id)
{
struct vf610_adc *info = (struct vf610_adc *)dev_id;
int coco;
/* 检查转换完成标志 */
coco = readl(info->regs + VF610_REG_ADC_HS);
if (coco & VF610_ADC_HS_COCO0) {
info->value = vf610_adc_read_data(info); // 读取ADC原始值
complete(&info->completion); // 通知read_raw完成
}
return IRQ_HANDLED;
}
ADC数据读取流程:
用户空间读取sysfs文件
→ 内核调用vf610_read_raw()
→ 写入HC0寄存器启动转换
→ wait_for_completion()等待中断
→ ADC转换完成,触发中断
→ vf610_adc_isr()读取数据寄存器
→ complete()唤醒等待
→ 返回ADC原始值给用户空间
-> Device Drivers
-> Industrial I/O support
-> Analog to digital converters
-> <*> Freescale vf610 ADC driver // 选中
# 进入IIO设备目录
cd /sys/bus/iio/devices/iio:device0
# 查看ADC原始值
cat in_voltage1_raw
# 输出: 991
# 查看ADC比例(分辨率)
cat in_voltage_scale
# 输出: 0.805664062
# 计算实际电压(mV)
# 实际电压 = in_voltage1_raw × in_voltage_scale
# 991 × 0.805664062 ≈ 798.4 mV
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* IIO框架对应的文件路径 */
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];
/* 读取scale(浮点数) */
ret = file_data_read(file_path[IN_VOLTAGE_SCALE], str);
dev->scale = atof(str);
/* 读取raw(整数) */
ret = file_data_read(file_path[IN_VOLTAGE_RAW], str);
dev->raw = atoi(str);
/* 转换为实际电压值(mV) */
dev->act = (dev->scale * dev->raw) / 1000.f;
return ret;
}
int main(int argc, char *argv[])
{
if (argc != 1) {
printf("Error Usage!\r\n");
return -1;
}
while (1) {
if (adc_read(&imx6ulladc) == 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
⚠️ 来源说明:本节架构图与缓冲区/Trigger 分类不属于《I.MX6U嵌入式Linux驱动开发指南》第七十五章内容,为扩展知识。原书 75.1 节只讲解
iio_dev、iio_info、iio_chan_spec三个核心数据结构,并说明 IIO 专为 ADC 类传感器设计。
graph TB
subgraph "用户空间"
APP["应用程序"]
SYSFS["sysfs文件系统<br>/sys/bus/iio/devices/"]
CHARDEV["字符设备<br>/dev/iio:deviceX"]
end
subgraph "IIO核心层"
IIO_CORE["IIO Core<br>iio_device_register()<br>iio_push_to_buffers()"]
IIO_TRIGGER["IIO Trigger Framework<br>iio_trigger_register()"]
end
subgraph "IIO驱动层"
DRV_ADC["ADC驱动<br>vf610_adc.c"]
DRV_IMU["IMU驱动<br>icm20608.c"]
DRV_LIGHT["光传感器驱动<br>ap3216c.c"]
end
subgraph "硬件层"
HW_ADC["内部ADC<br>I.MX6ULL"]
HW_SPI["SPI传感器<br>ICM20608"]
HW_I2C["I2C传感器<br>AP3216C"]
end
APP --> SYSFS
APP --> CHARDEV
SYSFS --> IIO_CORE
CHARDEV --> IIO_CORE
IIO_CORE --> DRV_ADC
IIO_CORE --> DRV_IMU
IIO_CORE --> DRV_LIGHT
IIO_CORE -.-> IIO_TRIGGER
DRV_ADC --> HW_ADC
DRV_IMU --> HW_SPI
DRV_LIGHT --> HW_I2C
| 特性 | IIO设备(Direct Mode) | IIO缓冲区(Buffer Mode) |
|---|---|---|
| 数据读取方式 | sysfs文件逐个读取 | 通过缓冲区批量读取 |
| 触发方式 | 手动触发(读取时转换) | 硬件/软件触发器触发 |
| 适用场景 | 低速采样、偶尔读取 | 高速连续采样 |
| 数据格式 | 单个值 | 带时间戳的结构化数据 |
| 典型应用 | 温度计、电压测量 | ADC连续采样、IMU数据流 |
IIO Trigger用于启动数据采集,支持三种触发源:
IIO子系统的核心数据结构,定义在include/linux/iio/iio.h中:
struct iio_dev {
int id;
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;
unsigned masklength;
const unsigned long *active_scan_mask;
bool scan_timestamp;
unsigned scan_index_timestamp;
struct iio_trigger *trig; // 触发器
struct iio_poll_func *pollfunc;
struct iio_chan_spec const *channels; // 通道描述
int num_channels; // 通道数量
struct list_head channel_attr_list;
struct attribute_group chan_attr_group;
const char *name; // 设备名称
const struct iio_info *info; // 驱动操作函数集
struct mutex info_exist_lock;
const struct iio_buffer_setup_ops *setup_ops;
struct cdev chrdev; // 字符设备
// ...
};
模式说明:
| 模式 | 说明 |
|---|---|
INDIO_DIRECT_MODE |
提供sysfs接口,直接读取 |
INDIO_BUFFER_TRIGGERED |
支持硬件触发的缓冲区 |
INDIO_BUFFER_SOFTWARE |
支持软件触发的缓冲区 |
INDIO_BUFFER_HARDWARE |
支持硬件缓冲区 |
iio_dev 的 setup_ops 成员为 iio_buffer_setup_ops 类型(原书示例代码 75.1.1.2),在使能/禁用缓冲区时被调用;未指定时默认使用 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);
};
驱动层需要实现的操作函数集:
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);
int (*debugfs_reg_access)(struct iio_dev *indio_dev,
unsigned int reg, unsigned int writeval,
unsigned int *readval);
// ...
};
通道描述结构体,定义每个采集通道的属性:
struct iio_chan_spec {
enum iio_chan_type type; // 通道类型(电压、温度、加速度等)
int channel; // 通道编号(indexed=1时有效)
int channel2; // 通道修饰符(modified=1时有效,如X/Y/Z轴)
unsigned long address; // 自定义地址(可选)
int scan_index; // 扫描索引(buffer模式使用)
struct {
char sign; // 符号:'u'无符号,'s'有符号
u8 realbits; // 实际有效位数
u8 storagebits; // 存储位数(≥realbits)
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; // channel2为修饰符时置1
unsigned indexed:1; // channel为通道编号时置1
unsigned output:1; // 输出通道
unsigned differential:1;// 差分通道
};
通道类型枚举(include/uapi/linux/iio/types.h):
enum iio_chan_type {
IIO_VOLTAGE, // 电压(ADC)
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_PRESSURE, // 气压
IIO_HUMIDITYRELATIVE, // 湿度
IIO_STEPS, // 步数
IIO_ENERGY, // 能量
IIO_DISTANCE, // 距离
IIO_VELOCITY, // 速度
};
info_mask属性掩码:
| 掩码 | sysfs文件名 | 说明 |
|---|---|---|
IIO_CHAN_INFO_RAW |
raw |
原始值 |
IIO_CHAN_INFO_PROCESSED |
input |
处理后的值 |
IIO_CHAN_INFO_SCALE |
scale |
比例/分辨率 |
IIO_CHAN_INFO_OFFSET |
offset |
偏移量 |
IIO_CHAN_INFO_CALIBBIAS |
calibbias |
校准偏置 |
IIO_CHAN_INFO_SAMP_FREQ |
sampling_frequency |
采样频率 |
原书 75.3.2 明确说明本章“设备树不需要做任何修改”,ICM20608 的 SPI 节点已在 SPI 实验章节中配置。其驱动在 icm20608_probe 中将 SPI 设为 MODE0(CPOL=0,CPHA=0),因此设备树中不应出现 spi-cpol/spi-cpha:
icm20608: icm20608@0 {
compatible = "alientek,icm20608";
reg = <0>; /* SPI片选 */
spi-max-frequency = <8000000>; /* 8MHz */
/* 不配置 spi-cpol / spi-cpha,驱动内使用 SPI_MODE_0 */
};
参考:Documentation/devicetree/bindings/iio/adc/vf610-adc.txt
常用IIO设备树属性:
| 属性 | 说明 |
|---|---|
compatible |
兼容性字符串 |
reg |
寄存器地址/SPI片选/I2C地址 |
interrupts |
中断定义 |
clocks |
时钟源 |
spi-max-frequency |
SPI最大频率 |
#address-cells |
子节点地址单元数 |
#size-cells |
子节点大小单元数 |
⚠️ 来源说明:
iio_device_alloc/iio_priv/iio_device_free/iio_device_register/iio_device_unregister来自原书 75.1.1 小节;iio_push_to_buffers/iio_triggered_buffer_setup原书第七十五章未涉及(ICM20608 未实现缓冲区),为扩展知识。
struct iio_dev *iio_device_alloc(int sizeof_priv)
sizeof_priv 私有数据大小使用示例:
struct icm20608_dev *dev;
struct iio_dev *indio_dev;
/* 申请iio_dev,同时分配icm20608_dev内存 */
indio_dev = iio_device_alloc(sizeof(*dev));
if (!indio_dev)
return -ENOMEM;
/* 获取私有数据地址 */
dev = iio_priv(indio_dev);
也可以使用devm_iio_device_alloc()自动管理内存。
int iio_device_register(struct iio_dev *indio_dev)
indio_dev 要注册的IIO设备int iio_push_to_buffers(struct iio_dev *indio_dev, const unsigned char *data)
indio_dev:IIO设备data:要推送的数据int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
irq_handler_t pollfunc,
irq_handler_t thread_fn,
const struct iio_buffer_setup_ops *buf_ops)
pollfunc:触发中断处理函数thread_fn:线程化中断处理函数buf_ops:缓冲区操作函数集void iio_device_unregister(struct iio_dev *indio_dev)
void iio_device_free(struct iio_dev *indio_dev)
struct icm20608_dev {
struct spi_device *spi;
struct regmap *regmap;
struct regmap_config regmap_config;
struct mutex lock;
};
/* 陀螺仪分辨率:±250、±500、±1000、±2000°/s */
static const int gyro_scale_icm20608[] = {7629, 15258, 30517, 61035};
/* 加速度计分辨率:±2、±4、±8、±16g */
static const int accel_scale_icm20608[] = {61035, 122070, 244140, 488281};
/* 温度参数 */
#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE 326800000
/* 扫描索引枚举(原书 75.3.2,注意顺序为 加速度X/Y/Z、温度、陀螺仪X/Y/Z、时间戳) */
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,
};
/* 通道定义宏(原书为 3 参数形式) */
#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通道数组(7个通道) */
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,
},
},
/* 陀螺仪X/Y/Z轴 */
ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_ICM20608_SCAN_GYRO_X),
ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),
ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),
/* 加速度计:原书顺序为 Y、X、Z */
ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),
ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),
ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),
};
分辨率计算:陀螺仪以 ±250°/s 量程为例,
500 / 2^16 ≈ 0.007629,扩大 1000000 倍为 7629;加速度计以 ±2g 量程为例,4 / 2^16 ≈ 0.000061035,扩大 1000000000 倍为 61035。
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_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_VEL: /* 读取陀螺仪数据 */
ret = icm20608_sensor_show(dev, ICM20_GYRO_XOUT_H,
chan->channel2, val);
break;
case IIO_ACCEL: /* 读取加速度计数据 */
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;
}
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:
/* 读取原始值 */
iio_device_claim_direct_mode(indio_dev);
mutex_lock(&dev->lock);
ret = icm20608_read_channel_data(indio_dev, chan, val);
mutex_unlock(&dev->lock);
iio_device_release_direct_mode(indio_dev);
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;
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;
case IIO_TEMP: /* 温度分辨率 */
*val = ICM20608_TEMP_SCALE / 1000000;
*val2 = ICM20608_TEMP_SCALE % 1000000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_OFFSET: /* 温度偏移值 */
switch (chan->type) {
case IIO_TEMP:
*val = ICM20608_TEMP_OFFSET;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_CALIBBIAS: /* 校准偏置 */
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;
}
}
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. 获取设备结构体地址 */
dev = iio_priv(indio_dev);
dev->spi = spi;
spi_set_drvdata(spi, indio_dev);
mutex_init(&dev->lock);
/* 3. 初始化iio_dev */
indio_dev->dev.parent = &spi->dev;
indio_dev->info = &icm20608_info;
indio_dev->name = "icm20608";
indio_dev->modes = INDIO_DIRECT_MODE;
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 */
dev->regmap_config.reg_bits = 8;
dev->regmap_config.val_bits = 8;
dev->regmap_config.read_flag_mask = 0x80;
dev->regmap = regmap_init_spi(spi, &dev->regmap_config);
if (IS_ERR(dev->regmap)) {
ret = PTR_ERR(dev->regmap);
goto err_regmap_init;
}
/* 6. 初始化SPI */
spi->mode = SPI_MODE_0;
spi_setup(spi);
/* 7. 初始化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");
原书 75.3.4 实现用户空间往驱动写数据,用于设置陀螺仪/加速度计的量程(分辨率)和校准值。配套函数如下:
/* 向指定通道寄存器写入数据,用于设置校准值 */
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;
}
/* 设置陀螺仪量程:在 gyro_scale_icm20608[] 中查找匹配值,换算成 GYRO_CONFIG 的 FS_SEL */
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;
}
/* 设置加速度计量程:在 accel_scale_icm20608[] 中查找匹配值,换算成 ACCEL_CONFIG 的 AFS_SEL */
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;
}
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;
iio_device_claim_direct_mode(indio_dev);
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;
}
iio_device_release_direct_mode(indio_dev);
return ret;
}
/* 指定用户空间写入的数据格式:陀螺仪扩大1e6倍,其余(加速度计)扩大1e9倍 */
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:
return IIO_VAL_INT_PLUS_MICRO;
default:
return IIO_VAL_INT_PLUS_NANO;
}
default:
return IIO_VAL_INT_PLUS_MICRO;
}
}
要点:ICM20_GYRO_CONFIG 的 bit4:3(FS_SEL)和 ICM20_ACCEL_CONFIG 的 bit4:3(AFS_SEL)决定量程,写值 i << 3。in_accel_scale 返回 IIO_VAL_INT_PLUS_NANO,所以用户空间写 0.000122070(±4g)时传入驱动的是 122070。
内核需使能 IIO 及缓冲区相关选项(原书 75.3.1):
-> Device Drivers
-> Industrial I/O support (IIO [=y])
-> [*] Enable buffer support within IIO
-> <*> Industrial I/O buffering based on kfifo
编译并加载驱动、运行测试 APP(原书 75.3.2 / 75.4.3):
depmod # 第一次加载驱动时需要
modprobe icm20608.ko # 加载驱动模块
arm-linux-gnueabihf-gcc icm20608App.c -o icm20608App # 编译测试APP
./icm20608App # 运行,持续打印陀螺仪/加速度计/温度数据
# 查看所有IIO设备
ls /sys/bus/iio/devices/
# iio:device0 → I.MX6ULL内部ADC
# iio:device1 → ICM20608 (SPI)
# 进入ICM20608设备目录
cd /sys/bus/iio/devices/iio:device1
# 查看文件列表
ls
# in_accel_scale 加速度计比例
# in_accel_x_raw 加速度计X轴原始值
# in_accel_x_calibbias 加速度计X轴校准值
# in_accel_y_raw 加速度计Y轴原始值
# in_anglvel_scale 陀螺仪比例
# in_anglvel_x_raw 陀螺仪X轴原始值
# in_temp_raw 温度原始值
# in_temp_offset 温度偏移
# in_temp_scale 温度比例
文件命名模式:[direction]_[type]_[index]_[modifier]_[info_mask]
| 组成部分 | 说明 | 示例 |
|---|---|---|
| direction | 方向(in/out) | in |
| type | 通道类型 | accel、anglvel、voltage |
| index | 通道编号 | x、y、z、1 |
| modifier | 修饰符 | x、y、z |
| info_mask | 属性掩码 | raw、scale、calibbias |
示例:in_accel_x_raw
in → 输入方向accel → 加速度类型(IIO_ACCEL)x → X轴(IIO_MOD_X)raw → 原始值(IIO_CHAN_INFO_RAW)# 读取加速度计比例(默认量程±16g)
cat in_accel_scale
# 输出: 0.000488281
# 读取加速度计Z轴原始值(静止时Z轴约为1g)
cat in_accel_z_raw
# 输出: 2074(原书图 75.3.3.2)
# 计算实际加速度
# 2074 × 0.000488281 ≈ 1.01g
# 读取陀螺仪X轴原始值
cat in_anglvel_x_raw
# 读取温度原始值
cat in_temp_raw
# 安装libiio
apt-get install libiio-dev iio-utils
# 查看IIO设备信息
iio_info -s
# 读取指定设备
iio_readdev -u iio:device0 voltage0
# 查看/设置设备属性
iio_attr -d iio:device1
IIO 的 sysfs 文件内容都是字符串(如 in_accel_scale 内容为 "0.000488281"),因此读取时要先按字符串取出,再用 atof/atoi 转换。原书 75.4.1 用到的标准 I/O 文件流 API:
| 函数 | 原型 | 说明 |
|---|---|---|
fopen |
FILE *fopen(const char *pathname, const char *mode) |
打开文件流,mode 可取 r/r+/w/w+/a/a+ |
fclose |
int fclose(FILE *stream) |
关闭文件流,成功返回0,失败返回EOF |
fread |
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) |
从流读取对象 |
fwrite |
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) |
向流写入对象 |
fscanf |
int fscanf(FILE *stream, const char *format, ...) |
格式化读取,遇空格/换行结束 |
核心转换逻辑(原书示例代码 75.4.2.1):
/* 读取文件路径(注意 ICM20608 是 iio:device1) */
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、获取温度原始值、offset、scale */
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);
/* 4、换算为实际值 */
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;
}
SENSOR_FLOAT_DATA_GET / SENSOR_INT_DATA_GET 是形参化宏,内部依次调用 file_data_read() + atof() / atoi()。temp_act = (temp_raw - temp_offset) / temp_scale + 25。⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | I.MX6ULL | STM32MP1 | RK3568 |
|---|---|---|---|
| ADC分辨率 | 12位 | 12位 | 10位 |
| ADC通道数 | 2通道 | 6通道 | 8通道 |
| ADC控制器 | vf610_adc | stm32-adc | rk30_adc |
| IIO驱动 | drivers/iio/adc/vf610_adc.c | drivers/iio/adc/stm32-adc.c | drivers/iio/adc/rk3x-adc.c |
| 参考电压 | 外部regulator | 内部参考 | 外部regulator |
| 设备树compatible | "fsl,imx6ul-adc" | "st,stm32h7-adc" | "rockchip,rk3066-adc" |
| 触发方式 | 软件触发 | 定时器/外部触发 | 软件触发 |
| buffer支持 | 支持 | 支持 | 支持 |
| IIO驱动框架 | platform + IIO | platform + IIO | platform + IIO |
I.MX6ULL(vf610_adc):
STM32MP1(stm32-adc):
RK3568(rk3x-adc):
尽管各平台ADC硬件不同,但IIO框架提供了统一的用户空间接口:
⚠️ 来源说明:本节面试题不属于《I.MX6U嵌入式Linux驱动开发指南》正文内容,为扩展知识(部分结论可对应原书 75.1 小节)。
答:
答:
IIO使用val和val2两个整数参数编码小数:
IIO_VAL_INT:纯整数,结果=valIIO_VAL_INT_PLUS_MICRO:结果=val + val2/1000000(微精度)IIO_VAL_INT_PLUS_NANO:结果=val + val2/1000000000(纳精度)IIO_VAL_FRACTIONAL_LOG2:结果=val >> val2(用于2的幂次比例)例如:分辨率0.000488281返回IIO_VAL_INT_PLUS_NANO,val=0,val2=488281
答:
info_mask_separate:每个通道独立的属性,在sysfs中为每个通道创建独立文件(如in_accel_x_raw、in_accel_y_raw)info_mask_shared_by_type:同类型通道共享的属性,只创建一个文件(如in_accel_scale所有轴共用)设计意义:原始值每个轴独立,所以用separate;分辨率/量程所有轴相同,所以用shared_by_type
答:
iio_chan_spec数组中定义每个通道的type、channel、scan_index等iio_push_to_buffers将多通道数据按scan_index顺序打包答:
| 特性 | 直接模式(INDIO_DIRECT_MODE) | 缓冲区模式 |
|---|---|---|
| 数据获取 | 每次read触发一次转换 | 连续转换,批量读取 |
| 延迟 | 高(每次转换+系统调用开销) | 低(硬件DMA传输) |
| CPU占用 | 高(轮询/中断等待) | 低(DMA+中断批量处理) |
| 采样率 | 受限于系统调用频率 | 可达硬件最高采样率 |
| 适用场景 | 低速、偶发读取 | 高速连续采集 |
缓冲区模式适合IMU、ADC连续采样等高带宽场景,直接模式适合温度计、电压监测等低速场景。
Documentation/driver-api/iio/index.rstdrivers/iio/adc/vf610_adc.c内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第七十五章 Linux IIO驱动实验
内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第七十六章 Linux ADC驱动实验