「正點原子Linux連載」第六十二章Linux SPI驅動實驗(二)

1)實驗平臺:正點原子Linux開發板

2)

摘自《正點原子I.MX6U嵌入式Linux驅動開發指南關注官方微信號公眾號,獲取更多資料:正點原子

「正點原子Linux連載」第六十二章Linux SPI驅動實驗(二)

在本章實驗中,我們採用同步傳輸方式來完成SPI數據的傳輸工作,也就是spi_sync函數。

綜上所述,SPI數據傳輸步驟如下:

①、申請並初始化spi_transfer,設置spi_transfer的tx_buf成員變量,tx_buf為要發送的數據。然後設置rx_buf成員變量,rx_buf保存著接收到的數據。最後設置len成員變量,也就是要進行數據通信的長度。

②、使用spi_message_init函數初始化spi_message。

③、使用spi_message_add_tail函數將前面設置好的spi_transfer添加到spi_message隊列中。

④、使用spi_sync函數完成SPI數據同步傳輸。

通過SPI進行n個字節的數據發送和接收的示例代碼如下所示:

示例代碼62.3.2.3 SPI數據讀寫操作

/* SPI多字節發送 */

staticint 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多字節接收 */

staticint 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;

}

62.4 硬件原理圖分析

本章實驗硬件原理圖參考26.2小節即可。

62.5 試驗程序編寫

本實驗對應的例程路徑為:開發板光盤->2、Linux驅動例程->22_spi。

62.5.1 修改設備樹

1、添加ICM20608所使用的IO

首先在imx6ull-alientek-emmc.dts文件中添加ICM20608所使用的IO信息,在iomuxc節點中添加一個新的子節點來描述ICM20608所使用的SPI引腳,子節點名字為pinctrl_ecspi3,節點內容如下所示:

示例代碼62.5.1.1 icm20608 IO節點信息

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官方的6ULL EVK開發板上沒有連接SPI設備。在imx6ull-alientek-emmc.dts文件最後面加入如下所示內容:

示例代碼62.5.1.2 向ecspi3節點加入icm20608信息

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: icm20608@0 {

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子節點,也就是我們在示例代碼62.5.1.1中新建的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系統。

62.5.2 編寫ICM20608驅動

新建名為"22_spi"的文件夾,然後在22_spi文件夾裡面創建vscode工程,工作區命名為"spi"。工程創建好以後新建icm20608.c和icm20608reg.h這兩個文件,icm20608.c為ICM20608的驅動代碼,icm20608reg.h是ICM20608寄存器頭文件。先在icm20608reg.h中定義好ICM20608的寄存器,輸入如下內容(有省略,完成的內容請參考例程):

示例代碼62.5.2.1 icm20608reg.h文件內容

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設備機構體,如下所示:

示例代碼62.5.2.2 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

37struct 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 signedint gyro_x_adc; /* 陀螺儀X軸原始值 */

47 signedint gyro_y_adc; /* 陀螺儀Y軸原始值 */

48 signedint gyro_z_adc; /* 陀螺儀Z軸原始值 */

49 signedint accel_x_adc; /* 加速度計X軸原始值 */

50 signedint accel_y_adc; /* 加速度計Y軸原始值 */

51 signedint accel_z_adc; /* 加速度計Z軸原始值 */

52 signedint temp_adc; /* 溫度原始值 */

53};

54

55staticstruct 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初始化、註冊與註銷代碼如下:

示例代碼62.5.2.3 icm20608的spi_driver初始化、註冊與註銷

1 /* 傳統匹配方式ID列表 */

2staticconststruct spi_device_id icm20608_id[]={

3 {"alientek,icm20608",0},

4 {}

5};

6

7 /* 設備樹匹配列表 */

8staticconststruct of_device_id icm20608_of_match[]={

9 {.compatible ="alientek,icm20608"},

10 {/* Sentinel */}

11};

12

13 /* SPI驅動結構體 */

14staticstruct 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 */

