STM32 Nucleo Boardでフラッシュにデータを保存する

#はじめに
私の手元にあるSTM32F303K8には64KBのフラッシュが搭載されています。
これまで、フラッシュはコードを保存するために使用していました。
コードの書き込みはgdb経由で行っていたので詳細な方法は知らないままでした。
そこで、自作のソフトウェア上で自由にデータを保存できるようフラッシュ制御を行います。
#開発ターゲット
STM32F303K8
#調査
リファレンスマニュアルにやり方が記載されています。
読み出し、書き込み、消去が可能です。
フラッシュの特性上、領域はPage単位に分けられています。
![flash_org.png](https://qiita-image-store.s3.amazonaws.com/0/140055/10cfb89b-67a4-295d-caab-9ebe2ca08b91.png)
読み出しはByte単位で可能ですが、書き込みはHalf Word(16bit)単位、消去はPage(2KByte)単位となります。
##読み出し手順
読み出し手順は以下のように記載されています。

![flash_read.png](https://qiita-image-store.s3.amazonaws.com/0/140055/8c5c952d-b3bd-7d95-2a76-88047fecda89.png)

通常のメモリのようにアクセスすることができます。

```c:sample_read
uint8_t read(uint8_t* address){
    return *address;
}
```

##書き込み・消去手順
フラッシュは書き込みと消去を繰り返すと劣化してしまうので、リセット後はロックがかかっているようです。
そのためロックを外してから書き込み・消去を行います。
また、書き込みは消去済み(未書き込み)の領域にしかできません。
書き込み前に該当の領域を消去する必要があります。
###ロック解除
ロック解除方法は以下のように記載されています。
![flash_unlock.png](https://qiita-image-store.s3.amazonaws.com/0/140055/18a62114-3744-9624-b62c-7b0b985fa00e.png)
FLASH_KEYRレジスタにKEY1とKEY2を順番に設定するとロックが解除され、FLASH_CRレジスタにアクセスできます。

```c:sample_unlock
void unlock(void){
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
}
```
###書き込み
書き込み手順は次のように記載されています。
![flash_prog.png](https://qiita-image-store.s3.amazonaws.com/0/140055/25e65915-e2d2-5a47-979c-4d05fae96b61.png)
コードにすると次のようになります。

```c:sample_write
okng_t write(uint16_t* address, uint16_t data){
    while(FLASH->FLASH_SR & FLASH_SR_BSY);
    FLASH->CR |= FLASH_CR_PG;
    *address = data;
    while(FLASH->FLASH_SR & FLASH_SR_BSY);
    if(FLASH->FLASH_SR & FLASH_SR_EOP){
        FLASH->FLASH_SR &= ~FLASH_SR_EOP;
        return OK;
    }else{
        return NG;
    }
}
```
###消去
Page消去と全体消去がありますが、ここではPage消去について説明します。
消去手順は次のように記載されています。
![flash_ers.png](https://qiita-image-store.s3.amazonaws.com/0/140055/38609a18-8a3d-d306-d3ec-24b41c7d79ac.png)
コードにすると次のようになります。

```c:sample_erase
okng_t erase(uint8_t* address){
    while(FLASH->FLASH_SR & FLASH_SR_BSY);
    FLASH->CR |= FLASH_CR_PER;
    FLASH->AR = (uint32_t)address;
	FLASH->CR |= FLASH_CR_STRT;
    while(FLASH->FLASH_SR & FLASH_SR_BSY);
    if(FLASH->FLASH_SR & FLASH_SR_EOP){
        FLASH->FLASH_SR &= ~FLASH_SR_EOP;
        return OK;
    }else{
        return NG;
    }
}
```
アドレスを指定していますが、アドレスを含むPage全体が消去されます。
このままだと分かりにくいので、インターフェースに何か工夫が必要かもしれません。

#作る
##テストコード
[Googleテストで組み込みソフトをテストする](https://qiita.com/mitazet/items/211f9dfb63f4e358cf83)でTDDを導入できる状態にしたので、まずはテストコードを書いてみます。
テストコード全体は長いため、WriteOKのテストを載せておきます。全体は[こちら](https://github.com/mitazet/stm32/blob/master/v10/Main/driver/flash/test/test_flash.cpp)にあります。
STM32は32bitであるため、ホストPCの仮想フラッシュ領域のアドレスも32bitである必要があります。
そのため、このテストは32bit環境でしか動きません。対策は今後の課題とさせてください。

```c++
class FlashTest : public ::testing::Test {
    protected:
        virtual void SetUp()
        {
            mock = new MockIo();
            // 仮想フラッシュレジスタ
            virtualFlash = new FLASH_TypeDef();
            // 仮想フラッシュ領域 ただの配列
	        virtualAddress = new uint8_t[100];
            // フラッシュ領域の判定のために配列の開始と終了のアドレスを設定
	        virtualStart = (uint32_t)&virtualAddress[0];
	        virtualEnd = (uint32_t)&virtualAddress[100]; // 範囲外のアドレスを取得
            FlashCreate(virtualFlash, virtualStart, virtualEnd);
        }
        
        virtual void TearDown()
        {
            delete mock;
            delete virtualFlash;
            delete virtualAddress;
        }
};

TEST_F(FlashTest, WriteOK)
{
    mock->DelegateToVirtual();

    // 書き込み予定領域を0クリア
    virtualAddress[0] = 0;
    virtualAddress[1] = 0;
　　// 仮想フラッシュの書き込みアドレス
    uint16_t* dummy = (uint16_t*)&virtualAddress[0];
　　// 書き込みデータ
    uint16_t data = 0xBEEF;

    EXPECT_CALL(*mock, ReadBit(&virtualFlash->CR, FLASH_CR_LOCK)).WillOnce(Return(0));
    EXPECT_CALL(*mock, ReadBit(&virtualFlash->SR, FLASH_SR_BSY)).WillRepeatedly(Return(FLASH_SR_BSY));
    EXPECT_CALL(*mock, ReadBit(&virtualFlash->SR, FLASH_SR_BSY)).WillRepeatedly(Return(0));
    EXPECT_CALL(*mock, SetBit(&virtualFlash->CR, FLASH_CR_PG));
    EXPECT_CALL(*mock, ReadBit(&virtualFlash->SR, FLASH_SR_BSY)).WillRepeatedly(Return(FLASH_SR_BSY));
    EXPECT_CALL(*mock, ReadBit(&virtualFlash->SR, FLASH_SR_BSY)).WillRepeatedly(Return(0));
    EXPECT_CALL(*mock, ReadBit(&virtualFlash->SR, FLASH_SR_EOP)).WillOnce(Return(FLASH_SR_EOP));
    EXPECT_CALL(*mock, ClearBit(&virtualFlash->SR, FLASH_SR_EOP));
	
    EXPECT_EQ(FLASH_RESULT_OK, FlashWrite(dummy, data));

    EXPECT_EQ(*dummy, data);
}
```
テストは以下を用意しました。
・読み出しOK
・読み出しNG(アドレスオーバー)
・書き込みOK
・書き込みNG(書き込み失敗)
・書き込みNG(アドレスオーバー)
・消去OK
・消去NG(消去失敗)
・消去NG(アドレスオーバー)
最終的にテストをパスに持っていくように実装します。
![gtest_flash.png](https://qiita-image-store.s3.amazonaws.com/0/140055/f464aa42-9415-f0cb-b1a0-8633c6249603.png)
##プロダクトコード
プロダクトではないですが、本流のコードのことです。
レジスタ操作などの詳細は内部関数にしてあります。
ソースコード全体は[こちら](https://github.com/mitazet/stm32/blob/master/v10/Main/driver/flash/flash_driver.c)。

```c:
// テストでVirtualな設定をするために作成
void FlashCreate(FLASH_TypeDef* flash_address, uint32_t start_address, uint32_t end_address)
{
    flashAddress = flash_address;
    flashStart = start_address;
    flashEnd = end_address;
}

// アドレス設定とロック解除
void FlashInit(void)
{
#ifndef DEBUG_GTEST
    FlashCreate(FLASH, (uint32_t)&_flash_addr, (uint32_t)&_flash_addr + (uint32_t)&_flash_size);
#endif
    flash_unlock();
}

// 読み出し
uint8_t FlashRead(uint8_t* address)
{
    if(!is_flash_area((uint32_t)address, sizeof(*address))){
        return 0;
    }

    return *address;
}

// 書き込み
flash_result_t FlashWrite(uint16_t* address, uint16_t data)
{
    if(!is_flash_area((uint32_t)address, sizeof(*address))){
        return FLASH_RESULT_NG;
    }

    if(is_flash_locked()){
        return FLASH_RESULT_NG;
    }

    while(is_flash_busy());

    flash_write(address, data);

    while(is_flash_busy());

    return check_flash_eop();
}

// 消去
flash_result_t FlashPageErase(uint8_t* address)
{
    if(!is_flash_area((uint32_t)address, sizeof(*address))){
        return FLASH_RESULT_NG;
    }

    if(is_flash_locked()){
        return FLASH_RESULT_NG;
    }

    while(is_flash_busy());

    flash_page_erase(address);

    while(is_flash_busy());

    return check_flash_eop();
}
```

##動かす
実際にフラッシュに書いてみます。

```c:
static int flash(int argc, char *argv[])
{  
    flash_result_t ret;

    FlashInit();

    ret = FlashPageErase((uint8_t*)0x0800F800);
    if(ret != FLASH_RESULT_OK){
        printf("ERASE ERROR!!\n");
        return 0;
    }

    printf("0x%X%X\n", FlashRead((uint8_t*)0x0800F801), FlashRead((uint8_t*)0x0800F800));

    ret = FlashWrite((uint16_t*)0x0800F800, 0xBEEF);
    if(ret != FLASH_RESULT_OK){
        printf("WRITE ERROR!!\n");
        return 0;
    }

    printf("0x%X%X\n", FlashRead((uint8_t*)0x0800F801), FlashRead((uint8_t*)0x0800F800));

    return 0;
}
```
実行します。
![flash_rw.png](https://qiita-image-store.s3.amazonaws.com/0/140055/2bf6c4f9-7216-f0b9-3bb1-5b2949246f2a.png)

BEEFが書き込まれました!!
#さいごに
これを利用して、フラッシュにコードを書き込み、メモリに読み出して起動するbootloaderを作りたいです。