Linux SPI ドライバーの実験

前の章では、Linux で I2C デバイス ドライバを作成する方法を説明しました。SPI も非常に一般的なシリアル通信プロトコルです。この章では、Linux で
SPI デバイス ドライバを作成する方法を学びます。
この章の実験の最終目標は、I.MX6U-ALPHA 開発ボード上の SPI インターフェイスを備えた ICM-20608 6 軸センサーを駆動することであり、ICM-20608のオリジナルのセンサー データをアプリケーション プログラムで読み取ることができます。

Linux での SPI ドライバー フレームワークの概要

SPI ドライバー フレームワークは I2C と非常によく似ており、ホスト コントローラー ドライバーとデバイス ドライバーに分かれており、ホスト コントローラーは
SOC の SPI コントローラー インターフェイスでもあります。たとえば、ベアメタルの章の「第 27 章 SPI 実験」では、
I.MX6U の SPI コントローラ ドライバである bsp_spi.c と bsp_spi.h の 2 つのファイルを作成しました。 SPI コントローラ部分のドライバは
、どのような SPI デバイスであっても同じです。ここでは、さまざま
な SPI デバイス ドライバに焦点を当てます。

SPIホストドライバー

SPI ホスト ドライバーは、I2C ドライバーのアダプター ドライバーに似た、SOC の SPI コントローラー ドライバーです。Linux カーネルは、
SPI ホスト ドライバーを表すために spi_master を使用します。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、転送関数、i2c_algorithm の master_xfer 関数と同じ、コントローラー データ転送関数

行 434 の transfer_one_message 関数は、SPI データの送信にも使用され、spi_message の送信に使用されます。SPI
データは spi_message にパッケージ化され、キューに送信されます。
つまり、SPI ホストは最終的に転送機能を介して SPI デバイスと通信することになるため、
SOC が異なれば SPI コントローラやレジスタも異なるため、SPI ホスト コントローラのドライバ作成者は転送機能を実装する必要があります。
サンプル。I2C アダプタ ドライバと同様に、SPI ホスト ドライバは通常 SOC 製造元によって作成されるため、
元の SOC 工場で作業している場合を除き、SOC ユーザーとしてドライバのこの部分について心配する必要はありません。内容は、SPI ホスト ドライバーを記述することです

SPI ホスト ドライバーの核心は、spi_master を適用し、次に spi_master を初期化し、最後に
spi_master を Linux カーネルに登録することです。
1. spi_master の適用とリリース
spi_master の適用には spi_alloc_master 関数を使用します。関数のプロトタイプは次のとおりです。

struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)

関数のパラメータと戻り値の意味は次のとおりです:
dev: device、通常は 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 ホスト ドライバーは、API 関数 spi_bitbang_start を使用して 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 からログオフする必要があります

SPIデバイスドライバー

spi デバイス ドライバーも i2c デバイス ドライバーに非常に似ています。Linux カーネルは spi_driver 構造体を使用して spi デバイス ドライバーを表します
。SPI デバイス ドライバーを作成するときに spi_driver を実装する必要があります。spi_driver 構造体は
include/linux/spi/spi.h ファイルで定義されており、構造体の内容は次のとおりです。
ここに画像の説明を挿入
spi_driver は基本的に i2c_driver および platform_driver と同じであり、
プローブ関数はSPI デバイスとドライバーは正常に一致しました。
同様に、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 デバイス ドライバーによって記述する必要があります。
i2c_driver や platform_driver と同じですので、詳細な説明は省略します。
39~42行目、ドライバーエントリ関数内でspi_register_driverを呼び出してspi_driverを登録します。
45 ~ 48 行目で、ドライバー終了関数の spi_unregister_driver を呼び出して、spi_driver の登録を解除します。

SPIデバイスとドライバーのマッチングプロセス

SPI デバイスとドライバーのマッチング プロセスは、プラットフォーム、I2C、および他のドライバーと同じ SPI バスによって実行されます。SPI バスは、
drivers/spi/spi.c ファイルで定義されている spi_bus_type であり、その内容は以下のとおりであります:

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 デバイス ノードの互換性属性が
of_device_id の互換性属性と等しいかどうかを比較し、等しい場合は、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 メンバー変数と等しいかどうかを比較します

I.MX6U SPI ホストドライバーの分析

