嵌入式驅動開發詳解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.pswp.cn/web/63757.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63757.shtml
英文地址,請注明出處:http://en.pswp.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…

《 火星人 》

題目描述 人類終于登上了火星的土地并且見到了神秘的火星人。人類和火星人都無法理解對方的語言&#xff0c;但是我們的科學家發明了一種用數字交流的方法。這種交流方法是這樣的&#xff0c;首先&#xff0c;火星人把一個非常大的數字告訴人類科學家&#xff0c;科學家破解這…

機器學習基礎算法 (二)-邏輯回歸

python 環境的配置參考 從零開始&#xff1a;Python 環境搭建與工具配置 邏輯回歸是一種用于解決二分類問題的機器學習算法&#xff0c;它可以預測輸入數據屬于某個類別的概率。本文將詳細介紹邏輯回歸的原理、Python 實現、模型評估和調優&#xff0c;并結合垃圾郵件分類案例進…

BiTCN-BiGRU基于雙向時間卷積網絡結合雙向門控循環單元的數據多特征分類預測(多輸入單輸出)

Matlab實現BiTCN-BiGRU基于雙向時間卷積網絡結合雙向門控循環單元的數據多特征分類預測&#xff08;多輸入單輸出&#xff09; 目錄 Matlab實現BiTCN-BiGRU基于雙向時間卷積網絡結合雙向門控循環單元的數據多特征分類預測&#xff08;多輸入單輸出&#xff09;分類效果基本描述…

云備份項目--工具類編寫

4. 文件工具類的設計 4.1 整體的類 該類實現對文件進行操作 FileUtil.hpp如下 /* 該類實現對文件進行操作 */ #pragma once #include <iostream> #include <string> #include <fstream> #include <vector> #include <sys/types.h> #include …

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是目前世界上最流行…

消息隊列 Kafka 架構組件及其特性

Kafka 人們通常有時會將 Kafka 中的 Topic 比作隊列&#xff1b; 在 Kafka 中&#xff0c;數據是以主題&#xff08;Topic&#xff09;的形式組織的&#xff0c;每個 Topic 可以被分為多個分區&#xff08;Partition&#xff09;。每個 Partition 是一個有序的、不可變的消息…

《Mycat核心技術》第06章:Mycat問題處理總結

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章匯總&#xff1a;https://binghe.gitcode.host/md/all/all.html 星球項目地址&#xff1a;https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀&#xff0c…

【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…

工具環境 | 工具準備

搭建一套驗證環境需要的工具如下&#xff1a; 虛擬機&#xff1a;推薦virtualbox ubuntu VM好用&#xff0c;但是免費的好像木有了&#xff0c;ubuntu界面版更加容易上手。 網上找安裝教程即可&#xff0c;注意實現文件共享、復制粘貼等功能。 EDA&#xff1a;VCS Veridi 工…

計算機網絡之王道考研讀書筆記-2

第 2 章 物理層 2.1 通信基礎 2.1.1 基本概念 1.數據、信號與碼元 通信的目的是傳輸信息。數據是指傳送信息的實體。信號則是數據的電氣或電磁表現&#xff0c;是數據在傳輸過程中的存在形式。碼元是數字通信中數字信號的計量單位&#xff0c;這個時長內的信號稱為 k 進制碼…

ROS2學習配套C++知識

第3章 訂閱和發布——話題通信探索 3.3.1 發布速度控制海龜畫圓 std::bind cstd::bind綁定成員函數時&#xff0c;需要加上作用域以及取址符號 因為不會將成員函數隱式的轉換成指針&#xff0c;因此需要加&符號&#xff1b; 后面的第一個參數必須跟具體對象&#xff0c;c…

法規標準-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模塊安全事件響應/網絡安全數據取證/…