RasPi 3B+ で SPI バスの動作周波数と波形を調べてみた

RasPi の SPI バスの動作周波数と波形を調査してみます。


#はじめに
RasPi 3B+ で、SPI バスの実際の通信速度と波形を調べてみました。
目的は、外付デバイスとのデータのやりとりが、どれくらいの速度でできるかを、見極めるためです(※)。

相手が FPGA やマイコンだと、LAN でデータをやり取りするのは手間がかかります。
汎用性を考慮して、40pin コネクタで最大スループットが期待できる SPI での通信速度を調べてみます。

※例えば SPI バスを LCD 表示に使う場合、上限が上がれば表示フレームレートを上げられます。

#実験環境
RaspberryPi 3B+
オシロスコープ (MSO6104A)
アクティブプローブ (1156A, 1157A)
パッシブプローブ (10073C)

#RasPi 40pin コネクタの SPI バス
SPI バスは、40pin コネクタの 19-26 番ピンに割り当てられています。
https://docs.microsoft.com/ja-jp/windows/iot-core/learn-about-hardware/pinmappings/pinmappingsrpi
![rp2_pinout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/1d29ceae-1291-d953-3d89-ae58e4bb2295.png)

19-MOSI
21-MISO
23-SCLK
24-nCS
20/25-GND
 
19(MOSI) - 21(MISO) ピン間を、ショートプラグで繋いでおきます。
こうすると、MOSI → MISO ループバックで、SPI 出力したデータを読み出して確認できます。
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/50e586d7-efaf-91a8-e2d8-2a8c5f813565.jpeg)


#RasPi で SPI の使用準備
まず、sudo raspi-config して、SPI を有効にします。

動作確認用のプログラムは、
wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-3.10.y/Documentation/spi/spidev_test.c
して spidev_test のソースをとってきたら、
gcc -o spidev_test spidev_test.c
でコンパイルして準備完了。


#ループバックで読み書きしてみる
./spidev_test -D /dev/spidev0.0
で、テストデータの送受信ができます。

```
pi@raspberrypi:~$ ./spidev_test -D /dev/spidev0.0
spi mode: 4
bits per word: 8
max speed: 2500000 Hz (2500 KHz)

FF FF FF FF FF FF
40 00 00 00 00 95
FF FF FF FF FF FF
FF FF FF FF FF FF
FF FF FF FF FF FF
DE AD BE EF BA AD
F0 0D
```
デフォルトの 2.5MHz で 38 バイトの SPI 通信が行われ、MISO から読み取ったデータが表示されました。

このとき、40pin バス上の波形は、下記のようになっています。
![SPI_2.5MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/eeeaccb8-d07c-e0c2-70f1-81b9837710f1.png)

38 バイトの全二重通信で、nCS がアクティブな期間を計ると、216us 程度かかっています。(およそ 172 KB/s)
nCS 期間は、都度ランダムに増減します。
何度か試すと最短で 200us ぐらい、最長で 220us ぐらいでした。

出力→入力でデータ化けが起こると、違うデータが見えます。
後ろの DEAD BEEF が見えなくなるので、分かりやすいです。
ショートプラグを抜くと、全部のデータが 00 になります。

#SPI バスの動作周波数を変化させてみる
./spidev_test -D /dev/spidev0.0 -s (KHz 単位のクロック周波数値)
で、SPI 出力のクロック周波数を指定することができます。

実際の周波数は、指定した値に応じて、ハードウェア上の分周比で設定可能な近い周波数が選択されます。

例えば、
./spidev_test -D /dev/spidev0.0 -s 10000000
とすると、10MHz で SPI バスが駆動されます。

```
pi@raspberrypi:~$ ./spidev_test -D /dev/spidev0.0 -s 10000000
spi mode: 4
bits per word: 8
max speed: 10000000 Hz (10000 KHz)

FF FF FF FF FF FF
40 00 00 00 00 95
FF FF FF FF FF FF
FF FF FF FF FF FF
FF FF FF FF FF FF
DE AD BE EF BA AD
F0 0D
```


-s 設定値を変化させて、SCLK の周波数をオシロで実測したところ、設定可能な周波数は下記となっているようです。

```
6000000 (5.88 MHz)
6100000 (6.06 MHz)
6500000 (6.44 MHz)
7000000 (6.9 MHz)
8000000 (8 MHz)
9000000 (8.7 MHz)
10000000 (10 MHz)
11000000 (10.53 MHz)
12000000 (11.77 MHz)
13000000 (12.48 MHz)
14000000 (13.3 MHz)
15000000 (14.29 MHz)
16000000 (15.4 MHz)
17000000 (16.67 MHz)
19000000 (18.18 MHz)
20000000 (20 MHz)
23000000 (22.2 MHz)
25000000 (25 MHz)
30000000 (28.6 MHz)
34000000 (33.3 MHz)
40000000 (40 MHz)
50000000 (50 MHz)
67000000 (66.6 MHz)
100000000 (100 MHz)
200000000 (200 MHz)
```


#実用最大速度を調べてみる
外部デバイスとの通信量を増やすためには、できるだけ SPI バスの周波数を上げて駆動しないといけません。

しかし、あまり周波数を上げると、正しく通信できなくなってしまいます。

試してみた結果、ループバックで DEAD BEEF が正しく読める上限は、50 MHz でした。
66.7 MHz 以上では、データ化けが起こります。

```
pi@raspberrypi:~$ ./spidev_test -D /dev/spidev0.0 -s 50000000
spi mode: 4
bits per word: 8
max speed: 50000000 Hz (50000 KHz)

FF FF FF FF FF FF
40 00 00 00 00 95
FF FF FF FF FF FF
FF FF FF FF FF FF
FF FF FF FF FF FF
DE AD BE EF BA AD
F0 0D
```


