KORG NTS-1 のカスタム・パネル リファレンス基盤でフラッシュ・メモリ領域にデータを保存する

# はじめに

KORG NTS-1 では、フロント・パネルを自作のカスタム・パネルと置き換えることで機能を変更、拡張できるようになる予定です（2020-02-25時点）。現時点ではこのカスタム・パネルのリファレンス基盤が公開されています。

https://korginc.github.io/nts-1-customizations/ja/

このリファレンス基板に様々なソフトウェアをインストールすることで、例えばシーケンサを組み込んで NTS-1 の音源を駆動する、といったことが可能となる予定です。その際に、カスタム・パネル上で動くソフトウェアの設定値などを保存したくなったりします。例えばシーケンサの場合は、ステップごとの発音情報などは電源をオン・オフしても保持したいですよね。

そういった場合に、リファレンス基板上でデータをフラッシュ・メモリに保存する方法を解説します。このリファレンス基盤はプロセッサに STM32F030R8T6 を使っており、このプロセッサがフラッシュ・メモリ領域を持っているので、そこにデータを保存する方法の説明となります。

# tl;dr;

最終的にはこんなコードになりました。

```cpp
#include "stm32f0xx_hal.h"

// cf. https://www.stmcu.jp/design/document/reference_manual/51370/
#define PAGE60_START_ADDR 0x0800F000

void save_config() {
  // Unlock flash.
  HAL_FLASH_Unlock();
  // Erase the page 60.
  FLASH_EraseInitTypeDef erase;
  erase.TypeErase = FLASH_TYPEERASE_PAGES;
  erase.PageAddress = PAGE60_START_ADDR;
  erase.NbPages = 1;
  uint32_t error = 0;
  if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) {
  }
  // Write to the page.
  uint32_t *data = (uint32_t*) &seq_config;
  for (uint32_t a = PAGE60_START_ADDR; a < PAGE60_START_ADDR + sizeof(seq_config); a += 4) {
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, a, *data) != HAL_OK) {
    }
    data++;
  }
  // Lock flash.
  HAL_FLASH_Lock();
}

void load_config() {
  // Load from flash.
  memcpy(&seq_config, (uint8_t*) PAGE60_START_ADDR, sizeof(seq_config));
}
```

このコードを使って、それぞれのパートで何を行っているかを解説します。

# 前提条件

## 想定する環境

STM32F030R8T6 を使ったリファレンス基盤をそのまま利用していることを想定します。

## 参考文書

