前回は、QEMU (ターゲットは STM32F4-Discovery)で動かしたサンプルソースのスタートアップルーチン以外の内容を確認しました。
今回は、サンプルソースのスタートアップルーチンの内容の確認と、アセンブラの動作の確認をしていきます。
それでは、やっていきます。
参考文献
STM32F4 のマニュアル
下記リンクのドキュメント→リファレンスマニュアル、ドキュメント→プログラミングマニュアルなどを参照してください。最近は、マニュアルが日本語化されていて、とても便利です。
デザイン/サポート | STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品
GNU アセンブラのマニュアル
英語しか見つかりませんでした。
https://sourceware.org/binutils/docs/as/
https://sourceware.org/binutils/docs/as/ARM-Directives.html
はじめに
「QEMUを動かす」の記事一覧です。良かったら参考にしてください。
QEMUを動かすの記事一覧
今回も、Interface のサンプルソースを使わせていただきます。
サンプルソースは、Interfaceのホームページからダウンロードします。
下記の「7月号 仮想から実機まで マイコン開発入門」の「特集 第3部第1章 エミュレータQEMUを活用した開発の手引き」の「関連ファイル一式」をダウンロードします。
www.cqpub.co.jp
それでは、やっていきます!
startup.S
スタートアップルーチンとは、マイコンが起動して最初に動くプログラムです。
理解する上で重要なレジスタ一覧と、メモリマップを貼っておきます(プログラミングマニュアルより)。
先頭からベクタテーブルまで
早速、スタートアップルーチンの先頭から見ていきます。
.syntax unified
:ARM命令とThumb命令で、統一した書き方をする宣言
.thumb
:Thumb命令で機械語を生成させる宣言
.section .isr_vector,"a",%progbits
:セクションの定義、.isr_vectorはリンカスクリプト(stm32f407vg.ld)で定義されている、"a"はセクション割り当て可能、%progbitsはこのセクションにdataを含んでいることを示す
.align 2
:2byteアライメントに合わせます(既にアライメントが合ってる場合は何もしない)
.globl __Vectors
:外部ファイルに公開する宣言
これ以降は、先頭がスタックアドレスの初期値、その後、4byteごとに、例外、割り込みの飛び先のアドレスが格納されています。
.syntax unified
.thumb
.section .isr_vector,"a",%progbits
.align 2
.globl __Vectors
__Vectors:
.long __main_stack_start
.long Reset_Handler
.long NMI_Handler
.long HardFault_Handler
.long MemManage_Handler
.long BusFault_Handler
.long UsageFault_Handler
(以降のベクタテーブルは省略)
リセットハンドラからモード設定まで
先ほどのベクタテーブルにあったリセットハンドラの飛び先です。
.text
:テキストセクションの宣言
.thumb_func
:この次にあるシンボルがThumbエンコードされた関数であることの宣言
.type Reset_Handler, %function
:このシンボル(この場合はReset_Handler)のタイプ(この場合は%function)を設定する(通常のラベルではなく関数であるという宣言)
Reset_Handler:
:関数ラベル(飛び先)
mrsは、controlレジスタの値をr0レジスタにコピーしています。__process_stack_start
と __main_stack_start
はリンカスクリプト(stm32f407vg.ld)でアドレスが定義されています。
orrは論理和で、controlレジスタのbit1をセットするためです(スタックポインタはPSPを有効にする)。bicはビットクリアで、controlレジスタのbit0をクリアするためです(特権モードにしてる)。
よく分からなかったのが、コメントには非特権レベルにするとありますが、特権レベルになってると思います。main関数でブレークポイントを設定して、デバッガでcontrolレジスタを確認しましたが、0x02となっていて、特権レベルに見えます。まぁ気にしないことにします。
b(分岐)の前の最後の3行のmrsは、それぞれのレジスタに設定した内容(汎用レジスタの内容)を転送しています。
.text
.align 2
.global Reset_Handler
.thumb_func
.type Reset_Handler, %function
Reset_Handler:
mrs r0, control
ldr r1, =__process_stack_start
orr r0, r0,
ldr r2, =__main_stack_start
bic r0, r0,
msr psp, r1
msr msp, r2
msr control, r0
b l1
data、bssセクションの初期化から最後まで
.ltorg
:リテラル定数をこの位置にダンプさせる宣言
dataセクションは初期値付きのグローバル変数の領域です。_sidata
は初期値を格納している領域で、_sdata
が dataセクションの開始アドレス、_edata
は終了アドレスです。
_sidata
と _sdata
のアドレスを比較して、同じだったら該当するデータがないので、bssセクションの初期化に飛びます。そうでなければ、1:
のラベルに進みます。
1:
のラベルからは、実際に初期値をdataセクションにコピーします。_sdata
と _edata
を比較して異なる場合(まだdataセクションが続いてる)、r3レジスタを介してコピーします。itt
は、if thenの命令で、neが真ならneが付いた命令を実行します。ldrne
と strne
を実行すると、r0とr1は4byteインクリメントされます。これでdataセクションの初期化が完了します。
bssセクションの初期化も同じような感じです。bssセクションの初期化が完了すると、main関数にジャンプします。
.ltorg
.thumb
l1:
ldr r0, =_sidata
ldr r1, =_sdata
ldr r2, =_edata
cmp r0, r1
beq 2f
1:
cmp r1, r2
itt ne
ldrne r3, [r0],
strne r3, [r1],
bne 1b
2:
mov r0,
ldr r1, =_sbss
ldr r2, =_ebss
3:
cmp r1, r2
bge 4f
str r0, [r1],
b 3b
4:
ldr r2, =main
bx r2
この後は、各例外ハンドラの同じ記述が続くだけなので、省略します。
デバッガでスタートアップルーチンの動きを確認する
ソースコードで確認した内容を、実際にデバッガを使って確認してみます。
事前に、Windows→Show View で、「Disassemblyビュー」と「Registersビュー」を起動しておきます。
下図は、main関数で停止した状態の画面です。
startup.S を開き、Reset_Handler の先頭にブレークポイントを設定して、再実行します。ブレークポイントの設定は、行番号の少し左あたりをダブルクリックすると設定できます(下図)。
あとは、ステップ実行していくと、スタートアップルーチンの動作が確認できます。
おわりに
今回は、スタートアップルーチンの内容の確認と、実際にスタートアップルーチンをデバッガでステップ実行して、理解を深めました。これで、サンプルソースの全体を見たことになります。
次回は、リンカスクリプトの内容を見ていきたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。