Skip to content

FlashNVM

朝日薫 edited this page Dec 19, 2022 · 13 revisions

FlashNVM リファレンス

フラッシュメモリ自己書換支援ツール。 これはスケッチプログラムを書き込んだ後の残りの、未使用フラッシュメモリ領域を スケッチの中から書換可能な不揮発記憶領域として扱うことを支援する。 これは EEPROM 領域に比べると数桁大きなまとまった容量を扱える一方、 書換可能限度回数は1桁以上低い。

この機能は以下の対応ブートローダーが、 スケッチプログラムよりも前方に存在していることを前提に動作する。

他のSDK付属のブートローダーに例え同種の機能があっても、それには対応しないことに注意。

端的に言えばこれらのブートローダーがスケッチプログラムをフラッシュメモリ領域に書き込む能力を プログラムから借り受けて使用するものだ。 当然のことながら、記憶されているスケッチプログラムそれ自体を 破壊したり消したりも容易に出来ることに注意されたい。

  • この支援ツールの機能は namespace FlashNVMに属するメンバー関数として提供される。

用例

#include <FlashNVM.h>

/* 機能サポート確認 */
if (! FlashNVM::spm_support_check()) {
  /* FlashNVM tools not supported */
  return;
}

/* NVNEN領域に1ページ分のスペースを確保 */
const char nvm_store[PROGMEM_PAGE_SIZE] PGM_ALIGN NVMEM;

/* SRAM上のバッファ確保 */
char nvm_buffer[PROGMEM_PAGE_SIZE];

/* <avr/pgmspace.h> の関数を使って */
/* NVMEM領域から SRAM にコピー : 領域位置が 64KiB以内の場合 : 以下同(接尾子 _P) */
memcpy_P(&nvm_buffer, &nvm_store, sizeof(nvm_buffer));

/* NVMEM領域から SRAM にコピー : 領域位置が 64KiB超の場合 : 以下同(接尾子 _PF) */
memcpy_PF(&nvm_buffer, pgm_get_far_address(nvm_store), sizeof(nvm_buffer));

/* nvm_storeバッファメモリを適当に書換えて。。。 */

/* ページ消去 */
if ( FlashNVM::page_erase_P( &nvm_store, sizeof(nvm_buffer) ) )
{ /* success */ }

if ( FlashNVM::page_erase_PF( pgm_get_far_address(nvm_store), sizeof(nvm_buffer) ) )
{ /* success */ }

/* ページ書込 */
if ( FlashNVM::page_update_P( &nvm_store, &nvm_buffer, sizeof(nvm_buffer) ) )
{ /* success */ }

if ( FlashNVM::page_update_PF( pgm_get_far_address(nvm_store), &nvm_buffer, sizeof(nvm_buffer) ) )
{ /* success */ }

ページ粒度

支援ツールが出来ることは、フラッシュメモリ領域の指定ページを消去することと、 そしてそれに書き込むことだけだ。 消去と書込は個別の機能関数である。

消去操作はフラッシュメモリ領域に固有の ページ粒度 を最小単位とする。 ページ粒度は 使用するAVRの型番に固有 の大きさを持っている。 そのバイト量はPROGMEM_PAGE_SIZE定数で知ることが出来る。

PROGMEM_PAGE_SIZEの大きさは 64、128、512 の何れかだ。256 の存在は現在知られていない。
詳細は[modernAVR 周辺機能比較一覧]フラッシュメモリ量目項を参照されたい。

ページ境界

書き換えるフラッシュメモリ領域の開始点もまたPROGMEM_PAGE_SIZEに支配されている。 従ってそれは正しいページアライメントに整列していなければならない。 これはそのスペースを確保する際にPGM_ALIGN属性を付加することで調整できる。

スケッチプログラム自体を破壊せずに済む、安全な書換可能領域を予約するには NVMEMまたはROMEM属性およびPGM_ALIGN属性を付加し、 PROGMEM_PAGE_SIZEを意識した構造体宣言などを設計しなければならない。

ページ数

