ブロックを積みながら(30) BBC micro:bit、512K SRAMをSPI接続

Joseph Halfmoon

昨日、micro:bit用のブレークアウトボード(横型)を用意したので、早速活用してみます。接続してみるのはSPI接続の512Kbit SRAM 23LC512です。ハードウエアの接続は簡単でしたが、MakeCodeで読み書き関数を作るところで躓きました。いつもよく文書を読まないで書いているから!直して読み書きOK。

※「ブロックを積みながら」投稿順 index はこちら

(末尾に実験に使用したJavaScript表現のコード全文を掲げました)

マイクロチップ社製のSPI接続 512Kbit SRAM 23LC512については、以下の投稿にてラズパイPicoに接続してみています。そのときの制御につかったのはMicroPythonのスクリプトでした。

MicroPython的午睡(18) ラズパイPico、SPIでシリアルSRAM接続

使用したのは8ピンDIPの版ですが、配線本数も少なく、それでいて512Kbit(64Kバイト)のメモリ容量が使えます。格納するのはデータに限られますが、本体のRAM容量が限られるmicro:bit、特にv1.x系には何かの折に便利じゃないかと思います。

接続図は以下のとおりです。

microbit_23LC512_schematic
ブレークアウトボードにジャンパ線6本で23LC512を刺したブレッドボード(パスコンくらいつけましたが)を接続すればそれでハードウエアの準備はOKです。

SRAMへのアクセス「フォーマット」で?

SPIは入出力独立したデータ線(MOSIとMISO)を持っているので、SCK端子でクロックを駆動すれば、クロック数分のビットの出力と入力を同時に行うことになります。しかし、この23LC512とのインタフェースでは、出力している間は入力は無視、入力している間は出力無視と、実質的に半二重的な動作となります。動作の基本は簡単で、micro:bit側からみて以下のような操作です。

  1. コマンドバイト(メモリへの読み書き、制御レジスタへの読み書きなど指定)の出力
  2. メモリアドレスの出力(64kバイトなので16ビット)
  3. データバイト列の入出力(読み、書き)

制御レジスタの読み書きについては、1と3の操作だけとなります。制御レジスタ幅は1バイトなので合計16ビット(SCK16クロック)です。

メモリの読み書きについては、データ1バイトの読み書きに限定すれば、1,2,3の各操作でコマンド1バイト、アドレス2バイト、データ1バイトの合計4バイト、32ビット(SCK32クロック)です。

MakeCodeエディタのAdvancedカテゴリのPinsブロックの中のSPI関係のブロックを見ていて目についたのが、”spi  format” という名のブロックです。これには1度に転送するbit数を指定する引数があるのです。

制御レジスタアクセスのときは16ビット、メモリアクセスのときは32ビットと指定すればいいんじゃね。

などと考えてしまいました。しかし、実際にやってみると表面上はエラーにはならないのですが挙動不審な動作をします。波形をオシロで眺めたりしてようやう分かりました。8ビットに指定すればSPIとして動作するけれども16や32ビットに指定すると動作しない(SCKも動かない)のです。理由は良くわからないのですが、同時にUARTまで挙動不審な動作をしている感じ。しかし、以下のマニュアルページを良く読んでいれば明らかだったのです。

spi Format

上記のページから1文引用させていただきます。

This value must be 8 since only 8 bits is currently supported for SPI data values.

ビット数指定のフィールドは存在するものの、8ビット以外の値をたててはいけないのでした。ということは、CS信号をアクティブにしている期間中、

  • 16ビットの時は8ビット2回
  • 32ビットの時は8ビット4回

に分割すれば済みます。SCKは8ビットずつ途切れ途切れに出力されますが、同期式の転送なので問題ないでしょう。『8ビットしか使えないって先に言ってよね』、って読んでない方が悪いか。

実際の初期設定コード

on startは以下のようにしてみました。MakeCodeエディタのドキュメントのサンプルに合わせCS信号はP0に割り当てました。デジタル出力可能な端子であればどの端子でも構わないと思います。動作周波数は控え目の1MHz、動作モードは8ビットのモード3、どちらもMakeCodeエディタのデフォルト値どおりです。

