NucleoボードでSTM32マイコンに書き込む

電子工作歴10年にもなってSTM32マイコンを使ったことがないのはさすがに嘘だよ、ということで触ってみた。ネット上で圧倒的な初学者向けの知見があるPICやAVRと違って、STM32はより本格的な用途に使われる分それなりの知識がある人向けの解説が多い印象がある。この記事ではNucleoボードから始めて、単品チップにLチカさせるまでを書きたいと思う。
なお、筆者はWindows環境なのでMacやLinuxの人は注意。
Nucleoボード

(画像は秋月から)
言わずと知れたSTM32マイコンの評価ボード(買ってきてすぐ簡単に使える基板)である。同じような形の製品がいくつもあるが、実際これらは基板としては同じものであって、コアとなるSTM32マイコンだけが載せ替えてあるものがいくつかあるようだ。といっても異なるサイズのチップにまで基板を流用できないので、バリエーションとしてコンパクトサイズのNucleo-32ボード、大型チップを乗せたNucleo-144ボードなどがある。
今回使ったのは正方形に近い形をした「Nucleo-64」ボードの製品の1つである「NUCLEO-F401RE」。これの書き込み機能を使う。

STM32に限らずマイコンの評価ボードのあるあるだが、評価ボードに乗っているチップに書き込むだけでなく、別途接続した他のチップに書き込むこともできるようになっている。これのおかげで評価ボードでお試し→もっと本格的に使いたくなったらチップ単体を購入→ライタには評価ボードをそのまま使う、という流れを踏みやすくなっている。
今回使ったチップ
家の部品箱の肥やしになっていたSTM32L010を使った。買った当時は95円だったが、今秋月で見ると200円に値上がりしている。

その代わりに今はもっと性能のいいSTM32C011が80円で売られているようだ。デバイスは世代交代するという当たり前のことである。もっとも秋月は在庫状況なんかではまず値上げしないし、これについては2021年ごろの半導体不足とかの影響な気がするが...
STM32開発に必要なソフト
自分はこれらを導入した。
- STM32CubeIDEはSTM32の統合開発環境で、開発用SDKもこれに含まれている。単体で動くIDE以外に、VSCode拡張機能としての提供もある。VSCodeを普段から使っている人はそちらを使う方が嬉しいかもしれない。
- STM32CubeMXというソフトを使うと初期化用のコードを手軽に生成できる。
- STM32CubeProgrammerを用いるとライタを細かく制御できる。ライタとチップの接続についてよく確認するには便利だと思う。
自分はPICマイコンに慣れ親しんでいるのでそれで例えると、
- STM32CubeIDE ≒ MPLAB IDE
- STM32CubeMX ≒ MPLAB Code Configurator(MCC)
- STM32CubeProgrammer ≒ MPLAB IPE
に相当すると思われる。
Nucleoボード上のSTMマイコンにプログラムを書き込む
まずは普通に評価ボード上のチップに書き込んでLチカする。
初期化コード生成
まずはSTM32CubeMXでプロジェクトを作成ついでに初期化用コードを生成する。New Project→Board Selectorで目的のボードを選択する。自分はNucleo-F401REを使ったのでそれを選択した。Start Projectで進む。

「Pinout & Configuration」で各種ペリフェラル(周辺機能)の設定を行うことができる。Board Selectorから始めた場合はすでにいろいろ設定されているはずだ。

「Project Manager」→「Project」でプロジェクトの設定をする。

- STM32CubeIDEで開発する場合、
Toolchain/IDEでSTM32CubeIDEを選択する。 - STM32CubeIDE for VSCodeで開発する場合、
Toolchain/IDEでCMakeを選択する。
「GENERATE CODE」でコードを生成する。設定を変えてコードを生成しなおすこともできる。
プロジェクトやコードを生成するための設定データは.iocファイルとして保存される。
コードを書く
コードは(生成したプロジェクトのディレクトリ)/Core/Src下に生成される。main.cの中に御丁寧にユーザーコードを書くセクションが用意されているのでそこに色々書いていくと良い。例えばLチカするコードであれば以下のように書ける。
// (略)
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
// (略)ビルド・書き込みは色々方法がある。
ビルドする(STM32CubeIDEの場合)
STM32CubeIDEを開き、メニューのFile→Open Projects from File System...でプロジェクトを生成したディレクトリを開けばよい。
開けたらメニューのProject→Build Allでビルドできる。ビルドバイナリはELF形式として出力され、Debug構成の場合は(プロジェクトのディレクトリ)/Debug/(プロジェクト名).elfに保存される。これが書き込みに必要なものだ。
ビルドする(STM32CubeIDE for VSCodeの場合)
生成したプロジェクトのディレクトリをVSCodeで開き、STM32Cube Key Actionsの中からSet Up STM32Cube project(s)をクリックするとプロジェクトの初期設定ができる。この時点でCMakeのConfigureまで完了する。

