ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

ARMマイコンでNewlibを使う

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でビルドします. 下記を参考にしてください.

prupru-prune.hatenablog.com

Newlibのヘッダフォルダとビルド後の生成物をWindowsにコピーしておきます. 今回はWindowsのドキュメントフォルダにコピーしました.

以降はWindowsで操作をします.

プロジェクトの作成

STM32CubeIDEを起動しFile->New->STM32 Project を選択してSTM32 Project を作成します. 私が持っているSTM32Nucleoボードに合わせ赤い枠で囲まれた項目を選びました. プロジェクト名は好きなものを設定します. その他の設定はいじらずにFinishで終了します.

適切なNucleoボードを選択する

どの項目を選択したかはプロジェクトのプロパティからも確認することが出来ます. グレーアウトしているため後から変更はできないようです. ここでRuntime libraryがStandard Cになっていることを必ず確認してください. Reduced C が選択されているとリンク時にツールチェーン付属のlibc_nano.aがリンクされてしまいます.

ターゲットは後からも確認可能. Standard C を選択していることを確認

C/C++プロジェクトではなくSTM32 Projectにすることで以下の作業を省略できます.

  1. Newlibを使うためのシステムコールの実装(IDEによって自動生成されるため)
  2. スタートアップの実装(IDEによって自動生成されるため)
  3. メモリマップに適したリンカスクリプトの実装(IDEによって自動生成されるため)
  4. アセンブラによるペリフェラルの設定(GUIのPinout&Configurationを利用できるため)
  5. ドライバの実装(HALを利用できるため)

ソースコードの編集・ペリフェラルの設定

以下の記事の内容を参考にUARTでprintfするプログラムを書きます.

yukblog.net

主な変更点は、標準出力のクリアの追加と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が無効になっているので有効にするためにペリフェラルの設定をします.

ペリフェラルの設定のために.iocファイルを開く

デフォルトではSTM32CubeIDEに付属するツールチェーンの標準Cライブラリをリンクしてしまうので、Newlibをリンクするようにプロジェクトのプロパティを変更します. プロジェクト名と同名の.iocファイルを開くとペリフェラル設定のためのGUIが表示されます. 参考記事に倣ってUSART2を有効にしてボーレートを115200 Bits/s、データ長8bit、パリティなし、ストップビット1Bit にします. 設定後は忘れずに歯車マークをクリックしてソースコードを自動生成します.

ペリフェラル(USART)の設定1

ペリフェラル(USART)の設定2

プロジェクトプロパティの変更

コンパイル時にサーチするフォルダの追加

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と書かれたモーダルが表示されれば書き込み完了です.

elfファイルを選択してボードへプログラミング

TeraTermで確認

ボードをリセット後TeraTermでprintf出来てるかを確認しましょう.

TeraTermでprintf出来てるか確認

無事printfすることが出来ました.