STM32 HALを使ってフラッシュメモリーにデータを保存する

  • 2020.04.11
  • HAL
STM32 HALを使ってフラッシュメモリーにデータを保存する

電源が切れても設定した値を保存して置きたいことがあると思います。
今回は HAL を使ってフラッシュメモリにデータを保存してみます。

今までMbedを扱って来ましたので、コードはMbedのクラス等でC++を使って書いてみました。

投稿時の開発環境を記しておきます。

PC:Windows10 OS
IDE: STM32CubeIDE Version1.3.0
Board: STM32Nucleo-F401RE

免責:
ちょっぴりトリッキーなことをやっていますので、取り扱いは自己責任を持ってお願いします。

フラッシュメモリの仕様を確認する

日本語のリファレンスマニュアルがありますのでダウンロードするなどして仕様を確認します。

ダウンロードは こちら からどうぞ。

フラッシュメモリはセクタという単位で構成されています。
RAM(randam access memory)は文字通りランダムにアクセスできるわけですが、フラッシュはそう簡単にはいきません。

ランダムに読むことはできますが書き込む際には、ひと手間かかります。

書き込みはセクタ単位で行われ

アンロック
消去
書き込み
ロック

という手順で行います。
簡単に書き換わらないようにロック機構が設けられています。

STM32F401REのセクター構成は以下のようになっています。

どのセクタに保存するか

セクタ0から7までを使うことができるわけですが、データ領域として使うなら0か7でしょうか。
プログラム等のコードを配置する領域は連続にしたいということで、単純な私はそう考えました。

ところがセクタ0には割り込みベクタが配置されていますので、ではセクタ7に決まり!ですね。
でももう皆さんならお気づきでしょう。そうですセクタ7は、なんと128KBの容量でフラッシュの1/4に相当します。

保存したいデータ量はたかが知れているので、それはもったいないですね。

そこで、ちょいと一工夫してセクタ0にデータを保存する方法を試してみました。

仕様概略

以前、割り込みベクタテーブルをRAMに移す方法を紹介しました。
まだ読まれていない方は こちら の記事に目を通しておくことをお勧めします。

フラッシュメモリを扱うのに FlashMemoryAccesor というクラスをつくってみました。

FlashMemoryAccesor構築(コンストラクタ)時に
ベクタテーブルを保存する
ベクタテーブルをRAMに移す

書き込み時
アンロック
セクタ0を消去する
まず保存しておいたベクタテーブルを書き込む
次に保存するデータを書き込む
ロック

読み込み時
保存したデータを読む

注意点というか知っておいて欲しいことは FlashMemoryAccesor のコンストラクタが動いた時点から割り込みベクタテーブルはRAMに置かれるということです。
上で紹介した記事にも書いた通り、これにより割り込み処理実行時の速度が向上します。

それからリセットがかかった時にリセット処理が正しく動かなくてはならないので、消去後には領域の先頭にベクタテーブルを書き戻す必要があります。

ソースコード

main.cpp

#include "mbed.h"
#include 
#include "FlashMemoryAccesor.h"
// データは構造体にパックしておく
struct Data
{
	int x;
	int y;
	char str[10];
};

SerialクラスとVCPを使って結果をTera Term等の端末アプリで確認できるようにする


