目錄
1.Linux下SPI驅動架構簡介
1)SPI主機驅動
2)SPI裝置驅動
3)SPI裝置和驅動比對過程
2.SPI裝置驅動編寫流程
1)SPI裝置資訊描述
2)SPI裝置資料收發處理流程
3.試驗程式編寫
1)修改裝置樹
2)編寫ICM20608驅動
3)編寫測試APP
4.運作測試
1)編譯驅動程式和測試APP
2)運作測試
上一章我們講解了如何編寫Linux下的I2C裝置驅動,SPI也是很常用的串行通信協定,本章我
們就來學習如何在Linux下編寫SPI裝置驅動。本章實驗的最終目的就是驅動I.MX6U-ALPHA開
發闆上的ICM-20608這個SPI接口的六軸傳感器,可以在應用程式中讀取ICM-20608的原始傳
感器資料。
1.Linux下SPI驅動架構簡介
SPI驅動架構和I2C很類似,都分為主機控制器驅動和裝置驅動,主機控制器也就是SOC的SPI
控制器接口。比如在裸機篇中的《第二十七章SPI實驗》,我們編寫了bsp_spi.c和bsp_spi.h這
兩個檔案,這兩個檔案是I.MX6U的SPI控制器驅動,我們編寫好SPI控制器驅動以後就可以直
接使用了,不管是什麼SPI裝置,SPI控制器部分的驅動都是一樣,我們的重點就落在了種類繁
多的SPI裝置驅動。
1)SPI主機驅動
SPI主機驅動就是SOC的SPI控制器驅動,類似I2C驅動裡面的擴充卡驅動。Linux核心使
用spi_master表示SPI主機驅動,spi_master是個結構體,定義在include/linux/spi/spi.h文
件中,内容如下(有縮減):
315 struct spi_master {
316 struct device dev;
317
318 struct list_head list;
......
326 s16 bus_num;
327
328 /* chipselects will be integral to many controllers; some others
329 * might use board-specific GPIOs.
330 */
331 u16 num_chipselect;
332
333 /* some SPI controllers pose alignment requirements on DMAable
334 * buffers; let protocol drivers know about these requirements.
335 */
336 u16 dma_alignment;
337
338 /* spi_device.mode flags understood by this controller driver */
339 u16 mode_bits;
340
341 /* bitmask of supported bits_per_word for transfers */
342 u32 bits_per_word_mask;
......
347 /* limits on transfer speed */
348 u32 min_speed_hz;
349 u32 max_speed_hz;
350
351 /* other constraints relevant to this driver */
352 u16 flags;
......
359 /* lock and mutex for SPI bus locking */
360 spinlock_t bus_lock_spinlock;
361 struct mutex bus_lock_mutex;
362
363 /* flag indicating that the SPI bus is locked for exclusive use */
364 bool bus_lock_flag;
......
372 int (*setup)(struct spi_device *spi);
373
......
393 int (*transfer)(struct spi_device *spi,
394 struct spi_message *mesg);
......
434 int (*transfer_one_message)(struct spi_master *master,
435 struct spi_message *mesg);
......
462 };
第393行,transfer函數,和i2c_algorithm中的master_xfer函數一樣,控制器資料傳輸函
數。
第434行,transfer_one_message函數,也用于SPI資料發送,用于發送一個
spi_message,SPI的資料會打包成spi_message,然後以隊列方式發送出去。
也就是SPI主機端最終會通過transfer函數與SPI裝置進行通信,是以對于SPI主
機控制器的驅動編寫者而言transfer函數是需要實作的,因為不同的SOC其SPI
控制器不同,寄存器都不一樣。和I2C擴充卡驅動一樣,SPI主機驅動一般都是
SOC廠商去編寫的,是以我們作為SOC的使用者,這一部分的驅動就不用操
心了,除非你是在SOC原廠工作,内容就是寫SPI主機驅動。
SPI主機驅動的核心就是申請spi_master,然後初始化spi_master,最後向Linux核心注冊
spi_master。
1、spi_master申請與釋放
spi_alloc_master函數用于申請spi_master,函數原型如下:
struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)
函數參數和傳回值含義如下:
dev:裝置,一般是platform_device中的dev成員變量。
size:私有資料大小,可以通過spi_master_get_devdata函數擷取到這些私有資料。
傳回值:申請到的spi_master。
spi_master的釋放通過spi_master_put函數來完成,當我們删除一個SPI主機驅動的時
候就需要釋放掉前面申請的spi_master,spi_master_put函數原型如下:
void spi_master_put(struct spi_master *master)
函數參數和傳回值含義如下:
master:要釋放的spi_master。
傳回值:無。
2、spi_master的注冊與登出
當spi_master初始化完成以後就需要将其注冊到Linux核心,spi_master注冊函數為
spi_register_master,函數原型如下:
int spi_register_master(struct spi_master *master)
函數參數和傳回值含義如下:
master:要注冊的spi_master。
傳回值:0,成功;負值,失敗。
I.MX6U的SPI主機驅動會采用spi_bitbang_start這個API函數來完成spi_master的注
冊,spi_bitbang_start函數内部其實也是通過調用spi_register_master函數來完成
spi_master的注冊。如果要登出spi_master的話可以使用spi_unregister_master函
數,此函數原型為:
void spi_unregister_master(struct spi_master *master)
函數參數和傳回值含義如下:
master:要登出的spi_master。
傳回值:無。
如果使用spi_bitbang_start注冊spi_master的話就要使用spi_bitbang_stop來登出掉
spi_master。
2)SPI裝置驅動
spi裝置驅動也和i2c裝置驅動也很類似,Linux核心使用spi_driver結構體來表示spi裝置驅
動,我們在編寫SPI裝置驅動的時候需要實作spi_driver。spi_driver結構體定義在
include/linux/spi/spi.h檔案中,結構體内容如下:
180 struct spi_driver {
181 const struct spi_device_id *id_table;
182 int (*probe)(struct spi_device *spi);
183 int (*remove)(struct spi_device *spi);
184 void (*shutdown)(struct spi_device *spi);
185 struct device_driver driver;
186 };
可以看出,spi_driver和i2c_driver、platform_driver基本一樣,當SPI裝置和驅動比對成功
以後probe函數就會執行。同樣的,spi_driver初始化完成以後需要向Linux核心注冊,
spi_driver注冊函數為spi_register_driver,函數原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函數參數和傳回值含義如下:
sdrv:要注冊的spi_driver。
傳回值:0,注冊成功;指派,注冊失敗。
登出SPI裝置驅動以後也需要登出掉前面注冊的spi_driver,使用spi_unregister_driver函
數完成spi_driver的登出,函數原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函數參數和傳回值含義如下:
sdrv:要登出的spi_driver。
傳回值:無。
spi_driver注冊示例程式如下:
1 /* probe 函數 */
2 static int xxx_probe(struct spi_device *spi)
3 {
4 /* 具體函數内容 */
5 return 0;
6 }
7
8 /* remove 函數 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具體函數内容 */
12 return 0;
13 }
14 /* 傳統比對方式 ID 清單 */
15 static const struct spi_device_id xxx_id[] = {
16 {"xxx", 0},
17 {}
18 };
19
20 /* 裝置樹比對清單 */
21 static const struct of_device_id xxx_of_match[] = {
22 { .compatible = "xxx" },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驅動結構體 */
27 static struct spi_driver xxx_driver = {
28 .probe = xxx_probe,
29 .remove = xxx_remove,
30 .driver = {
31 .owner = THIS_MODULE,
32 .name = "xxx",
33 .of_match_table = xxx_of_match,
34 },
35 .id_table = xxx_id,
36 };
37
38 /* 驅動入口函數 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(&xxx_driver);
42 }
43
44 /* 驅動出口函數 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(&xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);
第1~36行,spi_driver結構體,需要SPI裝置驅動人員編寫,包括比對表、probe函數等。
和i2c_driver、platform_driver一樣,就不詳細講解了。
第39~42行,在驅動入口函數中調用spi_register_driver來注冊spi_driver。
第45~48行,在驅動出口函數中調用spi_unregister_driver來登出spi_driver。
3)SPI裝置和驅動比對過程
SPI裝置和驅動的比對過程是由SPI總線來完成的,這點和platform、I2C等驅動一樣,SPI
總線為spi_bus_type,定義在drivers/spi/spi.c檔案中,内容如下:
131 struct bus_type spi_bus_type = {
132 .name = "spi",
133 .dev_groups = spi_dev_groups,
134 .match = spi_match_device,
135 .uevent = spi_uevent,
136 };
可以看出,SPI 裝置和驅動的比對函數為 spi_match_device,函數内容如下:
99 static int spi_match_device(struct device *dev, struct device_driver *drv)
100 {
101 const struct spi_device *spi = to_spi_device(dev);
102 const struct spi_driver *sdrv = to_spi_driver(drv);
103
104 /* Attempt an OF style match */
105 if (of_driver_match_device(dev, drv))
106 return 1;
107
108 /* Then try ACPI */
109 if (acpi_driver_match_device(dev, drv))
110 return 1;
111
112 if (sdrv->id_table)
113 return !!spi_match_id(sdrv->id_table, spi);
114
115 return strcmp(spi->modalias, drv->name) == 0;
116 }
spi_match_device函數和i2c_match_device函數的對于裝置和驅動的比對過程基本一樣。
第105行,of_driver_match_device函數用于完成裝置樹裝置和驅動比對。比較SPI裝置節
點的compatible屬性和of_device_id中的compatible屬性是否相等,如果相當的
話就表示SPI裝置和驅動比對。
第109行,acpi_driver_match_device函數用于ACPI形式的比對。
第113行,spi_match_id函數用于傳統的、無裝置樹的SPI裝置和驅動比對過程。比較SPI
裝置名字和spi_device_id的name字段是否相等,相等的話就說明SPI裝置和驅
動比對。
第115行,比較spi_device中modalias成員變量和device_driver中的name成員變量是否相
等。
2.SPI裝置驅動編寫流程
1)SPI裝置資訊描述
1、IO的的pinctrl子節點建立與修改
首先肯定是根據所使用的IO來建立或修改pinctrl子節點,這個沒什麼好說的,唯獨要
注意的就是檢查相應的IO有沒有被其他的裝置所使用,如果有的話需要将其删除掉!
2、SPI裝置節點的建立與修改
采用裝置樹的情況下,SPI裝置資訊描述就通過建立相應的裝置子節點來完成,我們
可以打開imx6qdl-sabresd.dtsi這個裝置樹頭檔案,在此檔案裡面找到如下所示内容:
308 &ecspi1 {
309 fsl,spi-num-chipselects = <1>;
310 cs-gpios = <&gpio4 9 0>;
311 pinctrl-names = "default";
312 pinctrl-0 = <&pinctrl_ecspi1>;
313 status = "okay";
314
315 flash: [email protected] {
316 #address-cells = <1>;
317 #size-cells = <1>;
318 compatible = "st,m25p32";
319 spi-max-frequency = <20000000>;
320 reg = <0>;
321 };
322 };
以上代碼是I.MX6Q的一款闆子上的一個SPI裝置節點,在這個闆子的ECSPI接口上接
了一個m25p80,這是一個SPI接口的裝置。
第309行,設定“fsl,spi-num-chipselects”屬性為1,表示隻有一個裝置。
第310行,設定“cs-gpios”屬性,也就是片選信号為GPIO4_IO09。
第311行,設定“pinctrl-names”屬性,也就是SPI裝置所使用的IO名字。
第312行,設定“pinctrl-0”屬性,也就是所使用的IO對應的pinctrl節點。
第313行,将ecspi1節點的“status”屬性改為“okay”。
第315~320行,ecspi1下的m25p80裝置資訊,每一個SPI裝置都采用一個子節點來描
述其裝置資訊。第315行的“[email protected]”後面的“0”表示m25p80的接到
了ECSPI的通道0上。這個要根據自己的具體硬體來設定。第318行,
SPI裝置的compatible屬性值,用于比對裝置驅動。第319行,“spi-
max-frequency”屬性設定SPI控制器的最高頻率,這個要根據所使用
的SPI裝置來設定,比如在這裡将SPI控制器最高頻率設定為
20MHz。第320行,reg屬性設定m25p80這個裝置所使用的ECSPI通
道,和“[email protected]”後面的“0”一樣。
我們一會在編寫ICM20608的裝置樹節點資訊的時候就參考之前代碼中的内容即可。
2)SPI裝置資料收發處理流程
SPI裝置驅動的核心是spi_driver,這個我們已經在之前的小節講過了。當我們向Linux内
核注冊成功spi_driver以後就可以使用SPI核心層提供的API函數來對裝置進行讀寫操作
了。首先是spi_transfer結構體,此結構體用于描述SPI傳輸資訊,結構體内容如下:
603 struct spi_transfer {
604 /* it's ok if tx_buf == rx_buf (right?)
605 * for MicroWire, one buffer must be null
606 * buffers must work with dma_*map_single() calls, unless
607 * spi_message.is_dma_mapped reports a pre-existing mapping
608 */
609 const void *tx_buf;
610 void *rx_buf;
611 unsigned len;
612
613 dma_addr_t tx_dma;
614 dma_addr_t rx_dma;
615 struct sg_table tx_sg;
616 struct sg_table rx_sg;
617
618 unsigned cs_change:1;
619 unsigned tx_nbits:3;
620 unsigned rx_nbits:3;
621 #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
622 #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
623 #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
624 u8 bits_per_word;
625 u16 delay_usecs;
626 u32 speed_hz;
627
628 struct list_head transfer_list;
629 };
第609行,tx_buf儲存着要發送的資料。
第610行,rx_buf用于儲存接收到的資料。
第611行,len是要進行傳輸的資料長度,SPI是全雙工通信,是以在一次通信中發送和接
收的位元組數都是一樣的,是以spi_transfer中也就沒有發送長度和接收長度之
分。
spi_transfer需要組織成spi_message,spi_message也是一個結構體,内容如下:
660 struct spi_message {
661 struct list_head transfers;
662
663 struct spi_device *spi;
664
665 unsigned is_dma_mapped:1;
......
678 /* completion is reported through a callback */
679 void (*complete)(void *context);
680 void *context;
681 unsigned frame_length;
682 unsigned actual_length;
683 int status;
684
685 /* for optional use by whatever driver currently owns the
686 * spi_message ... between calls to spi_async and then later
687 * complete(), that's the spi_master controller driver.
688 */
689 struct list_head queue;
690 void *state;
691 };
在使用spi_message之前需要對其進行初始化,spi_message初始化函數為
spi_message_init,函數原型如下:
void spi_message_init(struct spi_message *m)
函數參數和傳回值含義如下:
m:要初始化的spi_message。
傳回值:無。
spi_message初始化完成以後需要将spi_transfer添加到spi_message隊列中,這裡我們要
用到spi_message_add_tail函數,此函數原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
函數參數和傳回值含義如下:
t:要添加到隊列中的spi_transfer。
m:spi_transfer要加入的spi_message。
傳回值:無。
spi_message準備好以後既可以進行資料傳輸了,資料傳輸分為同步傳輸和異步傳輸,同
步傳輸會阻塞的等待SPI資料傳輸完成,同步傳輸函數為spi_sync,函數原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
函數參數和傳回值含義如下:
spi:要進行資料傳輸的spi_device。
message:要傳輸的spi_message。
傳回值:無。
異步傳輸不會阻塞的等到SPI資料傳輸完成,異步傳輸需要設定spi_message中的
complete成員變量,complete是一個回調函數,當SPI異步傳輸完成以後此函數就會被調
用。SPI異步傳輸函數為spi_async,函數原型如下:
int spi_async(struct spi_device *spi, struct spi_message *message)
函數參數和傳回值含義如下:
spi:要進行資料傳輸的spi_device。
message:要傳輸的spi_message。
傳回值:無。
在本章實驗中,我們采用同步傳輸方式來完成SPI資料的傳輸工作,也就是spi_sync函
數。綜上所述,SPI資料傳輸步驟如下:
1、申請并初始化spi_transfer,設定spi_transfer的tx_buf成員變量,tx_buf為要發送的數
據。然後設定rx_buf成員變量,rx_buf儲存着接收到的資料。最後設定len成員變量,
也就是要進行資料通信的長度。
2、使用spi_message_init函數初始化spi_message。
3、使用spi_message_add_tail函數将前面設定好的spi_transfer添加到spi_message隊列
中。
4、使用spi_sync函數完成SPI資料同步傳輸。
通過SPI進行n個位元組的資料發送和接收的示例代碼如下所示:
/* SPI 多位元組發送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 隊列 */
ret = spi_sync(spi, &m); /* 同步傳輸 */
return ret;
}
/* SPI 多位元組接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 隊列 */
ret = spi_sync(spi, &m); /* 同步傳輸 */
return ret;
}
3.試驗程式編寫
1)修改裝置樹
1、添加ICM20608所使用的IO
首先在imx6ull-alientek-emmc.dts檔案中添加ICM20608所使用的IO資訊,在iomuxc節
點中添加一個新的子節點來描述ICM20608所使用的SPI引腳,子節點名字為
pinctrl_ecspi3,節點内容如下所示:
1 pinctrl_ecspi3: icm20608 {
2 fsl,pins = <
3 MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */
4 MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
5 MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
6 MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
7 >;
8 };
UART2_TX_DATA這個IO是ICM20608的片選信号,這裡我們并沒有将其複用為
ECSPI3的SS0信号,而是将其複用為了普通的GPIO。因為我們需要自己控制片選信
号,是以将其複用為普通的GPIO。
2、在ecspi3節點追加icm20608子節點
在imx6ull-alientek-emmc.dts檔案中并沒有任何向ecspi3節點追加内容的代碼,這是因
為NXP官方的6ULLEVK開發闆上沒有連接配接SPI裝置。在imx6ull-alientek-emmc.dts文
件最後面加入如下所示内容:
1 &ecspi3 {
2 fsl,spi-num-chipselects = <1>;
3 cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* cant't use cs-gpios! */
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_ecspi3>;
6 status = "okay";
7
8 spidev: [email protected] {
9 compatible = "alientek,icm20608";
10 spi-max-frequency = <8000000>;
11 reg = <0>;
12 };
13 };
第2行,設定目前片選數量為1,因為就隻接了一個ICM20608。
第3行,注意!這裡并沒有用到“cs-gpios”屬性,而是用了一個自己定義的“cs-gpio”屬
性,因為我們要自己控制片選引腳。如果使用“cs-gpios”屬性的話SPI主機驅動
就會控制片選引腳。
第5行,設定IO要使用的pinctrl子節點,也就是我們在之前代碼中建立的
pinctrl_ecspi3。
第6行,imx6ull.dtsi檔案中預設将ecspi3節點狀态(status)設定為“disable”,這裡我們要
将其改為“okay”。
第8~12行,icm20608裝置子節點,因為icm20608連接配接在ECSPI3的第0個通道上,因
此@後面為0。第9行設定節點屬性相容值為“alientek,icm20608”,第10行
設定SPI最大時鐘頻率為8MHz,這是ICM20608的SPI接口所能支援的最大
的時鐘頻率。第11行,icm20608連接配接在通道0上,是以reg為0。
imx6ull-alientek-emmc.dts檔案修改完成以後重新編譯一下,得到新的dtb檔案,并使
用新的dtb啟動Linux系統。
2)編寫ICM20608驅動
建立名為“22_spi”的檔案夾,然後在22_spi檔案夾裡面建立vscode工程,工作區命名為
“spi”。工程建立好以後建立icm20608.c和icm20608reg.h這兩個檔案,icm20608.c為
ICM20608的驅動代碼,icm20608reg.h是ICM20608寄存器頭檔案。先在icm20608reg.h
中定義好ICM20608的寄存器,輸入如下内容(有省略,完整的内容請參考例程):
1 #ifndef ICM20608_H
2 #define ICM20608_H
3 /***************************************************************
4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
5 檔案名 : icm20608reg.h
6 作者 : 左忠凱
7 版本 : V1.0
8 描述 : ICM20608 寄存器位址描述頭檔案
9 其他 : 無
10 論壇 : www.openedv.com
11 日志 : 初版 V1.0 2019/9/2 左忠凱建立
12 ***************************************************************/
13 #define ICM20608G_ID 0XAF /* ID 值 */
14 #define ICM20608D_ID 0XAE /* ID 值 */
15
16 /* ICM20608 寄存器
17 *複位後所有寄存器位址都為 0,除了
18 *Register 107(0X6B) Power Management 1 = 0x40
19 *Register 117(0X75) WHO_AM_I = 0xAF 或 0xAE
20 */
21 /* 陀螺儀和加速度自測(出産時設定,用于與使用者的自檢輸出值比較) */
22 #define ICM20_SELF_TEST_X_GYRO 0x00
23 #define ICM20_SELF_TEST_Y_GYRO 0x01
24 #define ICM20_SELF_TEST_Z_GYRO 0x02
25 #define ICM20_SELF_TEST_X_ACCEL 0x0D
26 #define ICM20_SELF_TEST_Y_ACCEL 0x0E
27 #define ICM20_SELF_TEST_Z_ACCEL 0x0F
......
80 /* 加速度靜态偏移 */
81 #define ICM20_XA_OFFSET_H 0x77
82 #define ICM20_XA_OFFSET_L 0x78
83 #define ICM20_YA_OFFSET_H 0x7A
84 #define ICM20_YA_OFFSET_L 0x7B
85 #define ICM20_ZA_OFFSET_H 0x7D
86 #define ICM20_ZA_OFFSET_L 0x7E
87
88 #endif
接下來繼續編寫icm20608.c檔案,因為icm20608.c檔案内容比較長,是以這裡就将其分開
來講解。
1、icm20608裝置結構體建立
首先建立一個icm20608裝置結構體,如下所示:
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
......
22 #include <asm/io.h>
23 #include "icm20608reg.h"
24 /***************************************************************
25 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
26 檔案名 : icm20608.c
27 作者 : 左忠凱
28 版本 : V1.0
29 描述 : ICM20608 SPI 驅動程式
30 其他 : 無
31 論壇 : www.openedv.com
32 日志 : 初版 V1.0 2019/9/2 左忠凱建立
33 ***************************************************************/
34 #define ICM20608_CNT 1
35 #define ICM20608_NAME "icm20608"
36
37 struct icm20608_dev {
38 dev_t devid; /* 裝置号 */
39 struct cdev cdev; /* cdev */
40 struct class *class; /* 類 */
41 struct device *device; /* 裝置 */
42 struct device_node *nd; /* 裝置節點 */
43 int major; /* 主裝置号 */
44 void *private_data; /* 私有資料 */
45 int cs_gpio; /* 片選所使用的 GPIO 編号*/
46 signed int gyro_x_adc; /* 陀螺儀 X 軸原始值 */
47 signed int gyro_y_adc; /* 陀螺儀 Y 軸原始值 */
48 signed int gyro_z_adc; /* 陀螺儀 Z 軸原始值 */
49 signed int accel_x_adc; /* 加速度計 X 軸原始值 */
50 signed int accel_y_adc; /* 加速度計 Y 軸原始值 */
51 signed int accel_z_adc; /* 加速度計 Z 軸原始值 */
52 signed int temp_adc; /* 溫度原始值 */
53 };
54
55 static struct icm20608_dev icm20608dev;
icm20608的裝置結構體icm20608_dev沒什麼好講的,重點看一下第44行的
private_data,對于SPI裝置驅動來講最核心的就是spi_device。probe函數會向驅動提
供目前SPI裝置對應的spi_device,是以在probe函數中設定private_data為probe函數
傳遞進來的spi_device參數。
2、icm20608的的spi_driver注冊與登出
對于SPI裝置驅動,首先就是要初始化并向系統注冊spi_driver,icm20608的
spi_driver初始化、注冊與登出代碼如下:
1 /* 傳統比對方式 ID 清單 */
2 static const struct spi_device_id icm20608_id[] = {
3 {"alientek,icm20608", 0},
4 {}
5 };
6
7 /* 裝置樹比對清單 */
8 static const struct of_device_id icm20608_of_match[] = {
9 { .compatible = "alientek,icm20608" },
10 { /* Sentinel */ }
11 };
12
13 /* SPI 驅動結構體 */
14 static struct spi_driver icm20608_driver = {
15 .probe = icm20608_probe,
16 .remove = icm20608_remove,
17 .driver = {
18 .owner = THIS_MODULE,
19 .name = "icm20608",
20 .of_match_table = icm20608_of_match,
21 },
22 .id_table = icm20608_id,
23 };
24
25 /*
26 * @description : 驅動入口函數
27 * @param : 無
28 * @return : 無
29 */
30 static int __init icm20608_init(void)
31 {
32 return spi_register_driver(&icm20608_driver);
33 }
34
35 /*
36 * @description : 驅動出口函數
37 * @param : 無
38 * @return : 無
39 */
40 static void __exit icm20608_exit(void)
41 {
42 spi_unregister_driver(&icm20608_driver);
43 }
44
45 module_init(icm20608_init);
46 module_exit(icm20608_exit);
47 MODULE_LICENSE("GPL");
48 MODULE_AUTHOR("zuozhongkai");
第2~5行,傳統的裝置和驅動比對表。
第8~11行,裝置樹的裝置與驅動比對表,這裡隻有一個比對項:
“alientek,icm20608”。
第14~23行,icm20608的spi_driver結構體變量,當icm20608裝置和此驅動比對成功
以後第15行的icm20608_probe函數就會執行。同樣的,當登出此驅動的
時候icm20608_remove函數會執行。
第30~33行,icm20608_init函數為icm20608的驅動入口函數,在此函數中使用
spi_register_driver向Linux系統注冊上面定義的icm20608_driver。
第40~43行,icm20608_exit函數為icm20608的驅動出口函數,在此函數中使用
spi_unregister_driver登出掉前面注冊的icm20608_driver。
3、probe&remove函數
icm20608_driver中的probe和remove函數内容如下所示:
1 /*
2 * @description : spi 驅動的 probe 函數,當驅動與
3 * 裝置比對以後此函數就會執行
4 * @param - client : spi 裝置
5 * @param - id : spi 裝置 ID
6 *
7 */
8 static int icm20608_probe(struct spi_device *spi)
9 {
10 int ret = 0;
11
12 /* 1、建構裝置号 */
13 if (icm20608dev.major) {
14 icm20608dev.devid = MKDEV(icm20608dev.major, 0);
15 register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
16 } else {
17 alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT,ICM20608_NAME);
18 icm20608dev.major = MAJOR(icm20608dev.devid);
19 }
20
21 /* 2、注冊裝置 */
22 cdev_init(&icm20608dev.cdev, &icm20608_ops);
23 cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
24
25 /* 3、建立類 */
26 icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
27 if (IS_ERR(icm20608dev.class)) {
28 return PTR_ERR(icm20608dev.class);
29 }
30
31 /* 4、建立裝置 */
32 icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
33 if (IS_ERR(icm20608dev.device)) {
34 return PTR_ERR(icm20608dev.device);
35 }
36
37 /* 擷取裝置樹中 cs 片選信号 */
38 icm20608dev.nd = of_find_node_by_path("/soc/[email protected]/[email protected]/[email protected]");
39 if(icm20608dev.nd == NULL) {
40 printk("ecspi3 node not find!\r\n");
41 return -EINVAL;
42 }
43
44 /* 2、 擷取裝置樹中的 gpio 屬性,得到 CS 片選所使用的 GPIO 編号 */
45 icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
46 if(icm20608dev.cs_gpio < 0) {
47 printk("can't get cs-gpio");
48 return -EINVAL;
49 }
50
51 /* 3、設定 GPIO1_IO20 為輸出,并且輸出高電平 */
52 ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
53 if(ret < 0) {
54 printk("can't set gpio!\r\n");
55 }
56
57 /*初始化 spi_device */
58 spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0 */
59 spi_setup(spi);
60 icm20608dev.private_data = spi; /* 設定私有資料 */
61
62 /* 初始化 ICM20608 内部寄存器 */
63 icm20608_reginit();
64 return 0;
65 }
66
67 /*
68 * @description : spi 驅動的 remove 函數,移除 spi 驅動的時候此函數會執行
69 * @param – client : spi 裝置
70 * @return : 0,成功;其他負值,失敗
71 */
72 static int icm20608_remove(struct spi_device *spi)
73 {
74 /* 删除裝置 */
75 cdev_del(&icm20608dev.cdev);
76 unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
77
78 /* 登出掉類和裝置 */
79 device_destroy(icm20608dev.class, icm20608dev.devid);
80 class_destroy(icm20608dev.class);
81 return 0;
82 }
第8~65行,probe函數,當裝置與驅動比對成功以後此函數就會執行,第13~55行都
是标準的注冊字元裝置驅動。其中在第38~49行擷取裝置節點中的“cs-
gpio”屬性,也就是擷取到裝置的片選IO。
第58行,設定SPI為模式0,也就是CPOL=0,CPHA=0。
第59行,設定好spi_device以後需要使用spi_setup配置一下。
第60行,設定icm20608dev的private_data成員變量為spi_device。
第63行,調用icm20608_reginit函數初始化ICM20608,主要是初始化ICM20608指定
寄存器。
第72~81行,icm20608_remove函數,登出驅動的時候此函數就會執行。
4、icm20608寄存器讀寫與初始化
SPI驅動最終是通過讀寫icm20608的寄存器來實作的,是以需要編寫相應的寄存器讀
寫函數,并且使用這些讀寫函數來完成對icm20608的初始化。icm20608的寄存器讀
寫以及初始化代碼如下:
1 /*
2 * @description : 從 icm20608 讀取多個寄存器資料
3 * @param – dev : icm20608 裝置
4 * @param – reg : 要讀取的寄存器首位址
5 * @param – val : 讀取到的資料
6 * @param – len : 要讀取的資料長度
7 * @return : 操作結果
8 */
9 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
10 {
11 int ret;
12 unsigned char txdata[len];
13 struct spi_message m;
14 struct spi_transfer *t;
15 struct spi_device *spi = (struct spi_device *)dev->private_data;
16
17 gpio_set_value(dev->cs_gpio, 0); /* 片選拉低,選中 ICM20608 */
18 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
19
20 /* 第 1 次,發送要讀取的寄存位址 */
21 txdata[0] = reg | 0x80; /* 寫資料的時候寄存器位址 bit7 要置 1 */
22 t->tx_buf = txdata; /* 要發送的資料 */
23 t->len = 1; /* 1 個位元組 */
24 spi_message_init(&m); /* 初始化 spi_message */
25 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message */
26 ret = spi_sync(spi, &m); /* 同步發送 */
27
28 /* 第 2 次,讀取資料 */
29 txdata[0] = 0xff; /* 随便一個值,此處無意義 */
30 t->rx_buf = buf; /* 讀取到的資料 */
31 t->len = len; /* 要讀取的資料長度 */
32 spi_message_init(&m); /* 初始化 spi_message */
33 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message*/
34 ret = spi_sync(spi, &m); /* 同步發送 */
35
36 kfree(t); /* 釋放記憶體 */
37 gpio_set_value(dev->cs_gpio, 1); /* 片選拉高,釋放 ICM20608 */
38
39 return ret;
40 }
41
42 /*
43 * @description : 向 icm20608 多個寄存器寫入資料
44 * @param – dev : icm20608 裝置
45 * @param – reg : 要寫入的寄存器首位址
46 * @param – val : 要寫入的資料緩沖區
47 * @param – len : 要寫入的資料長度
48 * @return : 操作結果
49 */
50 static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
51 {
52 int ret;
53
54 unsigned char txdata[len];
55 struct spi_message m;
56 struct spi_transfer *t;
57 struct spi_device *spi = (struct spi_device *)dev->private_data;
58
59 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
60 gpio_set_value(dev->cs_gpio, 0); /* 片選拉低 */
61
62 /* 第 1 次,發送要讀取的寄存位址 */
63 txdata[0] = reg & ~0x80; /* 寫資料的時候寄存器位址 bit8 要清零 */
64 t->tx_buf = txdata; /* 要發送的資料 */
65 t->len = 1; /* 1 個位元組 */
66 spi_message_init(&m); /* 初始化 spi_message */
67 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message */
68 ret = spi_sync(spi, &m); /* 同步發送 */
69
70 /* 第 2 次,發送要寫入的資料 */
71 t->tx_buf = buf; /* 要寫入的資料 */
72 t->len = len; /* 寫入的位元組數 */
73 spi_message_init(&m); /* 初始化 spi_message */
74 spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message*/
75 ret = spi_sync(spi, &m); /* 同步發送 */
76
77 kfree(t); /* 釋放記憶體 */
78 gpio_set_value(dev->cs_gpio, 1);/* 片選拉高,釋放 ICM20608 */
79 return ret;
80 }
81
82 /*
83 * @description : 讀取 icm20608 指定寄存器值,讀取一個寄存器
84 * @param – dev : icm20608 裝置
85 * @param – reg : 要讀取的寄存器
86 * @return : 讀取到的寄存器值
87 */
88 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
89 {
90 u8 data = 0;
91 icm20608_read_regs(dev, reg, &data, 1);
92 return data;
93 }
94
95 /*
96 * @description : 向 icm20608 指定寄存器寫入指定的值,寫一個寄存器
97 * @param – dev : icm20608 裝置
98 * @param – reg : 要寫的寄存器
99 * @param – data : 要寫入的值
100 * @return : 無
101 */
102
103 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
104 {
105 u8 buf = value;
106 icm20608_write_regs(dev, reg, &buf, 1);
107 }
108
109 /*
110 * @description : 讀取 ICM20608 的資料,讀取原始資料,包括三軸陀螺儀、
111 * : 三軸加速度計和内部溫度。
112 * @param - dev : ICM20608 裝置
113 * @return : 無。
114 */
115 void icm20608_readdata(struct icm20608_dev *dev)
116 {
117 unsigned char data[14];
118 icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
119
120 dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
121 dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
122 dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
123 dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
124 dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
125 dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
126 dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
127 }
128 /*
129 * ICM20608 内部寄存器初始化函數
130 * @param : 無
131 * @return : 無
132 */
133 void icm20608_reginit(void)
134 {
135 u8 value = 0;
136
137 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
138 mdelay(50);
139 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
140 mdelay(50);
141
142 value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
143 printk("ICM20608 ID = %#X\r\n", value);
144
145 icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
146 icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
147 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
148 icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
149 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
150 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);
151 icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);
152 icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);
153 }
第9~40行,icm20608_read_regs函數,從icm20608中讀取連續多個寄存器資料。
第50~80行,icm20608_write_regs函數,向icm20608連續寫入多個寄存器資料。
第88~83行,icm20608_read_onereg函數,讀取icm20608指定寄存器資料。
第103~107行,icm20608_write_onereg函數,向icm20608指定寄存器寫入資料。
第115~126行,icm20608_readdata函數,讀取icm20608六軸傳感器和溫度傳感器原
始資料值,應用程式讀取icm20608的時候這些傳感器原始資料就會上
報給應用程式。
第133~153行,icm20608_reginit函數,初始化icm20608,和我們spi裸機實驗裡面的
初始化過程一樣。
5、字元裝置驅動架構
icm20608的字元裝置驅動架構如下:
1 /*
2 * @description : 打開裝置
3 * @param – inode : 傳遞給驅動的 inode
4 * @param - filp : 裝置檔案,file 結構體有個叫做 pr 似有 ate_data 的成員變量
5 * 一般在 open 的時候将 private_data 似有向裝置結構體。
6 * @return : 0 成功;其他 失敗
7 */
8 static int icm20608_open(struct inode *inode, struct file *filp)
9 {
10 filp->private_data = &icm20608dev; /* 設定私有資料 */
11 return 0;
12 }
13
14 /*
15 * @description : 從裝置讀取資料
16 * @param - filp : 要打開的裝置檔案(檔案描述符)
17 * @param - buf : 傳回給使用者空間的資料緩沖區
18 * @param - cnt : 要讀取的資料長度
19 * @param - offt : 相對于檔案首位址的偏移
20 * @return : 讀取的位元組數,如果為負值,表示讀取失敗
21 */
22 static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
23 {
24 signed int data[7];
25 long err = 0;
26 struct icm20608_dev *dev = (struct icm20608_dev * )filp->private_data;
27
28 icm20608_readdata(dev);
29 data[0] = dev->gyro_x_adc;
30 data[1] = dev->gyro_y_adc;
31 data[2] = dev->gyro_z_adc;
32 data[3] = dev->accel_x_adc;
33 data[4] = dev->accel_y_adc;
34 data[5] = dev->accel_z_adc;
35 data[6] = dev->temp_adc;
36 err = copy_to_user(buf, data, sizeof(data));
37 return 0;
38 }
39
40 /*
41 * @description : 關閉/釋放裝置
42 * @param - filp : 要關閉的裝置檔案(檔案描述符)
43 * @return : 0 成功;其他 失敗
44 */
45 static int icm20608_release(struct inode *inode, struct file *filp)
46 {
47 return 0;
48 }
49
50 /* icm20608 操作函數 */
51 static const struct file_operations icm20608_ops = {
52 .owner = THIS_MODULE,
53 .open = icm20608_open,
54 .read = icm20608_read,
55 .release = icm20608_release,
56 };
字元裝置驅動架構沒什麼好說的,重點是第22~38行的icm20608_read函數,當應用
程式調用read函數讀取icm20608裝置檔案的時候此函數就會執行。此函數調用上面
編寫好的icm20608_readdata函數讀取icm20608的原始資料并将其上報給應用程式。
大家注意,在核心中盡量不要使用浮點運算,是以不要在驅動将icm20608的原始值
轉換為對應的實際值,因為會涉及到浮點計算。
3)編寫測試APP
建立icm20608App.c檔案,然後在裡面輸入如下所示内容:
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 /***************************************************************
15 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
16 檔案名 : icm20608App.c
17 作者 : 左忠凱
18 版本 : V1.0
19 描述 : icm20608 裝置測試 APP。
20 其他 : 無
21 使用方法 :./icm20608App /dev/icm20608
22 論壇 : www.openedv.com
23 日志 : 初版 V1.0 2019/9/20 左忠凱建立
24 ***************************************************************/
25
26 /*
27 * @description : main 主程式
28 * @param - argc : argv 數組元素個數
29 * @param - argv : 具體參數
30 * @return : 0 成功;其他 失敗
31 */
32 int main(int argc, char *argv[])
33 {
34 int fd;
35 char *filename;
36 signed int databuf[7];
37 unsigned char data[14];
38 signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
39 signed int accel_x_adc, accel_y_adc, accel_z_adc;
40 signed int temp_adc;
41
42 float gyro_x_act, gyro_y_act, gyro_z_act;
43 float accel_x_act, accel_y_act, accel_z_act;
44 float temp_act;
45
46 int ret = 0;
47
48 if (argc != 2) {
49 printf("Error Usage!\r\n");
50 return -1;
51 }
52
53 filename = argv[1];
54 fd = open(filename, O_RDWR);
55 if(fd < 0) {
56 printf("can't open file %s\r\n", filename);
57 return -1;
58 }
59
60 while (1) {
61 ret = read(fd, databuf, sizeof(databuf));
62 if(ret == 0) { /* 資料讀取成功 */
63 gyro_x_adc = databuf[0];
64 gyro_y_adc = databuf[1];
65 gyro_z_adc = databuf[2];
66 accel_x_adc = databuf[3];
67 accel_y_adc = databuf[4];
68 accel_z_adc = databuf[5];
69 temp_adc = databuf[6];
70
71 /* 計算實際值 */
72 gyro_x_act = (float)(gyro_x_adc) / 16.4;
73 gyro_y_act = (float)(gyro_y_adc) / 16.4;
74 gyro_z_act = (float)(gyro_z_adc) / 16.4;
75 accel_x_act = (float)(accel_x_adc) / 2048;
76 accel_y_act = (float)(accel_y_adc) / 2048;
77 accel_z_act = (float)(accel_z_adc) / 2048;
78 temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
79
80 printf("\r\n 原始值:\r\n");
81 printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
82 printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
83 printf("temp = %d\r\n", temp_adc);
84 printf("實際值:");
85 printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
86 printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
87 printf("act temp = %.2f°C\r\n", temp_act);
88 }
89 usleep(100000); /*100ms */
90 }
91 close(fd); /* 關閉檔案 */
92 return 0;
93 }
第60~91行,在while循環中每隔100ms從icm20608中讀取一次資料,讀取到icm20608原
始資料以後将其轉換為實際值,比如陀螺儀就是角速度、加速度計就是g
值。
注意,我們在icm20608驅動中将陀螺儀和加速度計的測量範圍全部設定到了最大,分别
為±2000和±16g。是以,在計算實際值的時候陀螺儀使用16.4,加速度計使用2048。最終
将傳感器原始資料和得到的實際值顯示在終端上。
4.運作測試
1)編譯驅動程式和測試APP
1、編譯驅動程式
編寫Makefile檔案,本章實驗的Makefile檔案和之前實驗基本一樣,隻是将obj-m變量
的值改為“icm20608.o”,Makefile内容如下所示:
1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-
rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := icm20608.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,設定obj-m變量的值為“icm20608.o”。
輸入如下指令編譯出驅動子產品檔案:
make -j32
2、編譯測試APP
在icm20608App.c這個測試APP中我們用到了浮點計算,而I.MX6U是支援硬體浮點
的,是以我們在編譯icm20608App.c的時候就可以使能硬體浮點,這樣可以加速浮點
計算。使能硬體浮點很簡單,在編譯的時候加入如下參數即可:
-march-armv7-a -mfpu-neon -mfloat=hard
輸入如下指令使能硬體浮點編譯icm20608App.c這個測試程式:
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o
icm20608App
編譯成功以後就會生成icm20608App這個應用程式,那麼究竟有沒有使用硬體浮點
呢?使用arm-linux-gnueabihf-readelf檢視一下編譯出來的icm20608App就知道了,輸
入如下指令:
arm-linux-gnueabihf-readelf -A icm20608App
結果如圖1所示:

圖1 icm20608App檔案資訊
從圖1可以看出FPU架構為VFPv3,SIMD使用了NEON,并且使用了SP和DP,說明
icm20608App這個應用程式使用了硬體浮點。
2)運作測試
将上一小節編譯出來icm20608.ko和icm20608App這兩個檔案拷貝到
rootfs/lib/modules/4.1.15目錄中,重新開機開發闆,進入到目錄lib/modules/4.1.15中。輸入如
下指令加載icm20608.ko這個驅動子產品。
depmod //第一次加載驅動的時候需要運作此指令
modprobe icm20608.ko //加載驅動子產品
當驅動子產品加載成功以後使用icm20608App來測試,輸入如下指令:
./icm20608App /dev/icm20608
測試APP會不斷的從ICM20608中讀取資料,然後輸出到終端上,如圖2所示:
圖2 擷取到的ICM20608資料
可以看出,開發闆靜止狀态下,Z軸方向的加速度在1g左右,這個就是重力加速度。對于
陀螺儀來講,靜止狀态下三軸的角速度應該在0°/S左右。ICM20608内溫度傳感器采集到
的溫度在30多度左右,大家可以晃動一下開發闆,這個時候陀螺儀和加速度計的值就會有
變化。