Contents
1. データ型と演算子
組み込み開発では メモリ効率 が最重要課題です。
C 言語は静的型付けなので、変数のサイズや符号をコンパイル時に確定できます。適切な幅・符号を選ばないと、RAM 数十 KB しかないマイコンで メモリ不足 に陥りやすくなります。
|
1 2 3 4 5 6 |
#include <stdint.h> // 標準幅整数型 int8_t small_val = -12; // 1 バイト符号付き uint16_t timer_cnt = 0x03FFu; // 2 バイト符号なし float voltage = 3.3f; // 浮動小数点(必要最小限に) |
- 整数型は必ず幅を意識し、
int8_t・uint16_tのようにstdint.hを使うのが安全です。 - 浮動小数点演算はハードウェアが無い MCU ではソフトエミュレーションになるため、使用は極力避けます。
演算子の優先順位については Zenn の Chapter 5([参照¹])で表形式にまとめられています。基本は
* / %が+ -より高く、比較演算子はその下位です。
1‑1. 演算子の実践的な使い方
| 演算子 | 用途例 | メモ |
|---|---|---|
& (ビットAND) |
レジスタの特定ビットだけ抽出 | (reg & 0x01u) |
|= (ビットOR代入) |
複数フラグを同時に立てる | REG |= FLAG_A \| FLAG_B; |
<< / >> |
ビットシフトで割り算・掛け算の代替 | value << 3 は *8 と同等 |
2. 制御構文
組み込みコードは 状態遷移 や タイミング処理 が中心です。
if / for / while / switch を過度に入れ子にすると可読性が下がり、最適化もしにくくなるので、ロジックはできるだけフラットに保ちましょう。
|
1 2 3 4 5 6 |
/* LED を 1 秒間隔で点滅させる例(擬似コード) */ while (1) { LED_PORT ^= LED_PIN; // トグル for (volatile uint32_t i = 0; i < 1000000U; ++i) { /* ディレイ */ } } |
volatileを付けた変数は最適化対象外になるため、ハードウェアレジスタへのアクセスが必ず実行されます(Zenn Chapter 6[参照²])。- ループ内の空ディレイは CPU がフリーランニングで消費電力を上げる原因 になるので、実機ではタイマ割り込みに置き換えることが推奨されます。
3. ポインタとハードウェアレジスタ操作
ポインタは「メモリ上の任意のアドレス」へ直接アクセスできる唯一の手段です。組み込み MCU の GPIO・タイマなどは 物理アドレスにマッピング されているため、volatile ポインタで安全に操作します。
|
1 2 3 4 5 6 7 8 9 10 |
#define RCC_AHB1ENR ((volatile uint32_t*)0x40023830U) #define GPIOC_MODER ((volatile uint32_t*)0x48000800U) #define GPIOC_ODR ((volatile uint32_t*)0x48000814U) void led_init(void) { *RCC_AHB1ENR |= (1U << 2); // GPIOC クロック有効化 *GPIOC_MODER &= ~(0x3U << (13*2)); // 入力クリア *GPIOC_MODER |= (0x1U << (13*2)); // 出力設定 } |
- アドレスは データシート に必ず記載されているので、コピーミスが起きないようにマクロ化しておくと安心です。
*(volatile uint32_t*)addrの形で「読み書き」どちらも同じ構文になる点が、C 言語の大きな利点です。
本節のコードは STM32F4 系列(PC13 LED)を対象にしています。別シリーズの場合はレジスタベースアドレスとビット位置が変わりますので、必ず Reference Manual を確認してください。
4. WSL 上での開発環境構築
4‑1. WSL のインストールと Ubuntu 設定
|
1 2 3 4 5 6 7 8 |
# PowerShell(管理者)で実行 wsl --install -d Ubuntu-22.04 # WSL2 + Ubuntu 22.04 のインストール wsl # Ubuntu シェルへ移行 # Ubuntu 内で必要ツールを取得 sudo apt update sudo apt install build-essential gdb openocd git |
build-essential に含まれる gcc は 12 系列(2024 年リリース) がデフォルトです。ただし、Ubuntu のバージョンやリポジトリの更新状況により 11 系列や 13 系列がインストールされることもあります。以下のコマンドで実際のバージョンを必ず確認しましょう。
|
1 2 |
gcc --version # 例: gcc (Ubuntu 12.2.0-3ubuntu1) 12.2.0 |
注意:特定の GCC バージョンが必要な場合は
sudo apt install gcc-12のようにバージョン指定でインストールできます。また、update-alternativesを使ってデフォルトコンパイラを切り替えることも可能です。
4‑2. クロスコンパイラと追加ツール
組み込み向けには ARM 用クロスコンパイラ が必須です。WSL の apt リポジトリから以下のパッケージを取得できます。
|
1 2 |
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi |
Makefile で使用する典型的な設定例は次の通りです。
|
1 2 3 4 5 |
CC = arm-none-eabi-gcc CFLAGS = -mcpu=cortex-m4 -mthumb -Os -Wall \ -ffunction-sections -fdata-sections LDFLAGS = -Tstm32f4.ld -Wl,--gc-sections |
-Osは サイズ最適化、-ffunction-sections / -fdata-sectionsと--gc-sectionsの組み合わせで未使用コードを除去します。- さらに細かいオプションは Zenn Chapter 7・8([参照³])で解説されています。
5. ビルドフローとデバッグツール設定
5‑1. Makefile の全体像
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# ------------------------------------------------- # Simple Makefile for STM32F4 LED Blink # ------------------------------------------------- TARGET := led_blink SRC := main.c system.c gpio.c OBJS := $(SRC:.c=.o) CC := arm-none-eabi-gcc CFLAGS := -mcpu=cortex-m4 -mthumb -Os \ -ffunction-sections -fdata-sections -Wall LDFLAGS := -Tstm32f4.ld -Wl,--gc-sections all: $(TARGET).elf $(TARGET).elf: $(OBJS) $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) clean: rm -f *.o *.elf |
5‑2. OpenOCD と GDB の連携手順
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1️⃣ OpenOCD をバックグラウンドで起動(ST-Link 用設定例) openocd -f interface/stlink.cfg -f target/stm32f4x.cfg & # 2️⃣ GDB で接続し、書き込み・デバッグを実行 arm-none-eabi-gdb build/led_blink.elf <<'EOF' target remote localhost:3333 load # フラッシュへ書き込み monitor reset halt # デバイスリセット & 停止 break main # エントリポイントでブレーク continue EOF |
monitorコマンドは OpenOCD に対する指示です。デバッグ中に 安全なリセット を行うのに便利です(Zenn Chapter 6[参照²])。- GDB の
info registers、x/4xb 0x40023830などでレジスタ内容を直接確認できます。
6. 実践例 ― LED 点滅プログラム
6‑1. STM32F4 (PC13) 用コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <stdint.h> #define RCC_AHB1ENR ((volatile uint32_t*)0x40023830U) #define GPIOC_MODER ((volatile uint32_t*)0x48000800U) #define GPIOC_ODR ((volatile uint32_t*)0x48000814U) static inline void delay(volatile uint32_t cnt) { while (cnt--) { __asm__ volatile ("nop"); } } int main(void) { *RCC_AHB1ENR |= (1U << 2); // GPIOC クロック有効化 *GPIOC_MODER &= ~(0x3U << (13*2)); // 入力クリア *GPIOC_MODER |= (0x1U << (13*2)); // 出力設定 while (1) { *GPIOC_ODR ^= (1U << 13); // LED トグル delay(1000000U); } } |
static inlineにすることで 関数呼び出しオーバーヘッド を除去し、サイズと速度の両方を削減できます。- ディレイは CPU が何もしない状態になるため 電力消費が増える 点に注意してください。実機ではタイマ割り込みへ置き換えましょう。
6‑2. Arduino UNO (ATmega328P) 用コード
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <avr/io.h> #include <util/delay.h> int main(void) { DDRB |= (1 << DDB5); // PB5(内蔵 LED)を出力に設定 while (1) { PORTB ^= (1 << PORTB5); // トグル _delay_ms(500); } } |
コンパイルと書き込みの流れは次の通りです。
|
1 2 3 4 5 6 7 8 9 10 |
# コンパイル(サイズ最適化) avr-gcc -mmcu=atmega328p -Os -Wall -o led.elf led.c # ELF → HEX 変換 avr-objcopy -O ihex led.elf led.hex # USB 経由で書き込み avrdude -c arduino -p m328p -P /dev/ttyUSB0 -b 115200 \ -U flash:w:led.hex:i |
WSL からシリアルポートへアクセスするには、
sudo usermod -a -G dialout $USERでユーザーをdialoutグループに追加し、一度ログアウト・再ログインしてください。
7. リソース制約を考慮したベストプラクティス
| 項目 | 推奨設定 / コツ |
|---|---|
| volatile の使用範囲 | 必要箇所(ハードウェアレジスタ、割込みフラグ)に限定し、最適化の阻害を最小限に。 |
| 関数インライン化 | 小さなユーティリティは static inline にして呼び出しコスト削減。 |
| コードサイズ削減 | -Os -ffunction-sections -fdata-sections とリンク時の --gc-sections を必ず使用。 |
| スタック管理 | RTOS 未使用なら 1 KB 未満を目安に、関数呼び出し深さとローカル配列サイズを意識。 |
| サイズ確認 | ビルド後は arm-none-eabi-size で text / data / bss をチェックし、必要なら最適化フラグを追加調整。 |
7‑1. 実行時サイクルの測定例
|
1 2 3 4 5 6 |
# gprof 用にコンパイル(-pg オプション) arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -pg -o led_gprof.elf main.c ... # 実機で実行後、gprof 解析 arm-none-eabi-gprof led_gprof.elf gmon.out > profile.txt |
profile.txt に関数ごとのサイクル使用率が出力されるので、ボトルネック関数の最適化に役立ちます。
8. 次のステップ
- 本ガイドで構築した WSL 環境と Makefile を使い、Zenn のサンプルリポジトリ([参照⁴])をクローンしてビルドしてみましょう。
- LED 点滅が成功したら GPIO 割込み や PWM 出力 へ拡張し、割込みハンドラの書き方やタイマ設定を学習してください。
- デバッグ経験を積むことでレジスタマップへの理解が深まり、最終的には FreeRTOS 等のリアルタイム OS を導入した大規模プロジェクトへステップアップできます。
参考文献・リンク
| 番号 | タイトル / 説明 | URL |
|---|---|---|
| ¹ | Zenn – 演算子と式の優先順位(Chapter 5) | https://zenn.dev/yourname/articles/operator-precedence |
| ² | Zenn – volatile と最適化抑止(Chapter 6) |
https://zenn.dev/yourname/articles/volatile-usage |
| ³ | Zenn – GCC のビルドオプション解説(Chapter 7・8) | https://zenn.dev/yourname/articles/gcc-options |
| ⁴ | Zenn – 組み込み C 言語サンプルコード(GitHub リポジトリ) | https://github.com/yourname/embedded-c-samples |