嵌入式驱动开发详解20(IIO驱动架构)

文章目录

  • 前言
  • IIO子系统简介
    • 主要结构体
    • 主要API函数
  • IIO子系统实现
    • SPI框架
    • IIO框架
    • IIO通道详解
      • 通道结构体分析
      • 通道命名分析
      • icm20608设备通道实现
    • 读取函数
    • 写入函数
  • 测试
    • 测试效果
    • 命令行读取
    • 应用程序读取
  • 后续
  • 参考文献

前言

IIO 全称是 Industrial I/O,翻译过来就是工业 I/O,IIO 就是为 ADC 类或者 DAC 类传感器准备的,大家常用的陀螺仪、加速度 计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的 模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、SPI 等传输给 SOC。当使用的传感器本质是 ADC 或 DAC 器件的时候,可以优先考虑使用 IIO 驱动框架。IIO子系统驱动框架编写驱动代码可以将读写数据进行文件流化,可以直接通过linux系统的cat和echo进行数据的读写,且读写数据的命名格式和分辨率也是统一的。

IIO子系统简介

跟前面所学习的IIC、SPI、INPUT、RegMap等子系统一样,该系统也是通过几个主要的结构体对变量和函数进行封装,进而对结构体进行初始化实现的,接下来就通过几个主要的结构体和API函数讲解IIO子系统创建的过程。

主要结构体

IIO 子 系 统 使 用 结 构 体 iio_dev 来 描 述 一 个 具 体 IIO 设 备,此 设 备 结 构 体 定 义 在 include/linux/iio/iio.h 文件中。该结构体有以下内容:

* struct iio_dev - industrial I/O device* @id:			[INTERN] used to identify device internally* @modes:		[DRIVER] operating modes supported by device  * 				//这个模式一般需要配置为sysfs 接口,INDIO_DIRECT_MODE* @currentmode:	[DRIVER] current operating mode* @dev:		[DRIVER] device structure, should be assigned a parent*			and owner* @event_interface:	[INTERN] event chrdevs associated with interrupt lines* @buffer:		[DRIVER] any buffer present* @buffer_list:	[INTERN] list of all buffers currently attached* @scan_bytes:		[INTERN] num bytes captured to be fed to buffer demux* @mlock:		[INTERN] lock used to prevent simultaneous device state*			changes* @available_scan_masks: [DRIVER] optional array of allowed bitmasks* @masklength:		[INTERN] the length of the mask established from*			channels* @active_scan_mask:	[INTERN] union of all scan masks requested by buffers* @scan_timestamp:	[INTERN] set if any buffers have requested timestamp* @scan_index_timestamp:[INTERN] cache of the index to the timestamp* @trig:		[INTERN] current device trigger (buffer modes)* @pollfunc:		[DRIVER] function run on trigger being received* @channels:		[DRIVER] channel specification structure table* 		//channels 为 IIO 设备通道,为 iio_chan_spec 结构体类型,稍后会详细讲解 IIO 通道。* @num_channels:	[DRIVER] number of channels specified in @channels.* @channel_attr_list:	[INTERN] keep track of automatically created channel*			attributes* @chan_attr_group:	[INTERN] group for all attrs in base directory* @name:		[DRIVER] name of the device.* @info:		[DRIVER] callbacks and constant info from driver*				//info 为 iio_info 结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!* 				//我们从用户空间读取 IIO 设备内部数据,最终调用的就是 iio_info 里面的函数。* @info_exist_lock:	[INTERN] lock to prevent use during removal* @setup_ops:		[DRIVER] callbacks to call before and after buffer*			enable/disable*			//iio_buffer_setup_ops 里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。*			//如果未指定的话就默认使用 iio_triggered_buffer_setup_ops。* @chrdev:		[INTERN] associated character device* @groups:		[INTERN] attribute groups* @groupcounter:	[INTERN] index of next attribute group* @flags:		[INTERN] file ops related flags including busy flag.* @debugfs_dentry:	[INTERN] device specific debugfs dentry.* @cached_reg_addr:	[INTERN] cached register address for debugfs reads.