onstart

23LC512の制御レジスタへのアクセス関数は以下のようです。Returnで値を返さす、大域変数 retValへの書き込みという副作用で返しているのが良くないですが、spiからの読み取りを常にretValに書いているので、とりあえず動けばいいじゃん、という感じです。すみません。
readModeReg

メモリへのアクセス

メモリへのアクセス関数も8ビットに分割して書きます。23LC512自体はシーケンシャルアクセスとて、連続アドレスの複数バイトの読み書きがデフォルトで可能になっているのですが、ここでは最短1バイト固定とし、簡単にやっつけています。

ビット演算子(を記述するブロックが無い)を使いたかったので、例によって、いったんJavaScript表現に切り替えてビット演算子で処理するコードを書いてからブロック表現に戻ります。すると灰色のビット演算子を含むブロックが現れます。

write23LC512読み出し関数も同じです。読み出しデータは末尾の0書き込み(ダミー)のときに返ってきます。この関数を動かした直後にretValに格納されているバイトデータが結果という副作用依存の汚いコード。
read23LC512

書き込み/読み出しテスト用のメインループ

カウンタの値をインクリメントしながら、同じ番地(十進1000番地)にカウンタ値を書き込んでは、読み出してみるというだけのテスト用のループです。

forever実際に動かしたところがこちら。モードレジスタの値はデフォルトのシーケンシャル・モードを示しています。0かけば0が返る、1かけば1が返る。よってくだんの如し。

ModeReg:64
Count:0
ReadData:0
Count:1
ReadData:1
Count:2
ReadData:2
Count:3
ReadData:3
Count:4
ReadData:4

一応、横型ブレークアウト・ボードも問題なく使えるみたい。しかし、SPIと言えど、実際に動かしてみないと使い方、分からんものだな~。

ブロックを積みながら(29) BBC micro:bit、EEPROMをI2C接続 へ戻る

ブロックを積みながら(31) BBC micro:bit、バッググラウンド実行とループ制御 へ進む

JavaScript表現(TypeScript)の全ソース
function readModeRegister () {
    pins.digitalWritePin(DigitalPin.P0, 0)
    retVal = pins.spiWrite(5)
    retVal = pins.spiWrite(0)
    pins.digitalWritePin(DigitalPin.P0, 1)
}
function read23LC512 (adr: number) {
    pins.digitalWritePin(DigitalPin.P0, 0)
    retVal = pins.spiWrite(3)
    retVal = pins.spiWrite((adr >> 8) & 0xFF)
    retVal = pins.spiWrite(adr & 0xFF)
    retVal = pins.spiWrite(0)
    pins.digitalWritePin(DigitalPin.P0, 1)
}
function write23LC512 (adr: number, dat: number) {
    pins.digitalWritePin(DigitalPin.P0, 0)
    retVal = pins.spiWrite(2)
    retVal = pins.spiWrite((adr >> 8) & 0xFF)
    retVal = pins.spiWrite(adr & 0xFF)
    retVal = pins.spiWrite(dat & 0xFF)
    pins.digitalWritePin(DigitalPin.P0, 1)
}
let count = 0
let retVal = 0
basic.showString("Hello!")
serial.redirectToUSB()
serial.setBaudRate(BaudRate.BaudRate115200)
// P0: SPI CS
pins.digitalWritePin(DigitalPin.P0, 1)
pins.spiPins(DigitalPin.P15, DigitalPin.P14, DigitalPin.P13)
pins.spiFrequency(1000000)
pins.spiFormat(8, 3)
readModeRegister()
serial.writeLine("ModeReg:" + retVal)
basic.forever(function () {
    led.toggle(0, 0)
    serial.writeLine("Count:" + count)
    write23LC512(1000, count)
    read23LC512(1000)
    serial.writeLine("ReadData:" + retVal)
    basic.pause(1000)
    count += 1
})