iio 子系统
IIO(Industrial I/O)是 Linux 内核中的一个子系统,用于处理工业控制和测量设备的数据采集和处理。IIO 子系统提供了一个标准接口,使开发人员能够轻松地将各种传感器、放大器、数据转换器等设备连接到 Linux 系统中,并在用户空间中访问它们的数据。
IIO 子系统的主要组件包括以下内容:
IIO 核心:IIO 核心提供了驱动程序和用户空间之间的接口。它负责设备枚举、设备注册和设备管理等操作。IIO 核心也提供了一些框架,使得设备驱动程序的开发更容易。
IIO 设备驱动程序:IIO 设备驱动程序是用于控制和读取特定 IIO 设备的代码模块。每个设备驱动程序都负责将硬件抽象为一组可读取和可写入的寄存器。
IIO 缓冲区:IIO 缓冲区是存储传感器和其他测量设备数据的内存区域。当数据准备好时,设备驱动程序会将数据写入 IIO 缓冲区,用户空间应用程序可以通过读取缓冲区来访问这些数据。
IIO 事件处理:IIO 事件处理用于处理来自传感器和其他测量设备的中断和事件。事件处理器会监视设备驱动程序注册的事件,当事件发生时,会通过 IIO 核心通知用户空间。
总的来说,IIO 子系统提供了一个灵活和通用的框架,用于处理各种类型的工业控制和测量设备。通过 IIO 子系统,开发人员可以使用统一的接口来连接、读取和控制这些设备,使得应用程序的开发更加简单、快速和可靠。
IIO 子系统常用的接口函数主要包括以下几类:
设备和通道管理函数:
struct iio_dev *iio_device_alloc(void): 分配一个新的 IIO 设备结构体,并返回该结构体的指针。
void iio_device_free(struct iio_dev *indio_dev): 释放一个 IIO 设备结构体的内存空间。
struct iio_channel *iio_channel_get(struct iio_dev *indio_dev, const char *channel_name): 根据通道名称获取对应的 IIO 通道结构体指针。
int iio_channel_read(struct iio_channel *chan, int *val, int *val2): 读取指定 IIO 通道的值。
int iio_channel_write(struct iio_channel *chan, int val, int val2): 将指定值写入指定 IIO 通道。
缓冲区管理函数:
struct iio_buffer *iio_device_create_buffer(struct iio_dev *indio_dev, int size):创建一个新的 IIO 缓冲区,并返回该缓冲区的指针。
void iio_buffer_destroy(struct iio_buffer *buffer): 销毁一个 IIO 缓冲区。
int iio_buffer_push(struct iio_buffer *buffer): 将 IIO 缓冲区中的数据提交到用户空间。
int iio_buffer_refill(struct iio_buffer *buffer): 从 IIO 设备中读取新的数据,填充到 IIO 缓冲区中。
触发器管理函数:
struct iio_trigger *iio_trigger_alloc(const char *name, int (*set_trigger_state)(struct iio_trigger *trig, bool state)): 分配一个新的 IIO 触发器结构体,并返回该结构体的指针。
void iio_trigger_free(struct iio_trigger *trig): 释放一个 IIO 触发器结构体的内存空间。
int iio_trigger_register(struct iio_trigger *trig): 将 IIO 触发器注册到 IIO 子系统中。
void iio_trigger_unregister(struct iio_trigger *trig): 从 IIO 子系统中注销 IIO 触发器。
其他常用函数:
int iio_read_const_attr(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, const char *attr, char *buf): 读取 IIO 设备上指定通道的指定属性的值。
int iio_write_const_attr(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, const char *attr, char *buf): 将指定值写入 IIO 设备上指定通道的指定属性。
以上仅是 IIO 子系统常用的一部分接口函数,实际应用中可能还需要使用其他接口函数来实现特定的功能。
程序例子
这里给出一个简单的 MPU6050 驱动程序,包括设备树和驱动代码。这个驱动只是作为参考,实际使用时需要根据具体的硬件平台和操作系统做相应的修改。
设备树:
&i2c1 {
status = "okay";
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
interrupt-parent = <&gpio>;
interrupts = <2 0>;
pinctrl-names = "default";
pinctrl-0 = <&mpu6050_pins>;
clock-frequency = <400000>;
};
};
&gpio {
mpu6050_int: mpu6050-int {
gpio-hog;
gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
output-low;
line-name = "mpu6050_int";
linehandle-name = "mpu6050_int_handle";
};
};
驱动代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#define MPU6050_REG_ACCEL_XOUT_H 0x3B
#define MPU6050_REG_ACCEL_XOUT_L 0x3C
#define MPU6050_REG_ACCEL_YOUT_H 0x3D
#define MPU6050_REG_ACCEL_YOUT_L 0x3E
#define MPU6050_REG_ACCEL_ZOUT_H 0x3F
#define MPU6050_REG_ACCEL_ZOUT_L 0x40
#define MPU6050_REG_TEMP_OUT_H 0x41
#define MPU6050_REG_TEMP_OUT_L 0x42
struct mpu6050_data {
struct i2c_client *client;
int irq;
};
static int mpu6050_read(struct i2c_client *client, u8 reg, u8 *data, int len)
{
int ret;
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.flags = 0,
.buf = ®,
.len = 1,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.buf = data,
.len = len,
},
};
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
dev_err(&client->dev, "i2c_transfer failed (%d)\n", ret);
return -EIO;
}
return 0;
}
static int mpu6050_write(struct i2c_client *client, u8 reg, u8 data)
{
int ret;
u8 buf[2] = {reg, data};
ret = i2c_master_send(client, buf, 2);
if (ret != 2) {
dev_err(&client->dev, "i2c_master_send failed (%d)\n", ret);
return -EIO;
}
return 0;
}
static int mpu6050_read_xyz(struct i2c_client *client, s16 *x, s16 *y, s16 *z)
{
u8 data[6];
int ret;
scss
ret = mpu6050_read(client, MPU6050_REG_ACCEL_XOUT_H, data, sizeof(data));
if (ret)
return ret;
*x = (s16)((data[0] << 8) | data[1]);
*y = (s16)((data[2] << 8) | data[3]);
*z = (s16)((data[4] << 8) | data[5]);
return 0;
}
static int mpu6050_read_temp(struct i2c_client *client, s16 *temp)
{
u8 data[2];
int ret;
kotlin
ret = mpu6050_read(client, MPU6050_REG_TEMP_OUT_H, data, sizeof(data));
if (ret)
return ret;
*temp = (s16)((data[0] << 8) | data[1]);
return 0;
}
static int mpu6050_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{
struct mpu6050_data *data = iio_priv(indio_dev);
s16 x, y, z, temp;
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->channel2) {
case IIO_MOD_X:
ret = mpu6050_read_xyz(data->client, &x, &y, &z);
if (ret)
return ret;
*val = x;
break;
case IIO_MOD_Y:
ret = mpu6050_read_xyz(data->client, &x, &y, &z);
if (ret)
return ret;
*val = y;
break;
case IIO_MOD_Z:
ret = mpu6050_read_xyz(data->client, &x, &y, &z);
if (ret)
return ret;
*val = z;
break;
case IIO_MOD_TEMP:
ret = mpu6050_read_temp(data->client, &temp);
if (ret)
return ret;
*val = temp;
break;
default:
return -EINVAL;
}
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
*val2 = 1000;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static const struct iio_info mpu6050_iio_info = {
.read_raw = mpu6050_read_raw,
};
static const struct iio_chan_spec mpu6050_channels[] = {
{
.type = IIO_ACCEL,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.channel2 = IIO_MOD_X,
},
{
.type = IIO_ACCEL,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.channel2 = IIO_MOD_Y,
},
{
.type = IIO_ACCEL,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
.channel2 = IIO_MOD_Z,
},
{
.type = IIO_TEMP,
.info
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
},
};
static int mpu6050_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{
struct mpu6050_data *data = iio_priv(indio_dev);
int ret;
u8 buffer[2];
s16 accel_x, accel_y, accel_z;
s16 temp;
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_ACCEL:
switch (chan->channel2) {
case IIO_MOD_X:
ret = mpu6050_read_xyz(data->client, &accel_x, &accel_y, &accel_z);
if (ret < 0)
return ret;
*val = accel_x;
return IIO_VAL_INT;
case IIO_MOD_Y:
ret = mpu6050_read_xyz(data->client, &accel_x, &accel_y, &accel_z);
if (ret < 0)
return ret;
*val = accel_y;
return IIO_VAL_INT;
case IIO_MOD_Z:
ret = mpu6050_read_xyz(data->client, &accel_x, &accel_y, &accel_z);
if (ret < 0)
return ret;
*val = accel_z;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_TEMP:
ret = mpu6050_read(data->client, MPU6050_REG_TEMP_OUT_H, buffer, 2);
if (ret < 0)
return ret;
temp = (s16)((buffer[0] << 8) | buffer[1]);
*val = temp;
return IIO_VAL_INT;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_ACCEL:
switch (chan->channel2) {
case IIO_MOD_X:
case IIO_MOD_Y:
case IIO_MOD_Z:
val = 16384; /* scale in LSB/g */
*val2 = 0;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
case IIO_TEMP:
*val = 333;
*val2 = 0;
return IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static const struct iio_info mpu6050_info = {
.read_raw = mpu6050_read_raw,
};
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct mpu6050_data *data;
struct iio_dev *indio_dev;
int ret;
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(struct mpu6050_data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->client = client;
ret = mpu6050_write(client, 0x6b, 0x01);
if (ret < 0)
return ret;
ret = mpu6050_write(client, 0x1a, 0x01);
if (ret < 0)
return ret;
ret = mpu6050_write(client, 0x1b, 0x08);
if (ret < 0)
return ret;
indio_dev->dev.parent = &client->dev;
indio_dev->name = client->name;
indio_dev->info = &mpu6050_info;
indio_dev->channels = mpu6050_channels;
indio_dev->num_channels = ARRAY_SIZE(mpu6050_channels);
ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret) {
dev_err(&client->dev, "Failed to register IIO device: %d\n", ret);
return ret;
}
return 0;
}
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "invensense,mpu6050", },
{},
};
MODULE_DEVICE_TABLE(of, mpu6050_of_match);
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
};
module_i2c_driver(mpu6050_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("MPU6050 IIO driver");
MODULE_LICENSE("GPL");
这个mpu6050驱动代码是一个IIO(Industrial I/O)子系统的设备驱动程序,它通过I2C总线与MPU6050传感器通信。IIO子系统是一个通用的内核接口,用于提供从各种不同的传感器和ADC(模数转换器)读取数据的机制。
驱动程序初始化了iio设备结构体并在系统中注册它。设备的通道信息被定义为三个独立的加速度通道(X、Y、Z)和一个温度通道。iio框架会根据这些通道信息生成相应的sysfs节点,通过sysfs节点用户可以读取传感器数据。
mpu6050_read_xyz() 函数从MPU6050传感器读取X、Y、Z方向的加速度值。mpu6050_write() 函数向寄存器写入数据,用于配置传感器和启用它的功能。在设备初始化期间,驱动程序通过调用这些函数对MPU6050进行配置和初始化。