- [1] [RM0360
Reference manual
STM32F030x4/x6/x8/xC and STM32F070x6/xB
advanced ARM®-based 32-bit MCUs](https://www.stmcu.jp/design/document/reference_manual/51370/)

## HAL (Hardware Abstraction Library)

STM32 の各種機能へのアクセスには HAL (Hardware Abstraction Library) が用意されていて、フラッシュ・メモリ領域へのアクセスに関する機能も提供されています。NTS-1 の開発環境をセットアップすると、STM32 で利用可能な HAL も取得されるので、今回のコードでもそれを利用しています。

# コードの解説

## HAL の読み込み

前述のとおり、今回のコードではフラッシュ・メモリ領域へのアクセスに HAL を利用しています。以下の一行で STM32F0 シリーズ用の HAL を読み込んでいます。

```cpp
#include "stm32f0xx_hal.h"
```

## 保存に使うフラッシュ・メモリ領域

ドキュメント[1] の "Flash memory organization" というテーブルで説明されているように、フラッシュ・メモリ領域は 1 Kbyte 単位のページと、それを4つずつまとめたセクタとで管理されています。最初の方の領域はプログラム本体とコンフリクトしたりるすようなので、今回のコードでは後ろの方の page 60 (0x0800F000 - 0x0800F3FF) の領域を利用します。コード中でこの領域の開始アドレスを以下のように定義しています。

```cpp
#define PAGE60_START_ADDR 0x0800F000
```

## フラッシュ・メモリの Unlock / Lock

STM32 上のフラッシュ・メモリは通常のアドレス空間の一部としてアクセス可能です。そのため、意図せずに内容を変更されないように、変更に対するロックが掛けられています。

> After reset, the Flash memory is protected against unwanted write or erase operations. 

フラッシュ・メモリ領域にデータを保存する際はこのロックを解除する必要があります。

ドキュメント[1] にも書かれているように `FLASH_CR` に特定のデータを２つ書き込むことでロックを解除することができますが、今回はその手順をラップしている HAL の関数を利用します。

```cpp
  // Unlock flash.
  HAL_FLASH_Unlock();
```

これ以降はフラッシュ・メモリ領域への書き込みアクセスが可能となります。

書き込みが終わったあとは再度ロックをかけておきましょう。

```cpp
  // Lock flash.
  HAL_FLASH_Lock();
```

## 書き込み対象のフラッシュ・メモリ領域のクリア

STM32 シリーズのフラッシュ・メモリ領域に書き込む際は、一旦その内容をクリアした後に書き込む必要があります。
> The Flash memory interface preliminarily reads the value at the addressed main Flash
memory location and checks that it has been erased. If not, the program operation is
skipped and a warning is issued by the PGERR bit in FLASH_SR register. 

STM32F030R8T6 ではページ単位でのクリアが可能となっています。今回利用する page 60 を以下のようにクリアします。

```cpp
  // Erase the page 60.
  FLASH_EraseInitTypeDef erase;
  erase.TypeErase = FLASH_TYPEERASE_PAGES;
  erase.PageAddress = PAGE60_START_ADDR;
  erase.NbPages = 1; // Number of pages to erase.
  uint32_t error = 0;
  if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) {
  }
```

## データの書き込み

フラッシュ・メモリを Unlock し、対象の領域をクリアしたので、これで書き込みの準備が整いました。

フラッシュ・メモリ領域への書き込みはワード単位で行うことができます。ハーフ・ワードも可能ですが、今回はワード単位でドサッと書き込みます。STM32F030R8T6 のワードは 32 bits なので、一度に 32bits = 4 bytes を書き込むことになります。一方でメモリ領域は 1 byte 単位なので、一度の書き込みごとに書き込み先のアドレスを4つ進める必要があります。

今回のサンプルでは、 `seq_config` という構造体が保存すべきデータを格納している想定です。これを `uint32_t` のポインタとして読みつつ `HAL_FLASH_Program` に渡していきます。こちらは `data++` で一つずつ進めていて（32 bits）、書き込み先のアドレスは `a += 4` （8 bits x 4 bytes = 32 bits）としている点に注意してください。

```cpp
  // Write to the page.
  uint32_t *data = (uint32_t*) &seq_config;
  for (uint32_t a = PAGE60_START_ADDR; a < PAGE60_START_ADDR + sizeof(seq_config); a += 4) {
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, a, *data) != HAL_OK) {
    }
    data++;
  }
```

ここで `seq_config` の境界が 4 bytes の境界と一致しない場合が気になるかもしれませんが、これは後ほどの読み込み方法との兼ね合いで特に問題とはならないのでここでは無視しましょう。

## フラッシュ・メモリの Lock

先ほど Unlock / Lock でまとめて説明しましたが、書き込み終了後のここで Lock をかけるのを忘れずに。

```cpp
  // Lock flash.
  HAL_FLASH_Lock();
```

## データの読み出し

フラッシュ・メモリ領域は通常のアドレス空間の一部としてアクセス可能です。先程は `seq_config` から `PAGE60_START_ADDR` から始まる領域へとデータを書き込みましたが、今回は逆に `PAGE60_START_ADDR` が指すメモリのデータを `seq_config` にコピーします。この際、コピーするメモリ領域の大きさを `sizeof(seq_config)` として指定するので、先ほど書き込みの際にはみ出した分は特に問題にはなりません。

```cpp
  // Load from flash.
  memcpy(&seq_config, (uint8_t*) PAGE60_START_ADDR, sizeof(seq_config));
```

# まとめ

以上で、KORG NTS-1 のカスタム・パネルのリファレンス基盤上で走らせるプログラムから、フラッシュ・メモリ領域へのデータの保存とその読み出しが実現できました。今回は単一の `seq_config` のみを保存しましたが、基本的な書き込み、読み出しの考え方を応用することで複数の値の保存と読み出しが可能なはずです。ただし、領域をページ単位で一旦クリアする必要があることから、対象のデータを一旦退避するなどの対策が必要となるのでその点はご注意ください。