工業場合里面也有大量的模擬量和數字量之間的轉換,也就是我們常說的 ADC 和 DAC。而且隨着手機、物聯網、工業物聯網和可穿戴設備的爆發,傳感器的需求只持續增強。比如手機或者手環里面的加速度計、光傳感器、陀螺儀、氣壓計、磁力計等,這些傳感器本質上都是ADC,大家注意查看這些傳感器的手冊,會發現他們內部都會有個 ADC,傳感器對外提供 IIC或者 SPI 接口,SOC 可以通過 IIC 或者 SPI 接口來獲取到傳感器內部的 ADC 數值,從而得到想要測量的結果。Linux 內核為了管理這些日益增多的 ADC 類傳感器,特地推出了 IIO 子系統,本章我們就來學習如何使用 IIO 子系統來編寫 ADC 類傳感器驅動。
1.IIO子系統簡介
IIO 全稱是 Industrial I/O,翻譯過來就是工業 I/O,大家不要看到“工業”兩個字就覺得 IIO是只用於工業領域的。大家一般在搜索 IIO 子系統的時候,會發現大多數講的都是 ADC,這是因為 IIO 就是為 ADC 類傳感器准備的,當然了 DAC 也是可以的。大家常用的陀螺儀、加速度計、電壓/電流測量芯片、光照傳感器、壓力傳感器等內部都是有個 ADC,內部 ADC 將原始的模擬數據轉換為數字量,然后通過其他的通信接口,比如 IIC、SPI 等傳輸給 SOC。
因此,當你使用的傳感器本質是 ADC 或 DAC 器件的時候,可以優先考慮使用 IIO 驅動框架。
1.1 iio_dev
iio_dev 結構體
第 478 行,currentmode 為當前模式。
第 483 行,buffer 為緩沖區。
第 484 行,buffer_list 為當前匹配的緩沖區列表。
第 485 行,scan_bytes 為捕獲到,並且提供給緩沖區的字節數。
第 488 行,available_scan_masks 為可選的掃描位掩碼,使用觸發緩沖區的時候可以通過設置掩碼來確定使能哪些通道,使能以后的通道會將捕獲到的數據發送到 IIO 緩沖區。
第 490 行,active_scan_mask 為緩沖區已經開啟的通道掩碼。只有這些使能了的通道數據才能被發送到緩沖區。
第 491 行,scan_timestamp 為掃描時間戳,如果使能以后會將捕獲時間戳放到緩沖區里面。
第 493 行,trig 為 IIO 設備當前觸發器,當使用緩沖模式的時候。
第 494 行,pollfunc 為一個函數,在接收到的觸發器上運行。
第 496 行,channels 為 IIO 設備通道,為 iio_chan_spec 結構體類型,稍后會細講解 IIO通道。
第 497 行,num_channels 為 IIO 設備的通道數。
第 501 行,name 為 IIO 設備名字。
第 502 行,info 為 iio_info 結構體類型,這個結構體里面有很多函數,需要驅動開發人員編寫,非常重要!我們從用戶空間讀取 IIO 設備內部數據,最終調用的就是 iio_info 里面的函數。稍后會詳細講解 iio_info 結構體。
第 504 行,setup_ops 為 iio_buffer_setup_ops 結構體類型,內容如下:
iio_dev 申請與釋放
iio_dev 注冊與注銷
1.2 iio_info
iio_dev 有個成員變量:info,為 iio_info 結構體指針變量,這個是我們在編寫 IIO 驅動的時候需要着重去實現的,因為用戶空間對設備的具體操作最終都會反映到 iio_info 里面。iio_info結構體定義在 include/linux/iio/iio.h 中,結構體定義如下(有省略):
第 355 行,attrs 是通用的設備屬性。
第 357 和 370 行,分別為 read_raw 和 write_raw 函數,這兩個函數就是最終讀寫設備內部數據的操作函數,需要程序編寫人員去實現的。比如應用讀取一個陀螺儀傳感器的原始數據,那么最終完成工作的就是 read_raw 函數,我們需要在 read_raw 函數里面實現對陀螺儀芯片的讀取操作。同理,write_raw 是應用程序向陀螺儀芯片寫數據,一般用於配置芯片,比如量程、數據速率等。這兩個函數的參數都是一樣的,我們依次來看一下:
mask:掩碼,用於指定我們讀取的是什么數據,比如 ICM20608 這樣的傳感器,他既有原始的測量數據,比如 X,Y,Z 軸的陀螺儀、加速度計等,也有測量范圍值,或者分辨率。比如加速度計測量范圍設置為±16g,那么分辨率就是 32/65536≈0.000488,我們只有讀出原始值以及對應的分辨率(量程),才能計算出真實的重力加速度。此時就有兩種數據值:傳感器原始值、分辨率。Linux 內核使用 IIO_CHAN_INFO_RAW 和 IIO_CHAN_INFO_SCALE 這兩個宏來表示原始值以及分辨率,這兩個宏就是掩碼。至於每個通道可以采用哪幾種掩碼,這個在我們初始化通道的時候需要驅動編寫人員設置好。掩碼有很多種,稍后講解 IIO 通道的時候詳細講解!
第 376 行的 write_raw_get_fmt 用於設置用戶空間向內核空間寫入的數據格式,write_raw_get_fmt 函數決定了 wtite_raw 函數中 val 和 val2 的意義,也就是表 75.1.2.1 中的組合形式。比如我們需要在應用程序中設置 ICM20608 加速度計的量程為±8g,那么分辨率就是16/65536 ≈0.000244 ,我們 在 write_raw_get_fmt 函數 里面設置 加速度計的數 據格式 為IIO_VAL_INT_PLUS_MICRO。那么我們在應用程序里面向指定的文件寫入 0.000244 以后,最終傳遞給內核驅動的就是 0.000244* 1000000=244。也就是 write_raw 函數的 val 參數為 0,val2參數為 244
1.3 iio_chan_spec
IIO 的核心就是通道,一個傳感器可能有多路數據,比如一個 ADC 芯片支持 8 路采集,那么這個 ADC 就有 8 個通道。我們本章實驗用到的 ICM20608,這是一個六軸傳感器,可以輸出三軸陀螺儀(X、Y、Z)、三軸加速度計(X、Y、Z)和一路溫度,也就是一共有 7 路數據,因此就有 7 個通道。注意,三軸陀螺儀或加速度計的 X、Y、Z 這三個軸,每個軸都算一個通道。
Linux 內核使用 iio_chan_spec 結構體來描述通道,定義在 include/linux/iio/iio.h 文件中,內容如下:
來看一下 iio_chan_spec 結構體中一些比較重要的成員變量:
第 224 行,type 為通道類型, iio_chan_type 是一個枚舉類型,列舉出了可以選擇的通道類型,定義在 include/uapi/linux/iio/types.h 文件里面,內容如下:
從示例代碼 75.1.3.2 可以看出,目前 Linux 內核支持的傳感器類型非常豐富,而且支持類型也會不斷的增加。如果是 ADC,那就是 IIO_VOLTAGE 類型。如果是 ICM20608 這樣的多軸傳感器,那么就是復合類型了,陀螺儀部分是 IIO_ANGL_VEL 類型,加速度計部分是IIO_ACCEL 類型,溫度部分就是 IIO_TEMP。
繼續來看示例代碼 75.1.3.1 中的 iio_chan_spec 結構體,第 225 行,當成員變量 indexed 為 1時候,channel 為通道索引。
第 226 行,當成員變量 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 這三個軸。通道修飾符主要是影響 sysfs 下的通道文件名字,后面我們會講解 sysfs 下通道文件名字組成形式。
繼續回到示例代碼 75.1.3.1,第 227 行的 address 成員變量用戶可以自定義,但是一般會設置為此通道對應的芯片數據寄存器地址。比如 ICM20608 的加速度計 X 軸這個通道,它的數據首地址就是 0X3B。address 也可以用作其他功能,自行選擇,也可以不使用 address,一切以實際情況為准。
第 228 行,當使用觸發緩沖區的時候,scan_index 是掃描索引。
第 229~236,scan_type 是一個結構體,描述了掃描數據在緩沖區中的存儲格式。我們依次來看一下 scan_type 各個成員變量的涵義:
比如 ICM20608 加速度計的 X、Y、Z 這三個軸,在 sysfs 下這三個軸肯定是對應三個不同的文件,我們通過讀取這三個文件就能得到每個軸的原始數據。IIO_CHAN_INFO_RAW 這個屬性表示原始數據,當我們在配置 X、Y、Z 這三個通道的時候,在 info_mask_separate 中使能IIO_CHAN_INFO_RAW 這個屬性,那么就表示在 sysfs 下生成三個不同的文件分別對應 X、Y、Z 軸,這三個軸的 IIO_CHAN_INFO_RAW 屬性是相互獨立的。
第 238 行,info_mask_shared_by_type 標記導出的信息由相同類型的通道共享。也就是iio_chan_spec.type 成員變量相同的通道。比如 ICM20608 加速度計的 X、Y、Z 軸他們的 type 都是 IIO_ACCEL,也就是類型相同。而這三個軸的分辨率(量程)是一樣的,那么在配置這三個通道的時候就可以在 info_mask_shared_by_type 中使能 IIO_CHAN_INFO_SCALE 這個屬性,表示這三個通道的分辨率是共用的,這樣在 sysfs 下就會只生成一個描述分辨率的文件,這三個通道都可以使用這一個分辨率文件。
第 239 行,info_mask_shared_by_dir 標記某些導出的信息由相同方向的通道共享。
第 240 行,info_mask_shared_by_all 表設計某些信息所有的通道共享,無論這些通道的類型、方向如何,全部共享。
第 246 行,modified 為 1 的時候,channel2 為通道修飾符。
第 247 行,indexed 為 1 的時候,channel 為通道索引。
第 248 行,output 表示為輸出通道。
第 249 行,differential 表示為差分通道。
2.IIO驅動框架創建
2.1 基礎驅動框架建立
同spi和IIC驅動框架
2.2 IIO設備申請與初始化
第 2~7 行,用戶自定義的設備結構體。
第 12 行,IIO 通道數組。
第 16~71 行, 這部分為 iio_info,當應用程序讀取相應的驅動文件的時候,xxx_read_raw函數就會執行,我們在此函數中會讀取傳感器數據,然后返回給應用層。當應用層向相應的驅動寫數據的時候,xxx_write_raw 函數就會執行。因此 xxx_read_raw 和 xxx_write_raw 這兩個函數是非常重要的!需要我們根據具體的傳感器來編寫,這兩個函數是編寫 IIO 驅動的核心。
第 79~114 行,xxx_probe 函數,此函數的核心就是分配並初始化 iio_dev,最后向內核注冊iio_dev。第 86 行調用 devm_iio_device_alloc 函數分配 iio_dev 內存,這里連用戶自定義的設備結構體變量內存一起申請了。第 91 行調用 iio_priv 函數從 iio_dev 中提取出私有數據,這個私有數據就是設備結構體變量。第 97~102 行初始化 iio_dev,重點是第 98 行設置 iio_dev 的 info成員變量。第 101 行設置 iio_dev 的通道。初始化完成以后就要調用 iio_device_register 函數向內核注冊 iio_dev。整個過程就是:申請 iio_dev、初始化、注冊,和我們前面講解的其他驅動框架步驟一樣。
第 121~134 行,xxx_remove 函數里面需要做的就是釋放 xxx_probe 函數申請到的 IIO 相關資源,比如第 131 行,使用 iio_device_unregister 注銷掉前面注冊的 iio_dev。由於前面我們使用devm_iio_device_alloc 函數申請的 iio_dev,因此不需要在 remove 函數中手動釋放 iio_dev。
3. 驅動代碼
3.1 使能內核設置
3.2 代碼
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h> /* 異步通知 */
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include "icm20608reg.h"
#include <linux/regmap.h>
/* 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>
#include <linux/unaligned/be_byteshift.h>
#define ICM20608_NAME "icm20608_"
#define ICM20608_TEMP_OFFSET 0
#define ICM20608_TEMP_SCALE 326800000
/* 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};
/* 通道修飾符,用來指定 X、Y、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 原始數據類型 */
/* 為有符號類型、實際位數 16bit,存儲位數 16bit,大端模式 */
#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 路時間戳
*/
/* 自定義的掃描索引枚舉類型 inv_icm20608_scan,包括陀螺儀、加速度計的 6
個通道,溫度計的 1 個通道、以及 1 個 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,
};
struct icm20608_dev {
struct spi_device *spi; /* spi 設備 */
struct regmap *regmap; /* regmap */
struct regmap_config regmap_config;
struct mutex lock;
};
/*
* icm20608 通道,1 路溫度通道,3 路陀螺儀,3 路加速度計
*/
/* 這里定義了 7 個通道,分別是:1 個溫度通道,3 個陀螺儀
通道(X、Y、Z),3 個加速度計通道(X、Y、Z)。 */
static const struct iio_chan_spec icm20608_channels[] = {
/* 溫度通道 */
{
.type = IIO_TEMP,
/* IIO_CHAN_INFO_RAW
為溫度通道的原始值,IIO_CHAN_INFO_OFFSET
是 ICM20608 溫度 offset 值 */
/* IIO_CHAN_INFO_SCALE 是 ICM20608 的比例,
也就是一個單位的原始值為多少℃ */
.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,
},
},
/* IIO_MOD_X、IIO_MOD_Y 和 IIO_MOD_Z 分別是 X、Y、Z 這三個軸
的修飾符 */
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_Y, INV_ICM20608_SCAN_ACCL_Y),
ICM20608_CHAN(IIO_ACCEL, IIO_MOD_X, INV_ICM20608_SCAN_ACCL_X),
ICM20608_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_ICM20608_SCAN_ACCL_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);
icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18);
icm20608_write_onereg(dev, ICM20_CONFIG, 0x04);
icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04);
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);
}
/*
* @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 : 讀函數,當讀取 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) {
case IIO_CHAN_INFO_RAW: /* 讀取 ICM20608 加速度計、陀螺儀、溫度傳感器原始值 */
iio_device_claim_direct_mode(indio_dev);/* 保持 direct 模式 */
mutex_lock(&dev->lock); /* 上鎖 */
ret = icm20608_read_channel_data(indio_dev, chan, val);
mutex_unlock(&dev->lock); /* 釋放鎖 */
iio_device_release_direct_mode(indio_dev);
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 : 設置 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 - 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;
}
/*
* @descriptio : 寫函數,當向 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;
iio_device_claim_direct_mode(indio_dev);
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;
}
iio_device_release_direct_mode(indio_dev);
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:
return IIO_VAL_INT_PLUS_MICRO;
default:
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);
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 */
/* 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("Shao Zheming");
MODULE_INFO(intree, "Y");
4.應用代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "signal.h"
#include <linux/input.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) {
} 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;
}