SDカードを読む(RPiベアメタルMicroPython)

sdread.png

ベアメタルRaspberry Pi版MicroPythonでRAMディスクが使えるようになって喜んでいたのですが、やはりSDカードも使えるようにしたいという欲が出てきます。
スクリプトをPCから転送する用途には、シリアルポートとRAMディスクの組み合わせで実用上は十分ですが、データを扱おうとするとSDカードがあるほうが便利です。

RAMディスクを動作させる際にMicroPythonのファイルシステムについていろいろ調べましたので、その記憶が薄れないうちにSDカードへのアクセスも実装することにしました。

●MicroPythonからのSDカード利用方法

以下のリンク先にはESP8266でSDカードを利用する場合の利用方法が記載されています。
machine.SD(ピン番号)でブロックデバイスのインスタンスをつくり、それをmountしています。
ブロックデバイスを実装しているクラスがRAMディスクと違っています。逆に言うと違いはそれだけです。

class SD – secure digital memory card — MicroPython 1.9.4 documentation

ハードウェア的には、ESP8266にはeMMCインタフェースはありませんので、SPI接続になっています。
Raspberry Piでは、Raspberry Piのハードウェアに合わせてSDカードをブロックデバイスとしてアクセスするクラスを作ればいいことになります。

●起動時のSDカードの扱い

これまでMicroPythonが移植されたボードでは、MicroPythonは内蔵フラッシュから起動します。
Raspberry PiのようにMicroPython自体がSDカードから起動するデバイスはありませんが、MicroPythonが起動後最初に実行する「boot.py」「main.py」をSDカードから読み取る実装は存在します。

STM32シリーズだとpyboardも含め、microSDカードスロットが搭載されているものも結構あります。
そのようなボードでは、起動時にSDカードを自動マウントする場合があります。
以下のリンク先にはpyboardでの動作が記載されています。

General information about the pyboard — MicroPython 1.9.4 documentation

SDカードがあればSDカードを/sdにマウント、無ければ内蔵フラッシュのストレージを/flashにマウントし、そのマウントしたフォルダへcdで移動します。そしてboot.pyとmain.pyを実行します。
このあたりは、Cで実装しなくても、Pythonで書いてフローズンモジュールとして入れておけば良さそうです。

●ベアメタルでのSDカードへのアクセス

ベアメタルでSDカードにアクセスするには、Raspberry PiのコントローラとSDカードのコントローラの両方の知識が必要です。
前者はPeripheral Manualの5章(P65~)に書かれており、後者はSD Associationからダウンロードできます。
ただ、正直言って後者の英語のドキュメントを読み解くのはちょっと荷が重そうで、SDカードの制御を知るには以下のChaNさんのページが役に立ちます。
インタフェースはSPIが使われていますが、コマンド体系自体が異なるわけではありません。

MMC/SDCの使いかた

とはいえ、実際にこれらを読み解いて実装するのはかなり大変ですので、ネット上の先人の成果を使わせていただくことにします。

一番昔からあるのは、Raspberry Piフォーラムの以下のスレッドでhldswrthさんが公開しているもののようです。

[BARE-METAL][SDC/EMMC] How to access it properly?? – Raspberry Pi Forums

ライセンスは特に記載されていません。
これをリライトしたものがLdB-ECMさんのもので、ライセンスはCCです。

Raspberry-Pi/SD_FAT32 at master · LdB-ECM/Raspberry-Pi

これは2500行もありますが、FATの実装を含んでいます。

hldswrthさんの実装をそのままに近い形で使用しているものとして、Haribote OSのRaspberry Pi版があります。
オリジナルにあったバグが対策されているようです。
オリジナルのライセンスが不明なので、こちらもsdcard.cについてはライセンスが不明です。

RPiHaribote/sdcard.c at master · moizumi99/RPiHaribote

Raspberry Piの非公式?ブートローダであるjncroninさんのrpi-bootにもSDカードの実装があります。
MITライセンスです。

