SPIクラスを追加(RPiベアメタルMicroPython)


前回の実装を元に、ベアメタルRaspberry Pi版MicroPythonにSPIクラスを追加してみました。

既存のSPIクラスのAPI

I2Cのときと同様、今回も既存のSPIクラスのAPIを調べてみました。
調査対象はハード実装(pyb.SPI)、ソフト実装(machine.SPI)、CircuitPythonの3つです。

micropython/pyb.SPI.rst at master · micropython/micropython

micropython/machine.SPI.rst at master · micropython/micropython

SPI – a 3-4 wire serial protocol — Adafruit CircuitPython 0.0.0 documentation

類似点や違いはI2Cのときと大体同じでした。
定義されているメソッドや定数は以下の通りです。

MicroPython HW SPI:
pyb.SPI(bus, …)
SPI.deinit()
SPI.init(mode, baudrate=328125, \*, prescaler, polarity=1, phase=0, bits=8, firstbit=SPI.MSB, ti=False, crc=None)
SPI.recv(recv, \*, timeout=5000)
SPI.send(send, \*, timeout=5000)
SPI.send_recv(send, recv=None, \*, timeout=5000)
SPI.MASTER
SPI.SLAVE
SPI.LSB
SPI.MSB

MicroPython SW SPI:
SPI.init(baudrate=1000000, \*, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=None, mosi=None, miso=None, pins=(SCK, MOSI, MISO))
SPI.deinit()
SPI.read(nbytes, write=0x00)
SPI.readinto(buf, write=0x00)
SPI.write(buf)
SPI.write_readinto(write_buf, read_buf)
SPI.MASTER
SPI.LSB
SPI.MSB

CircuitPython SPI:
busio.SPI(clock, MOSI=None, MISO=None)
deinit()
configure(*, baudrate=100000, polarity=0, phase=0, bits=8)
try_lock()
unlock()
write(buffer, *, start=0, end=len(buffer))
readinto(buffer, *, start=0, end=len(buffer), write_value=0)
write_readinto(buffer_out, buffer_in, *, out_start=0, out_end=len(buffer_out), in_start=0, in_end=len(buffer_in))
frequency

MicroPythonのSW SPIとCircuitPythonは大体同じですが、CircuitPythonはバッファの一部を指定するためのパラメータ(start、end)が追加されています。I2Cのときと同様、MemoryViewを使わずに済ませるためと思われます。

Raspberry Pi向けのAPI設計

今回も、MicroPythonのSW実装のAPIをベースにすることにしました。MicroPythonで書かれたデバイスドライバとの相性はこれが一番良いと思われます。

使用できるSPIバスは1つだけで、IDは0とします。
クロックおよびMOSI、MISOの信号線の指定は行えません。

スレーブデバイスを選択するためのCS信号は、Raspberry Piではハードウェアでサポートされていますが、あえて使わないことにしました。
SPIを利用する既存のMicroPythonソフトウェアでは、CSをGPIO出力で実装しているためです。
CSは高速に処理する必要は無いので、MicroPythonからGPIO出力を制御する使い方で問題ありません。

また、Raspberry PiはSPIをMasterでしか使えないので、クラス定数SLAVEは省略します。
ハードウェアではMSB/LSBの指定や、ビット幅の指定もできませんが、互換性のためにパラメータとしては残し、対応できない値が与えられたときはエラーにすることにします。

machine.SPIクラスの実装

I2Cのときと比べれば、かなりスムーズに実装できました。
バグらしいバグは、大量のデータをwriteしたときハングするというもので、これはReadバッファがあふれたためでした。
SPIでは、Masterが送ったのと同じだけのデータが常にSlaveから送られてくるので、読み捨て処理をする必要がありました。

まずは前回同様、MOSIとMISOを直結して、送ったデータがきちんと読み取れるかのテストです。

問題なく読み書きできました。

続いて、おなじみのOLED、SSD1306への表示をMicroPython付属のドライバで行ってみました。
SSD1306では、SPIのClock、MOSI、MISOのほか、CS、DC(Data/Cmd切り替え)、Resetも配線する必要があります。

結線は以下のようにしてみました。

OLED Raspberry Pi
GND  GND(6)
VCC  3V3(17)
D0   CLK(23)
D1   MOSI(19)
RES  IO17(15)
DC   IO16(13)
CS   IO5(11)

この結線の場合、コードは以下のようになります。
(ドライバモジュールssd1306.pyは読み込み済みの想定です。)

ピン番号はGPIOの番号を使うことに注意してください。
実行すると、記事の先頭の写真のようにちゃんと表示されました。

コメント