こんにちは。

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進数とかに変換する時に、関数電卓が便利でした。

ぜひ買ってください。

 

 

 

(今回頑張ったし、少しぐらい宣伝してもいいですよね??)