0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

STM32F407のファームウェアをUSBメモリでアップデートする

Last updated at Posted at 2025-03-09

背景

組込み機器のMCUのファームウェアを書き換える方法はいくつかあります。
よく利用されるのは

  • 専用ライターでデバッグ端子を使って書き換え
  • PCとUSBで接続して、PCアプリを使って書き換える

というものでしょうか。

専用ライターはMCUメーカーだったり、サードパーティーから販売されています。
これ、意外と高価でして、5〜10万円強もします!
でも、メーカーの開発・製造現場で使うものとしては許容できる価格帯ですね。

しかし、市場で不具合や機能アップのためにファームウェアを変更するとしたらどうでしょう?
サービスマンが携帯したり、ユーザーにお願いするには上記の手法は使いづらいです。

そこで、私がメーカー勤務時に思いついた「USBメモリを使ってファームウェアを書き換える手法」をご紹介します。

対象MCU

本記事ではSTマイクロのSTM32F407を対象にします。
それ以外のSTM32、ルネサスのRX111でもUSBメモリを使ったファームウェアアップデートを開発しましたが、これらは後日気が向いたら記事にしようと思います。
STM32F407を搭載したマイコンボードはDiscoveryシリーズで提供されていて、容易に入手できます。
興味を持っていただけるようでしたら、ぜひ試してみてください!

ファームウェアの動作環境

基本はベアメタルで、RTOSなどのOSを使わないことを前提としています。
と言いながらも、uITRON準拠OSでの実績はあります。

USBメモリによるファームウェアアップデート用プログラム「アップデータ」を開発をする

プログラム自体の開発は、それほど難しくはありません。
これを実現するための仕組みを考えるのが大変でした。
ただ、これはARMマイコンのアーキテクチャだからできるという側面もあります。
一昔前に大ヒットした日立製作所の「SH」でも同じことができます。
実際にん〜十年前にやったことがあります。

アップデータに必要な機能

アップデータに必要な機能は下記に挙げるようなものです。

  • アップデートの要・不要を判断する
  • USBメモリにアクセスできること
  • USBメモリ内のファイルを解析すること
  • STM32F407の内蔵フラッシュを書き換えられること
  • 新しいファームウェアへの実行移行

アップデートの要・不要を判断する

この方式を採用するにあたり、電源起動時にはまずこの「アップデータ」が起動することになります。
このアップデータが、ファームのアップデートが必要であれば、USBメモリからプログラムを読み込んで、内蔵フラッシュを書き換えます。
ファームのアップデートが不要なら、ファームへ実行アドレスを移すという動きをします。

この判断にはいくつか方法が考えられますね。

  • ディップスイッチでファーム書き換えの要・不要を指定
  • USBメモリが見つかって、特定のファイルの有無で判断

など、いくつか考えられます。
手っ取り早いのは前者です。
後者はサービスマンに親切ですね。
これは利用する環境や会社の風土に応じて決めればいいでしょう。
スクリーンショット 2025-03-09 21.44.37.png

USBメモリにアクセスできること

これは難しくないですね。
STM32CubeMXを使って

  • USB Driver
  • USB Host Mass Strage Class
  • File System (FatFs)

を利用できるよにすればOKです。
STマイクロからドライバやミドルウェアが提供されています。
そのまま利用させてもらいましょう。

USBメモリ内のファイルを解析すること

書き込み用のデータの代表格は

  • Motorola S Record
  • Intel Hex

です。
どちらでもよく、どのアドレスに何のデータが書くかを判定できればOKです。
個人的にはテキストエディタで見ることができる「Motorola S Record」が好きです。
Motorola S Recordはウィキペディアに解説がありました。

STM32F407の内蔵フラッシュを書き換えられること

STM32マイコンはここが優秀です!
内蔵フラッシュを書き込む関数が用意されているのはどのメーカーも同じですが、プログラムが内蔵フラッシュで動いていても、内蔵フラッシュを書き換えることができます。
国内ベンダーだと、内蔵フラッシュを書き換えるプログラムはRAM上にないと、内蔵フラッシュを書き換えることができません。
STマイクロのフラッシュコントローラは優秀です!
しかも、1バイトずつ書き込むことができます。
他社の内蔵フラッシュでは、512バイトのアラインメント縛りがありました。

セクタの消去

セクタの消去
void eraseSector(int sectorNumber)
{
	uint32_t 				pageError = 0;
	FLASH_EraseInitTypeDef	erase;

	erase.TypeErase = FLASH_TYPEERASE_SECTORS;	// select sector
	erase.Sector = sectorNumber;		   		// set selector11
	erase.NbSectors = 1;						// set to erase one sector
	erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;	// set voltage range (2.7 to 3.6V)

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

1バイトプログラム

1バイトプログラム
void write1Byte(unsigned long addr, char data)
{
	HAL_FLASH_Unlock();
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, addr, data);
	HAL_FLASH_Lock();
}