書換可能な空領域が具体的にどの程度あるかを知ることは難しい。 これはプログラムが実際にビルドされ、リンクプロセスを経るまで知ることは出来ないからだ。 ただし目安として以下の値を参照することは出来る。

宣言名 説明 備考
PROGMEM_END フラッシュメモリ終了番地 0xFFFF等
__data_load_end フラッシュメモリ未使用先頭番地 要extern参照シンボル

extern const uint8_t* __data_load_end;

__data_load_endに続くページアライメント境界がNVMEM領域最初の先頭位置となる。 従って両者からその差を求めPROGMEM_PAGE_SIZEで割った商は、使用可能な最大ページ数に等しい。

消去と書込

フラッシュメモリ領域を確実に書き換えるにはその前に、そのページ内容を消去しなくてはならない。 これはページ全体を0xFFで埋め尽くすことに等しい。 そしてフラッシュメモリ領域の書込とは、新たな値と現在そこにある値との論理和をとって、それを書き残すことである。

例えば:旧値0xFF AND 新値0x5A → 結果0x5A

もしページ内容を確実に消さずに何らかの以前の値がそこに残っていた場合、 書込後の値は書換えたかった本当の結果とは異なるだろう。

例えば:旧値0x6C AND 新値0x5A → 結果0x48

よって 書換は常に消去と書込操作のペア でなくてはならない。

  • 実装回路レベルで言うと消去は領域内の各ビットに対して0→1に変化させる操作しかできず、
    書込は1→0に変化させる操作しかできない。

一方で、消去操作はフラッシュメモリ領域の寿命を必ず損ねる。 これは書込操作に比して常にダメージが大きい。 そこで書込結果が論理和になることを積極的に利用したストレージ設計を用いる戦略を考えることもできる。 消去と書込が別機能になっているのはそのような用法を可能にする。

例えば、ページ消去操作を減らすために空きページと使用ページの違いを詳細に管理したい場合、 空き/使用ページフラグを管理するビットマップページを論理和書込だけで更新する方法が考えられる。 この場合空きページがひとつもなくなるまでは、消去操作をせずに済ませることが可能だろう。

動作電圧

フラッシュ領域消去と書換には十分な電力供給が必要で、不足すると不正な結果になりうる。 書換後には<avr/pgmspace.h>に含まれるstrncmp_Pまたはstrncmp_PFで比較確認するのが好ましい。

操作中少なくとも 2.85V 以上は維持されるべきだ。
サーボモーターや無線デバイスを操作している最中にこれらを実行するのは危険である。

<FlashNVM.h>

依存性:<avr/io.h> <avr/pgmspace.h> <api/memspace.h>

#define NVMEM

セクション".nvmem"に指定要素を配置する属性。 <api/memspace.h>で定義される。

これを指定した要素は、初期値を与えても無視される。 ゼロ初期化もされない。 確保領域はプログラムコードより後方に配置される。 従って 64KiB 境界より前に配置されるとは限らない。

const char nvm_store[PROGMEM_PAGE_SIZE] PGM_ALIGN NVMEM;

PROGMEM属性との違いは配置される領域が異なるのと初期化されないことで、その他の機能は同じである。

#define ROMEM

セクション".progmem.nvm"に指定要素を配置する属性。 <api/memspace.h>で定義される。

これを指定した要素は、初期値を与えることが出来る。 しなければゼロ初期化される。 確保領域はプログラムの前方(64KiB境界以前)の PROGMEM属性領域より後方かつ プログラムコード前方に優先して配置される。

const char nvm_store[] PGM_ALIGN ROMEM = "Hello World!";

PROGMEM属性との違いは配置される領域が異なるだけで、その他の機能は同じである。

#define PGM_ALIGN

指定要素をPROGMEM_PAGE_SIZE境界に配置する属性。 <api/memspace.h>で定義される。

これを付加した要素は、ページ内で完結する。 つまりPROGMEM_PAGE_SIZEに満たない大きさの要素は 次の要素までの間に余白が空く。