int main(void)
{
  /* USER CODE BEGIN 1 */

	Serial s(SERIAL_TX, SERIAL_RX);
	s.baud(9600);
	s.format(8, SerialBase::None, 1);

	FlashMemoryAccesor fa;

	s.printf("Flash Memory Accesor Started.\r\n");

	Data ds = {0};
	Data dd = {0};

	ds.x = 123;
	ds.y = 456;
	ds.str[0] = 'a';
	ds.str[1] = 'b';
	ds.str[2] = 'c';
	ds.str[3] = '\0';

	bool b = fa.write(SECTOR0_ADDRESS, (uint8_t*)&ds, (uint32_t)sizeof(ds));
	if (b)
	{
		s.printf("Flash write.\r\n");
		fa.read(SECTOR0_ADDRESS, (uint8_t*)&dd, (uint32_t)sizeof(dd));
		if (0 == memcmp((const void*)&dd, (const void*)&ds, sizeof(dd)))
		{
			s.printf("x=%d, y=%d, str=%s\r\n", dd.x, dd.y, dd.str);
			s.printf("Flash write & read success.\r\n");
		}
		else
		{
			s.printf("Flash compare error.\r\n");
		}
	}
	else
	{
		s.printf("Flash write failure.\r\n");
	}

  /* USER CODE END 1 */

(以降 略)

FlashMemoryAccesor.h

#ifndef __FLASHMEMORYACCESOR_H
#define __FLASHMEMORYACCESOR_H

#include "stdint.h"

#define NVIC_NUM_VECTORS 101

#define SECTOR0_SIZE 16384
#define SECTOR0_ADDRESS 0x8000000

class FlashMemoryAccesor
{
public:

	FlashMemoryAccesor();
	void NVIC_MoveAndSaveVectors();
	void eraseSector0();
	void read(uint32_t adrs, uint8_t *data, uint32_t size);
	bool write(uint32_t adrs, uint8_t *data, uint32_t size);
private:
	uint32_t save_vectors[NVIC_NUM_VECTORS * 4];
};

#endif

FlashMemoryAccesor.cpp

#include "FlashMemoryAccesor.h"
#include "cmsis_nvic.h"
#include "stm32f4xx_hal.h"
#include 

#define NVIC_RAM_VECTOR_ADDRESS   (0x20000000)  // Vectors positioned at start of RAM
#define NVIC_FLASH_VECTOR_ADDRESS (0x08000000)  // Initial vector position in flash

FlashMemoryAccesor::FlashMemoryAccesor()
{
	NVIC_MoveAndSaveVectors();
}

void FlashMemoryAccesor::NVIC_MoveAndSaveVectors()
{
    uint32_t *vectors = (uint32_t *)SCB->VTOR;
    uint32_t i;

    if (SCB->VTOR == NVIC_FLASH_VECTOR_ADDRESS) {
        uint32_t *old_vectors = vectors;
        vectors = (uint32_t*)NVIC_RAM_VECTOR_ADDRESS;
        for (i = 0; i < NVIC_NUM_VECTORS; i++) {
            vectors[i] = old_vectors[i];
            save_vectors[i] = old_vectors[i];
        }
        SCB->VTOR = (uint32_t)NVIC_RAM_VECTOR_ADDRESS;
    }
}

void FlashMemoryAccesor::eraseSector0()
{
	FLASH_EraseInitTypeDef erase;
	erase.TypeErase = FLASH_TYPEERASE_SECTORS;
	erase.Sector = FLASH_SECTOR_0;
	erase.NbSectors = 1;
	erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;

	uint32_t pageError = 0;
	HAL_FLASHEx_Erase(&erase, &pageError);
}

void FlashMemoryAccesor::read(uint32_t adrs, uint8_t *data, uint32_t size)
{
	memcpy(data, (uint8_t*)(adrs + NVIC_NUM_VECTORS * 4), size);
}

bool FlashMemoryAccesor::write(uint32_t adrs, uint8_t *data, uint32_t size)
{
	if (SECTOR0_SIZE < size + NVIC_NUM_VECTORS * 4)
	{
		return false;
	}
	if (SECTOR0_ADDRESS != adrs)
	{
		return false;
	}
	HAL_FLASH_Unlock();
	eraseSector0();

	uint8_t *p = (uint8_t*)save_vectors;

	for ( uint32_t add = adrs; add < (adrs + NVIC_NUM_VECTORS * 4); add++ )
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, add, *p);
    	        p++;
	}
	for ( uint32_t add = (adrs +  NVIC_NUM_VECTORS * 4); add < (adrs + NVIC_NUM_VECTORS * 4 + size); add++ )
	{
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, add, *data);
    	        data++;
	}
	HAL_FLASH_Lock();
	return true;
}


FlashMemoryAccesor.cpp, h はファイルを作成してIDEのプロジェクトに追加してください。

リンカースクリプト(STM32F401RETX_FLASH.ld)を以下のように編集します。
FLASHの領域は割り込みベクタだけを配置し、FLASH2という領域を設けて、SECTOR1からがコード領域になるようにします。

/* Memories definition */
MEMORY
{
  RAM		(xrw)	: ORIGIN = 0x20000200,	LENGTH = 96K-512
  FLASH		(rx)	: ORIGIN = 0x8000000,	LENGTH = 16K
  FLASH2	(rx)	: ORIGIN = 0x8004000,	LENGTH = 512K-16K
}

/* Sections */
SECTIONS
{
  /* The startup code into "FLASH" Rom type memory */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data into "FLASH" Rom type memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH2

  /* Constant data into "FLASH" Rom type memory */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH2

  .ARM.extab   : { 
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
  } >FLASH2
  
  .ARM : {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
    . = ALIGN(4);
  } >FLASH2

  .preinit_array     :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
  } >FLASH2
  
  .init_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
  } >FLASH2
  
  .fini_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
  } >FLASH2

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into "RAM" Ram type memory */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
    
  } >RAM AT> FLASH2

ビルドと書き込み

エラーなくビルドを終えてDebugを選択し書き込みを始めると2回に1回、次のエラーが出ます。

フラッシュメモリのベリファイを行っているのかもしれませんね。
このエラーは次の設定で回避することができます。

Run - Run Configrations - デバッガ - Reset behaviour Type を None に設定します。

こちらでは今のところ上に書いたエラーが出る以外の不具合はありませんが、お使いになられる方は充分検証した後に自己責任でお願いします。

HALカテゴリの最新記事