在上面的iio_dev 结构体中有个成员变量:info,为 iio_info 结构体指针变量,这个是我们在编写 IIO 驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到 iio_info 里面。iio_info 结构体定义在 include/linux/iio/iio.h 中,内容如下

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);……};

read_raw和write_raw函数是最终读写设备内部数据的操作函数,需要程序编写人员去实现,这两个函数的参数都是一样的,我们依次来看一下:

变量备注
indio_dev需要读写的 IIO 设备。
chan需要读取的通道。
val数据整数部分
val2数据小数部分
mask掩码:用于指定读取的是什么数据,比如IIO_CHAN_INFO_RAW、IIO_CHAN_INFO_SCALE等
  • 对于 read_raw 函数: val 和 val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。
  • 对于 write_raw 函数。val 和 val2 共同组成具体值,val 是整数部分,val2 是小数部分。但是 val2 是对具体的小数部分扩大 N 倍后的整数值,不能直接从内核向应用程序返回一个小数值。比如现在有个值为 1.00236,那么 val 就是 1,vla2 理论上来讲是 0.00236,但是我们需要对 0.00236 扩大 N 倍,使其变为整数。具体是扩大多是倍或者是否需要小数部分可以通过下面这个宏定义实现。
    在这里插入图片描述write_raw_get_fmt函数是用于设置用户空间向内核空间写入的数据格式,决定了 wtite_raw 函数中 val 和 val2 的意义。

主要API函数

  • 申请函数 iio_device_alloc
    在使用IIO子系统之前要先申请 iio_dev,也可以使用devm_iio_device_alloc,加上devm_后卸载驱动的时候不需要手动释放,其他功能一样。
    函数原型如下:
struct iio_dev *iio_device_alloc(int sizeof_priv);

sizeof_priv:私有数据内存空间大小,一般我们会将自己定义的设备结构体变量作为 iio_dev 的私有数据,这样可以直接通过 iio_device_alloc 函数同时完成 iio_dev 和设备结构体变量的内存申请。申请成功以后使用 iio_priv 函数来得到自定义的设备结构体变量首地址。两者配合使用方式如下:
返回值:如果申请成功就返回 iio_dev 首地址,失败就返回 NULL。

struct icm20608_dev *dev;
struct iio_dev *indio_dev;
indio_dev = iio_device_alloc(sizeof(*dev)); /* 1、申请 iio_dev 内存 */
if (!indio_dev) return -ENOMEM;
dev = iio_priv(indio_dev);/* 2、获取设备结构体变量地址 */
  • 释放函数:iio_device_free
void iio_device_free(struct iio_dev *indio_dev)
  • 注册函数 iio_device_register
int iio_device_register(struct iio_dev *indio_dev)
  • 注销函数 iio_device_unregister
void iio_device_unregister(struct iio_dev *indio_dev)

以上便是主要会用到的四个函数,后面三个函数参数比较单一不做讲解。

IIO子系统实现

SPI框架

static int icm20608_probe(struct spi_device *spi)
{printk("icm20608_probe!!!\r\n");
//iio初始化
//regmap初始化spi->mode = SPI_MODE_0;spi_setup(spi);
//icm20608设备初始化return 0;
}static int icm20608_remove(struct spi_device *spi)
{
// regmap卸载
//iio卸载return 0;
}static const struct of_device_id icm20608_of_match[] = {{.compatible = "hbb,icm20608"},{       }
};   static const struct spi_device_id icm20608_id[] = {{"icm20608",0},/* 我们用的是of_device_id,i2c_device_id可以写的不对,但是必须要有*/
};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("hubinbin");

如上所示为SPI驱动框架,这个在SPI部分已经做了详细介绍,这里不再赘述。

IIO框架

