1、原因

  • 内部 ADC 将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、SPI等传输给SOC
  • 当你使用的传感器的本质是ADC或者DAC,优先考虑使用IIO驱动框架

2、IIO子系统

  • 主要介绍
    设备结构体iio_dev,(描述一个具体 IIO 设备)====申请注册
    以及内部包含的主要成员
    iio_info、(描述ADC的通道信息)
    iio_chan_spec结构体(用户空间对设备的具体操作最终都会反映到iio_inf里面)
/*
@  定义在include/linux/iio/iio.h文件
@  iio_dev 结构体来描述一个具体 IIO 设备
*/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; /*为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到IIO缓冲区*/unsigned                  masklength; const unsigned long        *active_scan_mask; /*为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区*/bool                      scan_timestamp; /*为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面*/unsigned                  scan_index_timestamp; struct iio_trigger          *trig; /*,trig为 IIO设备当前触发器,当使用缓冲模式的时候*/struct iio_poll_func         *pollfunc; /*一个函数,在接收到的触发器上运行*/struct iio_chan_spec const  *channels; /*channels 为 IIO 设备通道,*/int                     num_channels; /*IIO设备的通道数*/struct list_head            channel_attr_list; struct attribute_group       chan_attr_group; const char                *name; /*为 IIO设备名字*/const struct iio_info        *info; /*nfo为 iio_info结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要*/struct mutex                info_exist_lock; const struct iio_buffer_setup_ops   *setup_ops; /*里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数*/struct cdev               chrdev; /*chrdev为字符设备,由IIO 内核创建*/
..
};
/*
@  在使用之前要先申请 iio_dev,
@  sizeof_priv: 私有数据内存空间大小,
@  返回值:如果申请成功就返回 iio_dev首地址,如果失败就返回NULL
@  申请成功以后使用 iio_priv函数来得到自定义的设备结构体变量首地址。   一般iio_device_alloc和iio_priv之间的配合
*/
struct iio_dev *iio_device_alloc(int sizeof_priv)
/*
@  如果要释放iio_dev
@  indio_dev:需要释放的 iio_dev。
@  返回值:无
*/
void iio_device_free(struct iio_dev *indio_dev)
/*
@  前面分配好 iio_dev 以后就要初始化各种成员变量,初始化完成以后就需要将 iio_dev注册到内核中
@  indio_dev:需要注册的 iio_dev
@  返回值:0,成功;其他值,失败
*/
int iio_device_register(struct iio_dev *indio_dev)
/*
@  如果要注销iio_dev使用iio_device_unregister函数
@  indio_dev:需要注销的 iio_dev。
@  返回值:0,成功;其他值,失败
*/
void iio_device_unregister(struct iio_dev *indio_dev) /*
@   include/linux/iio/iio.h 中
@   iio_info 结构体
*/struct iio_info { struct module           *driver_module; struct attribute_group      *event_attrs; const struct attribute_group    *attrs; /*
@  分别为 read_raw 和write_raw 函数,这两个函数就是最终读写设备内部数据的操作函数,需要程序编写人员去实现的。
@  indio_dev:需要读写的 IIO 设备。
@  chan:需要读取的通道。
@  val,val2:对于 read_raw 函数来说 val 和 val2 这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等
@  val和val2共同组成具体值,val是整数部分,val2是小数部分
*/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);
... }; /*
@  支持8个通道,支持8路采集数据
@  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; };

3、IIO驱动框架

  • 首先上一个普通的字符设备驱动框架
 /* *******************************************************************SPI 驱动框架 * @description    : spi 驱动的 probe 函数,当驱动与 *                      设备匹配以后此函数就会执行 * @param - spi    : spi 设备 * @return            : 0,成功;其他值,失败 */  static int xxx_probe(struct spi_device *spi) { return 0; } /* * @description  : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行 * @param - spi    : spi 设备 * @return          : 0,成功;其他负值,失败 */ static int xxx_remove(struct spi_device *spi) { return 0; } /* 传统匹配方式 ID 列表 */ static const struct spi_device_id xxx_id[] = { {"alientek,xxx", 0}, {} }; /* 设备树匹配列表 */ static const struct of_device_id xxx_of_match[] = { { .compatible = "alientek,xxx" }, { /* Sentinel */ } }; /* SPI 驱动结构体 */ static struct spi_driver xxx_driver = { .probe = xxx_probe, .remove = xxx_remove, .driver = { .owner = THIS_MODULE, .name = "xxx", .of_match_table = xxx_of_match, }, .id_table = xxx_id, }; /* * @description    : 驱动入口函数 * @param          : 无 * @return         : 无 */ static int __init xxx_init(void) { return spi_register_driver(&xxx_driver); } /* * @description    : 驱动出口函数 * @param          : 无 * @return         : 无 */ static void __exit xxx_exit(void) { spi_unregister_driver(&xxx_driver); } module_init(xxx_init); module_exit(xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ALIENTEK");
  • 在此基础上添加IIO框架
 /* 自定义设备结构体 */ struct xxx_dev { struct spi_device *spi;          /* spi 设备 */ struct regmap *regmap;                /* regmap   */ struct regmap_config regmap_config;  struct mutex lock; }; /* * 通道数组 */ static const struct iio_chan_spec xxx_channels[] = { }; /* * @description    : 读函数,当读取 sysfs 中的文件的时候最终此函数会执行,*                      :此函数里面会从传感器里面读取各种数据,然后上传给应用。* @param - indio_dev  : IIO 设备 * @param - chan    : 通道 * @param - val      : 读取的值,如果是小数值的话,val 是整数部分。 * @param - val2     : 读取的值,如果是小数值的话,val2 是小数部分。 * @param - mask     : 掩码。 * @return            : 0,成功;其他值,错误 */ static int xxx_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { return 0; }    /* * @description        : 写函数,当向 sysfs 中的文件写数据的时候最终此函数*                         :会执行,一般在此函数里面设置传感器,比如量程等。* @param - indio_dev   : IIO 设备 * @param - chan         : 通道 * @param - val          : 应用程序写入值,如果是小数的话,val 是整数部分。 * @param - val2         : 应用程序写入值,如果是小数的话,val2 是小数部分。 * @return               : 0,成功;其他值,错误 */ static int xxx_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { return 0; } /* * @description    : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设 *                      :置传感器的分辨率,如果分辨率带小数,那么这个小数传递到 *                       : 内核空间应该扩大多少倍,此函数就是用来设置这个的。 * @param - indio_dev  : iio_dev * @param - chan     : 通道 * @param - mask     : 掩码 * @return            : 0,成功;其他值,错误 */ static int xxx_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, long mask) { return 0; } /* * iio_info 结构体变量 */ static const struct iio_info xxx_info = { .read_raw       = xxx_read_raw, .write_raw      = xxx_write_raw, .write_raw_get_fmt = &xxx_write_raw_get_fmt,  }; /* * @description  : spi 驱动的 probe 函数,当驱动与 *                    设备匹配以后此函数就会执行 * @param - spi  : spi 设备 *  */     static int xxx_probe(struct spi_device *spi) { int ret; struct xxx_dev *data; struct iio_dev *indio_dev; /*  1、申请 iio_dev 内存 */ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; /* 2、获取 xxx_dev 结构体地址 */ data = iio_priv(indio_dev);  data->spi = spi; spi_set_drvdata(spi, indio_dev); mutex_init(&data->lock); /* 3、初始化 iio_dev 成员变量 */ indio_dev->dev.parent = &spi->dev; indio_dev->info = &xxx_info; indio_dev->name = "xxx"; indio_dev->modes = INDIO_DIRECT_MODE;   /* 直接模式 / indio_dev->channels = xxx_channels; indio_dev->num_channels = ARRAY_SIZE(xxx_channels); iio_device_register(indio_dev); /* 4、regmap 相关设置 */ /* 5、SPI 相关设置*/ /* 6、芯片初始化 */ return 0; } /* * @description    : spi 驱动的 remove 函数,移除 spi 驱动的时候此函数会执行 * @param - spi    : spi 设备 * @return         : 0,成功;其他负值,失败 */ static int xxx_remove(struct spi_device *spi) { struct iio_dev *indio_dev = spi_get_drvdata(spi); struct xxx_dev *data; data = iio_priv(indio_dev); ; /* 1、其他资源的注销以及释放 */  /* 2、注销 IIO */ iio_device_unregister(indio_dev);return 0; }
  • 之前的文章说过简化IIC和SPI我们统一的提出了一个Regmap子系统主要就是描述怎么利用Linux内核提供的一些API,在总线(IIC、SPI等)上来对设备进行读写操作(简化底层代码的一种方法)。

