Raspberry PiベアメタルプログラミングでSPI


I2Cに続いて、SPIのベアメタルプログラミングを試してみました。

例題は、入出力を直結して自分で出力したデータを自分で読むものにしました。
この例題を動かすには、上の写真のように、19番ピン(GPIO10)と20番ピン(GPIO9)を接続します。
コードは以下に置いてあります。

bare_matal_rpi_zero/spi at master · boochow/bare_matal_rpi_zero

接続デバイスに依存しない例題にしたかったので、I2CのデバイススキャンのようなものがSPIでもないか、と考えていたところ、以下の記事でInとOutを接続しているのを見て、アイデアを拝借することにしました。

Raspberry PiのSPI通信 | TomoSoft

SPIの概要

SPIはI2Cよりもシンプルで、クロック、デバイスへの出力信号(Master Out Slave In = MOSI)、デバイスからの入力信号(Master In Slave Out = MISO)の3つを使って通信します。
I2Cのスレーブアドレスにあたるものは無く、通信相手を指定(enable)するための信号線を別に使用します。

クロックとデータ信号の関係を指定するために、Polarity(極性)とPhase(位相)の2つを指定する必要があります。

極性:通信をしていない状態ではクロック線はL(0)かH(1)か
位相:データ信号を読み取るのは、クロックの先頭(0)か末尾(1)か

それぞれ2通りあるので、組み合わせは4通りになります。

以下の図は極性=0、位相=0の組み合わせのクロックとMOSI信号です。
極性と位相は使用するデバイスに合わせて選択する必要がありますが、使用頻度が高いのは、極性=0、位相=0の組み合わせのようです。

SPIの解説は以下のリンク先の記事が分かりやすいと思います。

「SPI」の解説 – しなぷすのハード製作記

Raspberry PiのSPIインタフェース

Raspberry Piには、SPIインタフェースが3つあります。
今回使用しているのはSPI0で、SPI1とSPI2はmini-UARTと同様にAUXペリフェラルの一部になっています。

ちなみに、dwelch67さんのベアメタルプログラミングの例題にもSPI0を使っているものがありますが、AUXのSPI1 enableも設定しています。
私の試した限りでは、AUXペリフェラルを設定しなくても大丈夫だったのですが、何か理由があるのかもしれません。

使用するGPIOピンとAlternate Functionの指定は以下のようになっています。

・SPI0: GPIO7..11(ALT0)またはGPIO35..39(ALT0)
・SPI1: GPIO16..21(ALT4)
・SPI2: GPIO40..45(ALT4)

SPI2はRaspberry Pi Compute Moduleでは使えるようですが、それ以外のRaspberry Piでは拡張コネクタに信号が出ていないため、簡単には使えないようです。

クロックスピードは、マニュアルによるとAPBクロック(標準では250MHz)を16bitカウンタで分周した値になります。カウンタの値は2の階乗でなければならない(The divisor must be a power of 2)と記載されており、実際Linuxのデバイスドライバはそうなっているらしいですが、2の階乗ではなく2~65536(=0)の値で動作するという報告があります。(マニュアルのerrataも参照のこと)
実際、試した限りでは2の階乗である必要はないようで、上記の私の例題でも、1/833(約300KHz)を指定しています。

データ転送には、Polling、IRQ、DMAが使えます。
今回の例題はPollingです。Pollingの場合は、Control/StatusレジスタとFIFOレジスタ(データの読み書き)とCLKレジスタ(クロック速度指定)だけで使えるので、I2Cよりも実装は簡単でした。