#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>#define ICM20608_CNT     1
#define ICM20608_NAME    "icm20608"struct icm20608_dev{struct spi_device *spi;struct regmap *regmap;struct regmap_config regmap_config;struct mutex lock;
};static const struct iio_chan_spec icm20608_channels[] = {};static int icm20608_read_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, int *val, int *val2,long mask)
{return 0;
}static int icm20608_write_raw(struct iio_dev *indio_dev,struct iio_chan_spec const *chan, int val, int val2, long mask)
{return 0;
}static int icm20608_write_raw_get_fmt(struct iio_dev *indio_dev,struct iio_chan_spec const *chan,long mask)
{return 0;
}static const struct iio_info icm20608_info = { .driver_module		= THIS_MODULE,.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;printk("icm20608_probe!!!\r\n");indio_dev = devm_iio_device_alloc(&spi->dev,sizeof(*dev));if(!indio_dev)return -ENOMEM;dev = iio_priv(indio_dev);dev->spi = spi;spi_set_drvdata(spi,indio_dev);indio_dev->dev.parent = &spi->dev;indio_dev->info = &icm20608_info;indio_dev->name = ICM20608_NAME;indio_dev->modes = INDIO_DIRECT_MODE;indio_dev->channels = icm20608_channels;indio_dev->num_channels = ARRAY_SIZE(icm20608_channels);iio_device_register(indio_dev);
//互斥锁初始化
//regmap初始化
//SPI变量设置
//icm20608设备初始化return 0;
}static int icm20608_remove(struct spi_device *spi)
{struct iio_dev *indio_dev = spi_get_drvdata(spi);struct icm20608_dev *dev;printk("icm20608_remove!!!\r\n");dev = iio_priv(indio_dev);iio_device_unregister(indio_dev);
//regmap卸载return 0;
}

IIO的框架如上所示,iio_device_register会自动创建节点,因此不需要跟只使用spi框架一样还需要设置dev_id、cdev 以及 device 和 class。主要是需要在SPI的probe函数中初始化 indio_dev 的成员变量,完成info函数的定义,chanels 通道的定义等,这里需要注意 icm20608_dev 结构体与 indio_dev 结构体之间的关系。

IIO通道详解

通道结构体分析

IIO 的核心就是通道,一个传感器可能有多路数据,比如一个 ADC 芯片支持 8 路采集,那 么这个 ADC 就有 8 个通道。我们本章实验用到的 ICM20608,这是一个六轴传感器,可以输出三轴陀螺仪(X、Y、Z)、三轴加速度计(X、Y、Z)和一路温度,也就是一共有 7 路数据,因此就 有 7 个通道。Linux 内核使用 iio_chan_spec 结构体来描述通道,定义在 include/linux/iio/iio.h 文件中。