4、实战演练1(SPI)

  • 对象imx6ull开发板、ICM206086轴陀螺仪(IIO框架简化ADC驱动)、SPI总线外设(Regmap简化底层代码)

  • icm20608reg.h文件

#ifndef ICM20608_H
#define ICM20608_H
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : icm20608reg.h
作者      : 左忠凯
版本      : V1.0
描述      : ICM20608寄存器地址描述头文件
其他      : 无
论坛      : www.openedv.com
日志      : 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define ICM20608G_ID            0XAF    /* ID值 */
#define ICM20608D_ID            0XAE    /* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1  = 0x40*Register 117(0X75) WHO_AM_I                 = 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define ICM20_SELF_TEST_X_GYRO      0x00
#define ICM20_SELF_TEST_Y_GYRO      0x01
#define ICM20_SELF_TEST_Z_GYRO      0x02
#define ICM20_SELF_TEST_X_ACCEL     0x0D
#define ICM20_SELF_TEST_Y_ACCEL     0x0E
#define ICM20_SELF_TEST_Z_ACCEL     0x0F/* 陀螺仪静态偏移 */
#define ICM20_XG_OFFS_USRH          0x13
#define ICM20_XG_OFFS_USRL          0x14
#define ICM20_YG_OFFS_USRH          0x15
#define ICM20_YG_OFFS_USRL          0x16
#define ICM20_ZG_OFFS_USRH          0x17
#define ICM20_ZG_OFFS_USRL          0x18#define ICM20_SMPLRT_DIV            0x19
#define ICM20_CONFIG                0x1A
#define ICM20_GYRO_CONFIG           0x1B
#define ICM20_ACCEL_CONFIG          0x1C
#define ICM20_ACCEL_CONFIG2         0x1D
#define ICM20_LP_MODE_CFG           0x1E
#define ICM20_ACCEL_WOM_THR         0x1F
#define ICM20_FIFO_EN               0x23
#define ICM20_FSYNC_INT             0x36
#define ICM20_INT_PIN_CFG           0x37
#define ICM20_INT_ENABLE            0x38
#define ICM20_INT_STATUS            0x3A/* 加速度输出 */
#define ICM20_ACCEL_XOUT_H          0x3B
#define ICM20_ACCEL_XOUT_L          0x3C
#define ICM20_ACCEL_YOUT_H          0x3D
#define ICM20_ACCEL_YOUT_L          0x3E
#define ICM20_ACCEL_ZOUT_H          0x3F
#define ICM20_ACCEL_ZOUT_L          0x40/* 温度输出 */
#define ICM20_TEMP_OUT_H            0x41
#define ICM20_TEMP_OUT_L            0x42/* 陀螺仪输出 */
#define ICM20_GYRO_XOUT_H           0x43
#define ICM20_GYRO_XOUT_L           0x44
#define ICM20_GYRO_YOUT_H           0x45
#define ICM20_GYRO_YOUT_L           0x46
#define ICM20_GYRO_ZOUT_H           0x47
#define ICM20_GYRO_ZOUT_L           0x48#define ICM20_SIGNAL_PATH_RESET     0x68
#define ICM20_ACCEL_INTEL_CTRL      0x69
#define ICM20_USER_CTRL             0x6A
#define ICM20_PWR_MGMT_1            0x6B
#define ICM20_PWR_MGMT_2            0x6C
#define ICM20_FIFO_COUNTH           0x72
#define ICM20_FIFO_COUNTL           0x73
#define ICM20_FIFO_R_W              0x74
#define ICM20_WHO_AM_I              0x75/* 加速度静态偏移 */
#define ICM20_XA_OFFSET_H           0x77
#define ICM20_XA_OFFSET_L           0x78
#define ICM20_YA_OFFSET_H           0x7A
#define ICM20_YA_OFFSET_L           0x7B
#define ICM20_ZA_OFFSET_H           0x7D
#define ICM20_ZA_OFFSET_L           0x7E#endif
  • icm20608.c驱动文件
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : icm20608.c
作者      : 正点原子Linux团队
版本      : V1.0
描述      : ICM20608 SPI驱动程序
其他      : 无
论坛      : www.openedv.com
日志      : 初版V1.0 2021/03/22 正点原子Linux团队创建V1.1 2021/08/10 使用regmap读写SPI外设内部寄存器。V1.2 2021/08/13 使用IIO框架,参考bma220_spi.c
***************************************************************/
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include "icm20608reg.h"
#include <linux/gpio.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/regmap.h>
#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>
#include <linux/unaligned/be_byteshift.h>#define ICM20608_NAME    "icm20608"
#define ICM20608_TEMP_OFFSET         0
#define ICM20608_TEMP_SCALE          326800000#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的扫描元素,3轴加速度计、* 3轴陀螺仪、1路温度传感器,1路时间戳 */
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,
};struct icm20608_dev {struct spi_device *spi;      /* spi设备 */struct regmap *regmap;               /* regmap */struct regmap_config regmap_config; struct mutex lock;
};/** 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};/** icm20608通道,1路温度通道,3路陀螺仪,3路加速度计*/
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),    /* 陀螺仪X轴 */ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_ICM20608_SCAN_GYRO_Y),    /* 陀螺仪Y轴 */ICM20608_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_ICM20608_SCAN_GYRO_Z),    /* 陀螺仪Z轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_ICM20608_SCAN_ACCL_Y),   /* 加速度X轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),   /* 加速度Y轴 */ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_Z),   /* 加速度Z轴 */
};/** @description : 读取icm20608指定寄存器值,读取一个寄存器* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器* @return      :   读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 ret;unsigned int data;ret = regmap_read(dev->regmap, reg, &data);return (u8)data;
}/** @description  : 向icm20608指定寄存器写入指定的值,写一个寄存器* @param - dev:  icm20608设备* @param - reg:  要写的寄存器* @param - data: 要写入的值* @return   :    无*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{regmap_write(dev->regmap,  reg, value);
}/** @description      : ICM20608内部寄存器初始化函数 * @param - spi    : 要操作的设备* @return          : 无*/
void icm20608_reginit(struct icm20608_dev *dev)
{u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value);    icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00);     /* 输出速率是内部采样率       */icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18);  /* 陀螺仪±2000dps量程        */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18);     /* 加速度计±16G量程       */icm20608_write_onereg(dev, ICM20_CONFIG, 0x04);       /* 陀螺仪低通滤波BW=20Hz  */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz  */icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00);   /* 打开加速度计和陀螺仪所有轴    */icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00);  /* 关闭低功耗                */icm20608_write_onereg(dev, ICM20_INT_ENABLE, 0x01);       /* 使能FIFO溢出以及数据就绪中断 */
}/** @description      : 设置ICM20608传感器,可以用于陀螺仪、加速度计设置* @param - dev    : icm20608设备 * @param - reg    : 要设置的通道寄存器首地址。* @param - anix     : 要设置的通道,比如X,Y,Z。* @param - val   : 要设置的值。* @return          : 0,成功;其他值,错误*/
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;
}/** @description      : 读取ICM20608传感器数据,可以用于陀螺仪、加速度计、温度的读取* @param - dev  : icm20608设备 * @param - reg    : 要读取的通道寄存器首地址。* @param - anix     : 需要读取的通道,比如X,Y,Z。* @param - val      : 保存读取到的值。* @return            : 0,成功;其他值,错误*/
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;
}/** @description          : 读取ICM20608陀螺仪、加速度计、温度通道值* @param - indio_dev : iio设备 * @param - chan        : 通道。* @param - val        : 保存读取到的通道值。* @return              : 0,成功;其他值,错误*/
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;
}/** @description      : 设置ICM20608的陀螺仪计量程(分辨率)* @param - dev : icm20608设备* @param - val     : 量程(分辨率值)。* @return           : 0,成功;其他值,错误*/
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;
}/** @description      : 设置ICM20608的加速度计量程(分辨率)* @param - dev : icm20608设备* @param - val     : 量程(分辨率值)。* @return           : 0,成功;其他值,错误*/
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;
}/** @description      : 读函数,当读取sysfs中的文件的时候最终此函数会执行,此函数*                    :里面会从传感器里面读取各种数据,然后上传给应用。* @param - indio_dev    : iio_dev* @param - chan       : 通道* @param - val         : 读取的值,如果是小数值的话,val是整数部分。* @param - val2     : 读取的值,如果是小数值的话,val2是小数部分。* @param - mask    : 掩码。* @return             : 0,成功;其他值,错误*/
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) {/*使用type的值来区分是哪些通道*/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_onereg(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_onereg(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;}
}   /** @description       : 写函数,当向sysfs中的文件写数据的时候最终此函数会执行,一般在此函数*                   :里面设置传感器,比如量程等。* @param - indio_dev  : iio_dev* @param - chan       : 通道* @param - val         : 应用程序写入的值,如果是小数值的话,val是整数部分。* @param - val2     : 应用程序写入的值,如果是小数值的话,val2是小数部分。* @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;
}/** @description      : 用户空间写数据格式,比如我们在用户空间操作sysfs来设置传感器的分辨率,*                  :如果分辨率带小数,那么这个小数传递到内核空间应该扩大多少倍,此函数就是*                      : 用来设置这个的。* @param - indio_dev : iio_dev* @param - chan       : 通道* @param - mask    : 掩码* @return              : 0,成功;其他值,错误*/
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;
}/** iio_info结构体变量*/
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,    /* 用户空间写数据格式 */
};/** @description    : spi驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - spi     : spi设备* @return       : 0,成功;其他值,失败*/
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、获取icm20608_dev结构体地址 */dev = iio_priv(indio_dev); dev->spi = spi;spi_set_drvdata(spi, indio_dev);         /* 将indio_de设置为spi->dev的driver_data */mutex_init(&dev->lock);/* 3、iio_dev的其他成员变量 */indio_dev->dev.parent = &spi->dev;indio_dev->info = &icm20608_info;indio_dev->name = ICM20608_NAME; indio_dev->modes = INDIO_DIRECT_MODE;   /* 直接模式,提供sysfs接口 */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_config设置 */dev->regmap_config.reg_bits = 8;            /* 寄存器长度8bit */dev->regmap_config.val_bits = 8;         /* 值长度8bit */dev->regmap_config.read_flag_mask = 0x80;  /* 读掩码设置为0X80,ICM20608使用SPI接口读的时候寄存器最高位应该为1 *//* 6、初始化SPI接口的regmap */dev->regmap = regmap_init_spi(spi, &dev->regmap_config);if (IS_ERR(dev->regmap)) {ret = PTR_ERR(dev->regmap);goto err_regmap_init;}/* 7、初始化spi_device */spi->mode = SPI_MODE_0;   /*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);/* 初始化ICM20608内部寄存器 */icm20608_reginit(dev);  return 0;err_regmap_init:iio_device_unregister(indio_dev);
err_iio_register:return ret;
}/** @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行* @param - spi  : spi设备* @return          : 0,成功;其他负值,失败*/
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;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI驱动结构体 */
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,
};/** @description : 驱动入口函数* @param       : 无* @return       : 无*/
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/
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");
MODULE_INFO(intree, "Y");
  • 测试app
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : icm20608.c
作者      : 左忠凯
版本      : V1.0
描述      : icm20608设备iio框架测试程序。
其他      : 无
使用方法     :./icm20608App
论坛      : www.openedv.com
日志      : 初版V1.0 2021/8/17 左忠凯创建
***************************************************************/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\ret = file_data_read(file_path[index], str);\dev->member = atof(str);\
/* 字符串转数字,将整数字符串转换为整数数值 */
#define SENSOR_INT_DATA_GET(ret, index, str, member)\ret = file_data_read(file_path[index], str);\dev->member = atoi(str);\

