daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

STM32(ARM Cortex-M)をQEMUで動かす(スタートアップルーチン編)

前回は、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ごとに、例外、割り込みの飛び先のアドレスが格納されています。

/* Definition of Section */
        .syntax unified
        .thumb
        .section .isr_vector,"a",%progbits

    .align  2
    .globl  __Vectors
__Vectors:
        .long   __main_stack_start    /* Main stack pointer (MSP) */
        .long   Reset_Handler         /* Reset Handler */
        .long   NMI_Handler           /* NMI Handler */
        .long   HardFault_Handler     /* Hard Fault Handler */
        .long   MemManage_Handler     /* MPU Fault Handler */
        .long   BusFault_Handler      /* Bus Fault Handler */
        .long   UsageFault_Handler    /* Usage Fault 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

/* Reset Handler */
        .global Reset_Handler
        .thumb_func
        .type   Reset_Handler, %function
Reset_Handler:

        /* スレッドモード(PSP, 非特権レベル)で動作するように設定 */
        mrs     r0,  control
        ldr     r1,  =__process_stack_start
        orr     r0,  r0,  #0x02
        ldr     r2,  =__main_stack_start
        bic     r0,  r0,  #0x01
        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が付いた命令を実行します。ldrnestrne を実行すると、r0とr1は4byteインクリメントされます。これでdataセクションの初期化が完了します。

bssセクションの初期化も同じような感じです。bssセクションの初期化が完了すると、main関数にジャンプします。

        .ltorg
        .thumb
l1:

        /* Copy the data sections. */
        ldr     r0,  =_sidata
        ldr     r1,  =_sdata
        ldr     r2,  =_edata
        cmp     r0,  r1
        beq     2f
1:
        cmp     r1,  r2
        itt     ne
        ldrne   r3, [r0], #4
        strne   r3, [r1], #4
        bne     1b
2:
        /* Zero fill the bss segment. */
        mov     r0,  #0
        ldr     r1,  =_sbss
        ldr     r2,  =_ebss
3:
        cmp     r1,  r2
        bge     4f
        str     r0,  [r1],  #4
        b       3b
4:

        /* Call the application's entry point.*/
        ldr     r2, =main
        bx      r2

この後は、各例外ハンドラの同じ記述が続くだけなので、省略します。

デバッガでスタートアップルーチンの動きを確認する

ソースコードで確認した内容を、実際にデバッガを使って確認してみます。

事前に、Windows→Show View で、「Disassemblyビュー」と「Registersビュー」を起動しておきます。

下図は、main関数で停止した状態の画面です。

main関数で停止しているところ
main関数で停止しているところ

startup.S を開き、Reset_Handler の先頭にブレークポイントを設定して、再実行します。ブレークポイントの設定は、行番号の少し左あたりをダブルクリックすると設定できます(下図)。

Reset_Handlerの先頭で停止させたところ
Reset_Handlerの先頭で停止させたところ

あとは、ステップ実行していくと、スタートアップルーチンの動作が確認できます。

おわりに

今回は、スタートアップルーチンの内容の確認と、実際にスタートアップルーチンをデバッガでステップ実行して、理解を深めました。これで、サンプルソースの全体を見たことになります。

次回は、リンカスクリプトの内容を見ていきたいと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。