找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1029

积分

0

好友

140

主题
发表于 3 天前 | 查看: 7| 回复: 0

---TITLE---
Linux IIO驱动开发解析:ADC传感器驱动原理与代码实现
---TAGS---
Linux,IIO,ADC,I2C,驱动开发
---CONTENT---
当需要为ADC(模数转换器)、加速度计、陀螺仪、光线传感器或气压计等工业级传感器编写驱动程序时,传统的字符设备或输入子系统模型往往不再适用。此时,Linux内核提供的IIO(Industrial I/O)子系统便成为理想选择。它提供了统一的Sysfs接口,无论ADC芯片型号如何,用户都可以通过标准的cat in_voltage0_raw命令读取电压值。IIO子系统能够自动处理原始寄存器数值到标准物理单位(如毫伏、米/秒平方)的转换(通过Scale比例因子和Offset偏移量),并且支持硬件FIFO和DMA,可高效处理数千赫兹的高速采样数据。

在IIO驱动模型中,开发者不再直接实现read函数来返回字节流,而是需要定义“通道”。每个物理引脚或测量轴都对应一个通道,例如in_voltage0(通道0电压)、in_accel_x(X轴加速度)、in_temp(温度)。用户空间最终会看到两个主要文件:_raw(寄存器的原始值)和_scale(比例因子)。内核已定义好这套标准,用户空间的应用(如libiio)会自动根据这些文件完成计算。

下面以一个简单的I2C接口ADC芯片为例,说明IIO驱动的基本编写步骤。

步骤一:定义通道

首先,需要定义设备所拥有的通道。这段代码会在/sys/bus/iio/devices/下自动生成对应的接口文件,例如in_voltage0_rawin_voltage1_rawin_voltage_scale

#include <linux/iio/iio.h>

static const struct iio_chan_spec my_adc_channels[] = {
    {
        .type = IIO_VOLTAGE,           // 通道类型:电压
        .channel = 0,                  // 通道编号:0
        .indexed = 1,                  // 这是一个有索引的通道 (会生成voltage0)
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),  // 每个通道都有独立的`_raw`文件
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), // 同类型通道共享一个`_scale`文件
    },
    {
        .type = IIO_VOLTAGE,
        .channel = 1,
        .indexed = 1,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    },
};

步骤二:实现读取函数

这是驱动的核心,read_raw回调函数用于响应用户空间对_raw_scale等文件的读取操作。精通Linux驱动与I2C总线开发是完成这一步的关键。

struct my_adc_dev {
    struct i2c_client *client;
};

static int my_read_raw(struct iio_dev *indio_dev,
                       struct iio_chan_spec const *chan,
                       int *val, int *val2, long mask)
{
    struct my_adc_dev *data = iio_priv(indio_dev);
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW: // 用户读取 in_voltageX_raw 时进入此分支
        // 1. 根据 chan->channel 判断用户要读取哪个硬件通道
        // 2. 通过I2C发送命令,读取硬件寄存器
        ret = i2c_smbus_read_word_data(data->client, chan->channel);
        if (ret < 0)
            return ret;
        *val = ret; // 将读取到的原始值赋值给*val
        return IIO_VAL_INT; // 告知内核返回值是一个整数

    case IIO_CHAN_INFO_SCALE: // 用户读取 in_voltage_scale 时进入此分支
        // 假设参考电压为3300mV,ADC精度为12位(最大值4096)
        // 换算公式:真实电压 = raw * (3300 / 4096) ≈ raw * 0.80566
        // 此处返回分数形式 3300 / 2^12
        *val = 3300;  // 分子
        *val2 = 12;   // 分母以2为底的对数(即2^12=4096)
        return IIO_VAL_FRACTIONAL_LOG2; // 返回类型:分数对数形式

    default:
        return -EINVAL;
    }
}

static const struct iio_info my_adc_info = {
    .read_raw = my_read_raw, // 挂载核心回调函数
};

步骤三:Probe初始化

在设备的probe函数中,完成IIO设备的初始化与注册。

static int my_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct iio_dev *indio_dev;
    struct my_adc_dev *data;

    /* 1. 分配IIO设备结构体,并为其私有数据预留空间 */
    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
    if (!indio_dev)
        return -ENOMEM;

    /* 2. 获取私有数据结构的指针 */
    data = iio_priv(indio_dev);
    data->client = client;

    /* 3. 填充IIO设备的核心结构体 */
    indio_dev->name = "my_adc_demo";
    indio_dev->info = &my_adc_info; // 设置操作函数集
    indio_dev->modes = INDIO_DIRECT_MODE; // 设置为直接读取模式
    indio_dev->channels = my_adc_channels; // 设置通道数组
    indio_dev->num_channels = ARRAY_SIZE(my_adc_channels);

    /* 4. 向内核注册IIO设备 */
    return devm_iio_device_register(&client->dev, indio_dev);
}

用户空间访问模式

IIO驱动注册成功后,用户空间主要有两种访问方式:

  1. Sysfs模式:通过catecho命令直接读写/sys/bus/iio/devices/下的文件。这种方式简单,但速度慢,适用于低速、单次或低频次查询。
  2. Buffer模式:启用IIO缓冲区和触发机制,可以实现高速、连续的数据采集。数据通过/dev/iio:deviceX字符设备节点读取,这涉及到DMA和内核缓存机制,适合处理实时流数据。



上一篇:朴素贝叶斯与逻辑回归深度对比:原理差异、应用场景与Python实战案例
下一篇:雷池WAF社区版部署实战:开源Web防火墙的智能语义分析与防护配置指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 23:28 , Processed in 0.104606 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表