/* icm20608 iio框架对应的文件路径 */
static char *file_path[] = {/*在开发板下面的绝对路径*/"/sys/bus/iio/devices/iio:device1/in_accel_scale","/sys/bus/iio/devices/iio:device1/in_accel_x_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_x_raw","/sys/bus/iio/devices/iio:device1/in_accel_y_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_y_raw","/sys/bus/iio/devices/iio:device1/in_accel_z_calibbias","/sys/bus/iio/devices/iio:device1/in_accel_z_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_scale","/sys/bus/iio/devices/iio:device1/in_anglvel_x_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_x_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_y_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_y_raw","/sys/bus/iio/devices/iio:device1/in_anglvel_z_calibbias","/sys/bus/iio/devices/iio:device1/in_anglvel_z_raw","/sys/bus/iio/devices/iio:device1/in_temp_offset","/sys/bus/iio/devices/iio:device1/in_temp_raw","/sys/bus/iio/devices/iio:device1/in_temp_scale",
};/* 文件路径索引,要和file_path里面的文件顺序对应 */
enum path_index {IN_ACCEL_SCALE = 0,IN_ACCEL_X_CALIBBIAS,IN_ACCEL_X_RAW,IN_ACCEL_Y_CALIBBIAS,IN_ACCEL_Y_RAW,IN_ACCEL_Z_CALIBBIAS,IN_ACCEL_Z_RAW,IN_ANGLVEL_SCALE,IN_ANGLVEL_X_CALIBBIAS,IN_ANGLVEL_X_RAW,IN_ANGLVEL_Y_CALIBBIAS,IN_ANGLVEL_Y_RAW,IN_ANGLVEL_Z_CALIBBIAS,IN_ANGLVEL_Z_RAW,IN_TEMP_OFFSET,IN_TEMP_RAW,IN_TEMP_SCALE,
};/** icm20608数据设备结构体*/
struct icm20608_dev{int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;int accel_x_raw, accel_y_raw, accel_z_raw;int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;int gyro_x_raw, gyro_y_raw, gyro_z_raw;int temp_offset, temp_raw;float accel_scale, gyro_scale, temp_scale;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;
};struct icm20608_dev icm20608;/** @description            : 读取指定文件内容* @param - filename  : 要读取的文件路径* @param - str       : 读取到的文件字符串* @return               : 0 成功;其他 失败*/
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;
}/** @description  : 获取ICM20608数据* @param - dev : 设备结构体* @return         : 0 成功;其他 失败*/
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、获取温度值 */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);/* 3、转换为实际数值 */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;
}/** @description      : main主程序* @param - argc   : argv数组元素个数* @param - argv    : 具体参数* @return            : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int ret = 0;if (argc != 1) {printf("Error Usage!\r\n");return -1;}while (1) {ret = sensor_read(&icm20608);if(ret == 0) {            /* 数据读取成功 */printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", icm20608.gyro_x_raw, icm20608.gyro_y_raw, icm20608.gyro_z_raw);printf("ax = %d, ay = %d, az = %d\r\n", icm20608.accel_x_raw, icm20608.accel_y_raw, icm20608.accel_z_raw);printf("temp = %d\r\n", icm20608.temp_raw);printf("实际值:");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", icm20608.gyro_x_act, icm20608.gyro_y_act, icm20608.gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", icm20608.accel_x_act, icm20608.accel_y_act, icm20608.accel_z_act);printf("act temp = %.2f°C\r\n", icm20608.temp_act);}usleep(100000); /*100ms */}return 0;
}

IIO子系统(Linux驱动开发篇)相关推荐

  1. linux驱动开发篇(三)—— 总线设备驱动模型

    linux系列目录: linux基础篇(一)--GCC和Makefile编译过程 linux基础篇(二)--静态和动态链接 ARM裸机篇(一)--i.MX6ULL介绍 ARM裸机篇(二)--i.MX6 ...

  2. linux驱动开发篇(四)—— platform平台设备驱动

    linux系列目录: linux基础篇(一)--GCC和Makefile编译过程 linux基础篇(二)--静态和动态链接 ARM裸机篇(一)--i.MX6ULL介绍 ARM裸机篇(二)--i.MX6 ...

  3. linux内核驱动子系统,Linux 驱动开发 / IIO子系统入门1

    1. 什么是 IIO 子系统? 1.1 IIO 概述 Industrial I/O 子系统旨在为某种意义上是模数或数模转换器 (ADC,DAC) 的设备提供支持,于2009年由 Huawei 的 Jo ...

  4. linux I2C驱动(Linux驱动开发篇)

    1.linux IIC驱动 由于裸机的驱动迎合驱动的分离和分层的思想,分为IIC主机驱动(接口驱动)和IIC设备驱动. 这种思想的好处,请看我写的另外一篇文章Linux驱动的分离和分层. 其中上面说的 ...

  5. linux PWM驱动屏幕亮度及pwm子系统框架(Linux驱动开发篇)

    1.对象 imx6ull单片机,控制其下面的pwm3的外设.关于对象的详细介绍看裸机pwm控制屏幕亮度 在dtsi中的位置 /soc/aips1/pwm3 pwm3: pwm@02088000 { c ...

  6. 网络驱动简介==PHY子系统(linux驱动开发篇)

    一.PHY芯片简介 PHY是IEEE 802.3规定的一个标准模块 SOC可以对PHY 进行配置或者读取PHY 相关状态,这个就需要 PHY 内部寄存器去实现了. PHY 芯片寄存器地址空间为 5位( ...

  7. platform总线(Linux驱动开发篇)

    简单介绍platform驱动中的led驱动,input设备驱动,i2c驱动,spi驱动 1.Platform led驱动 最简单的了解platform平台的例子,可以理解为3部分,由驱动层,系统核心层 ...

  8. platform平台驱动模型简述(linux驱动开发篇)

    此篇是驱动分离(总线.驱动和设备模型)的应用扩展,主要简述platform虚拟总线平台 一个现实的Linux设备和驱动通常挂接在一种总线上,对于本身依附于PCI.USB.I2C.SPI等的设备而言,这 ...

  9. WIFI驱动配置实战(Linux驱动开发篇)

    1.对象 正点原子的imx6ull-mini开发板的USB-HOST接口 芯片RTL8188EUS/CUS USB WIFI 2.目的 单片机经过wifi芯片可以连接热点. WIFI驱动不需要我们编写 ...

最新文章

  1. 使用jQuery中trigger()方法自动触发事件
  2. Linux - How to use LVM in Linux
  3. jvm调优:jmap -histo的使用
  4. 简易的bbs论坛开发过程上(后台开发)
  5. boost::contract模块实现是否constexpr的测试程序
  6. 获“CAIS紫金奖”,腾讯民汉翻译践行“科技向善”
  7. 查看Apache的并发请求数及其TCP连接状态
  8. GDAL写入FileGDB中文属性乱码问题
  9. 二阶无源低通滤波器幅频特性曲线_几种无源滤波器的电路及频响特性对比
  10. php中控车牌识别push协议,2、实时车牌识别上传及返回
  11. BIGEMAP如何下载高程卫星地图
  12. 来自ThoughtWorks肖然的推荐
  13. 树莓派搭建http服务器实现网页监控摄像头
  14. 计算机二级office无法评分,计算机二级OFFICE评分标准
  15. uniapp 微信小程序开发 解决旧版本缓存
  16. 所有科技人员是懂计算机的,指出违反什么规律.PDF
  17. 【转】达摩祖师《悟性论》
  18. 计算机组成原理六单元答案,计算机组成原理第六章中央处理器(含答案)
  19. C# 微信支付宝 Winform 开发支付宝微信二维码 付款功能(扫码付,条码付,退款、查询、撤销)
  20. 量子计算机预言未来,科学家预测,未来5-10年内,将出现家用量子计算机

热门文章

  1. 网络芯片比较:DP83848、DM9000、enc28j60、RLD8019、w5100
  2. android设备如何苹果,手机怎么玩:如何像安卓一样管理iPhone文件?_苹果 iPhone 5_手机生活新闻-中关村在线...
  3. ARIMA模型实例讲解:时间序列预测需要多少历史数据?
  4. 读书笔记:《吴军阅读与写作讲义》
  5. python使用多进程高效处理任务
  6. LVGL的PC端移植和STM32F429阿波罗开发板移植
  7. android espresso跨程序,Android中使用Espresso进行UI测试
  8. 我的四轴飞行器经验总结(一)
  9. Linux系统安全工具之:Sxid和Skey(转)
  10. 基于haar+adaboost的人脸检测、深度学习的人脸识别技术应用综述