こんにちは。
Yukiです。今回は、CH32V003F4P6でFLASH Memory領域を使用し、EEPROMもどきを再現してみました。
FLASH Memory領域とは
簡単に言うと、マイコンの中に入っているフラッシュメモリ領域です。
この領域に、プログラムデータやらconstで宣言した定数が入ったりします。
CH32V003F4P6にはFLASHが16kB搭載されています。
ただ、16kBすべてをプログラムデータ領域として使っているわけではなく、最初の方のみ使っているようです。(ただし、プログラムデータが大きくなった場合の動作は不明です)
FLASH書き込み
基本的にはFLASHのアドレスを指定して、そこに代入することで、書き込みができます。が、注意点もあり、削除してから書き込まないと、データが正常に書き込めないことがあります。
ということで、やっていきましょう。
FLASHデータ削除
書き込むためには、まず削除しなければいけません(詳しくは研究に記載)
削除は1kByteサイズでまとめて削除します。
上の表を見ると、書き込みができるところはMain Memoryです。
Page0などは初期から値が入っているので、書き込まないほうが良いです。
できれば最後の方。Page256とかは狙い目だと思います。
が、ここで注意点があり、削除は1kByte単位です。
つまり、Page256のみでの削除はできないのです。(詳しくは下の研究に記載)
ここで情報を整理しておくと、Page1につき64Byte確保されています。
1kByte削除されるということは、つまり16Page分ということになります。
なので、削除を開始する場所はPage256 から16PageさかのぼったPage241であることがわかると思います。
つまり、Page241 = 0x0800 3C00 からが削除対象です。 (実は削除の先頭をPage256の先頭 つまり 0x0800 00C0にしても正常に削除でき、かつ書き込みもできたのですが、これも後述の研究の方に乗っけます)
ということで、これで削除される領域は Page241(0x0800 3C00)からPage256(0x0800 3FFF)ということになりました。
この領域が未使用か、予め確認しておいた方が良いでしょう。
(確認方法については、下の方の”メモリが未使用か見る”でやります)
削除する先頭アドレスもわかったので、早速削除していきましょう。
まずFLASHは書き込み保護されている場合があります。
書き込み保護されている場合は、解除します。
//FLASHがLOCK状態だったら解除
if (FLASH->CTLR & FLASH_CTLR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
次にスタンダード削除モードというモードを指定します。
(ちなみにスタンダード削除モードと全て削除モードの2種類しかありません)
//標準削除モード
FLASH->CTLR |= FLASH_CTLR_PER;
次に先程求めた先頭アドレスを指定します。
//削除する先頭アドレス指定
FLASH->ADDR = 0x08003C00
次に、削除を実行します。
//削除開始
FLASH->CTLR |= FLASH_CTLR_STRT;
次に、削除が完了するまで、待機します。
//削除が完了するまで待機
while(FLASH->STATR & FLASH_STATR_BSY);
次に、STATRレジスタのEOPビットに1を書き込んで、0に戻します。
(よく分かりませんが、1を書き込むと0に戻る謎仕様です)
FLASH->STATR |= FLASH_FLAG_EOP;
最後に、スタンダード削除モードを無効にし、終了です。
//終了
FLASH->CTLR &= ~FLASH_CTLR_PER;
FLASHにデータ書き込み
次に、FLASHにデータを書き込んでいきます。
書き込む際には、アドレスを指定して、書き込みます。
また、書き込みの条件が2btyte(ハーフワード 16bit)単位で書き込まないと行けないらしいので、uint16_tを使用し、明示的に16bitであることを宣言します。
ということで、やってみます。
まずはFLASHがロック状態か確認し、ロックされていたら、解除します。
//FLASHがLOCK状態だったら解除
if (FLASH->CTLR & FLASH_CTLR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
次に、標準プログラミングモードを有効にします。
これにより、2byteずつ書き込みできます。
//スタンダードプログラミング有効
FLASH->CTLR |= FLASH_CTLR_PG;
次に、データを代入します。
今回は、関数化しようと思っているので、AddressとかDataとかにしてあります。
*(volatile uint16_t *)Address = Data;
次に、FLASHの処理が終了するまで待機します。
//FLASHがBUSYでなくなるまで待機
while(FLASH->STATR & FLASH_STATR_BSY);
次に、EOPを0にします。
FLASH->STATR |= FLASH_FLAG_EOP;
次に、標準プログラミングモードを無効にします。
FLASH->CTLR &= ~FLASH_CTLR_PG;
最後にFLASHをロックして終了です。
FLASH->CTLR |= FLASH_CTLR_LOCK;
FLASHからのデータ読み出し
アドレス値から読み出すだけです。
データ読み出しは8/16/32bit単位でのアクセスが認められています。ただ、書き込みが16bitだったので、読み出しも16bitの方がいろいろ都合が良い気もします。
(そこらへんは自由に実装してください)
今回は、関数の参考例のみ書いておきます。
//FLASH読み出し関数
uint16_t FLASH_read(volatile uint32_t Address){
volatile uint16_t *Data = (uint16_t*)Address;
return *Data;
}
(ちなみに、Data変数にvolatileをする必要はない気もします。まあ修正するのも面倒ですし、とりあえずこれで動いているので放置しますが…)
これらの関数化と使用例
//FLASH書き込み関数
//Address:アドレス | Data:書き込むデータ
void FLASH_write(volatile uint32_t Address, volatile uint16_t Data) {
//FLASHがLOCK状態だったら解除
if (FLASH->CTLR & FLASH_CTLR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
//スタンダードプログラミング有効
FLASH->CTLR |= FLASH_CTLR_PG;
*(volatile uint16_t *)Address = Data;
//FLASHがBUSYでなくなるまで待機
while(FLASH->STATR & FLASH_STATR_BSY);
FLASH->STATR |= FLASH_FLAG_EOP;
FLASH->CTLR &= ~FLASH_CTLR_PG;
FLASH->CTLR |= FLASH_CTLR_LOCK;
}
//FLASH読み出し関数
uint16_t FLASH_read(volatile uint32_t Address){
volatile uint16_t *Data = (uint16_t*)Address;
return *Data;
}
void FLASH_Erase(volatile uint32_t Address){
//FLASHがLOCK状態だったら解除
if (FLASH->CTLR & FLASH_CTLR_LOCK) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
//標準削除モード
FLASH->CTLR |= FLASH_CTLR_PER;
//削除する先頭ページアドレス指定
FLASH->ADDR = Address;
//削除開始
FLASH->CTLR |= FLASH_CTLR_STRT;
//削除が完了するまで待機
while(FLASH->STATR & FLASH_STATR_BSY);
FLASH->STATR |= FLASH_FLAG_EOP;
//終了
FLASH->CTLR &= ~FLASH_CTLR_PER;
}
少し雑ですが、関数化してみました。
下は利用例です。
#define ADD 0x08003FC0
#define ADD2 0x08003F80
#define ADD3 0x08003C00
int main(void) {
FLASH_write(ADD , 0x5678);
FLASH_Erase(ADD3);
FLASH_write(ADD , 0x1234);
while(1) {
asm("NOP");
}
}
ADDは書き込むアドレス(今回はPage256の先頭)
ADD3は削除するアドレス(今回はPage241の先頭)
となっています。
今回の参考例だと、ADDに0x5678を書き込んだ後、削除し、0x1234を書き込んでいます。
メモリの値を見る
メモリの値を見るには、MounRiver Stduioのデバッグ機能を使うか、WCH-LinkUtilityを使う方法があります。個人的には、WCH-LinkUtilityを使ったほうが楽だと思います。
まずはWCH-LinkUtilityを開きます。
ソフトが無ければ、ダウンロードしてきてください。
次に、みたいアドレスを指定します。
今回は、0x0800 3FC0 (Page256の先頭)を指定し、そこから0x100まで見るような設定にしてみました。
ということは 0x0800 3FC0 から 0x080040C0までということですかね。本来であれば、アクセス禁止領域まで行ってるような気もしますが、気にしないでおきます。
これで、上の方にあるFlashというボタンをおします。
すると、下のテキストボックスに出てきます。
FF というのが未使用ですね。
今回だと、 0x0800 3FC0から 00 00 05 00という値が書き込まれていることがわかります。
メモリが未使用か確認する (研究)
次に、メモリが未使用か確認する方法ですが、これは、コンパイルしたときのメッセージで分かります。
コンパイルすると、サイズみたいなのが出てきますが、あれがいわゆるどこまでFLASH領域を使用したか表しています。
具体的には、Textというやつで表しています。
例えば今回だと、 Textは900となっていますが、これを16進数に直すと、0x384になります。これは 0x0800 0000(つまりPage0から)なので、 0x0800 0000 から 0x0800 0384まで、プログラム領域で使われているということになります。
実際に、WCH-LinkUtilityで確認しましたが、上のような構成になっていました。
ただ、実際問題として、プログラム領域16kBをすべて使うとは考えにくいです。
まあ実際として、FLASHをEEPROM領域として使う場合には、削除の容量問題のことも踏まえて15kBになりますが、それでも15kBはかなり多いです。
(もちろんArduinoみたいなコードを組み込んだりすれば、少ないのかもしれませんが、レジスタ操作している分には十分なサイズだと思います)
まあコンパイル時にでてきたTextだけを過信するのではなく、 実際には余裕を持ったり、メモリの中身をダンプしてみて、本当に未使用領域か確認してから使うのが良いと思います。
(今回のプログラムでは、 0x386以降はすべて、FFで書き込まれていました)
研究2 FLASHの謎仕様
この辺からは、FLASHの仕様について実験でわかったことを書きます。
上で作ったFLASH_write関数で 0xCCCCを書き込んだ後、0xBBBBを書き込むと、メモリに残る値が0x8888になる現象がありました。
まあ正直16bitで考える必要はないので、今回はLSBの4bitのみを考えてみたいと思います。
図にするとこんな感じです。つまり、 これを見る限り、 1から0にはできるが、0から1にはできないということだと思います。
なので、FLASHを削除するのは、1で埋めるということなんだと思います。
研究3 FLASHの削除仕様について
FLASHの削除は1kB単位でした。ただ、実はPage256の先頭(実質64Byte領域しかない)を指定しても、削除できますし、書き込みもできます。というかエラーにもなりません。
メモリマップを見ると、0x0800 4000以降はReserved(予約領域)となっています。
ということは、0x0800 4000以降は書き込み禁止だと思うのですが、なぜかできる。うーん謎です。
ただ、別に問題が起こるわけでもないですし、次の領域は0x1FFF F000という果てしなく向こうのアドレス(1kBどころではない)ので、特に、問題ないのかもしれません。
(もしくは、ハード的に0x0800 0400以降は1埋めしないような制御がされているのかもしれませんね)
ちなみに、0x0800 4000以降に書き込みをしてみましたが、HardFault Errorが発生しました。なので、0x0800 4000以降は、やはり書き込み禁止なんですよね。うーんやっぱり謎。
まあ自己責任にはなってしまいますが、0x0800 0FC0を指定すると、64byte領域のみの削除になるので、その分、プログラムメモリに割当することもできます。
(というか、実はどっかにこの記述もあるのかもしれません。英語分からん!)
宣伝 (広告)
今回、16進数から、10進数とかに変換する時に、関数電卓が便利でした。
ぜひ買ってください。
(今回頑張ったし、少しぐらい宣伝してもいいですよね??)