Arduino UNOをインラインアセンブラで爆速Lチカ!標準関数との実行速度・バイナリサイズ徹底比較

Programming

要約まとめ

  • Arduino UNO でインラインアセンブラを使うと、LED 点滅 1 万回が4 msで完了。標準関数では67 ms ― およそ 1/16 の高速化
  • コンパイルサイズも640 B → 924 B284 B 削減。メモリ節約は地味に効く
  • asm volatile() ひとつでレジスタ直叩き。レジスタ名・ビット位置は ATmega328P のデータシートで必ず確認
  • ハードウェア制御・厳密タイミングが必要な場面では「C+インライン ASM」が最後の切り札
  • ただし可搬性ゼロ。ボードが違えばレジスタも違う ― “高速化の前に保守性” のジレンマも要チェック

アセンブラは「低級」だが扱う人は「超級」

「低級言語」と呼ばれるアセンブラは CPU レジスタを直接操作できる最短距離の言語。
だが 低級なのは抽象度であって、習得難度はむしろ高級。タイミング厳守の制御やメモリ節約が求められる組み込みでは今も現役だ。

ミニ用語解説:インラインアセンブラ

高級言語のソース内にアセンブリ命令を直接埋め込む仕組み。GCC 系では asm または __asm__ キーワードを用いる。C の利便性と ASM の速さを“いいとこ取り”できるが、CPU ごとに書き換え必須。

Arduino IDE でインライン ASM を書く手順

Arduino (GCC-AVR) では C/C++ ソース中に次の形で書ける。

asm volatile (
  "命令列"
  : "出力オペランド"
  : "入力オペランド"
  : "clobber"   // 破壊されるレジスタ
);
  • volatile ─ コンパイラ最適化で命令が消されるのを防ぐ
  • "I" ─ 即値 (Immediate) として渡す指定子
  • clobber ─ 副作用を宣言しレジスタ保存ミスを回避

キーワード早わかり

sbi … 指定 I/O レジスタのビットを Set (1)
cbi … 指定 I/O レジスタのビットを Clear (0)
DDRB … Data Direction Register B:1 で出力、0 で入力
PORTB … 実際の HIGH/LOW を出力するレジスタ

サンプル:インライン ASM で LED 点滅

ATmega328P(Arduino UNO)の 13 番ピン (PB5) を 1 Hz で点滅。

void setup() {
  DDRB |= (1 << 5);          // PB5 を出力設定
}

void loop() {
  asm volatile ("sbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I" (5)); // ON
  delay(1000);
  asm volatile ("cbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I" (5)); // OFF
  delay(1000);
}
IDE 例 ― 行番号で ASM がハイライト。ビルド時に C と一緒に最適化される

コンパイルサイズ比較

方式 コード バイナリサイズ
インライン ASM sbi / cbi 640 B
標準関数 digitalWrite() 924 B
中間案 pinMode だけ直書き 826 B

10000 回点滅ベンチマーク

LED を 10000 回 ON/OFF した合計時間を Serial.println() で出力。

標準関数版

digitalWrite(13, HIGH);
digitalWrite(13, LOW);

→ 67 ms

インライン ASM 版

asm volatile ("sbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I"(5));
asm volatile ("cbi %0, %1" :: "I" (_SFR_IO_ADDR(PORTB)), "I"(5));

→ 4 ms

640 B ― 小さくて速いバイナリは書き込みも一瞬
標準関数版は 924 B。ROM が 50 % 以上埋まる大型スケッチでは無視できない

設定チェックリスト

  • CPU クロックは 16 MHz 固定(UNO 基板デフォルト)を前提
  • 高速化前に -Os 最適化と LTO (Link Time Optimization) を有効に
  • レジスタ直接操作は自己責任:別ボードに流用する場合は全て書き換える

落とし穴と対策

  1. 可搬性ゼロ問題
    ボード変更=レジスタ変更。プロトタイピング段階で MCU 固定かを見極める
  2. デバッガ困難
    ASM 部分はステップ実行しにくい。Serial.print() 等で逐次確認を
  3. メンテナンス性
    チーム開発では「コメントは 2 倍」を合言葉に

FAQ ― よくある質問

Q. asm() と asm volatile() の違いは?

A. volatile を付けると最適化で命令が消えない。タイミング命令は必ず付ける。

Q. C++ のクラス内部でも使える?

A. 使えるが this ポインタが clobber されないよう十分注意。

Q. さらに速くするコツは?

A. ループ展開(アンローリング)と nop でサイクル合わせ。AVR Studio のクロックカウント機能が便利。

Q. 他の MCU(ESP32/M0 等)でも同じ?

A. 命令セットもレジスタも違うため別物。ARM なら「__asm__ GCC 構文」で書き直し。

Q. オーバークロックしたらもっと速い?

A. 可能だが安定動作保証外。タイマー割り込み周期も全て再計算が必要。

次の一手

今回の 1 Hz LED 点滅は“Hello ASM”。次は SPI 自前実装PWM 無段階調光 に挑戦してみよう。
関連記事は当ブログ内「10 GbE と Arduino のハード制御」にも追記予定。

タイトルとURLをコピーしました