I2C アダプタ ドライバと同様に、SPI ホスト ドライバは通常 SOC 製造元によって作成され、imx6ull.dtsi
ファイルを開いて次の内容を見つけます。

示例代码62.2.1 imx6ull.dtsi 文件中的ecspi3 节点内容
1 ecspi3: ecspi@02010000 {
    
    
2 #address-cells = <1>;
3 #size-cells = <0>;
4 compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
5 reg = <0x02010000 0x4000>;
6 interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
7 clocks = <&clks IMX6UL_CLK_ECSPI3>,
8 <&clks IMX6UL_CLK_ECSPI3>;
9 clock-names = "ipg", "per";
10 dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
11 dma-names = "rx", "tx";
12 status = "disabled";
13 };

4 行目の互換性属性値に注目してください。互換性属性には、「fsl,imx6ul-ecspi」と
「fsl,imx51-ecspi」の 2 つの値があります。Linux カーネル ソース コードでこれら 2 つの属性値を検索して、I を見つけます。 . MX6U に対応した ECSPI (SPI)
ホスト ドライバー。I.MX6U の ECSPI ホスト ドライバー ファイルは drivers/spi/spi-imx.c であり、このファイルには次の内容が含まれています。

示例代码62.2.2 spi_imx_driver 结构体
694 static struct platform_device_id spi_imx_devtype[] = {
    
    
695 {
    
    
696 .name = "imx1-cspi",
697 .driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,
698 }, {
    
    
699 .name = "imx21-cspi",
700 .driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,
713 }, {
    
    
714 .name = "imx6ul-ecspi",
715 .driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
716 }, {
    
    
717 /* sentinel */
718 }
719 };
720
721 static const struct of_device_id spi_imx_dt_ids[] = {
    
    
722 {
    
     .compatible = "fsl,imx1-cspi", .data =
&imx1_cspi_devtype_data, },
......
728 {
    
     .compatible = "fsl,imx6ul-ecspi", .data =
&imx6ul_ecspi_devtype_data, },
729 {
    
     /* sentinel */ }
730 };
731 MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
......
1338 static struct platform_driver spi_imx_driver = {
    
    
1339 .driver = {
    
    
1340 .name = DRIVER_NAME,
1341 .of_match_table = spi_imx_dt_ids,
1342 .pm = IMX_SPI_PM,
1343 },
1344 .id_table = spi_imx_devtype,
1345 .probe = spi_imx_probe,
1346 .remove = spi_imx_remove,
1347 };
1348 module_platform_driver(spi_imx_driver);

......

行 714、spi_imx_devtype は SPI デバイス ツリー マッチング テーブルです。
行 721、spi_imx_dt_ids は、SPI デバイス ツリー マッチング テーブルです。
728 行目「fsl,imx6ul-ecspi」が一致するため、I.MX6U の ECSPI ドライバーがファイル
spi-imx.c であることがわかります。
1338 ~ 1347 行目、platform_driver ドライバー フレームワークは I2C アダプター ドライバーと同じであり、SPI ホスト ドライバーは
プラットフォーム ドライバー フレームワークを使用します。デバイスとドライバーが正常に一致すると、spi_imx_probe 関数が実行されます。
spi_imx_probe 関数は、デバイス ツリーから対応するノード属性値を読み取り、spi_master を適用して初期化し、最後に spi_bitbang_start 関数を呼び出して (
spi_bitbang_start は spi_register_master 関数を呼び出します)、
spi_master を Linux カーネルに登録します。
I.MX6U の場合、SPI ホストの最終的なデータ送受信関数は spi_imx_transfer であり、この関数は
次のレイヤーごとの呼び出しを通じて最終的に SPI データ送信を実現します。

spi_imx_transfer
	-> spi_imx_pio_transfer
		-> spi_imx_push
			-> spi_imx->tx


spi_imx は、spi_imx_data 型のメカニズム ポインタ変数で、2 つのメンバー変数 tx と rx はそれぞれSPI
データ送信関数と受信関数です。I.MX6U SPI ホスト ドライバーは、spi_imx_data 型変数 spi_imx を維持し、
spi_imx_setupxfer 関数を使用して spi_imx の tx および rx 関数を設定します。送信するデータのビット幅に応じて
、以下の 8 ビット、16 ビット、32 ビットの送信関数があります。

spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32

同様に、次のような 8 ビット、16 ビット、および 32 ビットのデータ受信関数もあります。

spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32

関数 spi_imx_buf_tx_u8 を例として、独自のデータを送信する方法を見てみましょう。spi
-imx.c ファイルで次の内容を見つけます。

示例代码62.2.3 spi_imx_buf_tx_u8 函数
152 #define MXC_SPI_BUF_TX(type) \
153 static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
154 {
    
     \
155 type val = 0; \
156 \
157 if (spi_imx->tx_buf) {
    
     \
158 val = *(type *)spi_imx->tx_buf; \
159 spi_imx->tx_buf += sizeof(type); \
160 } \
161 \
162 spi_imx->count -= sizeof(type); \
163 \
164 writel(val, spi_imx->base + MXC_CSPITXDATA); \
165 }
166
167 MXC_SPI_BUF_RX(u8)
168 MXC_SPI_BUF_TX(u8)

サンプル コード 62.2.3 から、spi_imx_buf_tx_u8 関数が MXC_SPI_BUF_TX マクロによって実装されていることがわかります

164 行目は、ECSPI の TXDATA レジスタに送信するデータ値を書き込みます。これは、SPI ベアメタルの実験方法と同じです。168 行目の MXC_SPI_BUF_TX(u8) を展開すると、spi_imx_buf_tx_u8 関数になります。
他の tx および rx 関数もこの方法で実装されているため、ここでは紹介しません。ここではI.MX6U のホスト ドライバーについて説明します
が、基本的な手順は I2C のアダプター ドライバーと同様です。

SPIデバイスドライバ書き込み処理

SPIデバイス情報の説明

1. IO の pinctrl サブノードの作成と変更は、
使用する IO に応じて、最初に pinctrl サブノードを作成または変更する必要があります。これについては何も言うことはありません。唯一注意すべきことは、対応する IO が存在するかどうかを確認することです。他のデバイスで使用されている場合は、削除する必要があります。
2. SPI デバイス ノードの作成と変更
デバイス ツリーを使用する場合、SPI デバイス情報の記述は、対応するデバイス サブノードを作成することで完了します。
デバイス ツリー ヘッダー ファイル imx6qdl-sabresd.dtsi を開いて見つけることができます。このファイルの内容は次のとおりです。

示例代码62.3.1.1 m25p80 设备节点
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: m25p80@0 {
    
    
316 #address-cells = <1>;
317 #size-cells = <1>;
318 compatible = "st,m25p32";
319 spi-max-frequency = <20000000>;
320 reg = <0>;
321 };
322 };

サンプルコード62.3.1.1はI.MX6Qのボード上のSPIデバイスノードであり、
このボードのECSPIインターフェースにSPIインターフェースのデバイスであるm25p80が接続されています。
行 309 では、属性「fsl,spi-num-chipselects」を 1 に設定します。これは、デバイスが 1 つだけ存在することを意味します。
行 310、「cs-gpios」属性を設定します。つまり、チップ選択信号は GPIO4_IO09 です。
行 311 では、SPI デバイスによって使用される IO 名である「pinctrl-names」属性を設定します。
行 312 では、「pinctrl-0」属性、つまり、使用される IO に対応する pinctrl ノードを設定します。
313行目で、ecspi1ノードの「status」属性を「ok」に変更します。
行 315 ~ 320、ecspi1 の m25p80 デバイス情報、各 SPI デバイスは子ノードを使用して
デバイス情報を記述します。315 行目の「m25p80@0」の後の「0」は、m25p80 が
ECSPI のチャネル 0 に接続されていることを示します。これは、独自のハードウェアに応じて設定する必要があります。
行 318 では、SPI デバイスの互換性のある属性値を使用して、デバイス ドライバーを照合します。
行 319 の属性「spi-max-frequency」は、使用する SPI デバイスに従って設定する必要がある SPI コントローラの最大周波数を設定します。たとえば、
ここでは SPI コントローラの最大周波数は 20MHz に設定されています。
行 320 の reg 属性は、m25p80 デバイスによって使用される ECSPI チャネルを設定します。これは、
「m25p80@0」の後ろの「0」と同じです。
サンプルコード 62.3.1.1 の内容は、後で ICM20608 のデバイスツリーノード情報を記述する際に参照します

SPIデバイスデータ送受信処理フロー