const char nvm_store0[4] PGM_ALIGN NVMEM;
/* ここに隙間が空く : あるいは隣接するとは限らない */
const char nvm_store1[4] PGM_ALIGN NVMEM;
  • PGM_ALIGNまたはPAGE_ALIGN()をない領域に消去/書込を行うと 同一ページ内に属していた他の情報は破壊されうる。

#define PAGE_ALIGN(sect)

後続要素を指定セクション"sect"PROGMEM_PAGE_SIZE境界に配置する属性。 セクション名は ".text" ".progmem" ".nvmem" が有効。 <api/memspace.h>で定義される。

PGM_ALIGN属性と違って効果を発揮するのは直後の同じセクションに属する要素だけである。

PAGE_ALIGN(".nvmem");
const char nvm_store0[4] NVMEM;
/* ここに隙間は空かず密に配置される */
const char nvm_store1[4] NVMEM;
  • PGM_ALIGNまたはPAGE_ALIGN()を伴わない領域に消去/書込を行うと 同一ページ内に属していた他の情報は破壊されうる。

bool FlashNVM::spm_support_check (void)

現在の環境がFlashNVMツールに対応しているなら真を返す。 これが偽の場合に他のメンバー関数を呼び出した場合の挙動は保証されない。

これは必要なスニペットが特定プログラムアドレスにマジックナンバーとして現れることを確認している。 正しく対応しているブートローダーが存在しなければ、マジックナンバーは合致しないだろう。

bool FlashNVM::page_erase_P (const void* _page_addr, size_t _page_size = 1)

_page_addr位置を含むページを消去する。 成功すると真を返す。

_page_sizeを省略したなら指定位置に該当する1ページだけが消される。 _page_sizeを指定すると(これはページ数ではない)その領域を含む複数ページが消される。 _page_addrがページ境界先頭でなかった場合、 _page_sizeは意図しない複数ページに跨っている場合があり、 その場合は境界を跨がれた全ページも消される。

消去操作はその操作毎に数十ミリ秒の遅滞を生じる。
_page_sizeの最大値は32767(INT_MAX)なので操作1回で消せるのは 32KiB までである。

FlashNVM::page_erase_P( &nvm_store, sizeof(nvm_buffer) )

_page_addrで指定するのは 64KiB境界を超えない範囲でなくてはならない。
megaAVR / tinyAVR 版ではPROGMEM_PAGE_SIZEに同量の追加SRAMを一時使用する。

bool FlashNVM::page_erase_PF (const nvmptr_t _page_addr, size_t _page_size = 1)

page_erase_Pを 64KiB を超える範囲に拡張した版。 _page_addrは常にpgm_get_far_addressマクロを介して与える。 成功すると真を返す。

FlashNVM::page_erase_PF( pgm_get_far_address(nvm_store), sizeof(nvm_buffer) )

フラッシュ総量が 64KiB以下の環境で使っても問題はない。

bool FlashNVM::page_update_P (const void* _page_addr, const void* _data_addr, size_t _save_size)

_page_addr位置に対してSRAM領域_data_addrから_save_sizeバイトを書き込む。 _page_addrが指す領域は事前に消去されていることが期待される。 成功すると真を返す。

FlashNVM::page_update_P( &nvm_store, &nvm_buffer, sizeof(nvm_buffer) );

_page_addrで指定するのは 64KiB境界を超えない範囲でなくてはならない。

bool FlashNVM::page_update_PF (const nvmptr_t _page_addr, const void* _data_addr, size_t _save_size)

page_update_Pを 64KiB を超える範囲に拡張した版。 _page_addrは常にpgm_get_far_addressマクロを介して与える。 成功すると真を返す。

FlashNVM::page_update_PF( pgm_get_far_address(nvm_store), &nvm_buffer, sizeof(nvm_buffer) );

フラッシュ総量が 64KiB以下の環境で使っても問題はない。

LINKS

multix.jp/てくにかるむ(休眠中)
Multix Zinnia Product SDK [*AVR]
AVR.JP(日本語訳)
AVR-LIBC(日本語訳)

Clone this wiki locally