1.はじめに
先日、とある勉強会で学んだことを、組み込み開発に適用出来るかを試行してみました。
ここでは、仕様駆動開発の考え方に則って、簡単なArduino向けのコードをGemini CLIから作ってみました。
2.仕様駆動開発の試行
- Gemini CLIの環境準備については、こちらを参照願います。
GEMINI.md ファイルを使用して Gemini CLI をカスタマイズし、Arduino向けのコードを生成してみます。
(1)仕様書を作る
Arduino版の仕様駆動開発の参考資料があまり見かけないので、 取っ掛かりのGEMINI.mdは、下記の条件を与えて、Geminiに作ってもらいました。
ここで試用したマイコンは、ややマイナーな、Teensy 4.0というデバイスをしていしましたが、その他のマイコンを指定してOKです。
GEMINI.mdを作ってください。 ・ArduinoIDEで直接開けるプロジェクトを、カレントディレクトリに作ってください。プロジェクト名はgenblinkとします ・マイコンはTeency4.0を使います。 ・マイコンボードに搭載のLEDを1秒おきに点滅します。 ・点滅はタイマー割込で実装します。 ・点滅に合わせて、USB-Serialに、マイコンが起動してからの時刻(0.1秒単位)と、点灯と消灯の状態を、1行の文字列で出力してください。(点灯=ON、消灯=OFFと表示してください) ・サンプルコードの部分は不要です ・コメントは日本語で書いてください
ここから生成された仕様書です。長いので省略します。
# 💡 genblink: Teensy 4.0 LED点滅と高精度シリアルロギング ## 📌 概要 本プロジェクトは、**Teensy 4.0** マイコンボードの搭載LEDを**タイマー割込**によって1秒周期で点滅させます。同時に、LEDの状態変化(点灯/消灯)とは独立して、**0.1秒周期**でUSB-Serial経由で現在の稼働時間($0.1$秒単位)とLEDの状態をログ出力します。 ### 📅 実装内容 (省略)
(2)仕様書の手直し
このなかで、意図通りになっていない箇所があったので、下記の様に修正しました。
①ディレクトリ構成が、最終的に下記のイメージになるように、仕様を修正しています。

## 📁 プロジェクト構成
このプロジェクトは、**Arduino IDEで直接開けるプロジェクト**として、カレントディレクトリに以下の構成で作成します。
/
├──genblink/
| └── genblink.ino <-- メインのスケッチファイル (実装が必要)
└── GEMINI.md <-- このドキュメント
②USB-Serialに出力するログの形式を、時刻、コロン、LEDの状態を文字列で表示するようにしました。
**期待される出力例:** 0 : OFF 1 : ON 2 : OFF ... 9 : OFF 10 : ON ...
最終的な仕様書としては、下記の内容としました。
# 💡 genblink: Teensy 4.0 LED点滅と高精度シリアルロギング ## 📌 概要 本プロジェクトは、**Teensy 4.0** マイコンボードの搭載LEDを**タイマー割込**によって1秒周期で点滅させます。同時に、LEDの状態変化(点灯/消灯)とは独立して、**0.1秒周期**でUSB-Serial経由で現在の稼働時間($0.1$秒単位)とLEDの状態をログ出力します。 ### 📅 実装内容 * **マイコンボード**: Teensy 4.0 * **機能**: 1. 搭載LED (Lピン) の1秒周期点滅制御。 2. USB-Serialへの状態と時間情報の高頻度ロギング。 * **制御方式**: タイマー割込 (`IntervalTimer` を使用予定)。 * **点滅周期**: 1秒 ($0.5$秒点灯, $0.5$秒消灯)。 * **ロギング周期**: $0.1$秒。 * **ロギング内容**: 起動からの経過時間($0.1$秒単位の整数)とLEDの現在の状態 (`ON` または `OFF`)。 --- ## 🛠 開発環境 | 項目 | 詳細 | | :----------------- | :--------------------------------------------------- | | **開発環境** | Arduino IDE (またはPlatformIO) | | **プロジェクト名** | `genblink` (カレントディレクトリ) | | **使用ボード** | Teensy 4.0 | | **言語** | C/C++ (Arduinoスケッチ) | | **ライブラリ** | Teensyduino Core Library (`IntervalTimer`, `Serial`) | --- ## 📁 プロジェクト構成 このプロジェクトは、**Arduino IDEで直接開けるプロジェクト**として、カレントディレクトリに以下の構成で作成します。 / ├──genblink/ | └── genblink.ino <-- メインのスケッチファイル (実装が必要) └── GEMINI.md <-- このドキュメント --- ## ⚙️ ハードウェア接続 Teensy 4.0ボード単体で動作します。 | 部品 | 接続先 | 備考 | | :------------------- | :---------------------- | :---------------------------- | | **LED (オンボード)** | Lピン (デジタルピン 13) | 基板に内蔵されているLEDを使用 | | **電源 & 通信** | USBケーブル | シリアル通信にも使用 | --- ## 💻 ソフトウェア実装のポイント ### 1. タイマー割込の多重利用 正確なタイミング制御のため、**2つの異なる周期のタイマー割込**を使用することを推奨します。 1. **LED制御タイマー**: $500,000$マイクロ秒 ($0.5$秒) 周期。LEDの状態反転に使用。 2. **ロギングタイマー**: $100,000$マイクロ秒 ($0.1$秒) 周期。経過時間カウンタの更新とシリアルロギングに使用。 ### 2. 状態管理 * LEDの状態 (`ON`/`OFF`) を保持する **グローバルな状態変数** (`volatile` boolなど) を使用します。 * 起動からの経過時間($0.1$秒単位)を保持する **グローバルなカウンタ変数** (`volatile` unsigned longなど) を使用します。 ### 3. 割込処理ルーチン (ISR) * **ロギングISR**: $0.1$秒ごとに実行され、カウンタをインクリメントし、**LEDの現在の状態を読み取って**シリアル出力を行います。 * **LED制御ISR**: $0.5$秒ごとに実行され、LEDの状態を反転 (`digitalToggle`) します。 ### 4. ロギングフォーマット シリアル出力は、起動からの経過時間($0.1$秒単位の整数)と、現在のLEDの状態を示す文字列(`ON`または`OFF`)を$1$行で出力します。 **期待される出力例:** 0 : OFF 1 : ON 2 : OFF ... 9 : OFF 10 : ON ...
(3)VSCode側の準備
今回は、任意のディレクトリにgem_arduinoというディレクトリを作って、
そこを基準に生成していきます。
ここに、GEMINI.mdファイルを作ります。
![]()
先ほど生成した、仕様書のmarkdownテキストを、GEMINI.mdファイルに貼り付けます。