SPI デバイス ドライバーのコアは spi_driver であり、これについてはセクション 62.1.2 ですでに説明しました。spi_driver を Linux カーネルに正常に登録したら
、SPI コア層によって提供される API 関数を使用してデバイスの読み取りと書き込みを行うことができます。
1 つ目は spi_transfer 構造体で、SPI 転送情報を記述するために使用されます。構造体の内容は次のとおりです。

示例代码62.3.2.1 spi_transfer 结构体
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は全二重通信なので
1回の通信で送受信するバイト数は同じなので送信長と受信長に差はありません。 spi_transfer 。
spi_transfer は spi_message に編成する必要があります。spi_message も構造体であり、内容は次のとおりです。

示例代码62.3.2.2 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 データ送信の手順は次のとおりです。
① 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多字节发送*/
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;
}

ハードウェア回路図解析

この章の実験用ハードウェアの概略図については、セクション 26.2 を参照してください。

テストプログラムの作成

この実験に対応するルーチン パスは、開発ボード CD -> 2、Linux ドライバー ルーチン -> 22_spi です。

デバイスツリーを変更する

1. ICM20608 によって使用される IO を追加します。
まず、ICM20608 によって使用される IO 情報を imx6ull-alientek-emmc.dts ファイルに追加し、
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. icm20608 サブノードを ecspi3 ノードに追加します。NXP 公式 6ULL​​ EVK 開発ボードが SPI デバイスに接続されていない
ため、imx6ull-alientek-emmc.dts ファイルには ecspi3 ノードにコンテンツを追加するコードがありません。
imx6ull-alientek-emmc.dts ファイルの末尾に
次の内容を追加します。