30staticint __init icm20608_init(void)

31{

32 return spi_register_driver(&icm20608_driver);

33}

34

35/*

36 * @description : 驅動出口函數

37 * @param : 無

38 * @return : 無

39 */

40staticvoid __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函數內容如下所示:

示例代碼62.5.2.4 probe和remove函數

1/*

2 * @description : spi驅動的probe函數,當驅動與

3 * 設備匹配以後此函數就會執行

4 * @param - client : spi設備

5 * @param - id : spi設備ID

6 *

7 */

8staticint 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/aips-bus@02000000/

spba-bus@02000000/ecspi@02010000");

39 if(icm20608dev.nd ==NULL){

40 printk("ecspi3 node not find!\\r\\n");

41 return-EINVAL;

42 }

43

44 /* 2、獲取設備樹中的gpio屬性,得到BEEP所使用的BEEP編號 */

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 return0;

65}

66

67/*

68 * @description : spi驅動的remove函數,移除spi驅動的時候此函數會執行

69 * @param – client : spi設備

70 * @return : 0,成功;其他負值,失敗

71 */

72staticint 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 return0;

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的寄存器讀寫以及初始化代碼如下:

示例代碼62.5.2.5 icm20608寄存器讀寫以及出初始化

1/*

2 * @description : 從icm20608讀取多個寄存器數據

3 * @param – dev : icm20608設備

4 * @param – reg : 要讀取的寄存器首地址

5 * @param – val : 讀取到的數據

6 * @param – len : 要讀取的數據長度

7 * @return : 操作結果

8 */

9staticint icm20608_read_regs(struct icm20608_dev *dev, u8 reg,

void*buf,int len)

10{

11int ret;

12unsignedchar txdata[len];

13struct spi_message m;

14struct spi_transfer *t;

15struct 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; /* 寫數據的時候寄存器地址bit8要置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

39return ret;

40}

41

42/*

43 * @description : 向icm20608多個寄存器寫入數據

44 * @param – dev : icm20608設備

45 * @param – reg : 要寫入的寄存器首地址

46 * @param – val : 要寫入的數據緩衝區

47 * @param – len : 要寫入的數據長度

48 * @return : 操作結果

49 */

50static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg,

u8 *buf, u8 len)

51{

52int ret;

53

54unsignedchar txdata[len];

55struct spi_message m;

56struct spi_transfer *t;

57struct 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 */

79return ret;

80}

81

82/*

83 * @description : 讀取icm20608指定寄存器值,讀取一個寄存器

84 * @param – dev : icm20608設備

85 * @param – reg : 要讀取的寄存器

86 * @return : 讀取到的寄存器值

87 */

88staticunsignedchar icm20608_read_onereg(struct icm20608_dev *dev,

u8 reg)

89{

90 u8 data =0;

91 icm20608_read_regs(dev, reg,&data,1);

92return data;

93}

94

95/*

96 * @description : 向icm20608指定寄存器寫入指定的值,寫一個寄存器

97 * @param – dev : icm20608設備

98 * @param – reg : 要寫的寄存器

99 * @param – data : 要寫入的值

100 * @return : 無

101 */

102

103staticvoid 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 */

115void icm20608_readdata(struct icm20608_dev *dev)

116{

117unsignedchar data[14];

118 icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data,14);

119

120 dev->accel_x_adc =(signedshort)((data[0]<<8

)| data[1]);

121 dev->accel_y_adc =(signedshort)((data[2]<<8)| data[3]);

122 dev->accel_z_adc =(signedshort)((data[4]<<8)| data[5]);

123 dev->temp_adc =(signedshort)((data

[6]<<8)| data[7]);

124 dev->gyro_x_adc =(signedshort)((data[8]<<8)| data[9]);

125 dev->gyro_y_adc =(signedshort)((data[10]<<8)| data[11]);

126 dev->gyro_z_adc

=(signedshort)((data[12]<<8)| data[13]);

127}