(4)コードの生成
ターミナルを開いて、gemini CLIを起動します。
プロンプトに下記を入力します。
> GEMINI.mdに従い、コードを作ってください
手元のパソコンの中のファイルを読み書きするときは、許可を求めてきますので、その都度許可の操作をします。
承知しました。GEMINI.mdの指示に従い、コードを作成します。 まず、blinkディレクトリを作成します。 │ Allow execution of: 'mkdir'? │ │ ● 1. Yes, allow once │ 2. Yes, allow always ... │ 3. No, suggest changes (esc)
1分程度でコードを生成します。

ここで、意図通りのコードが出てきていなければ、GeminiCLIのプロンプトで指摘することで即、コードを修正してくれます。 (コメントが英語のまま出てきたときは、日本語にして欲しい旨伝えると、修正してくれます)
(5)実機確認
生成したコードを、ArduinoIDEで開いて、 ビルド、マイコンに転送します。


問題無くビルドできます。
Teensy実機のLEDの点滅を確認しました。

シリアルモニターには、0.1秒おきに下記のログが出てきます。
0 : OFF 1 : OFF 2 : OFF 3 : OFF 4 : OFF 5 : ON 6 : ON 7 : ON 8 : ON 9 : ON 10 : OFF ・・・
(6)コードの確認
ざっとコードを眺めると、Teensy特有のタイマー割込の書き方を踏襲したコードになっています。
LEDDの点滅と、USB-Serialの出力の2つそれぞれを、タイマー割込で駆動しています。
#include <IntervalTimer.h> // LEDのピン定義 (Teensy 4.0の搭載LEDは13番ピン) const int ledPin = 13; // 0.1秒単位の経過時間カウンタ // この変数はISRで変更され、メインループや他のISRで読み取られる可能性があるため、'volatile'を付けます volatile unsigned long timeCount = 0; // 定期的なタスクを処理するためのIntervalTimerオブジェクト IntervalTimer loggingTimer; IntervalTimer ledTimer; /** * @brief ロギング用の割込サービスルーチン (ISR) * * この関数は0.1秒ごとに呼び出されます。経過時間と * LEDの現在の状態をUSB-Serialポートに出力します。 */ void loggingISR() { Serial.print(timeCount); Serial.print(" : "); // LEDピンの現在の状態を直接読み取ります if (digitalRead(ledPin) == HIGH) { Serial.println("ON"); } else { Serial.println("OFF"); } // 時間カウンタをインクリメントします timeCount++; } /** * @brief LED制御用の割込サービスルーチン (ISR) * * この関数は0.5秒ごとに呼び出されます。LEDの状態を * 反転させ、1秒の点滅周期(0.5秒ON、0.5秒OFF)を作り出します。 */ void ledControlISR() { digitalToggle(ledPin); } /** * @brief 起動時に一度だけ実行されるsetup関数 */ void setup() { // LEDピンを出力モードに設定します pinMode(ledPin, OUTPUT); // LEDがOFFの状態で始まるようにします digitalWrite(ledPin, LOW); // シリアル通信を初期化します Serial.begin(9600); // シリアルポートが接続されるのを待ちます(タイムアウト付き) // これは一部のボードで役立ちますが、Teensyでは厳密には不要なことが多いです while (!Serial && millis() < 4000) { // 待機 } // タイマーを開始します // ロギングタイマーは100,000マイクロ秒(0.1秒)ごとにloggingISRを呼び出します loggingTimer.begin(loggingISR, 100000); // LED制御タイマーは500,000マイクロ秒(0.5秒)ごとにledControlISRを呼び出します ledTimer.begin(ledControlISR, 500000); } /** * @brief メインループ * * すべての機能はタイマー割り込みによって処理されるため、このループは空のままです。 * この方法はこのタスクにとってより正確で効率的です。 */ void loop() { // メインループは意図的に空にされています }
4.まとめ
今回は、復習を兼ねて、Gemini CLIのWindowsでのインストールと、簡単な”仕様駆動開発”の試行をしてみました。
組み込みにおける、実践的な仕様駆動開発の試行については、キンニクマさん(キンニクマ|note)の記事が参考になるので、こちらも学んでいこうと思います。
参考資料
- 仕様の作り方
- 仕様駆動開発
- その他