示例代码62.5.1.2 向ecspi3 节点加入icm20608 信息
1 &ecspi3 {
    
    
2 fsl,spi-num-chipselects = <1>;
3 cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
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 行目では、ICM20608 が 1 つだけ接続されているため、現在のチップ選択数を 1 に設定します。
3 行目では、必ず「cs-gpios」属性を使用してチップ セレクト ピンを記述します。これにより、SPI ホスト ドライバーがチップ セレクト ピンを制御します。
5 行目は、IO によって使用される pinctrl 子ノードを設定します。これは、
サンプル コード 62.5.1.1 で作成した pinctrl_ecspi3 です。imx6ull.dtsi ファイルの 6 行目では、ecspi3 ノードのステータス (ステータス) がデフォルトで「無効」に設定されていますが、ここで「ok」に変更する
必要があります。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 システムを起動します。






ICM20608ドライバーを書き込む

「22_spi」という名前の新しいフォルダーを作成し、22_spi フォルダー内に vscode プロジェクトを作成し、ワークスペースに
「spi」という名前を付けます。プロジェクトの作成後、2 つの新しいファイル icm20608.c および icm20608reg.h を作成します。icm20608.c は
ICM20608 のドライバー コードであり、icm20608reg.h は ICM20608 レジスターのヘッダー ファイルです。まず、icm20608reg.h で ICM20608
レジスタを定義し、次の内容を入力します (一部省略されています。完全な内容については例を参照してください)。

#ifndef ICM20608_H
#define ICM20608_H
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: icm20608reg.h
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: ICM20608寄存器地址描述头文件
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 */

/* ICM20608寄存器 
 *复位后所有寄存器地址都为0,除了
 *Register 107(0X6B) Power Management 1 	= 0x40
 *Register 117(0X75) WHO_AM_I 				= 0xAF或0xAE
 */
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F

/* 陀螺仪静态偏移 */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18

#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A

/* 加速度输出 */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40

/* 温度输出 */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42

/* 陀螺仪输出 */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48

#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75

/* 加速度静态偏移 */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_ZA_OFFSET_L 			0x7E


#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
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 です。プローブ関数は、現在の SPI デバイスに対応する spi_device をドライバーに提供する
ため、プローブ関数によって渡される spi_device パラメーターとしてプローブ関数の private_data を設定します。
2. icm20608 の spi_driver の登録と登録解除
SPI デバイス ドライバの場合、最初に spi_driver を初期化し、システムに登録します icm20608 の spi_driver の初期化、登録、および登録解除のコードは次のとおりです

示例代码62.5.2.3 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 行目、デバイス ツリーのデバイスとドライバーの一致テーブル、ここには一致する項目が 1 つだけあります: 「alientek,icm20608」。
14 ~ 23 行目、icm20608 の spi_driver 構造体変数、icm20608 デバイスとこのドライバーが正常に一致すると、
15 行目の icm20608_probe 関数が実行されます。同様に、ドライバーが登録解除されると、icm20608_remove 関数が実行されます

30~33行目、icm20608_init関数はicm20608のドライバエントリ関数です。この関数でspi_register_driverを使用して、
上で定義したicm20608_driverをLinuxシステムに登録します。
40~43行目のicm20608_exit関数はicm20608のドライバ終了関数であり、この関数では
spi_unregister_driverを使用して、以前に登録されていたicm20608_driverの登録を解除しています。
3. プローブと削除関数
icm20608_driver のプローブと削除関数の内容は次のとおりです。

示例代码62.5.2.4 probe 和remove 函数
1 /*
2 * @description : spi驱动的probe函数,当驱动与
3 * 设备匹配以后此函数就会执行
4 * @param - client : i2c设备
5 * @param - id : i2c设备ID
6 *
7 */
8 static int icm20608_probe(struct spi_device *spi)
9 {
    
    
10 /* 1、构建设备号*/
11 if (icm20608dev.major) {
    
    
12 icm20608dev.devid = MKDEV(icm20608dev.major, 0);
13 register_chrdev_region(icm20608dev.devid, ICM20608_CNT,
ICM20608_NAME);
14 } else {
    
    
15 alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT,
ICM20608_NAME);
16 icm20608dev.major = MAJOR(icm20608dev.devid);
17 }
18
19 /* 2、注册设备*/
20 cdev_init(&icm20608dev.cdev, &icm20608_ops);
21 cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
22
23 /* 3、创建类*/
24 icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
25 if (IS_ERR(icm20608dev.class)) {
    
    
26 return PTR_ERR(icm20608dev.class);
27 }
28
29 /* 4、创建设备*/
30 icm20608dev.device = device_create(icm20608dev.class, NULL,
icm20608dev.devid, NULL, ICM20608_NAME);
31 if (IS_ERR(icm20608dev.device)) {
    
    
32 return PTR_ERR(icm20608dev.device);
33 }
34
35 /*初始化spi_device */
36 spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
37 spi_setup(spi);
38 icm20608dev.private_data = spi; /* 设置私有数据*/
39
40 /* 初始化ICM20608内部寄存器*/
41 icm20608_reginit();
42 return 0;
43 }
44
45 /*
46 * @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
47 * @param - client : i2c设备
48 * @return : 0,成功;其他负值,失败
49 */
50 static int icm20608_remove(struct spi_device *spi)
51 {
    
    
52 /* 删除设备*/
53 cdev_del(&icm20608dev.cdev);
54 unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
55
56 /* 注销掉类和设备*/
57 device_destroy(icm20608dev.class, icm20608dev.devid);
58 class_destroy(icm20608dev.class);
59 return 0;
60 }

8~43行目はプローブ関数で、デバイスとドライバのマッチングが成功した場合に実行されます、10~33行目は標準
登録キャラクタデバイスドライバです。
36 行目、SPI をモード 0、つまり CPOL=0、CPHA=0 に設定します。
37 行目、spi_device を設定した後、spi_setup を使用して構成する必要があります。
38 行目、icm20608dev の private_data メンバー変数を spi_device に設定します。
41 行目では、icm20608_reginit 関数を呼び出して ICM20608 を初期化し、主に ICM20608 の指定されたレジスタを初期化します

