hotch-potch, Note to self

いろいろ作業記録

Gemini CLIを活用したArduinoの仕様駆動開発の試行

1.はじめに

先日、とある勉強会で学んだことを、組み込み開発に適用出来るかを試行してみました。

ここでは、仕様駆動開発の考え方に則って、簡単なArduino向けのコードをGemini CLIから作ってみました。

2.仕様駆動開発の試行

GEMINI.md ファイルを使用して Gemini CLI をカスタマイズし、Arduino向けのコードを生成してみます。

(1)仕様書を作る

Arduino版の仕様駆動開発の参考資料があまり見かけないので、 取っ掛かりのGEMINI.mdは、下記の条件を与えて、Geminiに作ってもらいました。

ここで試用したマイコンは、ややマイナーな、Teensy 4.0というデバイスをしていしましたが、その他のマイコンを指定してOKです。

Teensy® 4.0

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特有のタイマー割込の書き方を踏襲したコードになっています。

Delay and Timing Functions

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 CLIWindowsでのインストールと、簡単な”仕様駆動開発”の試行をしてみました。

組み込みにおける、実践的な仕様駆動開発の試行については、キンニクマさん(キンニクマ|note)の記事が参考になるので、こちらも学んでいこうと思います。

参考資料