プロジェクトが読み込めていればCMakeプロジェクトの欄が出るので、そこからBuildボタンを押せばビルドできる。ビルドバイナリはDebug構成の場合(プロジェクトのディレクトリ)/build/Debug/(プロジェクト名).elfに出力される。

なおSTM32CubeMXでCMakeではなくSTM32CubeIDE設定でプロジェクトを生成してしまった場合、Convert Eclipse STM32CubeIDE projectを用いるとCMakeプロジェクトに変換することもできる。Source(変換元)とDestination(変換出力先)を指定すると変換できる。変換した後は通常のCMakeプロジェクトと同様に扱える。
書き込み・実行する(STM32CubeIDEの場合)
メニューのRun→Debugを選択する。初回は以下のような設定画面が出るが、大抵そのままOKを押してしまってよいと思う。

デバッグ設定だとコードの始めでストップするみたいな挙動になると思われるので、それだと困る場合はRun→Runの方を使うと良い。
書き込み・実行する(STM32CubeIDE for VSCodeの場合)
Nucleoボードを接続していればRun and Debug→STM32 Launch STLink GDB Serverでそのまますぐ実行できる。
書き込み・実行する(STM32CubeProgrammerの場合)
右のほうにライタ接続のための設定画面がある。

Nucleoボードを接続し、「ST-LINK」を選択した上で「接続」をクリックするとまずライタに接続できる。ライタ~チップ間の接続も正常なら、この時点でチップの読み込みも行われる。
プログラムを書き込むには左側の欄で上から2番目にある「消去&プログラミング」の画面を出す。

「ブラウザ」を選択して書き込むバイナリファイルを選び、「プログラミングの開始」をクリックすると書き込まれる。
書き込み・実行する(STM32Programmer_CLIの場合)
STM32CubeProgrammerにはCUIで動かせるバージョンもある。コマンドラインで作業したい場合、プログラムで何らかの自動化をしたい場合はこちらが向いている。
普通にSTM32CubeProgrammerをインストールすればPATHに入っているのでSTM32_Programmer_CLIで起動できると思われる。細かいオプションについては-hとかで出せるのでそちらに譲るとして、ELFファイルを書き込んで実行するには以下のようなコマンドを打てばよい。
STM32_Programmer_CLI -c port=SWD -d program.elf 0x8000000 -s
-cオプションでデバッガへ接続する。Windows環境でST-Linkに接続するにはport=SWDと指定すれば十分らしい。MacとかLinuxは知らない。-dオプションでデバイスに書き込む。アドレス指定は任意で、普通にフラッシュメモリに書き込む分には付けなくてもよい。0x8000000はSTM32マイコンのメモリマップにおいてフラッシュメモリが割り当てられている仮想アドレスだ。多く(全て?)のSTM32マイコンで共通する。(参考)-sオプションはコードを実行するオプションらしい。これを指定しなかった場合は書き込まれるが実行はされないことになる。
単品STM32チップに書き込む
次は単品のSTM32チップにNucleoボードを用いて書き込む方法を書いていく。
Nucleoボードのデバッガ・ライタ機能
上で説明したようにNucleo-64ボードにはデバッガ・ライタ機能があり、外の別チップに使うこともできる。このデバッガは公式デバッガ・ライタとして市販されているST-Linkと互換であり、なんならST-Linkより安いのでNucleoの本体とも言われる。
外のチップに対して使う場合は「SWD」「CN4」と書いてある6ピンヘッダを用いる。

ピンアサインは丸いマークが付いている1番から順に、
- VDD
- SWDCLK
- GND
- SWDIO
- NRST
- SWO
となっている。なお、ここのVDD/GNDはあくまで接続先の電源検出であり、デバッガ側から電源を供給するものではない。
近くに目を向けると「ST-LINK」「CN2」と書かれた4ピンヘッダに2本のジャンパピンが刺さっている。これが付いている場合デバッガはボード上のチップに向くが、ジャンパピンを外すとデバッガは外部チップに向く。なので、外部チップ用のデバッガ・ライタとして用いる際はこの2本のジャンパピンを外す。
一応回路図で仕組みを確認しておこう。
Nucleoボードの回路図(STマイクロエレクトロニクス公式)

