STM32Nucleo上のCortex-M0でNewlib(組み込み用標準Cライブラリ)を利用してprintfしました.
箇条書きでまとめると以下のことをします.
- LinuxでNewlibをビルド
- WindowsのSTM32CubeIDEでSTM32 Projectを作成する
- コンパイラ、バイナリツール、libgccはIDEに付属のツールチェーンのものを利用する.
- ドライバ、リンカスクリプトはIDEによって自動生成されたものを利用する.
- スタートアップはIDEによって自動生成されたものとツールチェーン付属のものを両方利用する.
- 標準ライブラリは自前でビルドしたNewlibを使用する.
- Newlibに必要なシステムコールはIDEによって自動生成されたものを利用する. ただし、writeシステムコールは自分で実装してオーバーライドする.
実行環境
項目 | 説明 |
---|---|
OS (Newlibのビルド用) | Ubuntu 20.04 |
OS (STM開発用) | Windows11 22H2 |
STM32CubeIDE | 1.10.1 |
STM32CubeProgrammer | 2.11.0 |
Newlibのビルド
linuxでビルドします. 下記を参考にしてください.
Newlibのヘッダフォルダとビルド後の生成物をWindowsにコピーしておきます. 今回はWindowsのドキュメントフォルダにコピーしました.
以降はWindowsで操作をします.
プロジェクトの作成
STM32CubeIDEを起動しFile->New->STM32 Project を選択してSTM32 Project を作成します. 私が持っているSTM32Nucleoボードに合わせ赤い枠で囲まれた項目を選びました. プロジェクト名は好きなものを設定します. その他の設定はいじらずにFinishで終了します.
どの項目を選択したかはプロジェクトのプロパティからも確認することが出来ます. グレーアウトしているため後から変更はできないようです. ここでRuntime libraryがStandard Cになっていることを必ず確認してください. Reduced C が選択されているとリンク時にツールチェーン付属のlibc_nano.aがリンクされてしまいます.
C/C++プロジェクトではなくSTM32 Projectにすることで以下の作業を省略できます.
- Newlibを使うためのシステムコールの実装(IDEによって自動生成されるため)
- スタートアップの実装(IDEによって自動生成されるため)
- メモリマップに適したリンカスクリプトの実装(IDEによって自動生成されるため)
- アセンブラによるペリフェラルの設定(GUIのPinout&Configurationを利用できるため)
- ドライバの実装(HALを利用できるため)
ソースコードの編集・ペリフェラルの設定
以下の記事の内容を参考にUARTでprintfするプログラムを書きます.
主な変更点は、標準出力のクリアの追加とwriteシステムコールをHALを用いたものにすることです. Core/Src/main.cのすべての変更点は以下になります
~略~ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stdio.h" // ヘッダの追加 #include "string.h" // ヘッダの追加 ~略~ int main(){ /* USER CODE BEGIN 1 */ char buff[256]; setbuf(stdout, NULL); strncpy(buff, "Hello World", 12); /* USER CODE END 1 */ ~略~ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ printf("%s", buff); HAL_Delay(1000); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ ~略~ /* USER CODE BEGIN 4 */ int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart2,(uint8_t *)ptr, len, 10); return len; } /* USER CODE END 4 */
Newlibが正しくビルド、リンクされていればstrncpyやprintfが正しく利用できるはずです.
また、デフォルトではUARTが無効になっているので有効にするためにペリフェラルの設定をします.
デフォルトではSTM32CubeIDEに付属するツールチェーンの標準Cライブラリをリンクしてしまうので、Newlibをリンクするようにプロジェクトのプロパティを変更します. プロジェクト名と同名の.iocファイルを開くとペリフェラル設定のためのGUIが表示されます. 参考記事に倣ってUSART2を有効にしてボーレートを115200 Bits/s、データ長8bit、パリティなし、ストップビット1Bit にします. 設定後は忘れずに歯車マークをクリックしてソースコードを自動生成します.
プロジェクトプロパティの変更
コンパイル時にサーチするフォルダの追加
Newlibのインクルードフォルダへのパスをサーチフォルダに追加します. 適宜自分の環境に適したパスを指定してください.
リンクの一般設定の変更
ビルドの詳細を表示するためにVerboseオプションを有効にします.
また、ツールチェーン付属の標準Cライブラリをリンクされないように設定します.
リンクするライブラリとサーチフォルダの追加
libc.aと(必要であれば)libm.aをリンクするためにライブラリとサーチディレクトリの追加をします. 適宜自分の環境に適したパスを指定してください.
プロジェクトのビルドとフラッシュへの書き込み
プロジェクトフォルダを右クリックしてBuildをクリックするとDebugフォルダにelfファイルが生成されます. フラッシュへの書き込みにはSTM32CubeProgrammerを利用しますが、stripをする必要はないようです. 上記の設定でビルドをした時の全コマンドラインは次のようになりました.
arm-none-eabi-gcc -o "NewLib.elf" @"objects.list" -lc -lm -mcpu=cortex-m0 -T"C:\Users\XXX\STM32CubeIDE\workspace_1.10.1\NewLib\STM32F072RBTX_FLASH.ld" --specs=nosys.specs -Wl,-Map="NewLib.map" -Wl,--gc-sections -Wl,--verbose -nodefaultlibs -static -L"C:\Users\XXX\Documents\build" -mfloat-abi=soft -mthumb
-Wl,--verboseオプションのおかげでビルドの詳細が表示されます. 次の記述からツールチェーン付属ではなく自作のNewlibのバイナリをリンクできていることが確認できます. ただし、ツールチェーン付属のライブラリを全く使っていないわけではなく、libgcc.aはツールチェーン付属のものをリンクしていることも分かります. ただしlibgcc.aはC標準ライブラリとはまた別枠ですね.
attempt to open C:\Users\XXX\Documents\build\libc.a succeeded C:\Users\XXX\Documents\build\libc.a (C:\Users\XXX\Documents\build\libc.a)libc_a-printf.o (C:\Users\XXX\Documents\build\libc.a)libc_a-puts.o (C:\Users\XXX\Documents\build\libc.a)libc_a-findfp.o ~略~ attempt to open c:/st/stm32cubeide_1.10.1/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.0.202111181127/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp\libgcc.a succeeded c:/st/stm32cubeide_1.10.1/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.0.202111181127/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp\libgcc.a (c:/st/stm32cubeide_1.10.1/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.0.202111181127/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp\libgcc.a)_udivsi3.o (c:/st/stm32cubeide_1.10.1/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.0.202111181127/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp\libgcc.a)_divsi3.o (c:/st/stm32cubeide_1.10.1/stm32cubeide/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.10.3-2021.10.win32_1.0.0.202111181127/tools/bin/../lib/gcc/arm-none-eabi/10.3.1/thumb/v6-m/nofp\libgcc.a)_dvmd_tls.o
elfファイル生成後はSTM32CubeProgrammerを起動し一番上のタブのMemory & File editingでST-LINKのConnectを選択しましょう. ボードが見つかった場合右上のステータスがConnectedになります.
ボードと接続が出来たら2番目のタブのErasing & Programmingを開きます. File pathにビルドしてできたelfファイルを指定し、Start Programmingを押せばフラッシュへの書き込みが始まります. File download completeと書かれたモーダルが表示されれば書き込み完了です.
TeraTermで確認
ボードをリセット後TeraTermでprintf出来てるかを確認しましょう.
無事printfすることが出来ました.