文章目錄
- 前言
- 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;
};
- type 為通道類型, iio_chan_type 是一個枚舉類型,列舉出了可以選擇的通道類 型,定義在 include/uapi/linux/iio/types.h 文件里面,如下圖所示,定義了很多傳感器的數據類型,ICM20608的陀螺儀部分是 IIO_ANGL_VEL 類型,加速度計部分是 IIO_ACCEL 類型,溫度部分就是 IIO_TEMP。
- indexed:當設置為 1 時,channel 為通道索引。
- 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, …… };
- address:一般會設 置為此通道對應的芯片數據寄存器地址,也可以用作其他功能,自行選擇。
- scan_index:當使用觸發緩沖區的時候,scan_index 是掃描索引。
- 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(小端)。
- 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, };
- info_mask_shared_by_type:與第7條所用到的屬性相同,如果通道存在某個屬性一致,那么可以通過共享只設置一個即可,比如ICM20608 加速度計的 X、Y、Z 軸他們的 type 都 是 IIO_ACCEL,也就是類型相同。而這三個軸的分辨率(量程)是一樣的,那么在配置這三個通 道的時候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 這個屬性,表示 這三個通道的分辨率是共用的,這樣在 sysfs 下就會只生成一個描述分辨率的文件,這三個通道 都可以使用這一個分辨率文件。
- info_mask_shared_by_dir:導出的信息由相同方向的通道共享
- info_mask_shared_by_all:無論這些通道的類 型、方向如何,全部共享。
- output:表示為輸出通道。
- 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 數字 > 文件 //寫入的是數字
應用程序讀取
在用命令行讀取的方式中我們可以知道,我們所讀取的是流文件,因此我們打開和讀寫操作要用文件流操作函數。
-
打開文件流操作函數是
FILE *fopen(const char *pathname, const char *mode)
- pathname:需要打開的文件流路徑。
- mode:打開方式
-
關閉文件流
int fclose(FILE *stream)
- stream:要關閉的文件流指針。
-
讀取文件流
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
- ptr:要讀取的數組中首個對象的指針。
- size:每個對象的大小。
- nmemb:要讀取的對象個數。
- stream:要讀取的文件流。
- 返回值:返回讀取成功的對象個數,如果出現錯誤或到文件末尾,那么返回一個短計數值 (或者 0)。
-
寫文件流
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- ptr:要寫入的數組中首個對象的指針。
- size:每個對象的大小。
- nmemb:要寫入的對象個數。
- stream:要寫入的文件流。
- 返回值:返回成功寫入的對象個數,如果出現錯誤或到文件末尾,那么返回一個短計數值 (或者 0)。
-
格式化輸入文件流
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子系統的框架參考。
參考文獻
- 個人專欄系列文章
- 正點原子嵌入式驅動開發指南
- 對代碼有興趣的同學可以查看鏈接https://github.com/NUAATRY/imx6ull_dev