Heng30的博客
搜索 分类 关于 订阅

Linux驱动使用IIO子系统和设备通信

2025-03-10

Industrial I/O (IIO)是 Linux 内核中的一个子系统,专为处理工业应用中的模拟数字转换器(ADC)、数字模拟转换器(DAC)、惯性测量单元(IMU)、光传感器、压力传感器等设备而设计。IIO 提供了统一的框架,简化了这些设备的驱动开发和用户空间访问。

主要特点

  • 设备支持:支持多种传感器和转换器,如加速度计、陀螺仪、磁力计、温度传感器等。

  • 统一接口:通过sysfs和字符设备提供标准接口,方便用户空间访问传感器数据。

  • 事件支持:支持硬件触发的事件,如阈值触发、数据就绪等。

  • 缓冲支持:提供数据缓冲机制,支持高效的数据采集和批处理。

  • 触发器:支持硬件和软件触发器,灵活控制数据采集时机。

实验环境

raspberry pi3b

Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux

设备树配置

// testoverlay.dts

/dts-v1/;
/plugin/;
/ {
    compatible = "raspberrypi,3-model-bbrcm,bcm2837";
    fragment@0 {
        target = <&i2c1>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;

            my_adc: my_adc@10 {
                compatible = "brightlight,myadc";
                reg = <0x10>;
                status = "okay";
            };
        };
    };
};

驱动代码

// simple.c

#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/init.h>
#include <linux/module.h>

#define CMD_GET_STATE 0x11
#define CMD_SET_STATE 0x22
#define CMD_GET_ADC_VAL 0x55

struct my_adc {
    struct i2c_client *client;
};

static struct of_device_id my_driver_ids[] = {
    {
        .compatible = "brightlight,myadc",
    },
    {},
};
MODULE_DEVICE_TABLE(of, my_driver_ids);

static struct i2c_device_id my_adc[] = {
    {"my_adc", 0},
    {},
};
MODULE_DEVICE_TABLE(i2c, my_adc);

// 读取`/sys/bus/iio/devices/iio:device0/in_voltage_raw`文件会调用该函数
static int my_adc_read_raw(struct iio_dev *indio_dev,
                           struct iio_chan_spec const *chan, int *val,
                           int *val2, long mask) {
    int ret = 0;
    struct my_adc *adc = iio_priv(indio_dev);

    if (mask == IIO_CHAN_INFO_RAW) {
        ret = i2c_smbus_read_byte_data(adc->client, CMD_GET_ADC_VAL);
        if (ret < 0) {
            pr_err("i2c_smbus_read_byte_data adc error\n");
            return ret;
        }

        *val = ret;
    } else {
        return -EINVAL;
    }

    return IIO_VAL_INT;
}

static const struct iio_chan_spec my_adc_channels[] = {
    {
        .type = IIO_VOLTAGE,
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    },
};

static const struct iio_info my_adc_info = {
    .read_raw = my_adc_read_raw,
};

// 要求i2c设备真实存在,且匹配成功才会调用该函数
static int _dt_probe(struct i2c_client *client,
                     const struct i2c_device_id *id) {
    struct iio_dev *indio_dev = NULL;
    struct my_adc *adc = NULL;
    int ret = 0;

    pr_info("simple_dt_probe\n");

    if (client->addr != 0x10) {
        pr_err("error: client addr != 10\n");
        return -1;
    }
    indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct iio_dev));

    if (!indio_dev) {
        pr_err("devm_iio_device_alloc error");
        return -ENOMEM;
    }

    adc = iio_priv(indio_dev);
    adc->client = client;

    indio_dev->name = id->name;
    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);

    ret = i2c_smbus_write_byte_data(adc->client, CMD_SET_STATE, 0x1);
    if (ret < 0) {
        pr_err("i2c_smbus_write_byte_data error");
        return -1;
    }

    ret = devm_iio_device_register(&client->dev, indio_dev);
    if (ret < 0) {
        pr_err("devm_iio_device_register error");
        return -1;
    }

    return 0;
}

static void _dt_remove(struct i2c_client *client) {
    struct iio_dev *indio_dev;
    struct my_adc *adc;

    pr_info("simple_dt_remove\n");

    indio_dev = i2c_get_clientdata(client);
    adc = iio_priv(indio_dev);
    i2c_smbus_write_byte_data(adc->client, CMD_SET_STATE, 0x0);
}

static struct i2c_driver my_dirver = {
    .probe = _dt_probe,
    .remove = _dt_remove,
    .id_table = my_adc,
    .driver =
        {
            .name = "my_adc",
            .of_match_table = my_driver_ids,
        },
};

// 这里会调用init和exit函数
module_i2c_driver(my_dirver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

Makefile编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all: dt driver

driver:
        make -C $(kernel-dir) modules M=$(top-dir)

dt:
        dtc -@ -I dts -O dtb -o testoverlay.dtbo testoverlay.dts

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 安装i2c-toolssudo apt install i2c-tools

  • 查看所有i2c总线:i2cdetect -l

  • 查看总线1上的i2c设备:i2cdetect -y 1

  • 编译程序:make

  • 安装设备树节点:sudo dtoverlay testoverlay.dtbo

  • 查看设备树节点信息

    • find /proc/device-tree/ -name "*adc*"
    /proc/device-tree/soc/i2c@7e804000/my_adc@10
    
    • ls /proc/device-tree/soc/i2c@7e804000/my_adc@10
    compatible  name  phandle  reg  status
    
  • 如果没有/sys/bus/iio目录,执行sudo modprobe industrialio安装驱动

  • 安装驱动:insmod simple.ko

  • 查看iio节点信息:ls /sys/bus/iio/devices/

  • 读取数据:cat /sys/bus/iio/devices/iio:device0/in_voltage_raw

  • 移除驱动:rmmod simple.ko

  • 移除设备树节点:sudo dtoverlay -r testoverlay