umtkmの忘備録

【Nucleo F103RB+mbed】内蔵フラッシュに構造体を書き込む

 前回の記事ではNucleoF401REのフラッシュにデータを書き込みましたが,今回はNucleoF103RBの内蔵フラッシュに書き込みをしてみようと思います.この方法は多分F0,F1,F3系で通用すると思います(まだ試してないんですが…).

環境

 前回同様,オフライン環境が前提です.mbedのオンラインコンパイラからExportしてもOK.

  • Nucelo F103RB
  • macOS
  • arm-none-eabi-gcc

前回のおさらい

 STM32の内蔵フラッシュは,あらかじめ消去(=全てのbitが1)された場所にしか自由に書き込みが行えません.そして,内蔵フラッシュは数K~数百KByte程度のまとまりであるページやセクタに分割されていて,ページやセクタごとにしか消去できないのでした.F0,F1,F3の場合は,数KByte程度のページに分割されていました.

どこにデータを置くか決める

 ではどのページにデータを置くか決めましょう.STのF103RBのページRM0008っていうリファレンスマニュアルがあるんで,それを参照します.なんと和訳されてますね,助かります.

 NucleoF103RBのフラッシュは128KBなので,「1.2 用語集」で定義されている中容量デバイスに該当します.で,表3の「フラッシュモジュールの構成(中容量デバイス)」を見ると(めんどくさいので画像は貼りません),ページ1つが1KByteでそれが128個あることがわかります.今回は一番最後のページ127を使います.このページのアドレスは0x801fc00から始まっていますね.

 当然ですが,別のSTM32マイコンを使う場合は同じような手順で調べてからやってください.ページのサイズやアドレスが異なるのでそのまま同じアドレスでやっても動くとは限りません.

リンカスクリプトをいじる

 前回同様,リンカスクリプトを編集しておきます.多分やらなくても動くとは思いますが,念のため.

1
2
3
4
5
6
7
/* Linker script to configure memory regions. */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 127K
DATA (rx) : ORIGIN = 0x0801fc00, LENGTH = 1K
RAM (rwx) : ORIGIN = 0x200000EC, LENGTH = 20K - 0xEC
}

 リンカスクリプトの上から7行目くらいまでの場所をこんな風にいじります.詳しいやり方は割愛します,よくわからんぞいって人は前回の記事の「リンカスクリプトを編集する」の部分を参考にしてやってください.

HALの関数で消去&書き込み

 F401の時はセクタの消去だったんですが,F103ではページの消去になるので,少し勝手が異なります.また,F0,F1,F3の場合は1Byteごとに書き込めないので,2~8Byteずつ書き込んでやる必要があります.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void _eraseFlash(uint32_t address)
{
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_PAGES; /* ページの消去を選択 */
erase.PageAddress = address; /* ページの先頭アドレスを指定 */
erase.NbPages = 1; /* 消すページの数.今回は1つだけ */

uint32_t pageError = 0;

HAL_FLASHEx_Erase(&erase, &pageError); /* HAL_FLASHExの関数で消去 */
}

void writeFlash(uint32_t address, uint8_t *data, uint32_t size)
{
uint32_t *dataWord = (uint32_t*)data; /* 書き込むデータへのポインタ(4Byteごと) */
uint32_t sizeWord = (size+3)/4; /* データのサイズ(4Byteで1) */

HAL_FLASH_Unlock(); /* フラッシュをアンロック */
_eraseFlash(address); /* 指定したアドレスのページを消去 */
do {
/* 4Byte(Word)ずつフラッシュに書き込む */
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, *dataWord);
} while (address+=4, ++dataWord, --sizeWord);
HAL_FLASH_Lock(); /* フラッシュをロック */
}

void loadFlash(uint32_t address, uint8_t *data, uint32_t size)
{
memcpy(data, (uint8_t*)address, size);
}

 一応HALの関数の説明はコード内に書いておきました.また,ついでに読み込み用の関数も貼っておきました(前回のやつのコピペ).

構造体を書き込む

 これで書き込める条件は整いました.mbedで動くサンプルを貼っておきます.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "mbed.h"

Serial pc(USBTX, USBRX);

/* チマメ構造隊 */
typedef struct {
int chino;
float maya;
uint16_t megu;
} Chimame;

int main(void)
{
const uint32_t address = 0x801fc00;

Chimame chimame;

/* 構造体の読み込み */
loadFlash(address, (uint8_t*)&chimame, sizeof(Chimame));
/* 構造体のメンバを編集 */
chimame.chino = 144;
chimame.maya = 140.0;
chimame.megu = 145;
/* 編集した構造体の中身を書き込む */
writeFlash(address, (uint8_t*)&chimame, sizeof(Chimame));

Chimame chimame2;
/* 書き込んだ構造体の読み込み */
loadFlash(address, (uint8_t*)&chimame2, sizeof(Chimame));
while (1) {
/* 構造体の中身を出力 */
pc.printf("%d, %f, %d\n", chimame2.chino, chimame2.maya, chimame2.megu);
wait_ms(800);
}

return 0;
}

 こんな感じですかね.編集した構造体を書き込み&読み込みして,UARTで出力しています.サンプルなのでちょっと変な使い方をしていますが,実際に運用するなら

  1. 構造体を宣言してそこにフラッシュのデータを読み込む
  2. 変更があった場合,構造体のデータを編集して書き込む

 こんな手順でやるといいんじゃないでしょうか.起動時に一回だけフラッシュからデータを読み込む感じです.

まとめ

 F103RBでも,F401REなどとほとんど同じ手順で書き込みが行えます.ページで分割されているかセクタで分割されているかなどはデバイスによって違うので,ちゃんとリファレンスマニュアルなどを確認してからやりましょう.