50~60行目、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 */
9 static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg,
void *buf, int len)
10 {
    
    
11
12 int ret = -1;
13 unsigned char txdata[1];
14 unsigned char * rxdata;
15 struct spi_message m;
16 struct spi_transfer *t;
17 struct spi_device *spi = (struct spi_device *)dev->private_data;
18
19 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
20 if(!t) {
    
    
21 return -ENOMEM;
22 }
23
24 rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存*/
25 if(!rxdata) {
    
    
26 goto out1;
27 }
28
29 /* 一共发送len+1个字节的数据,第一个字节为
30 寄存器首地址,一共要读取len个字节长度的数据,*/
31 txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */
32 t->tx_buf = txdata; /* 要发送的数据*/
33 t->rx_buf = rxdata; /* 要读取的数据*/
34 t->len = len+1; /* t->len=发送的长度+读取的长度*/
35 spi_message_init(&m); /* 初始化spi_message */
36 spi_message_add_tail(t, &m);
37 ret = spi_sync(spi, &m); /* 同步发送*/
38 if(ret) {
    
    
39 goto out2;
40 }
41
42 memcpy(buf , rxdata+1, len); /* 只需要读取的数据*/
43
44 out2:
45 kfree(rxdata); /* 释放内存*/
46 out1:
47 kfree(t); /* 释放内存*/
48
49 return ret;
50 }
51
52 /*
53 * @description : 向icm20608多个寄存器写入数据
54 * @param – dev : icm20608设备
55 * @param – reg : 要写入的寄存器首地址
56 * @param – val : 要写入的数据缓冲区
57 * @param – len : 要写入的数据长度
58 * @return : 操作结果
59 */
60 static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg,
u8 *buf, u8 len)
61 {
    
    
62 int ret = -1;
63 unsigned char *txdata;
64 struct spi_message m;
65 struct spi_transfer *t;
66 struct spi_device *spi = (struct spi_device *)dev->private_data;
67
68 t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
69 if(!t) {
    
    
70 return -ENOMEM;
71 }
72
73 txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
74 if(!txdata) {
    
    
75 goto out1;
76 }
77
78 /* 一共发送len+1个字节的数据,第一个字节为
79 寄存器首地址,len为要写入的寄存器的集合,*/
80 *txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零*/
81 memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里*/
82 t->tx_buf = txdata; /* 要发送的数据*/
83 t->len = len+1; /* t->len=发送的长度+读取的长度*/
84 spi_message_init(&m); /* 初始化spi_message */
85 spi_message_add_tail(t, &m);
86 ret = spi_sync(spi, &m); /* 同步发送*/
87 if(ret) {
    
    
88 goto out2;
89 }
90
91 out2:
92 kfree(txdata); /* 释放内存*/
93 out1:
94 kfree(t); /* 释放内存*/
95 return ret;
96 }
97
98 /*
99 * @description : 读取icm20608指定寄存器值,读取一个寄存器
100 * @param – dev : icm20608设备
101 * @param – reg : 要读取的寄存器
102 * @return : 读取到的寄存器值
103 */
104 static unsigned char icm20608_read_onereg(struct icm20608_dev *dev,
u8 reg)
105 {
    
    
106 u8 data = 0;
107 icm20608_read_regs(dev, reg, &data, 1);
108 return data;
109 }
110
111 /*
112 * @description : 向icm20608指定寄存器写入指定的值,写一个寄存器
113 * @param – dev : icm20608设备
114 * @param – reg : 要写的寄存器
115 * @param – data : 要写入的值
116 * @return : 无
117 */
118
119 static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
120 {
    
    
121 u8 buf = value;
122 icm20608_write_regs(dev, reg, &buf, 1);
123 }
124
125 /*
126 * @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
127 * : 三轴加速度计和内部温度。
128 * @param - dev : ICM20608设备
129 * @return : 无。
130 */
131 void icm20608_readdata(struct icm20608_dev *dev)
132 {
    
    
133 unsigned char data[14] = {
    
     0 };
134 icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
135
136 dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
137 dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
138 dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
139 dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
140 dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
141 dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
142 dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
143 }
144
145 /*
146 * ICM20608内部寄存器初始化函数
147 * @param : 无
148 * @return : 无
149 */
150 void icm20608_reginit(void)
151 {
    
    
152 u8 value = 0;
153
154 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
155 mdelay(50);
156 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
157 mdelay(50);
158
159 value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
160 printk("ICM20608 ID = %#X\r\n", value);
161
162 icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
163 icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
164 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
165 icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
166 icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
167 icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);
168 icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);
169 icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);
170 }