struct iio_chan_spec {enum iio_chan_type	type;int			channel;int			channel2;unsigned long		address;int			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_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;unsigned		indexed:1;unsigned		output:1;unsigned		differential:1;
};
  1. type 为通道类型, iio_chan_type 是一个枚举类型,列举出了可以选择的通道类 型,定义在 include/uapi/linux/iio/types.h 文件里面,如下图所示,定义了很多传感器的数据类型,ICM20608的陀螺仪部分是 IIO_ANGL_VEL 类型,加速度计部分是 IIO_ACCEL 类型,温度部分就是 IIO_TEMP。
    在这里插入图片描述
  2. indexed:当设置为 1 时,channel 为通道索引。
  3. modified:当设置为 1 时,channel2 为通道修饰符,Linux 内核给出了 可用的通道修饰符,定义在 include/uapi/linux/iio/types.h 文件里面,比如 ICM20608 的加速度计部分,类型设置为 IIO_ACCEL,X、Y、Z 这三个轴就用 channel2 的通道修饰符来区分。IIO_MOD_X、IIO_MOD_Y、IIO_MOD_Z 就分别对应 X、Y、Z 这三个 轴。
    enum iio_modifier {
    IIO_NO_MOD,
    IIO_MOD_X,
    IIO_MOD_Y,
    IIO_MOD_Z,
    IIO_MOD_X_AND_Y,
    IIO_MOD_X_AND_Z,
    IIO_MOD_Y_AND_Z,
    IIO_MOD_X_AND_Y_AND_Z,
    ……
    };
    
  4. address:一般会设 置为此通道对应的芯片数据寄存器地址,也可以用作其他功能,自行选择。
  5. scan_index:当使用触发缓冲区的时候,scan_index 是扫描索引。
  6. scan_type:是一个结构体,描述了扫描数据在缓冲区中的存储格式
    • 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(小端)。
  7. info_mask_separate:标记某些属性专属于此通道,include/linux/iio/iio.h 文件中 的 iio_chan_info_enum 枚举类型描述了可选的属性值,在 info_mask_separate 中使能 IIO_CHAN_INFO_RAW 这个属性,那么就表示在 sysfs 下生成三个不同的文件分别对应 X、Y、 Z 轴,这三个轴的 IIO_CHAN_INFO_RAW 属性是相互独立的。
    enum iio_chan_info_enum {
    IIO_CHAN_INFO_RAW = 0,
    IIO_CHAN_INFO_PROCESSED,
    IIO_CHAN_INFO_SCALE,
    IIO_CHAN_INFO_OFFSET,
    IIO_CHAN_INFO_CALIBSCALE,
    IIO_CHAN_INFO_CALIBBIAS,
    IIO_CHAN_INFO_PEAK,
    IIO_CHAN_INFO_PEAK_SCALE,
    IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW,
    IIO_CHAN_INFO_AVERAGE_RAW,
    IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY,
    IIO_CHAN_INFO_SAMP_FREQ,
    IIO_CHAN_INFO_FREQUENCY,
    IIO_CHAN_INFO_PHASE,
    IIO_CHAN_INFO_HARDWAREGAIN,
    IIO_CHAN_INFO_HYSTERESIS,
    IIO_CHAN_INFO_INT_TIME,
    IIO_CHAN_INFO_ENABLE,
    IIO_CHAN_INFO_CALIBHEIGHT,
    IIO_CHAN_INFO_CALIBWEIGHT,
    IIO_CHAN_INFO_DEBOUNCE_COUNT,
    IIO_CHAN_INFO_DEBOUNCE_TIME,
    };
    
  8. info_mask_shared_by_type:与第7条所用到的属性相同,如果通道存在某个属性一致,那么可以通过共享只设置一个即可,比如ICM20608 加速度计的 X、Y、Z 轴他们的 type 都 是 IIO_ACCEL,也就是类型相同。而这三个轴的分辨率(量程)是一样的,那么在配置这三个通 道的时候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 这个属性,表示 这三个通道的分辨率是共用的,这样在 sysfs 下就会只生成一个描述分辨率的文件,这三个通道 都可以使用这一个分辨率文件。
  9. info_mask_shared_by_dir:导出的信息由相同方向的通道共享
  10. info_mask_shared_by_all:无论这些通道的类 型、方向如何,全部共享。
  11. output:表示为输出通道。
  12. differential:表示为差分通道。

通道命名分析

在这里插入图片描述
通道属性的命名:[direction] _ [type] _ [index] _ [modifier] _ [info_mask]
以 in_accel_x_raw 为例,这是加速度计的 X 轴原始值,其定义的代码如下:

.type = IIO_ANGL_VEL, 
.modified = 1,
.channel2 = IIO_MOD_X, 
.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 = INV_ICM20608_SCAN_GYRO_X, 
.scan_type = {.sign = 's',.realbits = 16, .storagebits = 16, .shift = 0, .endianness = IIO_BE,},

如下图所示可以看出,对通道的定义和生成的文件名是有密切联系的,刚好我们定义的通道可以用字符一一对应出来。
在这里插入图片描述

icm20608设备通道实现

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, 
};#define ICM20608_CHAN(_type,_channel2,_index) \
{   \.type = _type, \.modified = 1, \.channel2 = _channel2, \.info_mask_separate  = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_CALIBBIAS), \.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),  \.scan_index = _index, \.scan_type = {  \.sign = 's', \.realbits = 16, \.shift = 0, \.endianness = IIO_BE, \}, \
}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),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),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_X,INV_ICM20608_SCAN_ACCL_X),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_ICM20608_SCAN_ACCL_Y),ICM20608_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_ICM20608_SCAN_ACCL_Z),
};

通过宏定义,实现了七个通道的具体配置:

  • 设置相同类型的通道 IIO_CHAN_INFO_SCALE 属性相同,“scale”是比例的意思,在这里就是量程(分辨率),因为 ICM20608 的陀螺仪和加速度计的量程是可以调整的,量程不同分辨率也就不同。陀螺仪或加速度计的三个轴也是一起设置的,因此对于陀螺仪或加速度计而言,X、Y、Z 这三个轴的量程是共享的。
  • 设置每个通道的 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_CALIBBIAS 这两个属性都是独立的, IIO_CHAN_INFO_RAW 表示 ICM20608 每个通道的原始值,这个肯定 是每个通道独立的。IIO_CHAN_INFO_CALIBBIAS 是 ICM20608 每个通道的校准值,这个是 ICM20608 的特性,不是所有的传感器都有校准值,一切都要以实际所使用的传感器为准。
  • 设置扫描数据类型,也就是 ICM20608 原始数据类型,ICM20608 的陀螺仪和加速度计都是 16 位 ADC,因此这里是通用的:为有符号类型、实际位数 16bit,存储位数 16bit,大端模式 (ICM20608 数据寄存器为大端模式)。

对应的通道文件如下图所示:
在这里插入图片描述

读取函数

/** 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};

在读写函数进行之前,先定义了陀螺仪和加速度计的分辨率数组,该数组可以实现通过读函数读取分辨率的两个bit位进而将数组对应的分辨率进行赋值,也可以通过写函数通过与数组进行匹配进而实现对寄存器控制分辨率的两个bit进行写入数据。
下面是读函数的代码:

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);  //将大端模式转化为原生CPU模式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);  /* channel2为X、Y、Z轴 */break;case IIO_ACCEL:		/* 读取加速度计数据 */ret = icm20608_sensor_show(dev, ICM20_ACCEL_XOUT_H, chan->channel2, val); /* channel2为X、Y、Z轴 */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:								/* 读取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_reg(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_reg(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;}
}

通过mask->type->channel的筛选方式将函数主要分为以下两种情况:

  • 一种是直接读取寄存器后将寄存器的值与数组的值进行匹配,匹配成功后,直接给 val 和 val2 赋一个定值,然后通过return返回,返回的形式参考本帖子最前面的表格。
  • 第二种是通过函数icm20608_sensor_show读取,其中icm20608_read_channel_data也是调用了函数icm20608_sensor_show,该函数直接读取对应寄存器的值然后,然后将外设寄存器的大端模式读取出来的值改为CPU的原生模式,进而通过return返回即可。

写入函数

写入函数的部分代码如下所示:

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;
}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_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;
}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;
}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;
}

写函数跟读函数有所不同,写函数通过 icm20608_write_raw_get_fmt 来设置倍数,通过 icm20608_write_raw 来写入具体的数值。

  • 函数 icm20608_write_raw_get_fmt 通过mask->type->channel的筛选方式确定每种类型文件的倍数。
  • 函数icm20608_write_raw 主要通过以下两种办法对寄存器进行赋值:
    • 第一种是直接从用户层获取数据然后与icm20608_write_raw_get_fmt设置的格式进行变换,变换后与数组的值进行匹配,匹配成功后,将对应的标志位写入到对应的寄存器。
    • 第二种是直接从用户层获取数据然后与icm20608_write_raw_get_fmt设置的格式进行变换,变换后将该数据从CPU原生端数据改为对应寄存器的大端模式或者小端模式,然后将变换后的数据写入到对应的寄存器即可。

测试

测试效果

Linux 内核默认使能了 IIO 子系统,但是有一些 IIO 模块没有选择上,这样会导致我们编译 驱动的时候会提示某些 API 函数不存在,需要使能的项目如下:

-> Device Drivers-> Industrial I/O support (IIO [=y])-> [*]Enable buffer support within IIO //选中 -> <*>Industrial I/O buffering based on kfifo //选中

命令行读取

读取方式:cat 文件   //打印出来的其实是字符串不是数字
写入方式:echo 数字 > 文件  //写入的是数字

应用程序读取

在用命令行读取的方式中我们可以知道,我们所读取的是流文件,因此我们打开和读写操作要用文件流操作函数。

  1. 打开文件流操作函数是

    FILE *fopen(const char *pathname, const char *mode)
    
    • pathname:需要打开的文件流路径。
    • mode:打开方式
      在这里插入图片描述
  2. 关闭文件流

    int fclose(FILE *stream)
    
    • stream:要关闭的文件流指针。
  3. 读取文件流

    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
    
    • ptr:要读取的数组中首个对象的指针。
    • size:每个对象的大小。
    • nmemb:要读取的对象个数。
    • stream:要读取的文件流。
    • 返回值:返回读取成功的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值 (或者 0)。
  4. 写文件流

    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    
    • ptr:要写入的数组中首个对象的指针。
    • size:每个对象的大小。
    • nmemb:要写入的对象个数。
    • stream:要写入的文件流。
    • 返回值:返回成功写入的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值 (或者 0)。
  5. 格式化输入文件流
    fscanf 函数用于从一个文件流中格式化读取数据,fscanf 函数在遇到空格和换行符的时候 就会结束。前面我们说了 IIO 框架下的 sysfs 文件内容都是字符串,比如 in_accel_scale 文件内 容为“0.000488281”,这是一串字符串,并不是具体的数字,因此我们在读取的时候就需要使用字符串读取格式。在这里就可以使用 fscanf 函数来格式化读取文件内容。

    int fscanf(FILE *stream, const char *format, ,[argument...])
    
    • stream:要操作的文件流。
    • format:格式。
    • argument:保存读取到的数据。
    • 返回值:成功读取到的数据个数,如果读到文件末尾或者读取错误就返回 EOF。

接下来着重介绍一下读函数是怎么实现的

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;
}

如上所示,第一个参数 filename 是要读取的文件路径。第二个参数 str 为读取到的文件内容,为字符串类型。主要步骤为先调用 fopen 函数打开指定的文件流,随后调用 fscanf 函数进行格式化读取,也就是按照字符串方式读取文件,文件内容保存到 str 参数里面。当读取到文件末 尾的时候,调用 fseek 函数将读取指针调整到文件头,以备下次重新读取。最后调用 fclose 函数关闭对应的文件流。
因为在此处读取到的数据都是字符串类型的数据,因此我们需要使用 atof 函数将浮点字符串转换为具体的浮点数值或者使用 atoi 函数将整数字符串转换为具体的整数数值;atof 和 atoi 这两个函数是标准的 C 库函数,并不需要我们自己编写,添加“stdlib.h”头文 件就可以直接调用。这两个函数都只有一个参数,就是要转换的字符串。返回值就是转换成功以后字符串对应的数值。

后续

最终完整代码在参考文献github链接第21个实验中
IIO子系统实验相对来说比较复杂,其复杂的点在于我们需要将读写函数通过MASK和TYPE进行区分并进行不同的封装,但是这样做有一个非常好的好处就是最终实现的数据都是通过流文件的形式展示出来,我们只需要通过读取流文件即可读取对应的数据。本帖子只对读取icm20608的数据进行了分析,不同的传感器数据类型不同,配置也不同,因此仅供作为IIO子系统的框架参考。

参考文献

  1. 个人专栏系列文章
  2. 正点原子嵌入式驱动开发指南
  3. 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/63757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 网络维护相关命令简介

目录 零. 概要一. ping二. ip命令2.1 ip address2.2 ip route2.3 ip neighbour 三. traceroute四. DNS查询4.1 nslookup4.2 dig 五. ss 查看网络连接状态 零. 概要 ⏹在Linux系统中有2套用于网络管理的工具集 net-tools 早期网络管理的主要工具集&#xff0c;缺乏对 IPv6、网…

Jenkins持续集成部署——jenkins安装

前言 Jenkins 是一个开源的自动化服务器&#xff0c;主要用于持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;。它为软件开发团队提供了一个易于使用的平台来自动化构建、测试和部署应用程序的过程。 Jenkins 主要功能 1. 持续集成 (CI) 自动构建…

PYG - Cora数据集加载 (自动加载+手动实现)

本文从Cora的例子来展示PYG如何加载图数据集。 Cora 是一个小型的有标注的图数据集&#xff0c;包含以下内容&#xff1a; data.x&#xff1a;2708 个节点&#xff08;即 2708 篇论文&#xff09;&#xff0c;每个节点有 1433 个特征&#xff0c;形状为 (2708, 1433)。data.ed…

机器学习基础算法 (二)-逻辑回归

python 环境的配置参考 从零开始&#xff1a;Python 环境搭建与工具配置 逻辑回归是一种用于解决二分类问题的机器学习算法&#xff0c;它可以预测输入数据属于某个类别的概率。本文将详细介绍逻辑回归的原理、Python 实现、模型评估和调优&#xff0c;并结合垃圾邮件分类案例进…

BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测(多输入单输出)

Matlab实现BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测&#xff08;多输入单输出&#xff09; 目录 Matlab实现BiTCN-BiGRU基于双向时间卷积网络结合双向门控循环单元的数据多特征分类预测&#xff08;多输入单输出&#xff09;分类效果基本描述…

51c大模型~合集94

我自己的原文哦~ https://blog.51cto.com/whaosoft/12897659 #D(R,O) Grasp 重塑跨智能体灵巧手抓取&#xff0c;NUS邵林团队提出全新交互式表征&#xff0c;斩获CoRL Workshop最佳机器人论文奖 本文的作者均来自新加坡国立大学 LinS Lab。本文的共同第一作者为上海交通大…

【大学英语】英语范文十八篇,书信,议论文,材料分析

关注作者了解更多 我的其他CSDN专栏 过程控制系统 工程测试技术 虚拟仪器技术 可编程控制器 工业现场总线 数字图像处理 智能控制 传感器技术 嵌入式系统 复变函数与积分变换 单片机原理 线性代数 大学物理 热工与工程流体力学 数字信号处理 光电融合集成电路…

一起学Git【第一节:Git的安装】

Git是什么&#xff1f; Git是什么&#xff1f;相信大家点击进来已经有了初步的认识&#xff0c;这里就简单的进行介绍。 Git是一个开源的分布式版本控制系统&#xff0c;由Linus Torvalds创建&#xff0c;用于有效、高速地处理从小到大的项目版本管理。Git是目前世界上最流行…

【day11】面向对象编程进阶(继承)

概述 本文深入探讨面向对象编程的核心概念&#xff0c;包括继承、方法重写、this和super关键字的使用&#xff0c;以及抽象类和方法的定义与实现。通过本文的学习&#xff0c;你将能够&#xff1a; 理解继承的优势。掌握继承的使用方法。了解继承后成员变量和成员方法的访问特…

随手记:小程序兼容后台的wangEditor富文本配置链接

场景&#xff1a; 在后台配置wangEditor富文本&#xff0c;可以文字配置链接&#xff0c;图片配置链接&#xff0c;产生的json格式为&#xff1a; 例子&#xff1a; <h1><a href"https://uniapp.dcloud.net.cn/" target"_blank"><span sty…

6.8 Newman自动化运行Postman测试集

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 1 安装Node.js2 安装Newman3 使用Newman运行Postman测试集3.1 导出Postman集合3.2 使用Newman运行集合3.3 Newman常用参数3.4 Newman报告格式 4 使用定时任务自动化执行脚本4.1 编写B…

计算机网络之王道考研读书笔记-2

第 2 章 物理层 2.1 通信基础 2.1.1 基本概念 1.数据、信号与码元 通信的目的是传输信息。数据是指传送信息的实体。信号则是数据的电气或电磁表现&#xff0c;是数据在传输过程中的存在形式。码元是数字通信中数字信号的计量单位&#xff0c;这个时长内的信号称为 k 进制码…

法规标准-C-NCAP评测标准解析(2024版)

文章目录 什么是C-NCAP&#xff1f;C-NCAP 评测标准C-NCAP评测维度三大维度的评测场景及对应分数评星标准 自动驾驶相关评测场景评测方法及评测标准AEB VRU——评测内容(测什么&#xff1f;)AEB VRU——评测方法(怎么测&#xff1f;)车辆直行与前方纵向行走的行人测试场景&…

第十七届山东省职业院校技能大赛 中职组“网络安全”赛项任务书正式赛题

第十七届山东省职业院校技能大赛 中职组“网络安全”赛项任务书-A 目录 一、竞赛阶段 二、竞赛任务书内容 &#xff08;一&#xff09;拓扑图 &#xff08;二&#xff09;模块A 基础设施设置与安全加固(200分) &#xff08;三&#xff09;B模块安全事件响应/网络安全数据取证/…

Halcon例程代码解读:安全环检测(附源码|图像下载链接)

安全环检测核心思路与代码详解 项目目标 本项目的目标是检测图像中的安全环位置和方向。通过形状匹配技术&#xff0c;从一张模型图像中提取安全环的特征&#xff0c;并在后续图像中识别多个实例&#xff0c;完成检测和方向标定。 实现思路 安全环检测分为以下核心步骤&…

Java——多线程进阶知识

目录 一、常见的锁策略 乐观锁VS悲观锁 读写锁 重量级锁VS轻量级锁 总结&#xff1a; 自旋锁&#xff08;Spin Lock&#xff09; 公平锁VS非公平锁 可重入锁VS不可重入锁 二、CAS 何为CAS CAS有哪些应用 1&#xff09;实现原子类 2&#xff09;实现自旋锁 CAS的ABA…

达梦 本地编码:PG_GBK, 导入文件编码:PG_UTF8错误

问题 达梦 本地编码&#xff1a;PG_GBK, 导入文件编码&#xff1a;PG_UTF8错误 解决 右键管理服务器 查看配置 新建一个数据库实例&#xff0c;配置跟之前的保持一致 新建一个用户&#xff0c;跟以前的用户名一样 在用户上&#xff0c;右键导入&#xff0c;选择dmp的位置 导…

深度学习卷积神经网络CNN之MobileNet模型网络模型详解说明(超详细理论篇)

1.MobileNet背景 2.MobileNet V1论文 3. MobileNett改进史 4. MobileNet模型结构 5. 特点&#xff08;超详细创新、优缺点及新知识点&#xff09; 一、MobileNet背景 随着移动设备的普及&#xff0c;深度学习模型的应用场景逐渐扩展至移动端和嵌入式设备。然而&#xff0c;传统…

垂起固定翼无人机大面积森林草原巡检技术详解

垂起固定翼无人机大面积森林草原巡检技术是一种高效、精准的监测手段&#xff0c;以下是对该技术的详细解析&#xff1a; 一、垂起固定翼无人机技术特点 垂起固定翼无人机结合了多旋翼和固定翼无人机的优点&#xff0c;具备垂直起降、飞行距离长、速度快、高度高等特点。这种无…

kubernates实战

使用k8s来部署tomcat 1、创建一个部署&#xff0c;并指定镜像地址 kubectl create deployment tomcat6 --imagetomcat:6.0.53-jre82、查看部署pod状态 kubectl get pods # 获取default名称空间下的pods kubectl get pods --all-namespaces # 获取所有名称空间下的pods kubect…