```
pi@raspberrypi:~$ ./spidev_test -D /dev/spidev0.0 -s 67000000
spi mode: 4
bits per word: 8
max speed: 67000000 Hz (67000 KHz)

7F 7F 7F 7F 7F 7F
20 00 00 00 00 4A
7F 7F 7F 7F 7F 7F
7F 7F 7F 7F 7F 7F
7F 7F 7F 7F 7F 7F
6F 56 5F 77 5D 56
78 06
```

化け具合から、データのセットアップタイムが不足しているようです。
(L → H に変化した直後のビット読み取り結果が、H になれていない)

50MHz 駆動のときの 40pin バス波形は、このようになっています。
![SPI_50MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/182ecbaa-446f-5f9d-1041-5f7f79414e5d.png)

2.5MHz 駆動時に 216us かかっていたのと同じ 38 バイトの転送が、25us 程度で完了しています。(およそ 1.45 MB/s)
ただ、実際の転送期間よりも、前後の nCS のアクティブ期間が過剰に長いので、オーバーヘッドが大きいです。
できるだけ一度に大きなデータにまとめて転送すると、転送効率が良くなります。


#波形を調べてみる
周波数ごとに波形を確認してみました。
nCS がアクティブになってから、最初にクロックが立ち上がった所を拡大しています。
最初のデータは 0xFF を出力しています。
※オシロのサンプリング速度を上げるため、CH2(nCS)を表示オフにしています。

![2.5MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/d0f541e9-b8e2-904b-622e-1945203596fd.png "2.5 MHz")
![10MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/63eefc6b-a9d1-e170-389d-9f54bb877a80.png "10 MHz")
![50MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/2e3cb0ef-db15-5232-ca1b-c5918ddc9e06.png "50 MHz")
![66.7MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/98ab95b8-564f-e2bd-79e5-88606bc7639d.png "66.7 MHz")
![100MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/71a4e404-75a2-c42d-24b6-0eb9922bb985.png "100 MHz")
![200MHz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/b3f13ab8-b5e4-5450-f7ab-dbb524d49b6d.png "200 MHz")


#GPIO の出力ドライブ電流を調整して、波形を改善してみる
66.7 MHz 以上での SPI 通信に向けて、波形が改善できないか試してみます。
最近の FPGA では、出力ピン 1 本づつ、個別に出力電流値を設定できます。

RasPi は、FPGA ほど便利にはできていませんが、GPIO ピンが 3 グループに分けられ、グループ毎に出力電流を設定できるようになっています。
ドライブ電流は、2,4,6,8,10,12,14,16 mA の 8 種類から選択できます。
デフォルトは、8mA に設定されています。

https://qiita.com/sasaki77/items/799f483e53b86a7cb291
で公開されているソースを利用させていただき、SPI バスに割り当てられている GPIO ピンのドライブ電流を変化させてみます。
ソース変更点は、２点あります。

```
#define BCM2708_PERI_BASE        0x20000000
 → RasPi 2/3 では、0x3F000000 に変更。(Pi4 では 0xFE000000 に？)

PADS1 = (0xFFFFFFF8 & PADS1) | 0x7 | 0x5a000000;
 → PADS1 を PADS0 に変更 (2ケ所)、0x7 を 0x1 ～ 0x7 で変化させる
```

結論として、どう波形を変えても、66.7MHz 以上ではデータ化けが発生してしまい、正しく受信できるようにはなりませんでした。
波形的には受信できてもおかしくないので、MISO 端子の内部的な制約で上限が決まっているようです。

##100 MHz 駆動時の波形
ドライブ電流値は、画像のキャプションに記載しています。
![W100-2mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/adf8d758-59d6-285a-6ad4-5df7a7d74ebe.png "100 MHz - 2mA (最小電流)")
![W100-4mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/edbda223-9bfa-14bf-19bd-5c910aaaf07b.png "100 MHz - 4mA")
![W100-8mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/6f96d13b-33ea-8980-4c01-2f84f41cb982.png "100 MHz - 8mA (デフォルト)")
![W100-10mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/42b26218-8cc2-a312-3bc3-bdbf4ec39239.png "100 MHz - 10mA (見た感じ最良)")
![W100-12mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/3c0b73b6-5dd8-454d-ff02-de520724c8ad.png "100 MHz - 12mA")
![W100-16mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/6d668d42-fba5-4b8e-f4ee-854c42735191.png "100 MHz - 16mA (最大電流)")


##200 MHz 駆動時の波形
ドライブ電流値は、画像のキャプションに記載しています。
![W200-8mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/d7da7b83-e330-4ea9-fc48-f7b33e42675f.png "200 MHz - 8mA (デフォルト)")
![W200-12mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/fc5d455b-90e5-1cf7-8089-cf5435573082.png "200 MHz - 12mA")
![W200-16mA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/7bb1ead2-adf8-02b8-be15-588bc9449d43.png "200 MHz - 16mA")


#まとめ
RasPi 3B+ の SPI バスについて、動作可能な周波数と波形を調査しました。

MOSI ⇒ MISO ループバックで正しくデータが読めるのは、50 MHz まででした。
出力波形を見ると、100 MHz までは、外部デバイスに受信してもらえる可能性があります。
200 MHz では、セットアップタイムが不足して受信エラーが起きそうです。

周波数は実機で確認した上限値です。
40pin バスに接続される配線や基板の性能によっては、これよりも低い周波数でないと、動作が怪しくなることに注意してください。

実験は、周囲温度の影響は考慮していません。
また、ドライブ電流や駆動周波数を上げると、EMI (放射ノイズ)性能が悪化するので、注意が必要です。