9 ~ 50 行目、icm20608_read_regs 関数は、icm20608 から複数の連続するレジスタ データを読み取ります; 注:
この実験では、SPI はいわゆる送受信の長さを持たない全二重通信です。N バイトを読み取るか送信するには
、N+1 バイトをカプセル化する必要があります。最初のバイトはデバイスに読み取りまたは書き込みを指示し、次の N バイトは
読み取りまたは送信するデータです。これは読み取り操作であるため、行 31 の最初のデータ bit7 を 1 に設定し、読み取り操作を示します。
60 ~ 96 行目、icm20608_write_regs 関数では、複数のレジスタ データを icm20608 に連続的に書き込みます。この関数と
icm20608_read_regs 関数の間にはほとんど違いはありません。
104~109行目、icm20608_read_onereg関数は、icm20608の指定されたレジスタデータを読み取ります。
119~123行目、icm20608_write_onereg関数、icm20608の指定レジスタにデータを書き込みます。
131 ~ 143 行目、icm20608_readdata 関数は、icm20608 6 軸センサーと温度センサーの元のデータ値を読み取り、アプリケーション プログラムが icm20608 を
読み取るときに、これらのセンサーの生データがアプリケーション プログラムに報告されます。
150 ~ 170 行目、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 */
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関数で、
アプリケーションプログラムがicm20608デバイスファイルを読み込むためにread関数を呼び出す際にこの関数が実行されます。この関数は、上で記述した
icm20608_readdata 関数を呼び出して、icm20608 の元のデータを読み取り、アプリケーション プログラムに報告します。皆さん、カーネルでは浮動小数点計算を使用しないように注意してください。浮動小数点計算が含まれる
ため、icm20608 の元の値をドライバー内の対応する実際の値に変換しないでください。

テストAPPを書く

新しい icm20608App.c ファイルを作成し、次の内容を入力します。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: icm20608App.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: icm20608设备测试APP。
其他	   	: 无
使用方法	 :./icm20608App /dev/icm20608
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/9/20 左忠凯创建
***************************************************************/

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    
    
	int fd;
	char *filename;
	signed int databuf[7];
	unsigned char data[14];
	signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
	signed int accel_x_adc, accel_y_adc, accel_z_adc;
	signed int temp_adc;

	float gyro_x_act, gyro_y_act, gyro_z_act;
	float accel_x_act, accel_y_act, accel_z_act;
	float temp_act;

	int ret = 0;

	if (argc != 2) {
    
    
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
    
    
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
    
    
		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) {
    
     			/* 数据读取成功 */
			gyro_x_adc = databuf[0];
			gyro_y_adc = databuf[1];
			gyro_z_adc = databuf[2];
			accel_x_adc = databuf[3];
			accel_y_adc = databuf[4];
			accel_z_adc = databuf[5];
			temp_adc = databuf[6];

			/* 计算实际值 */
			gyro_x_act = (float)(gyro_x_adc)  / 16.4;
			gyro_y_act = (float)(gyro_y_adc)  / 16.4;
			gyro_z_act = (float)(gyro_z_adc)  / 16.4;
			accel_x_act = (float)(accel_x_adc) / 2048;
			accel_y_act = (float)(accel_y_adc) / 2048;
			accel_z_act = (float)(accel_z_adc) / 2048;
			temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;


			printf("\r\n原始值:\r\n");
			printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
			printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
			printf("temp = %d\r\n", temp_adc);
			printf("实际值:");
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
			printf("act temp = %.2f°C\r\n", temp_act);
		}
		usleep(100000); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}


60~91行目のwhileループでは、icm20608から100msごとにデータを読み込み、icm20608の元データを
ジャイロの角速度や加速度計のg値などの実際の値に変換しています。
icm20608 ドライバではジャイロスコープと加速度計の測定範囲が最大値 (それぞれ ±2000 と ±16g) に設定されていることに注意してください。
したがって、実際の値を計算するときは、ジャイロスコープには 16.4、加速度センサーには 2048 を使用します。最後に、センサーの生データと取得された
実際の値が端末に表示されます。

テストを実行する

ドライバーをコンパイルしてアプリをテストする

1. ドライバーをコンパイルし、 Makefile を作成します この章の実験の Makefileは、obj-m 変数の値が "icm20608.o" に変更されている点を
除いて、基本的に第 40 章の実験と同じです。
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 に示されています。
ここに画像の説明を挿入
図 62.6.1.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

図 62.6.2.1 に示すように、テスト APP は ICM20608 からデータを継続的に読み取り、端末に出力します。
ここに画像の説明を挿入
開発ボードが静的状態にあるとき、Z 軸方向の加速度は重力加速度は約1gです。ジャイロスコープの場合
、静止状態では 3 軸の角速度が約 0°/S である必要があります。ICM20608内部の温度センサーが収集する温度は
約30度で、開発ボードを振ると、このときジャイロスコープと加速度センサーの値が変化します。

おすすめ

転載: blog.csdn.net/zhuguanlin121/article/details/130630012
おすすめ