第1章: ブートシーケンス#

リセット直後の CPU が何をするかを追いかけ、ベクタテーブル(例外や割り込みが起きたときに飛ぶ先のアドレスを並べた表)から main() に至る道筋を自分の手でたどります。

サンプルコード#

  • ディレクトリ: docs/os-on-arduino/code/01_boot

  • ビルドと書き込み:

uv run pio run -d 01_boot -t upload
uv run pio device monitor -b 115200

コードを見る: docs/os-on-arduino/code/01_boot

ソースコード#

この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。

docs/os-on-arduino/code/01_boot#

src/main.cpp — 63 行

GitHub で開く

 1/**
 2 * Chapter 1: Boot Sequence Check
 3 *
 4 * This sketch demonstrates the boot sequence by examining:
 5 * - BSS section (zero-initialized variables)
 6 * - DATA section (initialized variables copied from Flash)
 7 * - Vector table location
 8 * - Stack pointer
 9 */
10
11#include <Arduino.h>
12
13// Global variable in BSS section (uninitialized, should be zero)
14int bss_var;
15
16// Global variable in DATA section (initialized, copied from Flash)
17int data_var = 12345;
18
19void setup()
20{
21    Serial.begin(115200);
22    while (!Serial) { }
23    while (Serial.read() != 'S') { delay(1); }
24
25    Serial.println("=== Boot Sequence Check ===");
26    Serial.println();
27
28    // BSS section variable (should be zero-cleared)
29    Serial.print("bss_var (should be 0): ");
30    Serial.println(bss_var);
31
32    // DATA section variable (initial value should be copied)
33    Serial.print("data_var (should be 12345): ");
34    Serial.println(data_var);
35
36    Serial.println();
37    Serial.println("=== Memory Addresses ===");
38
39    // Show variable addresses
40    Serial.print("&bss_var:  0x");
41    Serial.println((uint32_t)&bss_var, HEX);
42
43    Serial.print("&data_var: 0x");
44    Serial.println((uint32_t)&data_var, HEX);
45
46    // Vector table location (VTOR register)
47    Serial.print("VTOR:      0x");
48    Serial.println(SCB->VTOR, HEX);
49
50    // Stack pointer
51    uint32_t sp;
52    __asm volatile ("MRS %0, MSP" : "=r" (sp));
53    Serial.print("MSP:       0x");
54    Serial.println(sp, HEX);
55
56    Serial.println();
57    Serial.println("If you see this, boot sequence completed!");
58}
59
60void loop()
61{
62    // Nothing to do
63}

docs/os-on-arduino/code/01_boot_vector_dump#

src/main.cpp — 60 行

GitHub で開く

 1// vector_dump.ino
 2// 第1章 演習1-2: ベクタテーブルの内容をダンプ
 3
 4#include <Arduino.h>
 5
 6void setup()
 7{
 8    Serial.begin(115200);
 9    while (!Serial) { }
10    while (Serial.read() != 'S') { delay(1); }
11
12    Serial.println("=== Vector Table Dump ===");
13    Serial.println();
14
15    // ベクタテーブルのアドレス(VTORから取得)
16    uint32_t *vtor = (uint32_t *)SCB->VTOR;
17
18    const char *names[] = {
19        "Initial SP",
20        "Reset",
21        "NMI",
22        "HardFault",
23        "MemManage",
24        "BusFault",
25        "UsageFault",
26        "Reserved",
27        "Reserved",
28        "Reserved",
29        "Reserved",
30        "SVCall",
31        "DebugMon",
32        "Reserved",
33        "PendSV",
34        "SysTick"
35    };
36
37    for (int i = 0; i < 16; i++) {
38        Serial.print("[");
39        if (i < 10) Serial.print(" ");
40        Serial.print(i);
41        Serial.print("] ");
42
43        // アドレス
44        char hex[16];
45        snprintf(hex, sizeof(hex), "0x%08lX", (unsigned long)vtor[i]);
46        Serial.print(hex);
47
48        // 名前
49        Serial.print("  ");
50        Serial.println(names[i]);
51    }
52
53    Serial.println();
54    Serial.println("Note: Handler addresses point to Flash (0x000xxxxx)");
55    Serial.println("      Initial SP points to RAM top (0x200xxxxx)");
56}
57
58void loop()
59{
60}

実機での出力#

Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。

S を 1 文字送ると出力が始まります。

../_images/ch01_boot.gif

モニタを開いてしばらくは何も出ず、S を送った瞬間にまとめて出てくるところ。 data_var が 12345 になっていれば、初期化済みデータのコピーが効いています#

=== Boot Sequence Check ===

bss_var (should be 0): 0
data_var (should be 12345): 12345

=== Memory Addresses ===
&bss_var:  0x200000C4
&data_var: 0x20000000
VTOR:      0x20007F00
MSP:       0x20007EC8

If you see this, boot sequence completed!
...

VTOR(Vector Table Offset Register)はベクタテーブルの置き場所を示すレジスタ、 MSP(Main Stack Pointer)はいま使っているスタックの位置を指すレジスタの値です。

演習1-2: ベクタテーブルの読み取り#

この出力は別のプロジェクト 01_boot_vector_dump のものです。書き込んでモニタを開き、 S を送ってください。

uv run pio run -d 01_boot_vector_dump -t upload
uv run pio device monitor -b 115200
=== Vector Table Dump ===

[ 0] 0x20007F00  Initial SP
[ 1] 0x000098DD  Reset
[ 2] 0x00009BA1  NMI
[ 3] 0x00006651  HardFault
[ 4] 0x000098D9  MemManage
[ 5] 0x000098D9  BusFault
[ 6] 0x000098D9  UsageFault
[ 7] 0x000098D9  Reserved
[ 8] 0x00000000  Reserved
[ 9] 0x00000000  Reserved
[10] 0x00000000  Reserved
[11] 0x000098D9  SVCall
[12] 0x000098D9  DebugMon
[13] 0x00000000  Reserved
[14] 0x000098D9  PendSV
[15] 0x000098D9  SysTick

Note: Handler addresses point to Flash (0x000xxxxx)
      Initial SP points to RAM top (0x200xxxxx)
...

右の名前は例外の種類です。主なものを挙げます。

  • NMI: 止める(マスクする)ことができない割り込み

  • HardFault: ほかで処理できない重大なエラーのときの例外。第6章で詳しく扱います

  • SVCall: svc 命令で呼び出す例外。応用編2でシステムコールの入り口に使います

  • PendSV: 「あとで実行して」と予約できる例外。第3章でタスクの切り替えに使います

  • SysTick: 一定周期で起きるタイマ割り込み。第4章でスケジューラの時計に使います

つまずきやすいポイント#

(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)