回路図上で大きくDEFAULTと書かれている箇所とRESERVEDと書かれている箇所がある。ボードの実物を見るとDEFAULTと書かれている4か所は接続され、RESERVEDと書かれている4か所は切り離されている。つまりRESERVEDと書かれている右側は無視してよい。
NucleoボードのST-Link互換デバッガはSTM32F103で作られているようだ。そしてそのデバッガとしての接続口はT_JTCK T_JTMS T_NRST T_SWOとして出ている。恐らくTはTargetのTだろう。
回路図上でラベルで接続されているのを見る限り、何もなくともデバッガは6ピンヘッダCN4と接続されているらしい。 ピンヘッダCN2で1-2がショートされるとT_JTCKは右上のTCK/SWCLKと接続され、3-4がショートされるとT_JTMSはTMS/SWDIOと接続される。回路図右上の4線が要するにボードに乗ったマイコンチップなので、これでジャンパピンでショートするとそちらに接続される訳が分かった。どうもT_NRSTとSWOについては何も処理されていないようだが、メインの2本が切り離されていれば割とどうでもいいのかもしれない。
なお、繋がっていない右側のRESERVEDの部分についてはデバッガ自体のコアSTM32F103と繋がっているように見える。これは想像だが、製造時にだけRESERVEDの4か所をショートしてCN2に接続し、デバッガ自体のファームウェアを書き込むのに使われているのではないかと思われる。
話を戻そう。
チップ側の配線
STM32チップに書き込む際は、信号線として以下の3本を引き出す。
- SWDCLK
- SWDIO
- NRST
プルアップとかプルダウンとかは必要なく、デバッガと直結で良いようだ。SWOは必ずしも使わなくてよいらしい。
チップの電源は別途適当に供給し、デバッガのVDD/GNDにも接続する。

なおNucleoのデバッガライタにはシリアル通信機能もついているので、使用するチップのUARTと繋げばデバッグに役立つ。慣れた人なら常識だろうが、UARTは一方のRXをもう一方のTXに繋ぐことに注意。

ビルド・書き込み
Nucleoボード上のSTM32に書き込むときとおおむね同じだ。STM32CubeMXでコード生成するときに諸々の設定をする。
- 単品チップなので、「MCU/MPU Selector」で使うチップをきっちり指定する。
- 各ピンの使い方は「Pinout & Configuration」で適宜設定する。
後は特に変わらない。
電源オン時のブート
上のようにするだけでとりあえず書き込みと実行はできるのだが、デバッガなしで電源だけ与えた場合に起動しない。これではマイコンとして致命的である。電源与えて即起動してくれなければ何の役にも立たない。
どうしてこうなるかというと、ブートモードがフラッシュメモリからの起動になっていないからだ。
Getting started with STM32L0xx hardware development

データシートではなくアプリケーションノートにしか書いていない。ふざけないで欲しい。
フラッシュメモリに書き込んだプログラムから起動するようにするにはBOOT0ピンを0にしておく。GNDに直結するか、抵抗を介してGNDに落としておくのが吉。これは今回使ったSTM32L0に限らず多くの(全ての?)STM32マイコンで共通と思われる。
なお多くの作例でBOOT0ピンとGNDの間には抵抗を噛ませてあるのだが、いざというとき1にできるようにする以上の意味があるのかはよく分からない。とりあえず手元で実験する限り直結でも問題なく動作した。
Lチカ
こんな感じでできた。

シリアル通信
UART通信についても配線して設定すれば通信できる。UARTペリフェラルを有効化し、NucleoボードのRX/TX(CN3)と目的のチップのTX/RXピンを繋げてCOMポートで開けば普通に使えた。どこまで高速にできるのか分からないが、とりあえず一般的な115200baudでは何の問題もなく通信できた。

テキストログが出せるとデバッグ効率は飛躍的に上がる。特に今ドキはAIを使った開発が流行りだが、メカや組み込みでAIコーディングをする場合はAIが状況を知る手段を整備する必要がある。そんなときシリアル通信一本あれば百人力だ。
ただし一点注意がある。このシリアルのRX/TXはNucleoボード上のSTM32チップとも直結しているということだ。


なのでNucleoボード上のSTM32チップでUARTを有効化していると簡単に競合する。対策としては以下の3通りだ。
- Nucleo-64ボード上のSTM32チップにはUARTを有効化しないようなプログラムを書き込んでおく。送受信しなくても有効化した時点でアウトなことに注意。
- 回路図に従い、Nucleo-64ボードのSB13,SB14をオープンにする。Nucleoボードの裏に0Ωチップ抵抗がずらっと並んでいるので、それをいじればよい。オンボードのSTM32とは当然シリアル通信できなくなることに注意。
- Nucleo-64ボードのデバッガ部分を切り取り線で切り離してしまう。完全にデバッガ・ライタとしてのみ使う。
Nucleoボードの方のSTM32のUARTを無効化するのが一番手っ取り早いと思う。工作が不要なため。
その他
- 結局.elfファイル(あるいは.binや.hex?)さえ手に入ればSTM32Programmerで書き込めるので、別に開発手段はSTM32CubeIDEである必要はないはずだ。Zephyrとかmbedとか。まあmbedは死んでしまったが。
- Windows以外の環境での開発についてはよく調べていないので責任が持てない。
- STM32CubeProgrammerについてはSTM32CubeMXのインストールにも含まれている?っぽいのだが、よく調べていないのでわからない。