2つのFLASH上でプログラムを動かしてみた

# 1. 今回のやりたいこと
マイコンに2つの外付けのFLASHを接続し、動作させたいプログラムをそれらに書き込み、動作させるということをします。動作させるプログラムは、LEDをちかちかさせるプログラムになります。

# 2. 環境
* __使用ボード__：nucleo l4r5zi
Cortex-M4搭載、2MBのFlash、640KのSRAM、USB OTG
* __開発環境__：Atollic TrueSTUDIO® for STM32, Built on Eclipse Neon.1a.(Version: 9.3.0 )
* __OS__：KOZOS
[12ステップで作る 組込みOS自作入門](http://kozos.jp/books/makeos/)で紹介されているOSになります。ソースコードも展開されています。
* __FLASH__ :[W25Q64JV](https://www.digikey.jp/ja/products/detail/winbond-electronics/W25Q64JVZEAM/15197306?utm_adgroup=&utm_source=google&utm_medium=cpc&utm_campaign=Dynamic%20Search_JP_Product_RLSA_Purchaser&utm_term=&productid=&utm_content=&utm_id=go_cmp-204831567_adg-19610356887_ad-65907387447_aud-504744218347:dsa-19959388920_dev-c_ext-_prd-_sig-Cj0KCQiAxOauBhCaARIsAEbUSQTkCzZ3FmAjBeq3-tTZrujz5ppMaxHYPonO7CcKh1EXbonA7UjM8L8aAq_QEALw_wcB&gad_source=1&gclid=Cj0KCQiAxOauBhCaARIsAEbUSQTkCzZ3FmAjBeq3-tTZrujz5ppMaxHYPonO7CcKh1EXbonA7UjM8L8aAq_QEALw_wcB)

# 3. 「2つのFLASH上でプログラムを動かす」とは？
私が使用しているボード(nucleo l4r5zi)には、外部シリアルFlashメモリとの通信インタフェースを提供してるOCTOSPIと呼ばれる周辺機能があります。このOCTOSPIという周辺機能を使用することで、2つのFLASH上でプログラムを動かすことが可能になります。順を追って詳しく説明していきます。

### 3.1 OCTOSPIとは
OCTOSPIはデータ線が8bit幅のSPI接続をサポートしており、以下3つのモードで動作します。
* __Indirect mode__
通常のSPIと同じように動作するモードです。
* __Automatic status-polling mode__
メモリのステータスレジスタをハードウェアで管理する自動ポーリングが可能なモードです。
* __Memory-mapped mode__
内蔵メモリと同じように、外付けのメモリにアクセスできるモードです。

例えば、インタフェースがQUADSPI（4本のデータ線を使用するIF）のメモリとの接続図は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/83384102-21ec-a407-abc5-cfa3e14c841a.png)
dm00310109-stm32l4-series-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdfから抜粋

外付けのフラッシュ上のプログラムを動作させるためには、OCTOSPIをMemory-mapped modeにする必要があります。

### 3.2 DUAL-QUAD-SPI
タイトルにある「2つのFLASH上でプログラムを動かす」ということはどういうことなのか？2つのFLASH上でプログラムを動かすためには、OCTOSPIのDUAL-QUAD-SPIという機能を使用します。DUAL-QUAD-SPIは、OCTOSPIが持つ8本のデータ線のうち4本を1つのQUADSPIのメモリ、残りの4本をもう1つのQUADSPIメモリに接続することで、2つのメモリに同時にアクセスすることを可能にします。接続の例は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/caa7c94c-2dc4-1182-ed40-b95c191ec9ee.png)
dm00310109-stm32l4-series-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdfから抜粋

DUAL-QUAD-SPIを使用してFlashのデータを読み込んだときのシーケンスは以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/3d284d40-75ab-1ae3-dfc8-1fa792dc9aa0.png)
an4760-quadspi-interface-on-stm32-microcontrollers-and-microprocessors--stmicroelectronics.pdfから抜粋

フラッシュのデータを読み込む際は、読み込み命令（上記図のInstruction）、アドレス（上記図のAddress）をフラッシュへ送信しますが、DUAL-QUAD-SPIでは、同じコマンド、同じアドレスがそれぞれのFLASHに送信されます。読み込み命令、アドレス0x00000000から2byteを読む際は、最初の1byte目はFlash1の0x00000000に格納されているデータ、次の2byte目にFlash2の0x00000000に格納されているデータがマイコン側へ送信されます。つまり、マイコン側からは、偶数のアドレスにはFlash1のデータ、奇数のアドレスにはFlash2のデータが格納されているように見えます。

2つのFLASH上でプログラムを動かすためには、そのプログラムをFLASH1、FLASH2にそれぞれ、交互に書き込む必要があります。また、同じコマンド、同じアドレスが送信されるため、2つのFLashはそれぞれ同じ種類であることも必要になります。

FLASHを接続しMemory-mapped modeに設定したときは、0x90000000~0x9FFFFFFFにマッピングされるため、FLASH上で動作させたいプログラムは0x90000000~0x9FFFFFFFに配置されるようにビルドする必要があります。