128/*

129 * ICM20608內部寄存器初始化函數

130 * @param : 無

131 * @return : 無

132 */

133void 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的字符設備驅動框架如下:

示例代碼62.5.2.6 icm20608字符設備驅動

1/*

2 * @description : 打開設備

3 * @param – inode : 傳遞給驅動的inode

4 * @param - filp : 設備文件,file結構體有個叫做pr似有ate_data的成員變量

5 * 一般在open的時候將private_data似有向設備結構體。

6 * @return : 0 成功;其他失敗

7 */

8staticint icm20608_open(struct inode *inode,struct file

*filp)

9{

10 filp->private_data =&icm20608dev;/* 設置私有數據 */

11 return0;

12}

13

14/*

15 * @description : 從設備讀取數據

16 * @param - filp : 要打開的設備文件(文件描述符)

17 * @param - buf : 返回給用戶空間的數據緩衝區

18 * @param - cnt : 要讀取的數據長度

19 * @param - offt : 相對於文件首地址的偏移

20 * @return : 讀取的字節數,如果為負值,表示讀取失敗

21 */

22static ssize_t icm20608_read(struct file *filp,char __user *buf,size_t cnt, loff_t *off)

23{

24 signedint 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 return0;

38}

39

40/*

41 * @description : 關閉/釋放設備

42 * @param - filp : 要關閉的設備文件(文件描述符)

43 * @return : 0 成功;其他失敗

44 */

45staticint icm20608_release(struct inode *inode,struct file *filp)

46{

47 return0;

48}

49

50/* icm20608操作函數 */

51staticconststruct 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的原始值轉換為對應的實際值,因為會涉及到浮點計算。

62.5.3編寫測試APP

新建icm20608App.c文件,然後在裡面輸入如下所示內容:

示例代碼62.5.3.1 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 */

32int main(int argc,char*argv[])

33{

34 int fd;

35 char*filename;

36 signedint databuf[7];

37 unsignedchar data[14];

38 signedint gyro_x_adc, gyro_y_adc,

gyro_z_adc;

39 signedint accel_x_adc, accel_y_adc, accel_z_adc;

40 signedint 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 return0;

93}

第60~91行,在while循環中每隔100ms從icm20608中讀取一次數據,讀取到icm20608原始數據以後將其轉換為實際值,比如陀螺儀就是角速度、加速度計就是g值。注意,我們在icm20608驅動中將陀螺儀和加速度計的測量範圍全部設置到了最大,分別為±2000和±16g。因此,在計算實際值的時候陀螺儀使用16.4,加速度計使用2048。最終將傳感器原始數據和得到的實際值顯示在終端上。

62.6 運行測試

62.6.1 編譯驅動程序和測試APP

1、編譯驅動程序

編寫Makefile文件,本章實驗的Makefile文件和第四十章實驗基本一樣,只是將obj-m變量的值改為"icm20608.o",Makefile內容如下所示:

示例代碼62.6.1.1 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

編譯成功以後就會生成一個名為"icm20608.ko"的驅動模塊文件。

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

結果如圖62.6.1.1所示:

「正點原子Linux連載」第六十二章Linux SPI驅動實驗(二)

圖62.6.1.1 icm20608App文件信息


從圖62.6.1.1可以看出FPU架構為VFPv3,SIMD使用了NEON,並且使用了SP和DP,說明icm20608App這個應用程序使用了硬件浮點。

62.6.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中讀取數據,然後輸出到終端上,如圖62.6.2.1所示:

「正點原子Linux連載」第六十二章Linux SPI驅動實驗(二)

圖62.6.2.1 獲取到的ICM20608數據


可以看出,開發板靜止狀態下,Z軸方向的加速度在1g左右,這個就是重力加速度。對於陀螺儀來講,靜止狀態下三軸的角速度應該在0°/S左右。ICM20608內溫度傳感器採集到的溫度在30多度左右,大家可以晃動一下開發板,這個時候陀螺儀和加速度計的值就會有變化。


分享到:


相關文章: