Arduino Uno環境下で動く診断ワークフローツール「dvcdbg」をリリースしました
📝 本記事は技術詳細編です。開発ストーリーはこちらで読めます。
対象読者:Arduino Uno(ATmega328P)などメモリ制約の厳しい環境で、最小限の依存でログ/I2Cデバッグを回したい人。
dvcdbgとは
dvcdbg
は no_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
(本記事の主役)
-
dvcdbg
はembedded-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);
scan_i2c!
2) 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 の機能一覧に記載してあります
write_hex!
3) バッファの中身を16進で:センサ生データや 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" のように出力
}
measure_cycles!
4) 実行時間/サイクルを測る:「このループ、どれくらい重い?」を計測器なしで大づかみに見ます。対象 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();
});
}
quick_diag!
(スキャン+計測の合体)
5) 一発診断:セットアップ直後に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 するだけ。
🧰 リポジトリ / ドキュメント
Discussion