-
Notifications
You must be signed in to change notification settings - Fork 0
FlashNVM
フラッシュメモリ自己書換支援ツール。 これはスケッチプログラムを書き込んだ後の残りの、未使用フラッシュメモリ領域を スケッチの中から書換可能な不揮発記憶領域として扱うことを支援する。 フラッシュメモリ領域は EEPROM 領域に比して数桁大きなまとまった容量を扱える一方、 書換可能限度回数は1桁以上低い。
この機能は以下の対応ブートローダーが、 スケッチプログラムよりも前方に存在していることを前提に動作する。
- 対応ブートローダー;
他のSDK付属のブートローダーに例え同種の機能があっても、それには対応しないことに注意。
端的に言えばこれらのブートローダーがスケッチプログラムをフラッシュメモリ領域に書き込む能力を プログラムから借り受けて使用するものだ。 当然のことながら、記憶されているスケッチプログラムそれ自体を 破壊したり消したりも容易に出来ることに注意されたい。
- この支援ツールの機能は namespace
FlashNVM
に属するメンバー関数として提供される。 - AVR_DU と AVR_EB にある BOOTROW 領域の書き換えも、このツールで対応する。
USERROW 領域は
<UrowNVM.h>
で支援される。
#include <FlashNVM.h>
/* NVMEN領域に1ページ分のスペースを確保 */
const char nvm_store[PROGMEM_PAGE_SIZE] PGM_ALIGN NVMEM;
/* 機能サポート確認 */
if (! FlashNVM::spm_support_check()) {
/* FlashNVM tools not supported */
return;
}
/* 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 */ }
以下の PROGMEMアドレスに、以下の固定値が書かれている。
対応 Bootloader FWV=3.71、HWV=48,50,51,52,53
Series | Address | マジックナンバー : uint32_t (LE) |
---|---|---|
megaAVR-0 tinyAVR-0/1/2 (HWV=48) | MAPPED_PROGMEM_START + 2 Byte | 0x95089361 |
AVR_DA/DB/DD/DU/EA/EB | PROGMEM_START + 2 Byte | 0x95089361 |
MAPPED_PROGMEM_START
は通常のデータ空間、PROGMEM_START
は PROGMEM 空間にある。
使用可能なスニペットは2種あるいは4種存在する。 これらは実行コード権限のプログラムカウンタ(PC)検査を回避するために、ここに配置されている。
Offset | HWV=48 | HWV=50以上 | OP-Code |
---|---|---|---|
$02 | nvm_stz | nvm_stz | ST Z+, R22 |
$06 | nvm_cmd | nvm_ldz | LD R24, Z+ |
$0A | - | nvm_spm | SPM Z+ |
$0E | - | nvm_cmd | (function) |
これらは BOOT領域保護特権で CODE領域 / APPEND領域(そして一部品種の BOOTCODE領域)の FLASH消去/書換を行うのに使うことが出来る。
- HWV=48は 16bitアドレス品種用のため
SPM+
が存在せず、LPM/LD
の使い分けもない。 -
LDZ/STZ
は BOOTROW 非採用デバイスでは不要だが、コード互換性のために存在する。 - C/C++言語からスニペットを呼ぶにはラッパーアセンブリが必要。
-
MCUdude
やDxCore
での同種の機能とは仕様が異なり、相互に互換性はない。
支援ツールが出来ることは、フラッシュメモリ領域の指定ページを消去することと、 そしてそれに書き込むことだけだ。 消去と書込は個別の機能関数である。
消去操作はフラッシュメモリ領域に固有の ページ粒度 を最小単位とする。
ページ粒度は 使用する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 | フラッシュメモリ終了番地 | 0x1FFFF等(16bit以下とは限らない) |
__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
に変化させる操作しかできない。
一方で、消去操作はフラッシュメモリ領域の寿命を必ず損ねる。 これは書込操作に比して常にダメージが大きい。 そこで書込結果が論理和になることを積極的に利用したストレージ設計を用いる戦略を考えることもできる。 消去と書込が別機能になっているのはそのような用法を可能にする。
例えば、ページ消去操作を減らすために空きページと使用ページの違いを詳細に管理したい場合、 空き/使用ページフラグを管理するビットマップページを論理和書込だけで更新する方法が考えられる。 この場合空きページが足りなくなるまでは一括消去操作(コンパクション)を遅らせることが可能だろう。
NVM領域消去と書換には十分な電力供給が必要で、不足すると不正な結果になりうる。
書換後には<avr/pgmspace.h>
に含まれるstrncmp_P
またはstrncmp_PF
で比較確認するのが好ましい。
操作中少なくとも 2.85V 以上は維持されるべきだ。サーボモーターや無線デバイスを操作している最中にこれらを実行するのは危険である。
一部の製品は低電圧下での NVM消去と書き換え結果が保証されていない。eratta情報を確認すること。
依存性:<avr/io.h>
<avr/pgmspace.h>
セクション".nvmem"
に指定要素を配置する属性。
これを指定した要素は、初期値を与えても無視される。 ゼロ初期化もされない。FRO 確保領域はプログラムコードより後方に配置される。 従って 64KiB 境界より前に配置されるとは限らない。
const char nvm_store[PROGMEM_PAGE_SIZE] PGM_ALIGN NVMEM;
PROGMEM
属性との違いは配置される領域が異なるのと初期化されないことで、その他の機能は同じである。 なのでこの領域宣言は(64KiB境界を超えていなければ)<String.h>
のP()
マクロでポインタ参照ができるから
セクション".progmem.nvm"
に指定要素を配置する属性。
これを指定した要素は、初期値を与えることが出来る。
しなければゼロ初期化される。
確保領域はプログラムの前方(64KiB境界以前)の
PROGMEM
属性領域より後方かつ
プログラムコード前方に優先して配置される。
const char nvm_store[] PGM_ALIGN ROMEM = "Hello World!";
PROGMEM
属性との違いは配置される領域が異なるだけで、その他の機能は同じである。 なのでこの領域宣言は(64KiB境界を超えていなければ)<String.h>
のP()
マクロでポインタ参照ができるから
指定要素をPROGMEM_PAGE_SIZE
境界に配置する属性。
これを付加した要素は、ページ内で完結する。
つまりPROGMEM_PAGE_SIZE
に満たない大きさの要素は
次の要素までの間に余白が空く。
const char nvm_store0[4] PGM_ALIGN NVMEM;
/* ここに隙間が空く : あるいは隣接するとは限らない */
const char nvm_store1[4] PGM_ALIGN NVMEM;
-
PGM_ALIGN
またはPAGE_ALIGN
をない領域に消去/書込を行うと 同一ページ内に属していた他の情報は破壊されうる。
後続要素を指定セクション"sect"
のPROGMEM_PAGE_SIZE
境界に配置する属性。
セクション名は ".text"
".progmem"
".nvmem"
が有効。
PGM_ALIGN
属性と違って効果を発揮するのは直後の同じセクションに属する要素だけである。
PAGE_ALIGN(".nvmem");
const char nvm_store0[4] NVMEM;
/* ここに隙間は空かず密に配置される */
const char nvm_store1[4] NVMEM;
ただしこれは複数の要素を一度に書き換えねばならないのと同義なので メモリ管理を完全に自前で行うのでなければ使用することはないだろう。 普通は構造体(セクター)を定義して、ページ単位で領域割り付けと書き換えを行うべきだ。
-
PGM_ALIGN
またはPAGE_ALIGN
を伴わない領域に消去/書込を行うと 同一ページ内に属していた他の情報は破壊されうる。
依存性:<avr/io.h>
<avr/pgmspace.h>
<api/memspace.h>
現在の環境がFlashNVM
ツールに対応しているなら真を返す。
これが偽の場合に他のメンバー関数を呼び出した場合の挙動は保証されない。
これは必要なスニペットが特定プログラムアドレスにマジックナンバーとして現れることを確認している。 正しく対応しているブートローダーが存在しなければ、マジックナンバーは合致しないだろう。
_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境界を超えない範囲でなくてはならない。
現在の実装ではPROGMEM_PAGE_SIZE
に同量の追加SRAMを一時使用するため STACK用 SRAM残量には注意が必要。特に AVR_Dx系統は 512byteを一時的に消費する。このため<TaskChanger.h>
との併用は要注意だ。
page_erase_P
を 64KiB を超える範囲に拡張した版。
_page_addr
は常にpgm_get_far_address
マクロを介して与える。
成功すると真を返す。
FlashNVM::page_erase_PF( pgm_get_far_address(nvm_store), sizeof(nvm_buffer) )
フラッシュ総量が 64KiB以下の環境で使っても問題はない。
_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以下の環境で使っても問題はない。
BOOTROW 領域を消去する。NVM操作に成功すれば真を返す。
- この関数は AVR_DU または AVR_EB 系統専用であり、他の品種では定義されていない。
- BOOTROW 領域は全体で1ページのフラッシュメモリ領域である。データ保存専用で、コード実行はできない。
BOOTROW 領域を _data_addr が示すデータメモリに複写する。先頭からのバイト数は _save_size で与える。省略時は BOOTROW_SIZE 定数の値に従う。NVM操作に成功すれば真を返す。
読み込み開始位置は BOOTROW 領域先頭に固定である。
この関数はbootrow_save
を使う事前準備としての、構造体に複写したりするショートカットを提供する。
- この関数は AVR_DU または AVR_EB 系統専用であり、他の品種では定義されていない。
BOOTROW 領域へ _data_addr が示すデータメモリ内容で書き換える。先頭からのバイト数は _save_size で与える。省略時は BOOTROW_SIZE 定数の値に従う。NVM操作に成功すれば真を返す。
書き込み開始位置は BOOTROW 領域先頭に固定である。
この関数はpage_update_P*
関数同様に現在値との論理積を結果として残す。つまり事前にbootrow_clear
関数で BOOTROW 領域全体を消去しておく前提だが、現在値を変更したくないバイト値には 0xFF
を書くことで追記操作が可能である。
- この関数は AVR_DU または AVR_EB 系統専用であり、他の品種では定義されていない。
uint8_t buf[BOOTROW_SIZE];
FlashNVM::bootrow_load(&buf);
Serial.printHex((const void *)&buf, sizeof(buf), ' ', 16).ln().ln();
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if ( FlashNVM::bootrow_clear()
&& FlashNVM::bootrow_save(&buf, sizeof(buf))
) Serial.println(F("[success]"));
else Serial.print(F("*failed* 0x")).println(NVMCTRL_STATUS, ZHEX, 2);
}
Note
bootrow_clear / bootrow_save
は正常終了しても NVMCTRL_STATUS
は 0x20
を返却する。(WRITEPROTECT 領域への書き込み検出)
BOOTROW 領域と _data_addr が示すデータメモリ内容で比較一致を試みる。先頭からのバイト数は _save_size で与える。省略時は BOOTROW_SIZE 定数の値に従う。一致すれば真を返す。
検査開始位置は BOOTROW 領域先頭に固定である。
- この関数は AVR_DU または AVR_EB 系統専用であり、他の品種では定義されていない。
/* 比較ベリファイ */
if (FlashNVM::bootrow_verify(&nvm_buffer, sizeof(nvm_buffer)))
{ /* success */ }
Twitter(X): @askn37
BlueSky Social: @multix.jp
GitHub: https://github.com/askn37/
Product: https://askn37.github.io/
Copyright (c) 2022,2023 askn (K.Sato) multix.jp
Released under the MIT license
https://opensource.org/licenses/mit-license.php
https://www.oshwa.org/
multix.jp/てくにかるむ(休眠中)
Multix Zinnia Product SDK [*AVR]
AVR.JP(日本語訳)
AVR-LIBC(日本語訳)