STM32 Nucleo Boardでブートローダをまた作る

#はじめに
[STM32 Nucleo Boardでブートローダを作る](https://qiita.com/mitazet/items/9e333404505f47600ca7)でブートローダを作成しました。
これは、ホストPCからメインプログラムをSTM32のRAMに転送して起動しています。
RAM上のコードは電源を切れば、消えます。
開発中は何度も修正を行うのでこの方式がいいですが、独立した運用はできません。
現在の私は[STM32 Nucleo Boardでフラッシュにデータを保存する](https://qiita.com/mitazet/items/67977385f501cd81e5d1)で書いた通り、内蔵フラッシュが使えるようになっています。
そこで、RAMからフラッシュへのコード保存とフラッシュからRAMへのコード展開を追加します。
#開発ターゲット
STM32F303K8
#作るもの
ブートローダです。
##作るブートローダの機能
__New__の機能を追加します。
・ホストPCからRAMへメインプログラムを転送する
・RAM上のメインプログラムをダンプする
・RAM上のメインプログラムにジャンプする
・フラッシュからRAMへメインプログラムを読み出す __←New__
・RAMからフラッシュへメインプログラムを書き込む __←New__
##完成イメージ
![write_code.png](https://qiita-image-store.s3.amazonaws.com/0/140055/23453eb1-1e5b-eb09-e224-c4ce8659527d.png)

![load_code.png](https://qiita-image-store.s3.amazonaws.com/0/140055/e9979f94-9a25-c188-fd5b-166835e6a69a.png)
#作る
##メモリ構成を考える

今回、メモリ構成は下記のようにしました。
フラッシュをコード保存とデータ保存(予定)との領域に分けています。
![memory_map.png](https://qiita-image-store.s3.amazonaws.com/0/140055/fbb81a47-debc-ea17-438e-057df808da94.png)

##リンカスクリプトを作る
設計したメモリ構成を実現するために、リンカスクリプトを書きます。
###ブートローダのリンカスクリプト
全体は[こちら](https://github.com/mitazet/stm32/blob/master/v10/Boot/STM32F303K8.ld)。

```
MEMORY
{
    RAM_BOOT (xrw)  : ORIGIN = 0x20000000, LENGTH = 0x00000200 /* 512B */
    RAM_VECTOR (rw) : ORIGIN = 0x20000200, LENGTH = 0x00000200 /* 512B */
    RAM_CODE (xrw) 	: ORIGIN = 0x20000400, LENGTH = 0x00001000 /* 4KB */
    RAM_WORK (rw)  	: ORIGIN = 0x20001400, LENGTH = 0x00001C00 /* 7KB */
    CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 0x00001000 /* 4KB */
    FLASH_BOOT (xr) : ORIGIN = 0x08000000, LENGTH = 0x00002000 /* 8KB */
    FLASH_CODE (rw) : ORIGIN = 0x08002000, LENGTH = 0x00004000 /* 16KB */ <-コードを保存する領域
    FLASH_DATA (rw) : ORIGIN = 0x08006000, LENGTH = 0x0000A000 /* 40KB */ <-データを保存する領域
}

_app_vector = ORIGIN(RAM_VECTOR);
_flash_code = ORIGIN(FLASH_CODE);
_main_code_size = LENGTH(RAM_VECTOR) + LENGTH(RAM_CODE) + LENGTH(RAM_WORK) - (_Min_Heap_Size + _Min_Stack_Size);
_main_code = ORIGIN(RAM_CODE);

_flash_addr = ORIGIN(FLASH_CODE);
_flash_size = LENGTH(FLASH_CODE) + LENGTH(FLASH_DATA);
```
`_flash_addr`、`_flash_size`はflashドライバでアドレス範囲の判定に使用しています。
ブートローダの書き換えを防ぐため、`FLASH_CODE`のアドレス以降をフラッシュ領域とします。
`_main_code_size`はロード時のバイナリサイズとして使用します。
これは`RAM_VECTOR`/`RAM_CODE`/`RAM_WORK`の合計からスタックとヒープのサイズを引いた値です。

###メインプログラムのリンカスクリプト
メインプログラムについてもメモリ構成はブートローダと同じにします。
全体は[こちら](https://github.com/mitazet/stm32/blob/master/v10/Main/STM32F303K8.ld)。

```
/* Specify the memory areas */
MEMORY
{
    RAM_BOOT (xrw)  : ORIGIN = 0x20000000, LENGTH = 0x00000200 /* 512B */
    RAM_VECTOR (rw) : ORIGIN = 0x20000200, LENGTH = 0x00000200 /* 512B */
    RAM_CODE (xr) 	: ORIGIN = 0x20000400, LENGTH = 0x00001000 /* 4KB */
    RAM_WORK (rw)  	: ORIGIN = 0x20001400, LENGTH = 0x00001C00 /* 7KB */
    CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 0x00001000 /* 4KB */
    FLASH_BOOT (xr) : ORIGIN = 0x08000000, LENGTH = 0x00002000 /* 8KB */
    FLASH_CODE (rw) : ORIGIN = 0x08002000, LENGTH = 0x00004000 /* 16KB */
    FLASH_DATA (rw) : ORIGIN = 0x08006000, LENGTH = 0x0000A000 /* 40KB */
}

_flash_addr = ORIGIN(FLASH_DATA);
_flash_size = LENGTH(FLASH_DATA);
```
メインプログラムでも`_flash_addr`、`_flash_size`はflashドライバでアドレス範囲の判定に使用しています。
ブートローダとコードの書き換えを防ぐため、`FLASH_DATA`のアドレス以降をフラッシュ領域とします。
各セクションは`RAM_VECTOR`/`RAM_CODE`/`RAM_WORK`に配置されるようにします。
ヒープスタック領域はRAM確保のために定義しており、バイナリには含まれません。
そのため、ブートローダで定義している`_main_code_size`よりサイズは大きくなりません。

##ロード
バイナリにすると最初のロード領域がベースアドレスとして使用されます。
領域間の相対位置は変わらないため、RAM_VECTOR(0x20000200)からバイナリをロードすればOKのはずです。
![load.png](https://qiita-image-store.s3.amazonaws.com/0/140055/5b906ef3-4577-f8be-bb8a-18145a8703c4.png)
##プログラムを作る
###ブートローダ
ソースコードは[こちら](https://github.com/mitazet/stm32/blob/master/v10/Boot/main.c)。
以下がブートローダの処理です。

```c
// form Linker Script
extern void _main_code();
extern int  _flash_code;
extern int  _main_code_size;

// コードの消去
// コードサイズからPage数を計算して消去する
static int erase_code(void)
{
    uint8_t* flash_addr = (uint8_t*)&_flash_code;
    uint32_t page_num_code = (uint32_t)&_main_code_size / FLASH_PAGE_SIZE_BYTE + 1; 
    
    printf("Code Area Address: 0x%X\n", flash_addr);
    printf("Page num of Code Area: %u\n", page_num_code);

    for(uint32_t i=0; i < page_num_code; i++){
        if(FlashPageErase(flash_addr) != FLASH_RESULT_OK){
            printf("erase error occured!!\n");
            return -1;
        }

        flash_addr += FLASH_PAGE_SIZE_BYTE;
    }

    return 0;
}

// コードの書き込み
// コードを事前に消去し、RAM上のバイナリを書き込む
static int write_code(char* buf, long size)
{
    long i;

    if(size < 0){
        printf("no data.\n");
        return -1;
    }
    
    uint16_t* flash_addr    = (uint16_t*)&_flash_code;
    uint16_t* data_addr     = (uint16_t*)buf;

    FlashInit();

    if(erase_code() != 0){
        return -1;
    }

    for(i=0; i<size; i++){
        if(FlashWrite(flash_addr, *data_addr) != FLASH_RESULT_OK){
            printf("write error occured!!\n");
            return -1;
        }

        flash_addr++;
        data_addr++;
    }

    return 0;
}

// コードの展開
// FLASH_CODE領域から_main_code_size分読み出してRAMにコピー
static void load_code(char *buf)
{
    uint8_t* flash_addr = (uint8_t*)&_flash_code;
    uint32_t main_code_size = (uint32_t)&_main_code_size;

    for(uint32_t i=0; i<main_code_size; i++){
        *buf = FlashRead(flash_addr);
        buf++;
        flash_addr++;
    }
}

// Main -----------------------------------------------------------------------
int main(void)
{
    static char buf[16];
    static long size = -1;
    static unsigned char *loadbuf = NULL;
    extern int _app_vector; // from Linker Script
    
    __disable_irq(); // disable interrupt

    UsartInit();

    printf("zloader started.\n");

    while(1){
        printf("zload> ");
        gets(buf);

        if(!strcmp(buf, "loadx")){
            loadbuf = (char*)(&_app_vector);
            size = XmodemRecv(loadbuf);
            wait();
            if(size < 0){
                printf("\nXMODEM receive error!\n");
            }else{
                printf("\nXMODEM receive succeeded.\n");
            }
        }else if(!strcmp(buf, "loadf")){ // <-コマンド追加
            loadbuf = (char*)(&_app_vector);
            load_code(loadbuf);
        }else if(!strcmp(buf, "dump")){
            printf("size: %d\n", size);
            dump(loadbuf, size);
        }else if(!strcmp(buf, "write")){ // <-コマンド追加
            int write_result = write_code(loadbuf, size);
            if(write_result != 0){
                printf("\nWrite Code error!\n");
            }else{
                printf("\nWrite Code is succeeded.\n");
            }
        }else if(!strcmp(buf, "run")){
            _main_code();
        }else{
            printf("unknown.\n");
        }
    }

    return 0;
}
```
フラッシュへのメインプログラムの書き込み処理と読み出し処理を作成しました。
それに対応するブートローダのコマンドとして書き込みを`write`、読み出しを`loadf`として追加しました。
リンカスクリプトで定義したアドレスは`extern`して使用します。

###メインプログラム
ソースコードは[こちら](https://github.com/mitazet/stm32/blob/master/v10/Main/main.c)。
mainプログラムは前の開発のままです。とりあえず起動することを確認します。

```c
int main(void)
    __attribute__ ((section (".entry_point")));
int main(void){  

    printf("main boot succeed!\n");  

    SysTick_Config(SystemCoreClock/10); // 1/10秒（=100ms）ごとにSysTick割り込み
    NVIC_SetPriority(SVCall_IRQn, 0x80);    // SVCの優先度は中ほど
    NVIC_SetPriority(SysTick_IRQn, 0xc0);   // SysTickの優先度はSVCより低く
    NVIC_SetPriority(PendSV_IRQn, 0xff);    // PendSVの優先度を最低にしておく

    __enable_irq(); // enable interrupt

    RtosInit();
    RtosThreadCreate((rtos_func_t)flash, "flash", 0, 0x100, 0, NULL);

    /* OSの動作開始 */  
    RtosStart();  

    /* ここには戻ってこない */  
    return 0;
}
```

#テスト
ブートローダを焼きこんで電源を入れます。
`loadx`でメインプログラムのバイナリをRAM転送します。
![loadx.png](https://qiita-image-store.s3.amazonaws.com/0/140055/0951ff6a-c553-d51f-eb67-604498c3ac77.png)

`write`でRAM上バイナリをフラッシュに書き込みます。
![write.png](https://qiita-image-store.s3.amazonaws.com/0/140055/441f7d44-e2fb-5efa-52bd-6315b1659bef.png)

`loadf`でフラッシュのバイナリを読み出してRAMに転送し、run!!
![loadf_run.png](https://qiita-image-store.s3.amazonaws.com/0/140055/4808c070-15a6-385b-fd0d-50a926baf67b.png)
メインプログラムが起動!!

#おわりに
これで目標としていたブートローダを作ることができました。
STM32ではフラッシュ上のコードを実行することができるため、RAMに展開する必要はあまりないです。
しかし、RAMのほうが速いため、速度が求められる場合は有効だと思います。