### 3.3 ボードとFLASHの接続図

今回はFlashデバイスとして、WinbondのW25Q64JVを使用します。ボードとW25Q64JVの接続図を以下に示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/d054169c-b64a-10fe-4359-e56c5920a967.png)

# 4. 実装
FLASH上でプログラムを動かすということで、まずはそのプログラムをFLASHに書く必要があります。プログラムはBlueTooth経由で受信することとします。

今回のソフトウェア構成図を以下に示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/d2f2855c-c889-e946-68a2-d1d1849d37a9.png)

今回で初めて作成したソフトウェアを黄色く塗っています。
各ソフトウェアの説明を以下に示します。

* __プログラム実行アプリ__
BlueTooth経由で受信したプログラムを交互に2つのFlashに書き込み、Flash上のプログラムを実行します。

* __FLASHマネージャ__
FLASHをアクセスするためのソフトになります。いろいろなアプリからFlashへのアクセスがあることを考慮し排他等の制御を行っています。

* __W25Q64JVデバイスドライバ__
W25Q64JVを制御するソフトになります。

プログラム実行アプリの一部を紹介したいと思います。

* __FLASHにプログラムを書き込むコード__
```C
// プログラムをフラッシュに書く
// (*)2つの外部フラッシュでxipを行う
static void loading_app_msg_write_dual_program(uint32_t par)
{
	LOADING_APP_CTL *this = &loading_app_ctl;
	int32_t ret;
	uint32_t i;
	uint8_t data;
	uint8_t verify_ret = 1;
	char snd_str[256];
	
	console_str_send("dual write program\n");
	
	// 書き込み
	for (i = 0; i < (this->buf_idx/2); i++) {
		// DeviceAに書き込む
		ret = flash_mng_write(FLASH_MNG_KIND_W25Q20EW1, i, &(this->load_program_buf[i*2]), 1);
		if (ret != E_OK) {
			console_str_send("DeviceA write error\n");
		}
		// DeviceBに書き込む
		ret = flash_mng_write(FLASH_MNG_KIND_W25Q20EW2, i, &(this->load_program_buf[i*2+1]), 1);
		if (ret != E_OK) {
			console_str_send("DeviceB write error\n");
		}
	}
	
	// ベリファイ
	for (i = 0; i < (this->buf_idx/2); i++) {
		// 読み込み
		ret = flash_mng_read(FLASH_MNG_KIND_W25Q20EW1, i, &data, 1);
		// 判定
		if (this->load_program_buf[i*2] != data) {
			sprintf(snd_str, "addr:%x expexted:%x read:%x\n", (unsigned int)(i*2), this->load_program_buf[i*2], data);
			console_str_send(snd_str);
			verify_ret = 0;
		}
		// 読み込み
		ret = flash_mng_read(FLASH_MNG_KIND_W25Q20EW2, i, &data, 1);
		// 判定
		if (this->load_program_buf[i*2+1] != data) {
			sprintf(snd_str, "addr:%x expexted:%x read:%x\n", (unsigned int)(i*2+1), this->load_program_buf[i*2+1], data);
			console_str_send(snd_str);
			verify_ret = 0;
		}
	}
	
	// 結果表示
	if (verify_ret) {
		console_str_send("write success\n");
	}
}
```
* __FLASHのプログラムを実行するコード__
LEDをちかちかさせるプログラムのmain関数が0x90000581に配置されているため、そこへジャンプします。
```C
・・・
#define FLASH_PROGRAM_ADDR		(0x90000581)	// フラッシュプログラム
・・・
// Execute in place
static void loading_app_msg_xip(uint32_t par)
{
	volatile int (*f)(void);
	
	// jump to main function in exterbal Flash
	f = (int(*)(void))(FLASH_PROGRAM_ADDR);
	f();
}
```


具体的な実装については、[Nucleo-L4R5ZI_System](https://github.com/Yas-Mi/Nucleo-L4R5ZI_System)を参照お願いいたします。

# 5. 動作確認

同じLEDをちかちかするプログラムを1つのFLASHで動作させたときと2つのFLASHで動作させたときを比べてたいと思います。期待としては、2つのFLASHで動作させたときのほうが、1つのFLASHで動作させたときに比べて、LEDの点滅周期が2倍になることです。

* __1つのFLASHで動作させたとき__
![1フラッシュ_実行-_online-video-cutter.com_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/e9f703c3-6d5e-6107-a952-3509a445088d.gif)

* __2つのFLASHで動作させたとき__
* ![2フラッシュ_実行-_online-video-cutter.com_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782242/f906523f-be0c-8ed2-3823-ba57b78f5db3.gif)

同じプログラムでも2つのFLASHで動作させたほうが早いことがわかるかと思います。

# 6. 最後に
初めて外付けのFLASH上でプログラムを動かすということをやってみました。OCTOSPIがどういうものかを知るいいきっかけになりました。業務でも外付けのFLASHでプログラムを動作させるというのはよくあるかと思うので、今回の経験を活かしたいと思います。

何か不明点、ご意見等ありましたらコメントいただけると助かります。