2018年05月20日

Raspberry PiベアメタルプログラミングでI2C制御

i2c-bm.png

MicroPythonに実装する前のテストとして、Raspberry PiのベアメタルでのI2C操作を試してみました。
今回のコードは以下に置いてあります。

bare_matal_rpi_zero/i2c at master ・ boochow/bare_matal_rpi_zero

今回いきなりMicroPythonに実装せずに単体でテストプログラムを書いてみたのは、なぜかネット上にあまりベアメタルでのI2Cの例が見当たらなかったためです。
唯一見つかったのは、アセンブラで書かれたこちらのものでした。

ddle/RaspberryPi

これを起点にCで書いてみましたが、簡単には動かず、ロジアナで信号を観察しながらの作業となりました。

I2Cデバイスには、以前動作確認できた実績のあるOLEDディスプレイを使い、サンプルとして汎用性を持たせるためにディスプレイそのものの操作ではなく、I2Cバスをスキャンしてデバイスを探すものを作ってみました。
処理内容としてはRaspbian上でi2cdetectコマンドを使う場合と同じようなものです。

●I2C概要

I2Cの規格を簡単にまとめると以下のようになります。
より詳しい解説は以下が分かりやすいです。

電子工作室

・クロックとデータの2本の信号でバスを構成する。バスにぶら下がるデバイスはマスタとスレーブがある。
・クロックは数百KHz程度までと、それほど高速ではない。
・デバイスは7bitの固定アドレスを持つ。0〜7と120〜127は予約されている。(色々なI2Cデバイスのアドレスの一覧表
・拡張として10bitのアドレス体系もあり。

・SDA、SCLはプルアップ。SCL=HのときSDA=H→Lで通信開始。
・クロック(SCL)=HのときSDAを読み取る。送受信は8bit単位。
・最初に送付先アドレスを送る。アドレスが7bitの場合、8bit目が通信の方向を表す。
・8bit目が0なら、続く通信はマスタからスレーブへ送信するデータ。1なら、マスタがスレーブから受信するデータ。
・送信側は、8ビットのデータを送ったあとでSDAをプルアップし制御を受信側へ渡す。9ビット目のクロックで受信側がSDAをLにしたらACK。
・9ビット目のクロックのあと、SCLがL→Hとなる。そのあとでSDAがL→Hになれば通信終了。

0x3Cに送信要求し、ACKが返ってきた場合
i2c-ack.png

0x3Dに送信要求し、応答が無かった場合
i2c-nack.png

●Raspberry PiのI2Cハードウェア

Raspberry PiのI2Cコントローラは「BSC(Broadcom Serial Controller )」という名称になっています。
Peripheral Manualの3章(P.28〜)に解説があります。
概要は以下の通りです。

・マスタにしかなれない。速度は400Kbpsまで(デフォルトは100Kbps)。
・ハードウェアとしてはBSC0、BSC1、BSC2の3つが用意されている。BSC2はHDMIで使われているため、ユーザ利用は不可。また、BSC0はID_SC(GPIO0)、ID_SD(GPIO1)に割り当てられており、Raspberry Pi3では内部利用されている。

・利用できるGPIOピン(SDA, SCLの順)は以下の通り。
 BSC0: 0, 1(ALT0)、28, 29(ALT0)、44, 45(ALT1)
 BSC1: 2, 3(ALT0)、44, 45(ALT2)

・以下のレジスタがある:
(制御関係)Control、Status
(通信関係)SlaveAddress、DataLength、FIFO
(通信パラメータ)ClockDivider、DataDelay、ClockStretchTimeOut

●実装

大した処理はしていないので、コードを見てもらえばいいのですが、作成中にひっかかった点としては以下のようなものがありました。

・FIFO ClearとStartはどちらもControlレジスタだが、同時にオンにするとうまく動かない。
・通信開始前にステータスレジスタのビットを適切にクリアしておく。また、readやwriteからエラーで戻る場合も、ステータスレジスタの当該ビットはクリアしておく。
・当然ながらGPIOピンを使用する前にALT0に設定しておかなくてはならない。

とりあえず動くようになったので、次はMicroPythonに組み込んでみます。
posted by boochow at 14:55| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年05月12日

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側の問題です。
ラベル:MicroPython
posted by boochow at 13:20| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年05月06日

フローズンモジュールを使う(RPiベアメタルMicroPython)

frozenmodule.png

RAMディスクが使えるようになったので、このPythonスクリプトをMicroPythonの起動時に自動実行させてみます。

フラッシュやSDカードを外部ストレージとして使える場合は、そのストレージに「boot.py」「main.py」というファイル名でスクリプトを保存しておけば、起動時に「boot.py」→「main.py」の順で実行されます。

ですが、作成中のRPiベアメタル版MicroPythonは、起動時に使えるストレージはありません。むしろそれが無いために、代用でRAMディスクを用意したのでした。

外部ストレージを持たないシステムでも、Pythonスクリプトを実行するための仕組みとして、MicroPythonには「フローズン・モジュール」という仕組みがあります。
この仕組みを使うと、PythonスクリプトをMicroPythonのファームウェア内に追加することができます。

スクリプトは、ソースコード形式およびコンパイル済みのバイトコード形式が使えます。

MicroPythonの実装でフローズン・モジュール関連の処理を有効にするには、ソースコードに

共通:
・マクロ「MICROPY_READER_VFS (MICROPY_VFS)」を定義(mpconfigport.hの中で)

ソースコード形式:
・マクロ「FROZEN_DIR」にソースコード形式のスクリプトのディレクトリを設定
・マクロ「MICROPY_MODULE_FROZEN_STR」を定義(一般的には、mpconfigport.hの中で)

バイトコード形式:
・マクロ「FROZEN_MPY_DIR」にバイトコード形式のスクリプトのディレクトリを設定
・マクロ「MICROPY_MODULE_FROZEN_MPY」「MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool」を定義(一般的には、mpconfigport.hの中で)

を追加します。

フローズンモジュールの実行は
pyexec_frozen_module("ファイル名");
です。
モジュールがソースコード形式でもバイトコード形式でも区別はありません。

今回は、以下のようなコードを起動時に実行するようにしました。
1MBのRAMディスクを作成してルートにマウントし、そこへ'RAM disk.txt'というファイルを作成しています。

このスクリプトをMicroPythonの移植ソースがあるディレクトリ直下の「modules」へ配置し、バイトコード化して、main()の中から呼び出しています。

フローズンモジュールを有効にするために必要な、MICROPY_MODULE_FROZEN_STRやMICROPY_MODULE_FROZEN_MPYの定義は、STM32の実装を参考に、mpconfigport.hではなくMakefileの中で行いました。
この方法のメリットは、フローズンモジュールの利用の有無をMakefileの書き換えだけで設定できることです。

このフローズンモジュールの仕組みですが、概ね以下のようになっているようです。

・フローズンモジュールのバイトコードの生成はクロスコンパイラ(mpy-cross)で行う
・フローズンモジュール(ソースコード形式およびバイトコード形式)をMicroPythonに埋め込むためのCコードは、自動生成されてbuild/へ配置(frozen.c、frozen_mpy.c)
・このCコードには、外部から関数を検索・参照するための定数が含まれる(ソースコードフローズンモジュールはmp_frozen_str_names[]/mp_frozen_str_sizes[]/mp_frozen_str_content[]、バイトコードフローズンモジュールはmp_frozen_mpy_names[]/mp_frozen_mpy_content[])
・実行時にコードを参照するためにvfsの関数が使われている(らしい)

ゴールデンウイークも今日で終わりなので、次に集中して作業できるのは夏休みでしょうか。
あと、予定としては

・I2C
・SPI
・PWM
・I2S
・SDカード
・Pin AFクラス
・UARTクラス
・USBホストクラス
・USBデバイスクラス
・スクリーンコンソール
・グラフィックスライブラリの整備
・チュートリアルの整備
・サーボモータドライバ
・NeoPixelドライバ
・WizNetドライバ
・test suiteへの対応
・RPi2や3への対応
・Raspbianへの移植

といったことをやりたいなと思っているのですが、依存関係を考えるとまずはPin AFクラスの実装が必要な気がします。


ラベル:MicroPython
posted by boochow at 19:00| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年05月05日

Raspberry Pi Zeroのキャッシュ・分岐予測・クロック設定

以前、ベアメタル版MicroPythonの速度がRaspbian版に比べて非常に遅いというベンチマーク結果を載せました。

Raspberry Pi版MicroPythonのベンチマークテスト(2): 楽しくやろう。

遅い原因は、キャッシュ設定やクロックだろうなあとは思っていたのですが、なかなか試す暇がなく、今日ようやく実験してみました。

ベンチマークは、とりあえずperformanceTestだけを使いました。
これは、以下のような「+1を10秒間やり続ける」という単純なループです。


キャッシュ設定と分岐予測はCP15レジスタのbit2, 11, 12をオンにすることで、クロックはSDカードに置くconfig.txtに指定することで、それぞれ設定できます。
クロックの設定は今回は、config.txtにこちらに載っていた以下の記載を追加してみます。
fake_vsync_isr=1
framebuffer_swap=0
gpu_mem=48
init_emmc_clock=100000000
arm_freq=1000
gpu_freq=300
core_freq=400
sdram_freq=450
以下、それぞれの設定変更の効果です。

(1)標準(CPU=700MHz、システムクロック=250MHz):
>>> performanceTest()
Count: 608061

(2)データキャッシュON+命令キャッシュON:
>>> performanceTest()
Count: 1277513

(3)2に加えて分岐予測ON:
>>> performanceTest()
Count: 1316526

(4)3に加えてクロックアップ:(CPU=1GHz、システムクロック=400MHz)
>>> performanceTest()
Count: 1984803

(5)Raspbian:
>>> performanceTest()
Count: 5503852

という具合に、無印に比べたら3倍以上高速化しましたが、それでもRaspbian上に比べると半分くらいです。
まだ他にも設定できるところがあるのかもしれません。
ラベル:MicroPython
posted by boochow at 13:44| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年05月04日

ファイルシステムを追加する(RPiベアメタルMicroPython)

ramdisk.png

今日もRaspberry PiへのMicroPythonの移植です。
これまで、ファイルシステムの実装は後回しにしてきました。
Raspberry PiのSDカードへアクセスするためのソースコードはいくつかネット上にあるのですが、ドキュメントが少なく、取り込むのはなかなか手間がかかりそうです。

ですが、REPLのペーストモードだけではこの先だんだん行き詰まりそうです。
ファイルシステムがあれば、ampyやuPyCraftのようなツールで、ホストからファイルを転送して実行することができるようになります。

そこで、SDカードは後回しにして、RAMディスクを実装してはどうかと考えました。
書き込んだ内容は電源を切れば消えてしまいますが、少なくとも一時的なファイル保存場所として、ホストからいろいろなファイルを転送して実行することはできそうです。

ということでMicroPythonのRAMディスク実装を探してみたところ、そもそもRAMディスクはMicroPythonで記述できることが分かりました。
サンプルが以下のページの最後のほうにあります。

uos – basic “operating system” services − MicroPython 1.9.3 documentation

MicroPythonの実装に含まれるCライブラリの中にvfsというモジュールの実装があり、これを使うと任意のブロックデバイスを用いてFATファイルシステムを構成することができます。(ちなみにFATライブラリはChaNさんのFatFsモジュールに手を加えたoofatfsです。)
このvfsをMicroPythonの実装に組み込めば、RAMも含め様々なブロックデバイス上でFATファイルシステムがPythonコードで実現できます。

上記のサンプルのRAMディスクは、MicroPythonのbytearrayを使ってブロックデバイスを実現しています。以下のコードは、このサンプルに特定のブロックの内容を16進数で表示するdump_block()というメソッドを追加したものです。



Raspberry Piベアメタル版MicroPythonにvfsを組み込むのは割合簡単です。
vfsはuosモジュールから利用しますので、ESP8266などの実装からuosモジュールを持ってきます。
uosモジュールは、vfsなどのライブラリ関数へのポインタをモジュールの関数にマップしているだけなので、プラットフォームに合わせたコードを書く必要はほぼありません。
基本的な移植作業はこれだけです。(他にmpconfigport.hで適切なオプション指定やextern宣言、、fopenなどのMicroPython組み込み関数にvfsを参照させるたっめの記述を追加する必要があります。)


これで早速上記のRAMディスクのコードを動かしてみたのですが、最初はエラーで止まってしまいました。
mkfsでフォーマットしたRAMディスクをマウントしようとすると、ENODEVエラーになってしまいます。
コードを調べてみると、oofatfsライブラリが「ファイルシステムを見つけられない」というエラーを返しているようです。

ところが、同じコードをQEMU上で動かすと問題なく動作しました。
上記で追加したdump_block()を使って、両者のmkfsでフォーマットした後の先頭ブロックのデータを比較してみると、以下のように違いがありました。

QEMUでの実行結果(正常)
>>> bdev.dump_block(0)
eb fe 90 4d 53 44 4f 53 35 2e 30 00 02 01 01 00
01 00 02 32 00 f8 01 00 3f 00 ff 00 00 00 00 00
00 00 00 00 80 00 29 00 00 00 00 4e 4f 20 4e 41
4d 45 20 20 20 20 46 41 54 20 20 20 20 20 00 00

Raspberry Piでの実行結果(異常)
>>> bdev.dump_block(0)
eb fe 90 00 53 44 4f 4d 53 35 30 00 02 01 01 00
01 00 02 32 00 f8 01 00 3f 00 ff 00 00 00 00 00
00 00 00 00 80 00 29 00 4e 4f 20 00 41 4d 45 4e
20 20 20 20 41 54 20 46 00 00 20 20 00 20 00 00


何箇所か違いがありますが、特に44バイト目からの文字列が目立ちます。
ディスクフォーマットする関数f_mkfsの処理では、
mem_cpy(buf + BS_VolLab, "NO NAME    " "FAT     ", 19);

となっていますので、44バイト目から「NO.NAME....FAT.....」(「.」はスペース)と入っているべきなのですが、Raspberry Piでの実行結果は40バイト目から「NO. AMEN....AT.F .. .」(スペースは0x00)というようにデータが崩れて入っています。

これは、きっとnaumsさんも嵌っていたアンアラインドアクセスの問題だと見当が付きました。
実は少し前に、アンアラインドアクセスのサポートをなくしてもMicroPythonの動作には特に問題なかったため、サポートをなくした状態にしていました。しかし、今回追加したoofatfsでは問題が出るようです。

ソースコードではmem_cpyを呼んでおり、これは#defineでmemcpyを呼ぶようになっています。
MicroPythonでは、lib/libc/string0.cというファイルでアンアラインドアクセスに配慮したmemcpy関数を用意しているので、問題ないはずです。

ところが、コンパイル結果をディスアセンブルしてみると、以下のようにmemcpyコールは行われずにロード/ストアで実現されていました。

    3a00:   e284202b    add r2, r4, #43 ; 0x2b
3a04: e2831010 add r1, r3, #16
3a08: e4930004 ldr r0, [r3], #4
3a0c: e1530001 cmp r3, r1
3a10: e4820004 str r0, [r2], #4
3a14: 1afffffb bne 3a08


memcpyをコールせず、このようにしたほうがオーバーヘッドが小さいという最適化のようです。
この最適化を抑制できればいいのですが、最適化が出現する条件がよく分からないので、アンアラインドアクセスのサポートを有効にしました。
その結果、RAMディスクが動作するところまで確認できました。

RAMディスクをマウントした状態でuPyCraftから接続し、エディタにマンデルブロ集合のコードを入れて、DownloadAndRunボタンを押してみると、ちゃんとファイルがダウンロードされ動作しました。

その後、gccのオプションで「-mno-unaligned-access」を指定すると、上記の最適化を禁止できることが分かったので、現在はMakefileでこのオプションを指定し、アンアラインドアクセスのサポートを再度無効にしています。
ラベル:MicroPython
posted by boochow at 11:25| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする
人気記事