RaspberryPi Pico と trykernelで dualcoreマイコン開発　その3 内蔵SPIフラッシュにデータ書き込み

## 設定データを内部FLASHに記憶させたい

・システムを作る中、設定データ(例えばIPアドレスなど)を内部に記憶させたいですよね。
・picoには1Mもフラッシュ領域があり、その一部を設定データ記憶に使えれば便利です。
・フラッシュ操作はSDKのAPIを使って簡単に実装できました。

## picoの内蔵フラッシュ
・picoの内蔵SPIフラッシュは「W25Q16JVUXIQ」という型式。
・容量は2MByte、アドレス:は0x000000 ～ 0x1FFFFFです。
・ただし2MBすべて使えるわけでなく、使えるのは1MByteのみです。
・ROMエリア(0x000000~0x0FFFFF)とXIP(0x100000~0x1FFFFF)エリアで分かれており、
　ROMエリアには工場出荷時に書き込まれるブートローダ(消せない)が実装されています。
・ユーザープログラムなど、自由に書き込める領域はXIPエリアになります。
・Pico起動時、CPUはROMに格納されたブートローダを実行します。ブートローダは、
　フラッシュメモリの初期化やXIPモードの設定などを行い、その後にXIP領域に格納された 
  　ユーザープログラムを実行します。
・ROMはPicoの起動処理を行い、XIPはユーザープログラムの実行領域として機能します。
<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/815894b5-3f8f-4383-83e6-14af5901d7ca.png)

## フラッシュ操作APIの説明
・[hardware/flash.h]をインクルードして使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/0b667173-493e-4969-8b2b-66944eb8b11c.png)

★削除
■flash_range_erase
・機能：指定されたアドレス範囲のフラッシュメモリを消去します。
・注意：フラッシュメモリは、書き込み前に消去する必要があります。
・引数：offset: 消去を開始するフラッシュメモリのアドレス　count: 消去するバイト数
・備考：フラッシュメモリは、セクタ単位で消去されます。セクタサイズは通常4KBです。
　　　　countは、セクタサイズの倍数である必要があります。

★書き込み
■flash_range_program
・機能：指定されたアドレス範囲のフラッシュメモリにデータを書き込みます。
・引数：offset: 書き込みを開始するフラッシュメモリのアドレス　
　　　　src: 書き込むデータのポインタ　count: 書き込むバイト数
・備考：フラッシュメモリは、ページ単位で書き込まれます。
　　　　ページサイズは通常256バイトです。

★読み込み
・特別なAPIは必要なく、アドレスを直接読むことで値をreadできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/d61bc695-d94f-4e88-a110-37ff0ca724b8.png)

## 消去、書き込み時の注意点
・フラッシュメモリへの削除、書き込み中に、別のコアがプログラムコードを含む
　フラッシュ領域にアクセスするとエラーが発生し、例外ハンドラへ飛びます。
・この期間は別のコアも命令をフェッチしたり、データを読み出したりできません。
・フラッシュ内の同じページではなく、フラッシュ自体へのアクセスが禁止されます。
・Picoのマルチコア環境では、core0とcore1が独立して動作するため、片方のコア
　がフラッシュ書き込みを行っている間も、もう片方のコアは動作していますので、
  　普通にフラッシュAPIを使うとエラーとなります。

## 消去、書き込み時の対策(割り込みの禁止)
・対策としてはそのままですが、別のコアを停止させるAPIが用意されています。
・その前に割り込みを禁止しておきます。
・save_and_disable_interrupts()は、割り込みの状態を保存し、すべての
　プロセッサコアの割り込みを無効化するために使用されます。
・これは割り込みによって中断されてはならないコード領域を保護するために
　使用します。(クリティカルセクション)
・使用した場合、必ず対応する restore_interrupts() を呼び出して、保存し
　ておいた割り込みの復元が必要です。そうしないと、システム全体の割り込みが
 　無効化されたままになり異常動作となります。(デッドロック)
<br>  
~~~c
    // 割り込み無効にする
    uint32_t ints = save_and_disable_interrupts();

    // コア0を停止
    multicore_lockout_start_blocking();

    // Flash消去
    //  消去単位　FLASH_SECTOR_SIZE(4096Byte) の倍数
    flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);
    // Flash書き込み
    //  書込単位　FLASH_PAGE_SIZE(256Byte) の倍数
    flash_range_program(FLASH_TARGET_OFFSET, wd, FLASH_PAGE_SIZE);

    // コア0を再開
    multicore_lockout_end_blocking();
    
    // 割り込みフラグを戻す
    restore_interrupts(ints);
~~~    
<br>

## 消去、書き込み時の対策(victimコアの宣言とロック)

・multicore_lockout_start_blockingは、「victim core」に定義したコアを一時的に停止して
　割り込み無効にするAPIです。 (実際はRAM 内のコードでタイトなループを実行)
・タイムアウト付きのmulticore_lockout_start_timeout_us()を使うこともできます。 
・使用方法として事前に「victim」コア(他のコアにロックされる可能性のあるコア）
　として定義する必要があります。multicore_lockout_victim_init()APIを呼び出して、
  　宣言しておきます。
<br>
~~~c
void main_c0(void)
{
    /*c0は停止可能なサブコアとして定義*/
    multicore_lockout_victim_init();
    
    gpio_init(LED_PIN);                 // GPIO初期化
    gpio_set_dir(LED_PIN, GPIO_OUT);    // GPIOを出力に設定

    i2cLcdPerfInit();    

    while (1) {
        disp_adc_val();
        sleep_ms(100);
    }
}
~~~
<br>
・ロックアウトが不要になると、multicore_lockout_end_blocking()APIを呼び出します。<br>
・タイムアウト付きのmulticore_lockout_end_timeout_us()を使うこともできます。
<br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3657924/4fd1c37f-a3f1-4641-9fdc-3c6b34d00bc3.png)


今日はここまで。