k-ogawa2025’s ブログ

メカトロ制御回路設計に関する情報発信ブログ

STM32CubeIDEを使ったアセンブリ言語プロジェクトの作成とデバッグ

最終更新日:2025-03-04

STマイクロエレクトロニクスの開発環境:STM32CubeIDEを使ってアセンブリ言語プログラムだけのビルドとデバッグを行った事例を紹介します。プログラムのターゲットは同じくSTマイクロエレクトロニクスMCUボード:NUCLEO-H503RBです。

STM32CubeIDEにはプログラムの自動生成機能がありソフトウェア開発には非常に有益です。しかし、今回はCPUの基本的な動作を確認するのが目的なので、リセット解除後の最初の動作を指示するベクターテーブルとリセットハンドラーを含めた処理全体に自分で作ったアセンブリ言語プログラムを使います。

内容


1. 背景

そもそものやりたいことから下図のように考えて、手段(ボードと開発環境)を選択しました。

そもそものやりたいことは、「費用を抑えてCPUの基本的な動作を確認する」環境を手に入れることです。

選択したボードはSTマイクロエレクトロニクスのNUCLEO-H503RBボードです。ARMのCPU:Cortex-M33を内蔵したSTマイクロエレクトロニクスMCU:STM32H503RBT6とデバッガーのST-Linkを搭載しています。

NUCLEO-H503RBボードの写真

NUCLEO-H503RBボード

選択した開発環境はボードに合わせたSTM32CubeIDEです。初めに書いたように、今回はリセット解除後の最初の動作を指示するベクターテーブルとリセットハンドラーを含めた処理全体に自分で作ったアセンブリ言語プログラムを使います。そこで、せっかく用意されている自動生成などの機能を使用しないのは申し訳ないですが、STM32CubeIDEを使って下記の2つを行い、「費用を抑えてCPUの基本的な動作を確認する」ための準備をしました。

  • アセンブリ言語プログラムに使えるまったく自動生成ファイルのないプロジェクトを作成する。
  • ST-LINKを使ったプログラムのダウンロードやデバッグの環境を設定する。

2. プロジェクト作成

アセンブリ言語プログラムに使えるまったく自動生成ファイルのないプロジェクトを作成します。

STM32CubeIDEのダウンロードとインストールに関してはWeb上に情報がたくさんありますからここでは触れません。使用したSTM32CubeIDEのバージョンは 1.17.0 です。

  1. STM32CubeIDEを起動します。
    ワークスペースはデフォルトの"C:\Users\<ユーザ名>\STM32CubeIDE\workspace_1.17.0"を使用しましたが、どこでもよいと思います。
    STM32CubeIDEを起動後にInformation Centerを閉じるとC/C++ パースペクティブが開きます。もし、既存のプロジェクトが開いていたら閉じておくのが無難です。
  2. File → New → C/C++ Project をクリックし「Templates for New C/C++ Project」のウィンドウを開きます。ここで「C Managed Build」を選択し「Next」をクリックします。

    「Templates for New C/C++ Project」ウィンドウの図

  3. 「C Project」のウィンドウで下記を設定し、「Next」をクリックします。
    ・Project name:LED1
    ・Project type:Executable - Empty Project ←デフォルトのまま
    ・Toolchains:MCU ARM GCC
  4. 「Select Configurations」ウィンドウではDebugとReleaseをチェックしたまま、「Next」をクリックします。
  5. 「Select default target for the project」ウィンドウで、MCUを「STM32H503RBTx」に設定して「Finish」をクリックします。
    Project Explorerに「LED1」のプロジェクト(フォルダ)が追加されます。

    「LED1」のプロジェクト(フォルダ)が追加されたProject Explorerの図

3. ソースファイル作成

