ロボカップでBLDCのモータドライバを設計しているんですが、マイコンにSTM32G431を使おうとしていまして、Flashにデータを書き込む工程が必要になったので、その過程を備忘録として残しておきます。
まずは報告
STM32G431の内蔵Flashにデータ書き込みできました!!!
STM32G4 – Flashを読んでみた
このPDFを読みました。
- STM32G4系はデュアルバンクブートに対応している。らしい。
- FLASHを2つに分割してプログラムを2つ用意できるらしい。
- STM32G4にはカテゴリという概念がある。
- カテゴリ2はシングルバンク。Flash Size = 128KB
- カテゴリ3はデュアルバンク・シングルバンクに対応している。Flash Size = 512KB
わかったこと:
STM32G431
は「カテゴリ2」でバンク数は1固定らしい。リファレンスマニュアルを読んでみよう
リファレンスマニュアルを読みました。
G431はカテゴリ2ですので、誤ってカテゴリ3のページを見ないようにしましょう。
- G431はカテゴリ2
- シングルバンク構成・1ページ2KB
STM32G43xのFLASHのページ構成を書き出してみました。これで任意のメモリにデータを書き込んだり読み出したりできますね。
カテゴリ2(シングルバンク 2KB構成)のページ構成
自分で作ったので変なところあったら教えてください。無いと思いますけど。
- Page0: 0x0800 0000 - 0x0800 07FF (2KB) - Page1: 0x0800 0800 - 0x0800 0FFF (4KB) - Page2: 0x0800 1000 - 0x0800 17FF (6KB) - Page3: 0x0800 1800 - 0x0800 1FFF (8KB) - Page4: 0x0800 2000 - 0x0800 27FF (10KB) - Page5: 0x0800 2800 - 0x0800 2FFF (12KB) - Page6: 0x0800 3000 - 0x0800 37FF (14KB) - Page7: 0x0800 3800 - 0x0800 3FFF (16KB) - Page8: 0x0800 4000 - 0x0800 47FF (18KB) - Page9: 0x0800 4800 - 0x0800 4FFF (20KB) - Page10: 0x0800 5000 - 0x0800 57FF (22KB) - Page11: 0x0800 5800 - 0x0800 5FFF (24KB) - Page12: 0x0800 6000 - 0x0800 67FF (26KB) - Page13: 0x0800 6800 - 0x0800 6FFF (28KB) - Page14: 0x0800 7000 - 0x0800 77FF (30KB) - Page15: 0x0800 7800 - 0x0800 7FFF (32KB) - Page16: 0x0800 8000 - 0x0800 87FF (34KB) - Page17: 0x0800 8800 - 0x0800 8FFF (36KB) - Page18: 0x0800 9000 - 0x0800 97FF (38KB) - Page19: 0x0800 9800 - 0x0800 9FFF (40KB) - Page20: 0x0800 A000 - 0x0800 A7FF (42KB) - Page21: 0x0800 A800 - 0x0800 AFFF (44KB) - Page22: 0x0800 B000 - 0x0800 B7FF (46KB) - Page23: 0x0800 B800 - 0x0800 BFFF (48KB) - Page24: 0x0800 C000 - 0x0800 C7FF (50KB) - Page25: 0x0800 C800 - 0x0800 CFFF (52KB) - Page26: 0x0800 D000 - 0x0800 D7FF (54KB) - Page27: 0x0800 D800 - 0x0800 DFFF (56KB) - Page28: 0x0800 E000 - 0x0800 E7FF (58KB) - Page29: 0x0800 E800 - 0x0800 EFFF (60KB) - Page30: 0x0800 F000 - 0x0800 F7FF (62KB) - Page31: 0x0800 F800 - 0x0800 FFFF (64KB) - Page32: 0x0801 0000 - 0x0801 07FF (66KB) - Page33: 0x0801 0800 - 0x0801 0FFF (68KB) - Page34: 0x0801 1000 - 0x0801 17FF (70KB) - Page35: 0x0801 1800 - 0x0801 1FFF (72KB) - Page36: 0x0801 2000 - 0x0801 27FF (74KB) - Page37: 0x0801 2800 - 0x0801 2FFF (76KB) - Page38: 0x0801 3000 - 0x0801 37FF (78KB) - Page39: 0x0801 3800 - 0x0801 3FFF (80KB) - Page40: 0x0801 4000 - 0x0801 47FF (82KB) - Page41: 0x0801 4800 - 0x0801 4FFF (84KB) - Page42: 0x0801 5000 - 0x0801 57FF (86KB) - Page43: 0x0801 5800 - 0x0801 5FFF (88KB) - Page44: 0x0801 6000 - 0x0801 67FF (90KB) - Page45: 0x0801 6800 - 0x0801 6FFF (92KB) - Page46: 0x0801 7000 - 0x0801 77FF (94KB) - Page47: 0x0801 7800 - 0x0801 7FFF (96KB) - Page48: 0x0801 8000 - 0x0801 87FF (98KB) - Page49: 0x0801 8800 - 0x0801 8FFF (100KB) - Page50: 0x0801 9000 - 0x0801 97FF (102KB) - Page51: 0x0801 9800 - 0x0801 9FFF (104KB) - Page52: 0x0801 A000 - 0x0801 A7FF (106KB) - Page53: 0x0801 A800 - 0x0801 AFFF (108KB) - Page54: 0x0801 B000 - 0x0801 B7FF (110KB) - Page55: 0x0801 B800 - 0x0801 BFFF (112KB) - Page56: 0x0801 C000 - 0x0801 C7FF (114KB) - Page57: 0x0801 C800 - 0x0801 CFFF (116KB) - Page58: 0x0801 D000 - 0x0801 D7FF (118KB) - Page59: 0x0801 D800 - 0x0801 DFFF (120KB) - Page60: 0x0801 E000 - 0x0801 E7FF (122KB) - Page61: 0x0801 E800 - 0x0801 EFFF (124KB) - Page62: 0x0801 F000 - 0x0801 F7FF (126KB) - Page63: 0x0801 F800 - 0x0801 FFFF (128KB)
ここで存在しないPage64(0x0802 0000 - 0x0802 07FF (130KB))を読み出してみました。存在しないので、読めないですね。リファレスマニュアル通りです。
実際にデータを書き込んでいきましょう。
HALのコードを読んで何が起こっているのか把握してみます。
stm32gxx_hal_flash.hを読んでみる
HAL_FLASHEx_Erase
これがFlashを消すメソッドらしい。引数は2つ
FLASH_EraseInitTypeDef
*pEraseInit
uint32_t
*PageError
PageErrorが0xFFFFFFFFになったら全部Eraseできたよってことらしいです。
FLASH_EraseInitTypeDef
HAL_FLASHEx_Erase()
で引数として渡す構造体。Flashのデータを消す際に必要な情報が載っています。
typedef struct { uint32_t TypeErase; /*!< Mass erase or page erase. This parameter can be a value of @ref FLASH_Type_Erase */ uint32_t Banks; /*!< Select bank to erase. This parameter must be a value of @ref FLASH_Banks (FLASH_BANK_BOTH should be used only for mass erase) */ uint32_t Page; /*!< Initial Flash page to erase when page erase is disabled. This parameter must be a value between 0 and (max number of pages in the bank - 1) (eg : 127 for 512KB dual bank) */ uint32_t NbPages; /*!< Number of pages to be erased. This parameter must be a value between 1 and (max number of pages in the bank - value of initial page)*/ } FLASH_EraseInitTypeDef;
今回使うSTM32G431とやりたいことに合わせてパラメータを入れてみましょう。
- TypeErase = FLASH_TYPEERASE_PAGES
- 選択肢は
FLASH_TYPEERASE_PAGES
FLASH_TYPEERASE_MASSERASE
の2つ
- Banks = FLASH_BANK_1
- 選択肢は
FLASH_BANK_1
FLASH_BANK_2
FLASH_BANK_BOTH
の3つ
- Page =
63
- 任意のページの
- NbPages = 1
- 今回は1ページだけ消すので1
HAL_FLASH_Program
このメソッドでFlashにデータを書き込めるらしい。
引数は3つ
uint32_t
TypeProgram- 選択肢は
FLASH_TYPEPROGRAM_DOUBLEWORD
FLASH_TYPEPROGRAM_FAST
FLASH_TYPEPROGRAM_FAST_AND_LAST
の3つ - 基本的に64bitでしかFlashできない。
uint32_t
Address
uint64_t
Data
Flashにデータを書き込んでみる。
一番最後のページはPage63: 0x0801 F800 - 0x0801 FFFF (128KB)です。ここにデータを書き込んでみましょう。
以下の記事にHALを使ったFlashにデータを書き込む例が載っているので真似させていただきましょう。
気をつけるべきこととして、F446REとG431はFlashで扱うワード数が違います。アーキテクチャが違うので、当然中に書いてあるコードも全然違います!!!
以下がHAL_FLASH_ProgramでFlashに書き込み際に必要な引数
TypeProgram
の値です(使いそうなものだけ抜粋)FLASH_TYPEPROGRAM_BYTE
(8bit)
FLASH_TYPEPROGRAM_HALFWORD
(16bit)
FLASH_TYPEPROGRAM_WORD
(32bit)
FLASH_TYPEPROGRAM_DOUBLEWORD
(64bit)
F446REでは8bit,16bit,32bitでの書き込みができましたが、G431では64bit固定です。
なので、
int64
にキャストしてから書き込むか、ビット演算で複数のデータを64bitに加工する必要があります。後者は手間がかかりますがデータ容量がカツカツの場合に有効ですね。Flashへの読み出し・書き込みプログラム
保存するデータが1つだと簡単なのですが、複数になると管理が大変なので、変数の所在地(アドレス)を連番で管理できる構造体で管理します。
またG431は64bit毎でしかFlashにデータを書き込めない仕様なのですが、保存したいデータごとに64bit分メモリを消費するのは勿体無いので、64bitの変数の中に複数のデータを保存するようにします。unionでメモリ共有ができるのでそれを使用しました。
今回は2つのDouble Word書き込みをする仕様にして、
- i16でuint16_tを4つ
- f32でfloatを2つ
合計6つのデータをFlashに書き込みしてみます。
#define FLASH_START_ADDRESS 0x0801F800 // Page63: 0x0801 F800 - 0x0801 FFFF (128KB) typedef struct { // 64bitごとにしか書き込めないのでデータ分割をする union { uint64_t i; // 64bit uint16_t i16[4]; // 16bit x 4 = 64bit }; union { uint64_t f; // 64bit float f32[2]; // 32bit x 2 = 64bit }; } FlashData_t; // 書き込みメソッド void writeFlash(uint32_t address, uint64_t *data, uint32_t size) { HAL_FLASH_Unlock(); // フラッシュのロックを解除 FLASH_EraseInitTypeDef erase = { .TypeErase = FLASH_TYPEERASE_PAGES, // ページ単位で消去にする .Banks = FLASH_BANK_1, // バンク1を指定 .Page = 63, // ページ63を指定 .NbPages = 1, // 1ページだけ消すので1を指定 }; uint32_t pageError = 0; HAL_FLASHEx_Erase(&erase, &pageError); // HAL_FLASHExの関数で消去 // 64bitずつFlashに書き込む for (uint32_t i = 0; i < size; i += 8) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address + i, *(data + i / 8)); } HAL_FLASH_Lock(); // フラッシュのロックをかける } // 読み込みメソッド void loadFlash(uint32_t address, uint64_t *data, uint32_t size) { memcpy(data, (uint32_t *)address, size); } int main(void) { FlashData_t data; uint32_t address = FLASH_START_ADDRESS; loadFlash(address, (uint64_t *)&data, sizeof(FlashData_t)); // フラッシュから読み込み printf("load : i16[0]:%3d i16[1]:%3d i16[2]:%3d i16[3]:%3d f32[0]:%3.1f f32[1]:%3.1f\n", data.i16[0], data.i16[1], data.i16[2], data.i16[3], data.f32[0], data.f32[1]); if (data.i16[0] == 0xFFFF) { printf("Flash is empty.\n"); data.i16[0] = 0; data.i16[1] = 0; data.i16[2] = 0; data.i16[3] = 0; data.f32[0] = 0.0; data.f32[1] = 0.0; } // データ登録 data.i16[0] += 1; data.i16[1] += 2; data.i16[2] += 3; data.i16[3] += 4; data.f32[0] += 0.1f; data.f32[1] += 0.2f; writeFlash(address, (uint64_t *)&data, sizeof(FlashData_t)); // フラッシュに書き込み loadFlash(address, (uint64_t *)&data, sizeof(FlashData_t)); // フラッシュから読み込み printf("write: i16[0]:%3d i16[1]:%3d i16[2]:%3d i16[3]:%3d f32[0]:%3.1f f32[1]:%3.1f\n", data.i16[0], data.i16[1], data.i16[2], data.i16[3], data.f32[0], data.f32[1]); }
以下はプログラムを実行させた時のprintデバッグのログです。
load : i16[0]:65535 i16[1]:65535 i16[2]:65535 i16[3]:65535 f32[0]:nan f32[1]:nan Flash is empty. write: i16[0]: 1 i16[1]: 2 i16[2]: 3 i16[3]: 4 f32[0]:0.1 f32[1]:0.2
STM32CubeProgrammerでデータが書き込まれているかを確認してみました。
といった感じでSTM32G431のFlashにデータを書き込むことができました。これでEEPROMを外部につけなくても小さいデータを書き込むことができますね。
リンカスクリプトをいじってPage63にプログラムが書き込まれないように変更する必要がありますが、今回は端折ります。