k-ogawa2025’s ブログ

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

ソースコードの説明:STM32CubeIDEを使ったアセンブリ言語プロジェクトの作成とデバッグ

「STM32CubeIDEを使ったアセンブリ言語プロジェクトの作成とデバッグ」の記事に記載したアセンブリ言語ソースコードについて、主にアセンブラ ディレクティブ(アセンブラに指示する命令)の意図を説明します。説明するソースコードでビルドと実行は上手くいきましたが、アセンブラ ディレクティブの理解が誤っていたけど結果オーライだったところがあるかもしれません。そういう前提で見てください。
説明するソースコードを記載した記事は、STマイクロエレクトロニクスの開発環境:STM32CubeIDEを使ってアセンブリ言語プログラムだけのビルドとデバッグを行った事例を紹介したものです。プログラムのターゲットは同じくSTマイクロエレクトロニクスMCUボード:NUCLEO-H503RBです。

k-ogawa2025.hatenablog.com

内容


1. ソースコード

もと記事に記載していたソースコードです。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

2. 統合構文の選択

    .syntax unified

命令セットの構文に新しい統合構文をつかう宣言を行います。

デフォルトは古い分割構文で、こちらだとV6T2 アーキテクチャ(およびそれ以降)で新しく追加された命令が使えません。

3. セクションの設定

    .text

ソースコード全体に「.text」をいうセクションの名前を付けます。

4. グローバルシンボルの設定

    .global Reset_Handler

シンボル「Reset_Handler」をグローバルシンボルに設定します。これによりエントリーポイントの設定で使っている「Reset_Handler」がリンカーから見えるようになります。

5. シンボルの設定

    .equ    SP_init,0x20008000      //スタックポインタ初期値
(以下省略)

シンボルに値を設定します。

6. ベクターテーブル

// 例外ベクター
Vecter_Table:
    .word   SP_init                 //初期スタックポインタ
    .word   Reset_Handler           //初期プログラムカウンタ
    .word   dummy_Handler           //NMI_Handler
(以下省略)

スタックポインタの初期値と例外ベクターテーブルを設定します。このベクターテーブルはSTM32H503RBのNSBOOTADDが示すアドレス(メーカ出荷時は0x0800 0000)から配置する必要があり、ソースコードの先頭に置いています。

スタックはSRAM2の最上位アドレスから使います。そのためSRAM2の(Cortex-M33におけるSRAM領域での)最上位アドレス+1である0x2000 8000をスタックポインタの初期値にします。

リセット例外以外の例外ベクターはすべて同じダミーハンドラーとしました。

このプログラムでは割り込みは使わないので割り込みベクターは実装していません。

7. シンボル「Reset_Handler」のタイプ設定

// リセットハンドラー
    .type   Reset_Handler, %function

リセットハンドラーの先頭番地を示すシンボル「Reset_Handler」を関数名としてマークする設定を行います。関数名としてマークすることによりアセンブラベクターテーブルのリセットベクターの値をReset_Handlerの値+1にします。これは例外ベクターのbit0は例外ハンドラー実行開始時のEPSRレジスタTビットを定義するからです。このTビットは使用する命令セットの選択をしますが、Cortex-M33はT32命令セットだけを使用するので常に1の必要があります。

8. リセットハンドラー

Reset_Handler:
    MOVW    R0,#:lower16:SP_limit   //スタックポインタ下限
    MOVT    R0,#:upper16:SP_limit
    MSR     MSPLIM,R0               //スタック下限値設定
    B       Main

スタック ポインター制限レジスタがあるのでスタックポインタの下限値を設定しています。

他のCPUではリセットハンドラーの最初でスタックポインターの初期値を設定すると思いますが、Cortex-M33はハードウェアでスタックポインターの初期値を設定するのでプログラムでの設定はいりません。

9. シンボル「dummy_Handler」のタイプ設定

// ダミーハンドラー
    .type   dummy_Handler, %function

ダミーハンドラーの先頭番地を示すシンボル「dummy_Handler」を関数名としてマークする設定を行います。

10. ダミーハンドラー

dummy_Handler:
    B       .                       //無限ループ

リセット以外の例外が発生した場合は無限ループに入れています。

B命令の分岐先は「.」と記しています。これはアセンブラの特殊ドット記号でアセンブルする現在のアドレスを参照します。

11. メイン処理

// メイン処理
Main:
(以下省略)

IO初期設定サブルーチンを呼んだあと、「LD2の点灯 → 500msディレイ サブルーチンを呼ぶ → LD2の消灯 → 500msディレイ サブルーチンを呼ぶ → LD2の点灯へ分岐」を繰り返します。

12. 500msディレイ サブルーチン

// Delay 500ms
Delay_500ms:
(以下省略)

500msのディレイはカウンタの1減算を0になるまで繰り返すことで得ています。カウンタ初期値はカットアンドトライで決めました。

13. ループの先頭命令のアドレス指定

    .align  4                       //ループの先頭アドレスを16Byte境界に置く
loop2:
    SUBS    R0,#1
    BNE     loop2
(以下省略)

「.align 4」はロケーションカウンタを2の4乗である16の倍数になるまで進め、空いたアドレスはNOP命令で埋められます。この「.align」ディレクティブは繰り返しループの先頭の減算命令のアドレスを16Byte境界の先頭に置くために使っています。

これは、STM32H503RBは内蔵FlashROMに128bit(=16Byte)の読み取りデータバッファがあり、最後に読み取られたデータを格納しています。そして同じ16Byte境界に属するデータを読み出された場合はこの読み取りデータバッファからデータを出力することで高速に読み出すことができます。このプログラムではSTM32H503RBの命令キャッシュ(ICACHE)を有効にしていないので、繰り返しループの減算命令と条件分岐命令の2つの命令が配置されたアドレスが同じ16Byte境界に属するかどうかによりループ1回の実行時間が異なってしまいます。そこで、繰り返しループの先頭の減算命令のアドレスを16Byte境界の先頭に置くことでそれより小さいアドレスに配置された他の命令のサイズの影響を受けることなく一定のループ1回の実行時間を確保しています。

14. IO初期設定 サブルーチン

// IO初期設定
IO_init:
(以下省略)

IO初期設定はLD2を点滅するのに必要最小限の下記の設定を行っています。

  • GPIOAのクロック有効化
  • PA5ポートを汎用出力に設定

15. 参考資料

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)

リファレンスマニュアル

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