「LED1」のプロジェクト(フォルダ)の下に「src」フォルダを作成し、さらにその下にアセンブリ言語プログラムファイルの「LED1.s」ファイルを作成します。

  1. Project ExplorerのLED1をクリックして選択しておき、File → New → Folder をクリックして「Folder」ウィンドウを開きます。Folder nameに「src」と入力して「Finish」をクリックします。
    Project Explorerに「src」のフォルダが追加されます。
    「src」のフォルダが追加されたProject Explorerの図
  2. Project Explorerのsrcをクリックして選択しておき、File → New → Source File をクリックして「Source File」ウィンドウを開きます。下記の設定をして「Finish」をクリックします。
    ・Souce folder:LED1/src ←デフォルトのまま
    ・Source File:LED1.s
    ・Template:<None>
    Project Explorerに「LED1.s」のファイルが追加されます。
    「LED1.s」のファイルが追加されたProject Explorerの図
  3. STM32CubeIDEの中央のエディタウィンドウにLED1.sが開いているのでソースコードを入力します。入力後、File → Save をクリックして保存します。

    中央のエディタウィンドウにLED1.sが開いているSTM32CubeIDEの図

    入力したアセンブリ言語ソースコードを下記に示します。
    NUCLEO-H503RBボードのLD2を1秒周期で点滅します。(0.5秒点灯、0.5秒消灯の繰り返し)

    //
    // LED1.s
    //  NUCLEO-H503RBボード用LD2点滅ソフト
    //
        .syntax unified
        .text
        .global Reset_Handler
    
        .equ    SP_init,0x20008000      //スタックポインタ初期値
        .equ    SP_limit,0x20007000     //スタックポインタ下限
        .equ    RCC,0x44020C00          //RCCレジスタベースアドレス
        .equ    of_AHB2ENR,0x8C         //AHB2ENRレジスタ アドレスオフセット
        .equ    v_AHB2ENR,0x40000001    //RCC_AHB2ENR設定値 GPIOAEN=クロック有効
        .equ    GPIOA,0x42020000        //GPIOAレジスタベースアドレス
        .equ    of_MODER,0x0            //MODERレジスタ アドレスオフセット
        .equ    of_BSRR,0x18            //BSRRレジスタ アドレスオフセット
        .equ    of_BRR,0x28             //BRRレジスタ アドレスオフセット
        .equ    v_A_MODER,0xABFFF7FF    //GPIOA_MODER設定値 MODE5=汎用出力
        .equ    loop500ms,5333333       //Delay_500msのループ回数
    
    // 例外ベクター
    Vecter_Table:
        .word   SP_init                 //初期スタックポインタ
        .word   Reset_Handler           //初期プログラムカウンタ
        .word   dummy_Handler           //NMI_Handler
        .word   dummy_Handler           //HardFault_Handler
        .word   dummy_Handler           //MemManageFault_Handler
        .word   dummy_Handler           //BusFault_Hamdler
        .word   dummy_Handler           //UsageFault_Handler
        .word   dummy_Handler           //予約
        .word   dummy_Handler           //予約
        .word   dummy_Handler           //予約
        .word   dummy_Handler           //予約
        .word   dummy_Handler           //SVCall_Handler
        .word   dummy_Handler           //DebugMonitor_Handler
        .word   dummy_Handler           //予約
        .word   dummy_Handler           //PendSV_Handler
        .word   dummy_Handler           //SysTick_Handler
    
    
    // リセットハンドラー
        .type   Reset_Handler, %function
    Reset_Handler:
        MOVW    R0,#:lower16:SP_limit   //スタックポインタ下限
        MOVT    R0,#:upper16:SP_limit
        MSR     MSPLIM,R0               //スタック下限値設定
        B       Main
    
    // ダミーハンドラー
        .type   dummy_Handler, %function
    dummy_Handler:
        B       .                       //無限ループ
    
    // メイン処理
    Main:
        //IO初期設定
        BL      IO_init
        //GPIOAレジスタベースアドレス、PA5ビット設定
        MOVW    R0,#:lower16:GPIOA      //GPIOAレジスタベースアドレス
        MOVT    R0,#:upper16:GPIOA
        MOVS    R1,#0x00000020          //bit5=1
    loop1:
        //LD2点灯
        STR     R1,[R0,#of_BSRR]        //GPIOA_BSRR.BS5=1
        //500ms待つ
        BL      Delay_500ms
        //LD2消灯
        STR     R1,[R0,#of_BRR]         //GPIOA_BRR.BR5=1
        //500ms待つ
        BL      Delay_500ms
        //繰り返し
        B       loop1
    
    // Delay 500ms
    Delay_500ms:
        PUSH    {R0}
        //loop500ms回ループを回す
        MOVW    R0,#:lower16:loop500ms  //Delay_500msのループ回数
        MOVT    R0,#:upper16:loop500ms
        .align  4                       //ループの先頭アドレスを16Byte境界に置く
    loop2:
        SUBS    R0,#1
        BNE     loop2
        //サブルーチンから復帰
        POP     {R0}
        BX      LR
    
    // IO初期設定
    IO_init:
        PUSH    {R0,R1}
        //GPIOAクロック有効化
        MOVW    R0,#:lower16:RCC        //RCCレジスタベースアドレス
        MOVT    R0,#:upper16:RCC
        MOVW    R1,#:lower16:v_AHB2ENR  //RCC_AHB2ENR設定値
        MOVT    R1,#:upper16:v_AHB2ENR
        STR     R1,[R0,#of_AHB2ENR]     //RCC_AHB2ENR.GPIOAEN=有効
        //PA5 汎用出力モード
        MOVW    R0,#:lower16:GPIOA      //GPIOAレジスタベースアドレス
        MOVT    R0,#:upper16:GPIOA
        MOVW    R1,#:lower16:v_A_MODER  //GPIOA_MODER設定値
        MOVT    R1,#:upper16:v_A_MODER
        STR     R1,[R0,#of_MODER]       //GPIOA_MODER.MODE5=汎用出力
        //サブルーチンから復帰
        POP     {R0,R1}
        BX      LR
    
    // プログラムエンド
        .end
    

    ソースコードの説明の記事を追加しました。2025/03/04

    k-ogawa2025.hatenablog.com

4. リンカースクリプトファイル作成

「LED1」のプロジェクト(フォルダ)の下にリンカースクリプトファイルの「LinkerScript.ld」を作成します。

  1. Project ExplorerのLED1をクリックして選択しておき、File → New → Other... をクリックして「Select a wizard」ウィンドウを開きます。General → File を選択して「Next」をクリックします。
  2. 「File」ウィンドウでFile nameに「LinkerScript.ld」を設定をして、「Finish」をクリックします。
    Project Explorerに「LinkerScript.ld」のファイルが追加されます。
    LinkerScript.ldファイルはLED1の直下であることを確認しておきます。もし、srcの下だったら削除して作り直します。
    「LinkerScript.ld」のファイルが追加されたProject Explorerの図
  3. エディタウィンドウにLinkerScript.ldが開いているのでリンカースクリプトを入力します。入力後、File → Save をクリックして保存します。
    入力したリンカースクリプトを下記に示します。
    エントリーポイントとプログラムをロードするアドレスを設定しています。
    ENTRY (Reset_Handler)
    MEMORY
    {
        FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x20000
    }
    SECTIONS
    {
        .text :
            {
                KEEP( * (  .text  ) )
            } >FLASH
    }
    

    リンカースクリプトの説明の記事を追加しました。2025/03/04

    k-ogawa2025.hatenablog.com

5. ビルド設定

リンク時に不要なコードの追加を止めるために標準のシステム スタートアップ ファイルやライブラリの使用を止める設定を行います。

  1. Project ExplorerのLED1をクリックして選択しておき、File → Properties をクリックして「Properties for LED1」ウィンドウを開きます。C/C++ Build → Settings をクリックし、Tool SettingsタブのMCU/MPU GCC Linker のGeneralをクリックして下記にチェックを入れます。そして「Apply and Close」をクリックします。
    ・No startup or default libs(-nostdlib)
    「Properties for LED1」ウィンドウの図

6. ビルド実行

ソースファイルからターゲットボードにダウンロードするオブジェクトファイルを作成します。

  1. Project ExplorerのLED1を右クリックしてメニューを開き、「Build Project」をクリックします。
    DebugフォルダがLED1の下に作られて、オブジェクトファイルのLED1.elf、逆アセンブルリストのLED1.listなどが作られます。
    DebugフォルダがLED1の下にが作られたProject Explorerの図
  2. LED1.listファイルを開いて見ると、プログラムはリンカースクリプトファイルで指定した通り0x0800 0000番地からロードされていることが分かります。
    LED1.listファイルの図

7. デバッグ設定

下記の2つの設定を行います。

  • トレース機能を使うためにSWVを有効にします。
  • リセット解除時はベクターテーブルからスタートアドレスを読み込み、読み込んだ後CPUを停止します。
  1. Project ExplorerのLED1を右クリックしてメニューを開き、Debug As → Debug Configurations... をクリックして「Create, marge, and run configurations」ウィンドウを開きます。
    STM32 C/C++ Application を選択してNew Configurationボタン New Configurationボタンの図を押します。STM32 C/C++ Applicationの下にLED1 Debugが作られます。
    STM32 C/C++ Applicationの下にLED1 Debugが作られた図
  2. Mainタブはデフォルトのままで変更なしです。
  3. デバッガタブで下記の設定を変更します。
    シリアル ワイヤ ビューア(SWV
    ・有効にチェックを入れる
    ・Core Clock(MHz):32
  4. Startupタブで下記の設定を変更します。
    Runtime Options
    ・Start AddressのSpecify vector tableにチェックを入れ、「8000000」を記入
    ・Set breakpoint at:のチェックを外す
    ・Resumeのチェックを外す
  5. Sourceタブ、Commonタブはデフォルトのままで変更なしです。
  6. 「Apply」をクリックして、「Close」をクリックします。

8. プログラム実行

ターゲットボードにプログラムをダウンロードして実行します。

  1. NUCLEO-H503RBボードのコネクタCN1:ST-LINK USB Type-Cコネクタとパソコンをつなぎます。パソコンのエクスプローラで「NOD_H503RB」がドライブに接続していることがわかります。
  2. Project ExplorerのLED1を右クリックしてメニューを開き、Debug As → 2 STM32 C/C++ Application をクリックしてデバッガーを起動します。
    内蔵FlashROMへプログラムがダウンロードされます。ボードのLD1が赤と緑の表示を繰り返していてST-Linkが通信中であることを示しています。デバッガーの起動中はいつも赤と緑の表示を繰り返しています。
    パースペクティブの切り替えの確認が表示されたら「Switch」をクリックしてデバッグ パースペクティブに切り替えます。
  3. ツールバーの Resume ボタンResumeボタンの図をクリックするとプログラムの実行が始まり、LD2が1秒周期で点滅を繰り返します。
    そして、ツールバーの Suspend ボタンSuspendボタンの図をクリックするとプログラムが停止します。
    また、ツールバーの Terminate ボタンTerminateボタンの図をクリックするとデバッグが終了します。

9. デバッグ

CPUの動作を確認するために使うデバッガーの機能の内、ここでは下記の3つを行います。

  • CPUのレジスタ値の確認
  • FlashROMに書き込まれたプログラムの確認
  • トレースを使った点滅間隔の確認

9.1. CPUのレジスタ値を確認

  1. デバッガーが起動した状態でしたら、一旦ツールバーの TerminateボタンTerminateボタンの図をクリックしてデバッグを終了します。そして、「8. プログラム実行 2.」の手順で再度デバッガーを起動してデバッグ パースペクティブに切り替えます。この状態はリセットが解除後でプログラムが停止しています。
  2. Window → Show View → Registers をクリックすると右上のウィンドウにRegistersビューが表示され、プログラムが停止した状態でCPUの一般レジスタが表示されます。数値に10進数表記が混ざっていますが、Registersビューのメニューアイコン( メニューアイコンの図 )をクリックして Number Format → Hex をクリックすると全部16進数表記になります。
    ・PCは0x0800 0040になっていて、リセットベクターの値で停止しています。
    ・SPは0x2000 8000になっていて、初期値が設定されています。
    Registersビューの図

9.2. FlashROMに書き込まれたプログラムを確認

  1. Window → Show View → Memory をクリックすると下部のウィンドウにMemoryビューが表示されます。プログラムが停止した状態でAdd Memory Monitorボタン「Add Memory Monitor」の図をクリックし、モニターするアドレスとして0x08000000を設定して「OK」をクリックします。
  2. 0x0800 0000番地からのFlashROMの値が表示されますがレンダリングが「Hex」でビッグエンディアンの表示になっています。
    レンダリングが「Hex」でビッグエンディアンの表示になっているMemoryビューの図
  3. Cortex-M33はリトルエンディアンなのでNew Renderings...タブで「Hex Integer」を選択して「Add Rendering(s)」をクリックするとリトルエンディアンで表示されたタブが追加されます。
    「Hex Integer」を選択してリトルエンディアンで表示されたMemoryビューの図
    <Hex Integer>のタブでは0x0800 0000番地の値は0x2000 8000です。LED1.listに示されているベクターテーブルの値と同じで正しくプログラムがFlashROMに書き込まれていることがわかります。

9.3. トレース機能を使い、LD2を点灯している時間と消灯している時間を確認

  1. Window → Show View → SWVSWV トレースログ をクリックすると中央下部にSWV トレースログ ビューが表示されます。
  2. プログラムが停止した状態でSWV トレースログ ビューのトレースの設定ボタンConfigure Traceボタンの図をクリックして、Serial Wire Viewer 設定のウィンドウを開き下記の設定を行い「OK」をクリックします。ここで、Comparater 0はLD2を点灯する書込み、Comparater 1はLD2を消灯する書込みを取り込む設定です。
    データトレースの設定値
    項目 Comparater 0 Comparater 1
    許可する チェックを入れる 同左
    変数名/アドレス 0x42020018 0x42020028
    アクセス種別 Write 同左
    サイズ Word 同左
    生成 Data 同左
  3. SWV トレースログ ビューのStart TraceボタンStart Traceボタンの図をクリックして、トレースを開始します。
    ツールバーのReset the chip and restart debug sessionボタンReset the chip and restart debug sessionボタンの図をクリックするとプログラムが最初から実行します。2、3回LD2が点滅したら、ツールバーのSuspendボタンSuspendボタンの図をクリックしてプログラムを停止します。
    SWV トレースログ ビューでcomp0とcomp1の時間の差を見ると間隔が0.5sであることがわかります。(comp0がLD2を点灯する書込み動作、comp1がLD2を消灯する書き込み動作)
    SWV Trace logビューの図

10. 参考資料

開発環境:STM32CubeIDE(STMicroelectronics)
インストールガイド UM2563: STM32CubeIDE installation guide
ユーザーマニュアル UM2609: STM32CubeIDE ユーザ・ガイド
GNU Toolchain(Free Software Foundation)
アセンブラ ユーザーガイド The GNU Assembler Using as
リンカー ユーザーガイド The GNU linker Using ld
コンパイラ ユーザーガイド Using the GNU Compiler Collection
ボード:NUCLEO-H503RB(STMicroelectronics)
データシート DB2196: STM32 Nucleo-64 boards
回路図 MB1814-H503RB-C01 Board schematic
ユーザーマニュアル UM3121: STM32H5 Nucleo-64 board (MB1814)
MCU:STM32H503RBT6(STMicroelectronics)
データシート DS14053: STM32H503xx
リファレンスマニュアル RM0492: STM32H503 line Arm®-based 32-bit MCUs
プログラミングマニュアル PM0264: STM32 Cortex®-M33 MCUs programming manual
CPU:Cortex-M33(Arm Limited)
テクニカルリファレンスマニュアル 100230_0100_03_en: Arm Cortex-M33 Processor Technical Reference Manual
アーキテクチャリファレンスマニュアル DDI0553B.y: Armv8-M Architecture Reference Manual
Eclipseアセンブラファイルを使用する方法
Todotaniのはやり物Log:FM3マイコンをOpenOCD + Eclipseデバッグ https://todotani.cocolog-nifty.com/blog/2012/05/fm3openocd-ecli.html