* Tue Aug 06 21:50:00 JST 2002 Naoyuki Sawa
フラッシュメモリの話題・最終回です。
前回、フラッシュメモリの消去・書き込みの実験を行いました。今回は、消去・書き込みの手順を再確認します。
P/ECEのフラッシュメモリ「SST39VF400A」は、三種類の消去モードを持っています。
・セクタ消去モード ( 4キロバイト単位で消去します)
・ブロック消去モード ( 64キロバイト単位で消去します)
・チップ消去モード (フラッシュメモリ全体を消去します)
三種類の消去モードのうち、P/ECEカーネルが実際に使っているのは、「セクタ消去モード」だけです。
「セクタ」単位は、pceFileReadSct()APIの引数や、P/ECEコミュニケータの残りサイズ表示などでお馴染みだと思います。
一番細かい単位で消去できるので使い勝手がよく、P/ECEのシステムプログラムがこのモードを選択した理由も納得できます。
ところが実は4KB単位の消去ができるフラッシュメモリは非常に稀で、事実上SST社の製品でしか使えない消去単位だそうです。
富士通社の製品やシャープ社の製品を調べてみると、確かに4KB単位の消去モードを持っていません。
一般的なフラッシュメモリの消去単位は、64キロバイト。
P/ECEカーネルは4キロバイト単位の消去モードに深く依存しているので、他社のフラッシュメモリへの換装はかなり難しいそうです。
SST39VF400Aでは、「ブロック消去モード」が一般的な64キロバイト単位の消去となっています。
[情報源]みゃん☆みゃん☆ふぁくとりぃさんのきまぐれ雑文 2002/01/05
六波羅さんのスペースさんのめっちゃ幸せFLASHメモリ4倍増計画
※セクタやブロックの用語定義は、各社の資料ごとにまちまちみたいです。
富士通社では1セクタ=64KB。シャープ社では1ブロック=64KB。SST社では1セクタ=4KB・1ブロック=64KBとなっています。
恐怖の全消去「チップ消去モード」は、問答無用でフラッシュメモリ全体を消去します。
P/ECEラボラトリさんの瀕死のP/ECEを救え復旧プログラムがこの消去モードを使っていますが、特殊ケースです。
ちゃんと動いているP/ECEにチップ消去モードを適用すると、逆に瀕死のP/ECEになってしまうので、要注意です。
P/ECEのフラッシュメモリ「SST39VF400A」は、単純な一種類の書き込みモードしか持っていません。
・ワード書き込みモード (2バイト単位で書き込みます)
2バイト単位と書きましたが、実際には前回実験した通り、同じアドレスに対して何度も書き込みを行うことができます。
いったん0にしたビットを1に戻すことはできませんが、1のビットを0にすることはいつでもできます。
そう考えれば、ビット書き込みモード言っても良さそうです。
他社のフラッシュメモリは、高速な連続書き込みや、時間のかかる消去処理を途中で中断する機能を備えているものが多いようです。
起動プログラムなどの入った重要な領域を保護し、このような領域への消去・書き込みを禁止する機能を備えている製品もあります。
しかし、P/ECEのフラッシュメモリ「SST39VF400A」は、そのような高度な機能を一切備えていません。
一見、欠点のようにも見えますが、僕のようなフラッシュメモリ入門者には覚えることが少なくってかえってありがたいです。
高度な機能がない代りに、扱いやすいセクタ消去モードを持っている点など、SST39VF400Aは単純で小回りの効く製品のようです。
それでは、消去・書き込みの具体的な手順を再確認します。
今回も、フラッシュメモリ全体をハーフワードの配列と見なして説明します。
unsigned short FMEM[256 * 1024]; /* 2バイト×256K = 512Kバイト */
まずは消去。データシートどおりなので、一気に行きます。
各消去モードの手順の前半は、セクタ消去モードもブロック消去モードもチップ消去モードも共通です。
FMEM[0x5555] に 0xaa を書き込む
FMEM[0x2aaa] に 0x55 を書き込む
FMEM[0x5555] に 0x80 を書き込む
FMEM[0x5555] に 0xaa を書き込む
FMEM[0x2aaa] に 0x55 を書き込む
最後の手順だけが異なります。
セクタ消去モードの場合:
消去したいセクタの任意のワードに 0x30 を書き込む
例えば 0xc70000〜0xc70fff のセクタを消去するには、0xc70000に0x30を書き込みます。
0xc70002や0xc70004や...0xc70ffeも同じセクタに属するので、これらのアドレスへの書き込みでも構いません。
ブロック消去モードの場合:
消去したいブロックの任意のワードに 0x50 を書き込む
例えば 0xc70000〜0xc7ffff のブロックを消去するには、0xc70000に0x50を書き込みます。
0xc70002や0xc70004や...0xc7fffeも同じブロックに属するので、これらのアドレスへの書き込みでも構いません。
チップ消去モードの場合:
FMEM[0x5555] に 0x10 を書き込む
消去モードの手順の途中で、別のフラッシュメモリアドレスへの読み書きが発生すると、手順がキャンセルされてしまします。
だから、ソフトウェアIDモードやCFIクエリーモードの時と同じように、全ての割り込みを禁止しておかなければいけません。
消去を行うプログラム自身がフラッシュメモリ上にあると、プログラムの読み出しによって手順がキャンセルされてしまうので、
消去を行うプログラムはCPU内蔵RAMか、SRAM上に置いておく必要があります。
P/ECEカーネルでは、フラッシュメモリ上にある消去プログラムを、いったんフラッシュメモリからCPU内蔵RAMにコピーし、
コピー先のプログラムを呼び出してフラッシュメモリの消去を行っています。
問題はここからです。
フラッシュメモリの消去には、少し時間がかかります。
フラッシュメモリの消去が完了するまで、フラッシュメモリからデータを読み出すことはできなくなっています。
読み出せないのは、いま消去を行ったセクタ(またはブロック)だけではありません。
あるセクタの消去が完了するまでは、別のセクタからの読み出し・消去・書き込みもできません。
当然、あるセクタの消去が完了する前に、同じセクタへの書き込みを開始することもできません。
つまり、フラッシュメモリの消去が完了するまでは、何もせずに待つ必要があります。
それでは、いつまで待てばいいのでしょうか。
一つ目の方法として、消去に要する最大時間、空ループを回して待つ、という方法があります。
消去に要する時間は、データシートに記載されています。
また、CFIクエリーモードを使って、消去に要する時間を実行時に取得することもできます。
SST39VF400Aの場合、セクタまたはブロック消去に要する時間は32ミリ秒、チップ全体なら128ミリ秒、となっています。
意外と長い時間が必要なのですね。
規定の時間だけ空ループを回して待つというのは一番単純な方法ではありますが、いざ作ってみると結構手間がかかります。
フラッシュメモリの消去を行っている間、フラッシュメモリ上にあるシステムプログラム(API)は使えないからです。
pceTimerGetCount()やpceTimerGetPrecisionCount()、pceTimerAdjustPrecisionCount()を使って時間を計ることができません。
(そもそも消去中はNMIを止めているので、仮にこれらのAPIを使えたとしても、タイマがカウントアップされていません)
時間を計るには、8ビットタイマや16ビットタイマ、あるいはリアルタイムクロックの値を直接読み出すしかありません。
別の方法として、フラッシュメモリの消去完了通知を利用する、という方法があります。
SST39VF400Aには、「Toggle Bit(DQ6)」と「Data# Polling(DQ7)」という、二種類の消去完了通知があります。
「Toggle Bit(DQ6)」とは、
消去が完了するまでの間、フラッシュメモリからデータを読み出す度に、読み出した値のビット6が反転しつづける
というものです。ビット6が反転しなくなったら、消去が完了したことになります。
「Data# Polling(DQ7)」とは、
消去が完了するまでの間、フラッシュメモリから読み出したデータのビット7は、最終的な値のビット7を反転した値になっている
というものです。ビット7が最終値と同じになったら、消去が完了したことになります。
ちなみに、消去が完了するまでの間の、ビット6と7以外の値は規定されていません。
実験したところ、消去が完了するまでの間は、ビット6と7以外は0として読み出されるようですが、不定と考えた方がいいでしょう。
消去したメモリは0xffffになりますので、最終値は0xffffです。
ビット6は読み出す度に反転し続け(1→0→1→0→...)、ビット7は最終値を反転した値(0)ですので、二進数で表すと次のようになります。
ビット番号:fedcba9876543210
----------------
????????01?????? ←消去開始
????????00??????
????????01??????
????????00??????
????????01??????
????????00??????
(中略)
????????01??????
????????00??????
1111111111111111 ←「Data# Polling(DQ7)」による消去完了判定
1111111111111111 ←「Toggle Bit(DQ6)」による消去完了判定
「Toggle Bit(DQ6)」による判定のほうが、「Data# Polling(DQ7)」よりも読み出し一回分だけ遅れることに注目してください。
「Toggle Bit(DQ6)」による判定条件は、“ビット6が反転しなくなること”ですので、
変化しなくなった値すなわち最終値を二回連続で読み出してから、やっと消去完了が判定できます。
対して「Data# Polling(DQ7)」は、“ビット7が期待する最終値になったか”調べるだけなので、すぐに判定できます。
それに、ビット7が最終値の逆ということは、ワード単位で見ても最終値とは異なっているはずなので、
単にワード単位で期待する最終値と比較し、一致したら消去完了と見なすことができます。
どう考えても「Data# Polling(DQ7)」による判定の方が簡単で、これだけで充分に思えます。
ではなぜ「Toggle Bit(DQ6)」による判定方法も用意されているのでしょうか?
あくまで想像ですが、フラッシュメモリの一部が壊れた場合への対策ではないかと推測します。
例えば、0xc70000〜0xc70001のハーフワードのビット6と7に相当するセルが壊れ、消去できなくなった(0固定)と仮定します。
0xc70000〜0xc70fffのセクタを消去し、0xc70000を読み出して消去完了判定をしようとすると、
ビット番号:fedcba9876543210
----------------
????????01?????? ←消去開始
????????00?????? ※消去中のビット6と7の値はフラッシュメモリ内のコントローラが返していると思うので、
????????01?????? セルが壊れていても期待通りの値を返すと推測。
????????00??????
????????01??????
????????00??????
(中略)
????????01??????
????????00??????
1111111100111111 ←消去完了したが、ビット6と7が壊れて1に戻らない
1111111100111111 ←「Toggle Bit(DQ6)」による消去完了判定は可能
「Data# Polling(DQ7)」で消去完了判定を行ったのでは、永遠に終わりません。
対して「Toggle Bit(DQ6)」で消去完了判定を行った場合は、ビット6の値が期待する最終値と異なっていても、
ビット6が反転しなくなることに変わりはないので、消去完了を判定することはできます。
消去完了判定後に、期待する値と比較して同じになっているかどうかを調べれば、フラッシュメモリが壊れているか判定できます。
フラッシュメモリは数万回程度の書き換えで壊れてしまうそうなので、このような対策も必要なのだと思います。
ところが!
実際に試したところ、「Toggle Bit(DQ6)」は期待通りの動作をせず、ビット6がぜんぜん反転していないようなのです。
検証プログラムを作ってみました。
セクタ消去開始後、そのセクタ内の特定ワードを連続で読み出し、SRAMに格納しておいて、消去完了後にダンプ表示します。
ソースはこちら