概要
STM32シリーズにはDFUがあり、Boot0/1に対応するピンを適切な入力に設定する事で強制的にDFUから起動させる事ができます。一方で、Option Bytesの設定によりBoot0/1の値を上書きし、DFUを殺すこともできます。この場合SWDから接続しない限り今後一切のデータ変更ができなくなります。この状況でSWDが動かなかった場合の確認事項・対処方法などをまとめてみました。
原因と対策
SWDIO/SWCLKピンが外部から制御不能
回路的な問題でSWDIO/SWCLKに該当するピンが他のICなどによりドライブされていると、プログラマーからのSWDIO/SWCLK信号が正しく伝わらないためにターゲットデバイスが認識できなくなります。Pull-up/downなら問題ありません。
ドライブ元のチップをリセットなりでハイインピーダンス状態を出すようにしてやるか、最悪チップを外すか配線をカットするなどの対処が必要です。
そもそもこれらのピンを別の用途で利用しているとSWDIO/SWCLK経由でのステップ実行やメッセージ出力ができないので、このような問題が起きるのはGPIOが致命的に足りていない状況に限られるでしょう。
リセットがうまく入っていない
SWDIO/SWCLKピンが別の用途で利用されているなら、当然これらのピンは起動直後にGPIOとして再アサインされているはず。この状態ではSWDからの制御はききません。ハードウェアリセットを併用してGPIOに切り替わる前にSWDからの制御を開始する必要があります。正規のプログラマーを使っていればリセットも適宜制御してくれるので問題ありませんが、安価なST-LINK互換機やリセット信号のないプログラマーなどを使っていると問題になります。
対策としては、STM32CubeProgrammerでMode: Normal, Reset mode: Hardware resetの設定にし、ターゲットデバイスを手動でリセット開始、Connectボタンを押して接続を開始しデバイス待ち状態に入ってから手動でリセット解除する事で制御を奪う事ができました。緊急回避には良いかもしれませんが開発中の手順としてはいささか面倒です。
それでもSWDIO/SWCLKピンを使いたい場合
初期状態でピンを開放する
入力になっている場合には外部のドライブしているICをリセットするなり無効化するなりの対処が必要。出力になっている場合には、起動直後しばらく待ってからGPIOを有効にする、アイドル状態ではSWDIO/SWCLKに戻す、などの対処が有効。
DFUを強制起動するための口を用意しておく
特定の操作をした際にDFUにジャンプするようなコードを用意しておくと安心。デバイス毎のDFUのアドレスは公式資料「AN2606: STM32 microcontroller system memory boot mode」に書かれています。直接ジャンプするのではなく、メモリになんらかの印をつけてソフトウェアリセット、初期化シーケンスの最初期に印を見つけたらDFUに飛ぶような処理を書きます。初期化シーケンスはweak referenceで用意されている__initialize_hardware_earlyという関数を上書きすればよく、例えばSTM32F04系なら
int boot_to_dfu = 0;
void __initialize_hardware_early(void)
{
if (boot_to_dfu) {
boot_to_dfu = 0;
((void (*)(void))0x1fffc400)(); // STM32F04系固有のアドレス
} else {
SystemInit();
}
}
...
// DFUを起動したい箇所にて
boot_to_dfu = 1;
NVIC_SystemReset();
...
として置けばソフトウェアリセットからDFUモードへの移行を実装できます。他にもDFUへリセットするかわりにSWDピンの設定を元に戻す、あるいは特定の操作を定義するのが難しい時は起動時にboot_to_dfuを常時flipするようにして外からリセットをかけたらDFUモードへ移行する、などの工夫があるかと思います。USBとして動作しているならDFUモードへ移行するバックドアを仕掛けることは比較的簡単かと思います。
おまけ
そもそもBoot0/1ピンを無効化するには
SWD用のピンを再利用するよりはまずBoot0/1だけを再利用した方が安全だし引き続きデバッグ機能も使えます。Pull-up/downさえ適切に設定できるならそのままBoot0/1ピンを再利用できますが、外部Pull-upが必要だが最終的にはDFUを起動させたくない、といった場合にはOption Bytesというフラッシュメモリ上のデータ書き換えてピンを無効化できます。プログラム中から書き換える事もできるようですが、通常はプロテクトがかかっており特定の手順を守らないと上書きできません。STM32CubeProgrammerから書き換えるのが楽かと思います。
STM32CubeProgrammerを起動したら左側の上下にアイコンがならんでいると思いますが「OB」と書かれたアイコンがOption Bytesを書き換える機能へのリンクです。User Confugurationの中に
- nBOOT0
- nBOOT1
- BOOT_SEL(またはnSWBOOT0/1)
といった項目があるはず。詳しくは説明が書かれているはずですが、STM32F04系を例にすると、BOOT_SELのチェックを外すと、BOOT0ピンを殺してnBOOT0の設定値を使うようになります。nから始まる設定項目は極性がピンとは反対なので注意。このあたりもAN2606にデバイスごとの説明があります。
SWDからprintf
プロジェクト設定「C/C++ Build > Settings > Tool Settings > MCU GCC Linker」以下の
- 「Libraries > Libraries (-l)」に「rdimon」を追加
- 「Miscellaneous > Linker flags」に「 -specs=rdimon.specs」を追記
デバッグ設定「Startup > Initialization Commsnds」に「monitor arm semihosting enable」を追加
mainの最初に以下の初期化コードを入れる。
extern void initialise_monitor_handles();
int main(void)
{
initialise_monitor_handles();
...
}
以上でprintf系の関数の出口がSWD経由になるはず。リリースビルドからは消さないとSWDにメッセージを送るタイミングでハードウェア例外に捕まって死ぬと思う。
まとめ
以上、SWDが機能しなかった場合の対処方法についてまとめつつ、問題のBoot0/1ピンの無効化方法やprintf周りの設定についてもおまけで紹介しました。