🛠️

Arduino Uno環境下で動く診断ワークフローツール「dvcdbg」をリリースしました

に公開

📝 本記事は技術詳細編です。開発ストーリーはこちらで読めます。
対象読者:Arduino Uno(ATmega328P)などメモリ制約の厳しい環境で、最小限の依存でログ/I2Cデバッグを回したい人。

dvcdbgとは

dvcdbgno_std 対応軽量デバッグ補助クレートです。
機能は以下。

  • SerialLogger による軽量ロギング
  • scan_i2c! によるI2Cアドレススキャン
  • write_hex!バッファHex表示
  • measure_cycles!実行サイクル計測
  • quick_diag!一発診断ワークフロー(I2Cスキャン+任意コードの計測)

開発環境

  • ボード:Arduino Uno R3(ATmega328P)
  • Rust toolchain:nightly(AVR向け)
  • 主要依存:
    • arduino-hal(Arduino向け HAL)
    • embedded-hal(抽象トレイト)
    • heapless(固定長コレクション)
    • dvcdbg(本記事の主役)

dvcdbgembedded-hal / heapless を内部で利用。no_std 前提・小さなバイナリを志向


導入(Cargo)

cargo add dvcdbg --features "macros"

マクロ群を使うなら features = ["macros"] を有効化

Cargo.toml の最小例:

[dependencies]
arduino-hal = "0.3"         # 例: 実環境に合わせて
dvcdbg = { version = "0.1.2", features = ["macros"] }

[profile.release]
lto = true
strip = true

release 時の LTO/strip でサイズ削減(README 記載)


1) シリアルロガーのセットアップ

dvcdbg::prelude::* をインポートすると主要型/マクロに一括アクセスできます。SerialLogger を Uno の default_serial! に被せるだけで文字列出力が可能。

use arduino_hal::prelude::*;
use dvcdbg::prelude::*;

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);

    let mut serial = arduino_hal::default_serial!(dp, pins, 57600);
    let mut logger = SerialLogger::new(&mut serial);

    // 最小ログ
    let _ = core::fmt::Write::write_str(&mut logger, "hello from dvcdbg");

    loop {}
}

(応用)既存シリアル型に core::fmt::Write を付与

impl_fmt_write_for_serial! マクロで、独自/別HALのシリアル型にも簡単に fmt::Write 実装を付与できます。

use dvcdbg::prelude::*;

// impl_fmt_write_for_serial!(TypeName, field_ident) のような形で
// ラッパー型に Write を実装できる想定(詳細は自分のHAL型に合わせて定義)
impl_fmt_write_for_serial!(MySerialWrapper, inner);

2) I2Cアドレスを一括スキャンする:scan_i2c!

配線やプルアップ、アドレス設定の初期切り分けは「まずスキャン」が鉄板。scan_i2c!0x03〜0x77 を走査して見つかったアドレスをロギングします。

use dvcdbg::prelude::*;

fn scan_bus<I2C: embedded_hal::i2c::I2c>(logger: &mut SerialLogger<impl core::fmt::Write>, i2c: &mut I2C) {
    scan_i2c!(logger, i2c);
}
  • 見つかったデバイスは logger に出力
  • no_std でも動作する軽量実装

マクロ名・存在は README の機能一覧に記載してあります


3) バッファの中身を16進で:write_hex!

センサ生データや I2C レジスタダンプを軽量に確認する場合に便利。

use dvcdbg::prelude::*;

fn dump(logger: &mut SerialLogger<impl core::fmt::Write>) {
    let bytes: [u8; 6] = [0xDE, 0xAD, 0xBE, 0xEF, 0x12, 0x34];
    write_hex!(logger, &bytes); // => "DE AD BE EF 12 34\n" のように出力
}

4) 実行時間/サイクルを測る:measure_cycles!

「このループ、どれくらい重い?」を計測器なしで大づかみに見ます。対象 HAL のタイマを渡して、ブロックの前後で計測します。

use dvcdbg::prelude::*;
use arduino_hal as hal;

fn bench(logger: &mut SerialLogger<impl core::fmt::Write>, timer1: hal::hal::timer::Timer1) {
    measure_cycles!(logger, timer1, {
        // 計測対象の処理
        expensive_operation();
    });
}

5) 一発診断:quick_diag!(スキャン+計測の合体)

セットアップ直後にI2Cが見えるか自前コードがどれくらい重いか一発で確認できます。

use dvcdbg::prelude::*;
use arduino_hal as hal;

fn quick(logger: &mut SerialLogger<impl core::fmt::Write>,
         i2c: hal::I2c,
         timer1: hal::hal::timer::Timer1) {
    quick_diag!(logger, i2c, timer1, {
        // ここに「試したい処理」を書く
        your_test_code();
    });
}

6) そのほか便利マクロ

  • loop_with_delay!:固定ディレイで試験ループ
  • assert_log!panic せずログに主張を出す

7) no_std × メモリ節約の実務Tips

  • heapless を活用:固定長バッファでヒープ不使用
  • LTO + strip:ビルド設定でサイズ最適化(上記 Cargo.toml 参照)
    → いずれも dvcdbg の方針と相性〇

8) 最小サンプル(Arduino Uno)

README の Arduino サンプルをベースに、ブート直後に I2C をスキャンし、簡単な処理を計測するまで。

use arduino_hal::prelude::*;
use dvcdbg::prelude::*;

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);

    // Serial
    let mut serial = arduino_hal::default_serial!(dp, pins, 57600);
    let mut logger = SerialLogger::new(&mut serial);

    // I2C & Timer
    let mut i2c = arduino_hal::I2c::new(
        dp.TWI,
        pins.a4.into_pull_up_input(),
        pins.a5.into_pull_up_input(),
        100_000,
    );
    let timer1 = arduino_hal::hal::timer::Timer1::new(
        dp.TC1,
        arduino_hal::hal::clock::Clock::new(),
    );

    // 1) I2Cスキャン
    scan_i2c!(logger, &mut i2c);

    // 2) 実行時間計測付きの一発診断
    quick_diag!(logger, i2c, timer1, {
        // ここに診断したい処理を書く(I/O叩き、簡単な描画、等)
        your_test_code();
    });

    loop {}
}

まとめ

  • dvcdbg は no_std 向けの最小デバッグ基盤:ログ、I2Cスキャン、計測をマクロで一括
  • Arduino Uno でも現実的に使える軽さ・依存の少なさ。
  • 導入は cargo add dvcdbg --features "macros" → prelude を import するだけ。

🧰 リポジトリ / ドキュメント

https://github.com/p14c31355/dvcdbg

https://docs.rs/dvcdbg/latest/dvcdbg/

Discussion