組み込み開発は「実機がないと始められない」と思っていませんか?
実は、QEMU(キューエミュ)という仮想マシンエミュレータを使えば、PC上だけでベアメタル開発を始めることができます。しかも、QEMUは無料で使えて、環境構築も意外と簡単です。
本記事では、Windows環境を対象に、
という最小限のステップで、OSなしで動くCプログラムをQEMU上で動かす手順を丁寧に解説します。
CMakeのセットアップが済んでいない方は、先に こちらの環境構築ガイド を完了してから読み進めるとスムーズです。こちらもチェックしてみてください。
それでは、どうぞ。
開発に必要な3つのツールをインストールする
ベアメタル開発環境を構築するには、次の3つのツールをインストールします。
インストールするツール一覧
ツール名 | 目的 | 入手方法 |
---|---|---|
QEMU | 仮想マシン実行環境 | 公式インストーラ |
arm-none-eabi-gcc | ARM向けクロスコンパイラ | 公式インストーラ |
Ninja | 高速ビルドツール | wingetで導入 |
QEMUのインストール
QEMUは、エミュレーションにより仮想ハードウェア上でコードを実行できるツールです。
以下のサイトから qemu-w64-setup-*.exe
をダウンロードします。
ダウンロードした .exe
ファイルを実行して、QEMUをインストールします。
インストールディレクトリ(例:C:\Program Files\qemu
)に qemu-system-arm.exe
が含まれていればOKです。インストールディレクトリのパスを通します。
ARM用クロスコンパイラのインストール
ここでは Arm 公式の GNU Toolchain(arm-none-eabi)を使用します。
以下のURLから「Windows, AArch32 bare-metal target (arm-none-eabi)」のインストーラをダウンロードします。
こちらはパスは通さなくてOKです。後述する方法でビルド時のみ一時的にパスを追加する形で作業します。
Ninjaのインストール(winget使用)
ターミナルを開いて以下のコマンドを実行してください。
winget install Ninja-build.Ninja
インストール後、以下のコマンドで確認します。
ninja --version
補足:arm-none-eabi-gcc の PATH を一時的に通す
cmakeによるビルド操作などを行う前にPowerShell で環境変数を一時的に追加します。
$env:PATH = "C:\Path\To\ArmGCC\bin;" + $env:PATH
※ C:\Path\To\ArmGCC\bin
は、実際のインストール先に置き換えてください。
※ 永続化はせず、一時的な追加で構いません。
ここまでで、以下の3つのコマンドが実行できるようになっていれば準備完了です。
qemu-system-arm --version
arm-none-eabi-gcc --version
ninja --version
QEMUが正しく動くか確認する
QEMUのインストールが完了したら、仮想マシンが正常に起動するかどうかを確認してみましょう。
versatilepb ボードで仮想マシンを起動する
次のようにコマンドを実行してみましょう。
qemu-system-arm -M versatilepb -nographic
この時点では何も表示されずに止まっていて問題ありません。次のステップで、仮想マシン上で動作するプログラムを作って、UART出力を表示させます。
最小構成のベアメタルプロジェクトを作る
以下のような構成でプロジェクトを作成します。
qemu-arm-bare-metal/
├── CMakeLists.txt
├── toolchain_arm.cmake
├── linker/
│ └── arm_versatilepb.ld
├── src/
│ ├── main.c
│ ├── startup.s
│ ├── syscalls.c
│ └── uart.c
それでは実際にファイルの中身を見ていきます。
main.c
#include <stdio.h>
void main(void) {
printf("Hello Embedded from QEMU\n");
while (1){}
}
uart.c
#define UART_BASE 0x101f1000
#define UART_DR (*(volatile unsigned int *)(UART_BASE + 0x00))
#define UART_FR (*(volatile unsigned int *)(UART_BASE + 0x18))
#define UART_FR_TXFF 0x20
void uart_putc(char c) {
while (UART_FR & UART_FR_TXFF) {
}
UART_DR = c;
}
void uart_puts(const char *s) {
while (*s) {
uart_putc(*s++);
}
}
int putchar(int c) {
uart_putc((char)c);
return c;
}
startup.s
.global _start
_start:
ldr sp, =_stack_end
bl main
b .
linker.ld
ENTRY(_start)
MEMORY
{
RAM : ORIGIN = 0x00010000, LENGTH = 32M
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text .text.*)
PROVIDE(_text_end = .);
} > RAM
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} > RAM
.data : {
PROVIDE(_data_start = .);
*(.data .data.*)
PROVIDE(_data_end = .);
} > RAM
.bss : {
PROVIDE(_bss_start = .);
*(.bss .bss.*)
PROVIDE(_bss_end = .);
} > RAM
PROVIDE(_end = _bss_end);
.stack : {
. = ALIGN(16);
PROVIDE(_stack_start = .);
. += 4096;
PROVIDE(_stack_end = .);
} > RAM
}
toolchain_arm.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CROSS_COMPILE_PREFIX arm-none-eabi-)
set(CMAKE_C_COMPILER ${CROSS_COMPILE_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_PREFIX}g++)
set(CMAKE_ASM_COMPILER ${CROSS_COMPILE_PREFIX}as)
set(CMAKE_OBJCOPY ${CROSS_COMPILE_PREFIX}objcopy)
set(CMAKE_OBJDUMP ${CROSS_COMPILE_PREFIX}objdump)
set(CMAKE_SIZE ${CROSS_COMPILE_PREFIX}size)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(baremetal C CXX ASM)
add_executable(${PROJECT_NAME}.elf
src/startup.s
src/main.c
src/syscalls.c
src/uart.c
)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=arm926ej-s")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -mcpu=arm926ej-s")
target_link_options(${PROJECT_NAME}.elf PRIVATE -T${CMAKE_SOURCE_DIR}/linker/linker.ld)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -g")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -g")
target_link_options(${PROJECT_NAME}.elf PRIVATE
-nostartfiles
-lc
)
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
)
CMakeでビルドして実行用バイナリを作成する
cmake -S . -B build -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="toolchain_arm.cmake"
cmake --build build
build/にbaremetal.elf、baremetal.binが生成されていれば成功です。
QEMU上で「Hello Embedded from QEMU」を表示する
qemu-system-arm -M versatilepb -nographic -kernel build/baremetal.bin
表示結果
Hello Embedded from QEMU
まとめ
本記事では、以下のステップで実機なしのベアメタル開発を実現しました:
この構成をベースに、割り込み処理やRTOS、デバッグ機能なども拡張できます。
GDBによるQEMUデバッグ方法も記事を書く予定です。お楽しみに。
コメント