rpi-boot/emmc.c at master · jncronin/rpi-boot

bztsrcさんによるRaspberry Pi3用のベアメタルチュートリアルには、SDカードへアクセスする例題が含まれています。
「0B_readsector 」にはセクタを読み出すだけの実装があります。
400行足らずとコンパクトで、ライセンスはMITライセンスです。

raspi3-tutorial/0B_readsector at master · bztsrc/raspi3-tutorial

●実装方針

結論から言うと、上記の最後のbztsrcさんの実装をMicroPythonに移植してみることにしました。

MicroPythonはMIT Licenseで実装されていますので、これと互換性のあるライセンスのコードを用いるのが良さそうです。
そうすると、ライセンスが記載されていないものは避けるべきです。
また、MicroPythonにはFAT FSの実装は含まれていますので、必要なのは低レベルのI/Oだけです。

この条件で見てみると、rpi-bootの実装とbztsrcさんの実装がマッチします。
ただ、rpi-bootの実装はrpi-bootのファイルシステム実装から呼び出す想定の作りになっていて、これをMicroPythonのvfsに合わせて作り変える必要があります。
bztsrcさんの実装は単体でほぼ完結しており、移植しやすそうでした。
書き込みはできませんが、ファイルシステムをマウントしてファイルを読み出すことができれば、かなり役に立ちそうです。
書き込みが必要になった場合でも、他の実装を参考にセクタを書き込む関数を作るのは、それほど難しくないだろうと思われました。

コードはRaspberry Pi 3用ではありますが、レジスタのアドレスが変わるだけで、大きな違いはありません。
レジスタの名称がこれまで私が実装したもの(マニュアル記載の名称をそのまま使っています)と同じになっているのは助かりました。
また、このチュートリアルは64bitのチュートリアルなので、long型は64bitです。必要に応じて、宣言をlong longに変更します。
他にはメッセージをUARTへ出力している部分を書き換えれば、コンパイルは通るようになります。

sd.cと、MicroPython用にポーティングしたものとの差分はこんな感じです。

fix: modified to access GPIO and UART through existing functions · boochow/micropython-raspberrypi@c1d43b5

●先頭セクタを読んでみる

試しに、MicroPythonのmain関数の中で以下のようなコードを実行して、先頭セクタを読んで内容をダンプさせてみました。

すると、以下のような感じでちゃんと読み取れました。

EMMC: GPIO set up
EMMC: GPIO set up
EMMC: reset OK
sd_clk divisor 00000068, shift 00000006
EMMC: Sending command 00000000 arg 00000000
EMMC: Sending command 08020000 arg 000001AA
EMMC: Sending command 37000000 arg 00000000
EMMC: Sending command 29020000 arg 51FF8000
EMMC: CMD_SEND_OP_COND returned VOLTAGE 0000000000F98000
EMMC: Sending command 37000000 arg 00000000
EMMC: Sending command 29020000 arg 51FF8000
EMMC: CMD_SEND_OP_COND returned COMPLETE VOLTAGE CCS FFFFFFFFC1F98000
EMMC: Sending command 02010000 arg 00000000
EMMC: Sending command 03020000 arg 00000000
EMMC: CMD_SEND_REL_ADDR returned 0000000000070000
sd_clk divisor 00000002, shift 00000000
EMMC: Sending command 07030000 arg 00070000
EMMC: Sending command 37020000 arg 00070000
EMMC: Sending command 33220010 arg 00000000
EMMC: Sending command 37020000 arg 00070000
EMMC: Sending command 06020000 arg 00070002
EMMC: supports SET_BLKCNT CCS
sd_readblock lba 00000000 num 00000001
EMMC: Sending command 11220010 arg 00000000
0000:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0010:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
(ずっと00が続く)
01a0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01b0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02
01c0:03 01 0b 08 c8 c4 00 20 00 00 00 80 76 00 00 00
01d0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01e0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01f0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa

Windows系のシステムでは、容量2TBまでのストレージはMBRと呼ばれる方式で管理されています。MBRでは、先頭セクタの末尾は0x55 0xAAと決まっており、その直前には4つのパーティションテーブル(16バイト×4)が入ります。
MBRについては、例えば以下のリンクに詳しく解説されています。

MBR(Master Boot Recode)

ハードディスクの構造とパーティション by eslab

上のデータから先頭パーティションの情報(青字の部分)を読み取ると、

起動 (アクティブ) フラグ     00
パーティション開始位置 (CHS方式) 02 03 01
パーティションタイプ       0b
パーティション終了位置 (CHS方式) 08 c8 c4
開始セクタ番号 (LBA方式)     00 20 00 00
パーティションサイズ (LBA方式)  00 80 76 00

となります。
パーティションタイプは0bですので、こちらの一覧によるとWIN95 OSR2 FAT32です。

開始セクタ番号は0x00002000となっていますので、次に同様にこのセクタを読んでみます。
以下のようなデータが格納されていました。

0000:eb 58 90 4d 53 44 4f 53 35 2e 30 00 02 40 98 18
0010:02 00 00 00 00 f8 00 00 3f 00 ff 00 00 20 00 00
0020:00 80 76 00 b4 03 00 00 00 00 00 00 02 00 00 00
0030:01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00
0040:80 00 29 fe 49 a1 9e 4e 4f 20 4e 41 4d 45 20 20
0050:20 20 46 41 54 33 32 20 20 20 33 c9 8e d1 bc f4
0060:7b 8e c1 8e d9 bd 00 7c 88 4e 02 8a 56 40 b4 41
0070:bb aa 55 cd 13 72 10 81 fb 55 aa 75 0a f6 c1 01
(後略)

これはFATのブートセクタの形式に沿っています。4バイト目から「MSDOS5.0」とありますし、71バイト目から「NO.NAME…FAT32…」と入っています。RAMディスクの時に見たセクタと同様ですが、今回はFAT32なのでボリュームラベルの記録される位置は異なっています。

●ブロックデバイスをMicroPythonのクラスとして実装

セクタが読み取れれば、RAMディスクと同様にVFSを使ってSDカードをリードオンリーのファイルシステムとしてマウントすることができます。
そのためには、SDカードをブロックデバイスとして扱うクラスが必要です。
クラス名はこれまでの実装にならってSDとし、machineモジュールの中に含めます。

リードオンリーのブロックデバイスに最低限必要なメソッドは、

・オブジェクトの生成・初期化
・readblocks(self, block_num, buf)
・ioctl(self, op, arg) (op == 1: initialise the device; op == 5: get block size)

の各メソッドです。ioctlの引数argは使用しません。
ioctlについてはuosモジュールのドキュメントに記載されています。

実装は、STM32 portのsdcard.cの後半部分(前半部分はSDカードのデバイスドライバ相当になっています)を参考にしました。
やっている内容自体は、sd.cの関数を呼び出すだけです。

その他、STM32ではMicroPythonの起動中にSDカードをマウントする機能があります。
Raspberry Piではどうしようか迷ったのですが、オプション扱いにしてMakefile(mpconfigport.mk)でオンオフするようにしました。

クラス実装に関する追加のコードはこちら
やることが橋渡しだけなので、分量は大したことはありません。

●テスト

これでSDカードがMicroPythonから使えるようになったので、画像ファイルをSDカードに置いて、画面に表示させてみました。

MicroPythonには画像コーデックは入っていないので、IrfanviewでRGBのRAW形式に変換して保存しておきます。
画像ファイル名を「test1.raw」、画像解像度を320×240ピクセルとしました。

MicroPython側のコードは以下のようになります。1ライン分ずつデータを読んで表示させています。

試しに動かしてみた結果です。

sdcard-show-img.jpg

うまく表示できました。
アスペクト比がちょっとおかしいのは、LCD側の問題です。

コメント