STM32 + HAL Flashの書き込み・読み込み

2021年1月13日

STM32マイコンのペリフェラル関連記事を一覧にまとめました。

こんにちは。今回は、STM32+HALドライバでフラッシュメモリへの書き込み、読み込みの仕方を書いていきたいと思います。

マイコンはSTM32F405RGT,コンパイルはmakefileの構成でやっていきます。

また、今回の私の実験ボードのマイコンへの供給電圧は3.3Vです。

2019 8/10日追記

HALAPIを使用しないでレジスタを直接たたいてFLASHに書き込みをする方法の記事を書いたので興味がある方は合わせて見ていただければと思います。

 




STM32CubeMxの設定

Flashに読み書きができているかどうかの確認を視覚的に行えるように、USARTを有効にしました。

CubeMXの設定は以下の通りです。

 

STM32CubeMX USARTの設定

その後、ペリフェラルごとにファイルを分割するというところにチェックをいれて、コード生成をしたのち、printfを使用できる状態にしました。

HALでのprinfの実装の仕方はこちらの記事を参考にしていただければと思います。

 

書き込みの仕方を知る

STM32F4のリファレンスマニュアルを読んでいきたいと思います。

内臓フラッシュメモリについて

内蔵フラッシュメモリのアドレス、特徴は以下のようになっているようです。

RM0090より

RM0090より

RM0090より

 

セクタごとに管理されていて、セクタごとに書き込み、読み込みが可能なようですね。

STM32の場合、フラッシュメモリにプログラムに書き込まれているため、プログラムを削除しないように気を付けながら使用する必要があります。

また、フラッシュメモリに書き込みや消去をする場合は、フラッシュ制御レジスタのアンロックをする必要があるそうです。

今回は、プログラムの大きさが約900kbまで行くわけがないという考えのもと、基本的にに未使用だと思われるセクタ11を使用することに決めました。

また、プログラムを書き込まれないように、リンカスクリプトを一部変更して安全性を高めることに決めました。

 

書き込みについて

RM0090より

RM0090より

 

RM0090より

データシートより、ビットを"1″から"0″に変更するときは消去処理を実施する必要がないと書いてあります。すなわち、ビットを"0″から"1″にするときは消去処理をする必要があるということと、消去をされるとセクタ内のビットがすべて"1″に設定されているということが読み取れました。

ここまでの点をすべて踏まえて、データを書き込むまでの流れは次のようになることがわかりました。

  1. セクタの設定
  2. マイコンの動作電圧の設定
  3. フラッシュのアンロック
  4. 消去
  5. 書き込み
  6. フラッシュをロック

 

読み込みの仕方

前提条件として書き込んだデータの長さがわかっているとすると、書き込んだデータの長さの分だけ、フラッシュからメモリのデータをコピーするという方法でフラッシュからデータを読み込むことで、書き込んだデータを読むことができるということが考えられます。

メモリからデータをコピーする方法を調べたところ、<string.h>に定義されているmemcpyを使用することでできそうだということがわかりました。

C言語関数辞典よりmemcpy

void *memcpy(
    void * restrict s1,
    const void * restrict s2,
    size_t n
);

void型で定義されているため、型の指定と長さを指定すれば構造体などのコピーに使用することができそうなので、構造体でデータを取り扱っていこうと思います。

 

ソースコードの実装

HALドライバのマニュアルを読んで使用する関数を探す

HALドライバのリファレンスマニュアルから、先程考えた処理を行うことのできる情報を探していきます。

  • フラッシュメモリのロック、アンロック用関数

UM1725より

  • 消去に使用する構造体の定義

UM1725より

  • 消去に使用する関数

UM1725より

  • 書き込み用関数

UM1725より

 

各種定義(文字列での引用)

  • セクターの定義 : FLASH_SECTOR_11 Sector Number 11
  • マイコンの供給電圧の定義 : FLASH_VOLTAGE_RANGE_3 Device operating range: 2.7V to 3.6V
  • 書き込み時のビット数(8bit)の定義 : FLASH_TYPEPROGRAM_BYTE Program byte (8-bit) at a specified address

 

リンカスクリプトを一部変更する

STM32F405RGTx_FLASH.ldの42行目当たりのMEMORYのところを一部変更します。

FLASHメモリのセクタ11の部分をDATA(rx)という領域に指定し、FLASH領域とは違うということを明示しました。

MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 896K
DATA (rx)        : ORIGIN = 0x80E0000, LENGTH = 128k
}

 

これまでの内容を踏まえて次の関数を実装することに決めました。

  • セクタの消去をする
  • 書き込みをする
  • 読み込みをする

 

実装する

汎用性を上げることができるように、読み込み、書き込みの引数をデータのポインタをとるようにし、データの長さも指定できるようにしました。

#include "stm32f4xx_hal.h"
#include <string.h>
#include <stdint.h>

const uint32_t start_address = 0x80E0000; //sentor11 start address
const uint32_t end_adress = 0x80FFFFF; // sector11 end address

/*
 *@brief erase sector11
*/
void eraseFlash( void )
{
	FLASH_EraseInitTypeDef erase;
	erase.TypeErase = FLASH_TYPEERASE_SECTORS;	// select sector
	erase.Sector = FLASH_SECTOR_11;		       // set selector11
	erase.NbSectors = 1;		// set to erase one sector
	erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;	// set voltage range (2.7 to 3.6V)

	uint32_t pageError = 0;

	HAL_FLASHEx_Erase(&erase, &pageError);	// erase sector
}

/*
 * @brief write flash(sector11)
 * @param uint32_t address sector11 start address
 * @param uint8_t * data write data
 * @param uint32_t size write data size
*/
void writeFlash(uint32_t address, uint8_t *data, uint32_t size  )
{
	HAL_FLASH_Unlock();		// unlock flash
	eraseFlash();			// erease sector11

  for ( uint32_t add = address; add < (address + size); add++ )
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, add, *data); // write byte
    data++;  // add data pointer
	} 
  
	HAL_FLASH_Lock();		// lock flash
}

/*
 * @brief write flash(sector11)
 * @param uint32_t address sector11 start address
 * @param uint8_t * data read data
 * @param uint32_t size read data size
*/
void loadFlash(uint32_t address, uint8_t *data, uint32_t size )
{
	memcpy(data, (uint8_t*)address, size); // copy data
}

 

使い方

//構造体 MAZE_DATA maze_dataが定義されているとする

//書き込み
writeFlash( start_address, (uint8_t*)maze_data, sizeof(MAZE_DATA) ); 

// 読み込み
loadFlash( start_address, (uint8_t*)maze_data, sizeof(MAZE_DATA) );

構造体と構造体の大きさを与えてあげれば使用可能です。

複数の構造体の書き込み、読み込みを行いたい場合は、セクタ11の書き込みをするアドレスを移動してずらすことで可能ですが、フラッシュの上限である128kByte以上のアドレスは指定しないようにしてください。おかしくなる可能性があります。1

また、データを書き込む場合はセクタのデータすべてを消すので、必要なデータは一度RAMに退避しておくようにしましょう。

 

さいごに

STM32+HALドライバでフラッシュを使うことができました。

これで、電源を消してもデータを保持することができるようになりました。

 

参考文献

UM1725 User Manual

RM0090 リファレンスマニュアル

C言語関数辞典よりmemcpy

  1. 多分エラーでとまるとは思いますが・・・