「STM32CubeIDEを使ったアセンブリ言語プロジェクトの作成とデバッグ」の記事に記載したアセンブリ言語のソースコードについて、主にアセンブラ ディレクティブ(アセンブラに指示する命令)の意図を説明します。説明するソースコードでビルドと実行は上手くいきましたが、アセンブラ ディレクティブの理解が誤っていたけど結果オーライだったところがあるかもしれません。そういう前提で見てください。
説明するソースコードを記載した記事は、STマイクロエレクトロニクスの開発環境:STM32CubeIDEを使ってアセンブリ言語プログラムだけのビルドとデバッグを行った事例を紹介したものです。プログラムのターゲットは同じくSTマイクロエレクトロニクスのMCUボード:NUCLEO-H503RBです。
内容
- 1. ソースコード
- 2. 統合構文の選択
- 3. セクションの設定
- 4. グローバルシンボルの設定
- 5. シンボルの設定
- 6. ベクターテーブル
- 7. シンボル「Reset_Handler」のタイプ設定
- 8. リセットハンドラー
- 9. シンボル「dummy_Handler」のタイプ設定
- 10. ダミーハンドラー
- 11. メイン処理
- 12. 500msディレイ サブルーチン
- 13. ループの先頭命令のアドレス指定
- 14. IO初期設定 サブルーチン
- 15. 参考資料
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. 参考資料
|
アセンブラ ユーザーガイド |
The GNU Assembler Using as |
|
リンカー ユーザーガイド |
The GNU linker Using ld |
|
コンパイラ ユーザーガイド |
Using the GNU Compiler Collection |
|
データシート |
DB2196: STM32 Nucleo-64 boards |
|
回路図 |
MB1814-H503RB-C01 Board schematic |
|
ユーザーマニュアル |
UM3121: STM32H5 Nucleo-64 board (MB1814) |
|
リファレンスマニュアル |
RM0492: STM32H503 line Arm®-based 32-bit MCUs |
|
プログラミングマニュアル |
PM0264: STM32 Cortex®-M33 MCUs programming manual |
|
テクニカルリファレンスマニュアル |
100230_0100_03_en: Arm Cortex-M33 Processor Technical Reference Manual |
|
アーキテクチャリファレンスマニュアル |
DDI0553B.y: Armv8-M Architecture Reference Manual |