Arduino_STM32でFlashにEEPROM emulatorでアクセスする

# はじめに

先日STM32を使用する際に、
**Firmwareを書き換えてもクリアされないデータ領域**
が欲しかったのですが、使用したいSTM32には不揮発メモリがのっていませんでした。

そこで
- Programを書き込むFlashの一部をEEPROM EmulatorでRead/Write
- BinarryをLoadするときに上記のEEPROM Emulate領域をクリアしない
とったように無理やり対応しました。

そのときやった方法を簡単にシェアします。

正直ところどころ力技な部分があるので、もっとスマートな解決方法があれば教えていただけると有り難いです。

## 環境

マイコン
- [STM32F103C8](https://www.st.com/ja/microcontrollers-microprocessors/stm32f103c8.html)

Arudino環境
- Arduino_STM32

STM32のArudino環境は主に2つあり

- [Arduino_STM32](https://github.com/rogerclarkmelbourne/Arduino_STM32)
    - Roger Clarkというおじさんがメンテしているらしきやつ
- [Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)
  - stm32duinoのリポ
  - STMが支援しているらしい

どちらも実装されている機能・利用可能なライブラリに差分があるので、自身が利用されたいものが使えるやつや環境にあった方をご使用されることをおすすめします。

上記含めた、STM32のArduino系の開発環境についてはこちらの記事が参考になります。
[STM32F103の開発環境](https://qiita.com/nanbuwks/items/594568db3f5dc1d6c5fd)

今回は前者Arduino_STM32の環境を使用しています。

## EEPROM Emulatorの使い方
Arduino_STM32にはEEPROM Emulatorが用意されています

[Application note: EEPROM emulationin STM32F10x microcontrollers](https://www.st.com/content/ccc/resource/technical/document/application_note/ee/ef/d7/87/cb/b7/48/52/CD00165693.pdf/files/CD00165693.pdf/jcr:content/translations/en.CD00165693.pdf)

これはSTMのドキュメントがあるので、STM32として用意されているみたいです。

このためのソースコードもArduino_STM32内にありました。

Arduino_STM32/STM32F1/libraries/EEPROM/
[EEPROM.cpp](https://github.com/rogerclarkmelbourne/Arduino_STM32/blob/master/STM32F1/libraries/EEPROM/EEPROM.cpp)
[EEPROM.h](https://github.com/rogerclarkmelbourne/Arduino_STM32/blob/master/STM32F1/libraries/EEPROM/EEPROM.h)


### EEPROM.hのMCU向けの変更

EEPROM.hを見ると、マイコンの型番によって、Pageサイズとサイズが規定されています。
この既存のコードの#ifの条件に無いMCUをArduino IDEのボードで指定して、EEPROM.hを参照すると当然ながらビルドエラーになります。

ですので、もし該当MCUが無い場合はここに追記します。(今回は```MCU_STM32F103C8```を追加)

```
#ifndef EEPROM_PAGE_SIZE
	#if defined (MCU_STM32F103RB) || defined (MCU_STM32F103C8)
		#define EEPROM_PAGE_SIZE	(uint16)0x400  /* Page size = 1KByte */
	#elif defined (MCU_STM32F103ZE) || defined (MCU_STM32F103RE) || defined (MCU_STM32F103RD)
		#define EEPROM_PAGE_SIZE	(uint16)0x800  /* Page size = 2KByte */
	#else
		#error	"No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
	#endif
#endif
```

```
#ifndef EEPROM_START_ADDRESS
	#if defined (MCU_STM32F103C8)
		#define EEPROM_START_ADDRESS	((uint32)(0x8000000 + 64  * 1024 - 2 * EEPROM_PAGE_SIZE))
	#elif defined (MCU_STM32F103RB)
		#define EEPROM_START_ADDRESS	((uint32)(0x8000000 + 128 * 1024 - 2 * EEPROM_PAGE_SIZE))
	#elif defined (MCU_STM32F103ZE) || defined (MCU_STM32F103RE)
		#define EEPROM_START_ADDRESS	((uint32)(0x8000000 + 512 * 1024 - 2 * EEPROM_PAGE_SIZE))
	#elif defined (MCU_STM32F103RD)
		#define EEPROM_START_ADDRESS	((uint32)(0x8000000 + 384 * 1024 - 2 * EEPROM_PAGE_SIZE))
	#else
		#error	"No MCU type specified. Add something like -DMCU_STM32F103RB to your compiler arguments (probably in a Makefile)."
	#endif
#endif
```

STM32のFlashに関するドキュメントはこちら

[STM32F10xxx Flash memory microcontrollers](https://www.st.com/resource/en/programming_manual/cd00283419.pdf)

マイコンのPageサイズとROMサイズは調べると出てきます。

使用したF103C8は
- Page Size : 1KB
- ROM Size : 64KB
でした。
このEEPROM Emulatorは2KBをEEPROMとして使用し、EEPROM_START_ADDRESSの箇所にあるように、ROMのサイズから最後の2KBを使用するようになっています。


### EEPROM emulatorのRead/Write

EEPROM emulatorの使用方法は、サンプルがあるのでそれを動かすと大分イメージ掴めます。

[EEPROM_example.ino](https://github.com/rogerclarkmelbourne/Arduino_STM32/blob/master/STM32F1/libraries/EEPROM/examples/EEPROM_example/EEPROM_example.ino)

具体的には、2Byteごとにアクセスするので、下記のように実装するだけです。


```EEPROMAccess.cpp
//Read
void EEPROMAccess::read(uint16_t addr, uint16_t *data, uint16_t length)
{
    uint16_t status = 0;

    for(uint16_t i = 0; i < length; i++){
        status = EEPROM.read(addr, &data[i]);
        addr++;
    }
}


```


```EEPROMAccess.cpp
//Write
void EEPROMAccess::write(uint16_t addr, uint16_t *data, uint16_t length)
{
    for(uint16_t i = 0; i < length; i++){
        EEPROM.write(addr, data[i]);
        addr++;
    }
}
```

```EEPROMAccess.cpp
//Example of Read/Write

uint16_t write_data[10] = {10};

uint16_t start_addr = 0; //page0

EEPROMAccess eeprom_access;

eeprom_access.init();
eeprom_access.write(start_addr, write_data, sizeof(write_data)/sizeof(uint16_t));

uint16_t read_data[10] = {0};
eeprom_access.write(start_addr, read_data, sizeof(read_data)/sizeof(uint16_t));
```

書き込み処理時間はそれなり（数ms以上）かかりますので、何かのセンシング・制御中にWriteというのは厳しいですが、そういった処理に影響しないタイミングで個体固有の情報などを書き込んで判別したい場合などには利用できます。

## EEPROM領域をBinary書き込み時にEraseしないように変更
ここまでで、Program Flashの最後の2KBをEEPROMライクにアクセスできるようになりましたが、これではFirmware(Binary)を書き込むたびにその領域がクリアされてしまいます。

ですので、（ここが一番の力技ですが）Arduino IDEで書き込みするときの、Tool側を変更して、該当マイコンのときは、最後の2KBをEraseしないように変更します。


### 書き込みROMサイズの変更

まずSTM32F103C8の最大ROMサイズを変更します。
ここを変更すると62KBしかROMが使えなくなります。

```diff

--- a/STM32F1/boards.txt
+++ b/STM32F1/boards.txt
@@ -348,7 +348,7 @@ genericSTM32F103C.upload.protocol=maple_dfu
 genericSTM32F103C.menu.device_variant.STM32F103C8=STM32F103C8 (20k RAM. 64k Flash)
 genericSTM32F103C.menu.device_variant.STM32F103C8.build.cpu_flags=-DMCU_STM32F103C8
 genericSTM32F103C.menu.device_variant.STM32F103C8.build.ldscript=ld/jtag_c8.ld
-genericSTM32F103C.menu.device_variant.STM32F103C8.upload.maximum_size=65536
+genericSTM32F103C.menu.device_variant.STM32F103C8.upload.maximum_size=63488
 genericSTM32F103C.menu.device_variant.STM32F103C8.upload.maximum_data_size=20480
 ```

### Eraseサイズの調整

最初は上記のROMサイズだけ変更すれば、その分だけEraseしてくれるのではと淡い期待を抱いていましたが、そうはなっていませんでした。

Arduino IDEで書き込みするときのPrint文から検索したところ、

tools/linux/src/stm32flash_serial/src/main.c

の処理が、ROM書き込み時のErase処理を行っていましたので、
これを該当MCUの場合に、無理やり62KBだけEraseするように変更しました。


```diff
--- a/tools/linux/src/stm32flash_serial/src/main.c
+++ b/tools/linux/src/stm32flash_serial/src/main.c
@@ -204,6 +204,10 @@ int main(int argc, char* argv[]) {
        int             failed = 0;
        int             first_page, num_pages;
 
+       //calc page count
+       npages = (stm->dev->fl_end - stm->dev->fl_start) / stm->dev->fl_ps;
+    fprintf(diag, "npages %d\n", npages);
+
        /*
         * Cleanup addresses:
         *
@@ -251,13 +255,14 @@ int main(int argc, char* argv[]) {
                        end = flash_page_to_addr(first_page + num_pages);
                        if (end > stm->dev->fl_end)
                                end = stm->dev->fl_end;
                } else {
                        end = stm->dev->fl_end;
                        num_pages = flash_addr_to_page_ceil(end) - first_page;
                }
-
-               if (!first_page && end == stm->dev->fl_end)
-                       num_pages = 0xff; /* mass erase */
+               //avoid forcelly to erase all memory
+               //if (!first_page && end == stm->dev->fl_end)
+               //              num_pages = 0xff; /* mass erase */
        }
 
        if (rd) {
```

```diff

--- a/tools/linux/src/stm32flash_serial/src/dev_table.c
+++ b/tools/linux/src/stm32flash_serial/src/dev_table.c
@@ -33,7 +33,7 @@ const stm32_dev_t devices[] = {
        {0x448, "STM32F072xx"       , 0x20001800, 0x20004000, 0x08000000, 0x08020000,  2, 2048, 0x1FFFF800, 0x1FFFF80F, 0x1FFFC800, 0x1FFFF800},
        /* F1 */
        {0x412, "Low-density"       , 0x20000200, 0x20002800, 0x08000000, 0x08008000,  4, 1024, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
-       {0x410, "Medium-density"    , 0x20000200, 0x20005000, 0x08000000, 0x08020000,  4, 1024, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
+       {0x410, "Medium-density"    , 0x20000200, 0x20005000, 0x08000000, 0x0800F800,  4, 1024, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
        {0x414, "High-density"      , 0x20000200, 0x20010000, 0x08000000, 0x08080000,  2, 2048, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
        {0x420, "Medium-density VL" , 0x20000200, 0x20002000, 0x08000000, 0x08020000,  4, 1024, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
        {0x428, "High-density VL"   , 0x20000200, 0x20008000, 0x08000000, 0x08080000,  2, 2048, 0x1FFFF800, 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800},
```

dev_table.cにstm32flash_serialで参照するMCU種別ごとの各領域のサイズ（アドレス）定義されています。
F103C8はMedium-densityに分類されるので、ここのROM終端を2KB分短くします。

さらに、```tools/linux/src/stm32flash_serial/src/main.c```のコードは一応ROMサイズからErase分を調整しているようにみえるのですが、途中でそのサイズチェックがうまく行えずにすべて消すような処理になってしまっているので、その処理をコメントアウトしました。

これで62KBだけEraseされるようになり、EEPRON領域がBinary書き込み後も参照できるようになりました。（めでたしめでたし）

## まとめ
不揮発メモリの無いSTM32でFlashをEEPRONライクに使う方法を書きました。
また、それをBinary書き込み後もクリアされないように対応しました。
同じような手段を取りたい方がいましたら、ご参考になればと思います。

また、できればあまりArudino_STM32側はいじりたくなかったので（しかもstm32_flash_serialのmainを変更してしまっている）、そこがもっとうまくできる方法があれば共有してもらえると助かります。