[RPi][Linux] Raspberry Pi で SPI 通信

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 経由の通信方法はこの他にもいろいろあります(ここでは割愛します)。

参考


コメント

タイトルとURLをコピーしました