SPI通信が使える環境がないか探していて、Raspberry Pi なら 40pin ヘッダに出ていて直接使えるのでこれで何かモジュールを繋げば通信できそうということで試してみます。
Raspbian 環境なら手順がいろいろ書かれているので、ここでは敢えて mainline kernel 環境で SPI を有効にしてみることにします。SPI はシリアル通信の一つでI2Cと同様に周辺デバイスのアクセス手段として利用されています。
SPI 通信の動作を確認するため、対向デバイスとして温度センサモジュールを接続して温度を取得してみることにします。温度センサは ADT7310 を使いますが、モジュール化された "MDK001" が便利です。
まず、Raspberry Pi と 温度センサモジュールは次のように接続します (色は写真の配線に合わせたもの)。
No. | MDK001 | Raspberry Pi | No. | |
1 | Vcc | 赤 | Vcc | 2 or 4 |
2 | SCL | 緑 | SCLK (GPIO11) | 23 |
3 | SDO | 橙 | MISO (GPIO9) | 21 |
4 | SDI | 黄 | MOSI (GPIO10) | 19 |
5 | ~CS | 白 | CE#0 (GPIO8) | 24 |
6 | GND | 黒 | GND | 6 etc. |
私の場合、Raspberry Pi の40pin ヘッダをフラットケーブルで外に出していたので、実際のピン配置とは左右逆になっています(写真右)。
次に kernel を準備します。
RPi の SPI ドライバ spi-bcm2835 はモジュールとしてビルドされます。また、userland からアクセスするためのドライバ spidev も同様ですが、後で devicetree から識別するための"仮の"文字列を追加しておきます。以下はその patch です。何故"仮"なのかは後述。
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -669,6 +669,7 @@ static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "lineartechnology,ltc2488" },
{ .compatible = "ge,achc", },
{ .compatible = "semtech,sx1301", },
+ { .compatible = "linux,spidev", },
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
kernel では下記の defconfig にある通り、必要なドライバはすでにモジュールとしてビルドされるため、"make modules" でモジュールが得られます。モジュールファイル (spi-bcm2835.ko, spidev.ko)はネットワークかUSB経由でターゲットに転送しておきます。
CONFIG_SPI_BCM2835=m CONFIG_SPI_SPIDEV=m
RPi の devicetree (例えば RPi3 では arch/arm/boot/dts/bcm2837-rpi-3.dts) に spi を有効にする記述を追加します。spidev ノードでは先にパッチで追加した"仮の" compatible 文字列 "linux,spidev" を使っています。devicetree は "make dtbs" でビルドします。
注:仮というのは、現在の spi subsystem の見解として汎用定義は行わず、デバイス専用の定義を作成することになっているためです。そのため、ドライバに未定義の文字列("spidev"も含む) がcompatible 指定されると WARN_ON() により警告が出ます。
&spi { pinctrl-names = "default"; pinctrl-0 = <&spi0_gpio7>; status = "okay"; spidev { compatible = "linux,spidev"; reg = <0>; }; };
上記 kernel を起動し、モジュールをロードすると /dev/spidev0.0 というファイルができるはずです。userland ではこれを経由して SPI にアクセスします。
注:modprobe でロードするにはビルド時に "make modules_install" でroot ファイルシステム上に直接モジュールファイルをインストールしておく必要があります。モジュールファイルを指定してロードするには insmod を使います。
# modprobe spi-bcm2835.ko # modprobe spidev.ko # ls /dev/spi* /dev/spidev0.0
spidev_test を使って送受信をしてみます。このツールは spidev ドライバを経由して SPI 通信を行え、kernel ディレクトリの tool/spi にあります。単体プログラムなので kernel とは別にビルドしてモジュールと同様にターゲットに転送しておきます。
$ cd tool/spi $ make CROSS_COMPILE=aarch64-linux-gnu- $ ls spidev_test spidev_test
ターゲットにてコマンドを実行します。転送の極性として "モード3"を使用するため、"-O" "-H" を指定します。
# spidev_test -D /dev/spidev.0.0 -O -H -p "\xff\xff\xff\xff" # spidev_test -D /dev/spidev.0.0 -O -H -p "\x54\xff\xff" -v spi mode: 0x3 bits per word: 8 max speed: 500000 Hz (500 KHz) TX | 54 FF FF __ __ __ __ __ RX | 0E 70 00 __ __ __ __ __
- まず 0xff 0xff 0xff 0xff でリセット
- 0x54 で連続出力スタート
- 0xff 0xff で出力取り出し → RX の 0x0e 0x70 が出力値
16bit センサなので 0x0e70 となり、これを 3bit 右シフトして 16.0 で割った小数点値が「温度」になります。(0x0e70 >> 3) / 16.0 = 28.875[℃]
とりあえず SPI でモジュールからのデータ受信はできたので目標は達成しましたが、userland での spidev 経由の通信方法はこの他にもいろいろあります(ここでは割愛します)。
コメント