複数バイトプログラム

複数バイトプログラム
void writeBytes(unsigned long startAddr, const char* container, int length)
{
	HAL_FLASH_Unlock();

	while(length-- != 0)
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, startAddr++, *container++);

	HAL_FLASH_Lock();
}

新しいファームウェアへの実行移行

新しいファームウェアを書き込んだあとは、通常はリセットを待つのがいいですね。
あとはSTM32のソフトウェアリセットの機能を利用すれば、勝手にリセットして起動するからいいかもしれません。

アップデータが起動し、アップデートが必要ない時、どうやってファームウェアの実行をするか...
これは、単純にプログラム・カウンタを書き換えるだけでいけます。
アップデータが起動した時点で、スタックポインタは設定されていてすでに動作しています。
C言語での記述は、ジャンプ先を直にアドレスで指定します。

指定したアドレスにジャンプ
((void(*)(void))0x08010004)( );

ファームウェアのリンク設定の変更

電源を入れた時、まずアップデータが起動するので、アップデータは0番地スタートです。
STM32F407では0x0800_0000番地が該当します。

そのため、ファームウェア(メインのアプリ)はここからずらした場所に配置しなくてはいけません。

「何バイト先に配置するか」はアップデータのサイズによります。
私が当時開発したものは、シリアル通信でプログラムの状態を表示したり、簡単なファイルブラウザも実装しているため、おおよそ64KByte以内で収めるようにしていました。

そのため、単純なアップデートシステムならプログラムは64KByte先...つまり、0x0801_0000番地というわけです。

system_stm32f4xx.cの修正

ARM-Cortex M4はベクタテーブルの配置を自由に決められます。
Core/Src/system_stm32f4x.cの中でベクタテーブルの配置を決めています。

USER_VECT_TAB_ADDRESSというマクロがコメントアウトされているので、まずはこのマクロを生かします。

このマクロを有効にする
#define USER_VECT_TAB_ADDRESS

次に、VECT_TAB_OFFSETを変更します。
VECT_TAB_OFFSETの初期値は0x0000_0000で、実際はこの数値に0x0800_0000を加算されたものがベクタテーブルのアドレスになります。
VECT_TAB_OFFSETを64KByteに変更します。

ベクタテーブルのオフセットを64KByteに変更
#define VECT_TAB_OFFSET         0x00010000U

リンカスクリプトの修正

リンカスクリプトSTM32F407VGTX_FLASH.ldの中身を見てみましょう。
初期状態は下記のようになっています。

初期状態
/* Memories definition */
MEMORY
{
  CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K
  RAM       (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH     (rx)     : ORIGIN = 0x08000000,   LENGTH = 1024K
}

プログラム配置の先頭が0x0800_0000になっているので、これを64KByte先のアドレスに変更します。
また、内蔵フラッシュの容量を64KByte引きます。

リンカスクリプトの修正
/* Memories definition */
MEMORY
{
  CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K
  RAM       (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH     (rx)     : ORIGIN = 0x08010000,   LENGTH = 960K
}

アップデータからメインプログラムへのジャンプ

書き込む先頭アドレスは0x0801_0000番地とわかっています。
しかし、ジャンプ先は0x0801_0000番地ではなく、0x0801_0004番地です。
詳細はARM Cortex-M4の資料を見ていただくとわかります。
0番地はスタックポインタの初期値が格納されています。
ジャンプ先は4番地に格納されています。
なので、ジャンプ先は0x0801_0004番地なのです。

メインプログラムへジャンプ
((void(*)(void))0x08010004)( );

出来上がったSレコード

Sレコードをテキストエディタで見てみましょう。
プログラムの開始位置が0x0801_0000になっています。
これを内蔵フラッシュに書き込めばOKです。

実運用上は...

今回は主に仕組みと、簡単な使い方を解説しました。
実際はいろんなガード処理や、決まり事が必要です。

* 決まったファイル名しか書き換えない
* ダウングレードを許すか
* ファイルブラウザはあったほうがいい
* アップデートの過程をUARTに出力する
* 失敗した時の表示(LED点滅とか)

STM32F407では、ガードなどの処理を省けば、ひとまず簡単に試すことができます。
ちなみに、STM32F407だけでなく、他のSTM32Fシリーズでも同じようなことができることを確認しています。

最後に

もともと、ファームウェア書き換えの専用機が高価で、サービスマンの人数分用意できないという要望から、細々と開発をしたものです。
実際使ってみると、現場での書き換えがUSBメモリを挿すだけでいいので、結構簡単で便利です。

いろいろ機能を追加したり、セキュリティなどを工夫することで、ファームウェアのアップデート作業が効率よくできるようになると思います。

ぜひ活用してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?