天天看點

Linux驅動開發篇-SPI驅動實驗1.Linux下SPI驅動架構簡介2.SPI裝置驅動編寫流程3.試驗程式編寫4.運作測試

目錄

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所示:

Linux驅動開發篇-SPI驅動實驗1.Linux下SPI驅動架構簡介2.SPI裝置驅動編寫流程3.試驗程式編寫4.運作測試

 圖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所示:

Linux驅動開發篇-SPI驅動實驗1.Linux下SPI驅動架構簡介2.SPI裝置驅動編寫流程3.試驗程式編寫4.運作測試

圖2 擷取到的ICM20608資料

              可以看出,開發闆靜止狀态下,Z軸方向的加速度在1g左右,這個就是重力加速度。對于

              陀螺儀來講,靜止狀态下三軸的角速度應該在0°/S左右。ICM20608内溫度傳感器采集到

              的溫度在30多度左右,大家可以晃動一下開發闆,這個時候陀螺儀和加速度計的值就會有

              變化。

繼續閱讀