前回の続きです。
ボタンの改良とモードチェンジ
10秒を測る機能を作ったので、さらなる改善と機能追加に取り組むことにしました。
主な改良ポイントは以下の3点です。
- 2つ目のボタンを追加し、モードチェンジを可能にする
- より視認性の高い、大きなボタンに変更する
- ディスプレイに現在のモードと経過秒数を表示する
ボタンの課題と解決策
最低限の機能は動作するようになりましたが、現在使用しているボタンはブレッドボードに直接取り付ける小さなものです。舞台上で実際に使用するとなると、押しにくく、周りの演者や観客からも何が行われているのか分かりづらいのが問題でした。そこで、大きく目立つボタンを用意し、押したときに確実に反応するようにすることにしました。
また、ボタンの種類には大きく分けて2種類あることが分かりました。
- モーメンタリスイッチ:押している間だけオンになり、離すとオフになる
- オルタネートスイッチ:押すごとにオン・オフが切り替わる
スターターキットに入っていたのはモーメンタリスイッチでしたが、今回の用途にはオルタネートスイッチの方が適していると判断しました。たとえば、
- 1回押すと10秒計測を開始
- もう1回押すとリセット
という操作の方が、直感的で分かりやすいためです。
モードチェンジ用のボタンについても、1回押すとモード変更、もう1回押すとリセット という仕様にすることで、操作ミスを防ぎやすくなると考えました。
スイッチを求めていざ秋葉原
しかし、スターターキットにはオルタネートスイッチが入っていませんでした。
モノタロウやAmazonで探してみても、サイズ感が分からず、実際に触って確かめる必要があると感じました。そこで、秋葉原の 秋月電子通商へ足を運ぶことにしました。
1階には様々な電子部品が並んでおり、店員さんに相談しながら必要なスイッチを探しました。*1
オルタネートスイッチが手に入ればよかったのですが、購入するつもりだったオルタネートスイッチは、ブレッドボードに直接挿せるタイプではなくワイヤーをはんだ付けする必要がありました。そこで、はんだ付けに失敗した場合のことも考えて予備のスイッチも購入しました。
帰宅後、早速オルタネートスイッチをArduinoとブレッドボードに接続します。はんだ付けはまだ行わず、クリップとワイヤーでボタンを繋げました。
スイッチを押すと シリアルモニターに「1」が表示 されました。オルタネートスイッチの実装は問題なくできそうです。
ディスプレイ悪戦苦闘
ボタンの問題が解決したので、次はいよいよ ディスプレイ の実装に進みます。
幸い、スターターキットにはディスプレイが付属していたので、まずはこれとサンプルプログラムを活用し、現在のモードや経過秒数を表示していきます。
最初の問題:文字が表示されない
ディスプレイを接続しスイッチオン、プログラムを動かしてみたところ、画面には 白い四角 が表示されるだけで、文字が出てきませんでした。
最初からうまくいくわけ無い、とは思っていても実際に目の当たりにすると少し凹みます…。
明らかにハードウェアの問題であることは分かったので、抵抗や接続方法、接続先を見直し、少しずつ接続方法を変えながら試してみることにしました。
想定通りではありませんが、文字が表示されるようにはなりました。
配線ミスも発見し、慎重に修正を加えながら進めることで、ついにディスプレイに期待していた文字が表示されるように なりました。
しかし、ここで 奇妙な現象 に遭遇しました。
なぜか アルファベットの「O」だけが表示されない のです。プログラムのミスかと思い、コードを見直しても問題は見当たりません。配線の問題と思われますが、どうやっても直りません。仕方がないので、 「O」の代わりに「0(ゼロ)」を使うことで対応 することにしました。まあこれも味があっていいかなと無理やり自分を納得させました。
実装の完了と新たな課題
最後の課題は、緑のスイッチを押すことでモードチェンジを行う機能 の実装でした。
さらに、ディスプレイの表示にもこだわり、横スクロールや点滅 などの演出を加えたかったため、ChatGPTに相談しながら実装を進めました。
その結果、以下のプログラムが完成しました。
// ピン設定
const int switch1Pin = 3; // スイッチ1(10秒タイマー開始・リセット用)
const int switch2Pin = 13; // スイッチ2(モードチェンジ用)
const int redPin = 4;
const int greenPin = 12;
const int bluePin = 2;
// 状態管理用変数
unsigned long startTime = 0;
unsigned long blinkInterval = 100;
unsigned long lastBlinkTime = 0;
bool timerActive = false;
bool ledState = LOW;
bool modeChangeActive = false;
unsigned long modeChangeStartTime = 0;
int scrollCount = 0;
// LCDライブラリ
#include
LiquidCrystal lcd(6, 7, 8, 9, 10, 11);
void setup() {
pinMode(switch1Pin, INPUT_PULLUP);
pinMode(switch2Pin, INPUT_PULLUP);
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
// 初期設定
digitalWrite(redPin, LOW);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, LOW);
digitalWrite(LED_BUILTIN, LOW);
// LCD初期化
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("MODE:MAG=MELL"));
lcd.setCursor(0, 1);
lcd.print("Sec: 0 ");
}
void loop() {
bool switch1State = digitalRead(switch1Pin) == LOW;
bool switch2State = digitalRead(switch2Pin) == LOW;
Serial.print(" | TimerActive: "); Serial.println(timerActive);
Serial.print("Switch1: "); Serial.print(switch1State);
Serial.print(" | Switch2: "); Serial.print(switch2State);
if (modeChangeActive) {
if (!switch2State) {
modeChangeActive = false;
lcd.clear();
lcd.print(F("MODE:MAG=MELL"));
lcd.setCursor(0, 1);
lcd.print("Sec: 0 ");
} else {
handleModeChange();
return;
}
}
if (switch1State && !timerActive) {
startTime = millis();
timerActive = true;
Serial.print("タイマー開始");
}
if (!switch1State) {
timerActive = false;
digitalWrite(redPin, LOW);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, LOW);
ledState = LOW;
startTime = 0;
lastBlinkTime = 0;
Serial.print("時間OFF");
lcd.setCursor(0, 1);
lcd.print(F("Sec: 0 "));
return;
}
if (timerActive) {
int currentSec = (millis() - startTime) / 1000;
lcd.setCursor(5, 1);
lcd.print(" ");
lcd.setCursor(5, 1);
lcd.print(currentSec);
if (millis() - startTime >= 20000) {
if (millis() - lastBlinkTime >= blinkInterval) {
ledState = !ledState;
digitalWrite(redPin, ledState);
digitalWrite(LED_BUILTIN, ledState);
lastBlinkTime = millis();
}
Serial.print("LED点滅");
}
}
if (switch2State) {
if (!modeChangeActive) {
modeChangeActive = true;
modeChangeStartTime = millis();
lcd.clear();
lcd.print("Mode change");
lcd.setCursor(0, 1);
lcd.print("processing...");
}
}
}
void handleModeChange() {
unsigned long elapsed = millis() - modeChangeStartTime;
if (elapsed < 10000) {
if (scrollCount < 40) {
lcd.setCursor(0, 1);
lcd.print("processing... ");
lcd.scrollDisplayLeft();
scrollCount++;
delay(100);
}
lcd.setCursor(0, 1);
if ((elapsed / 500) % 2 == 0) {
lcd.print("processing... ");
} else {
lcd.print(" ");
}
} else if (elapsed < 12000) {
lcd.setCursor(0, 1);
if ((elapsed / 500) % 2 == 0) {
lcd.print("Completed. ");
} else {
lcd.print(" ");
}
delay(500);
} else {
lcd.clear();
lcd.print("Undertaking the");
lcd.setCursor(0, 1);
lcd.print("Gray-Gnome ");
digitalWrite(redPin, LOW);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, HIGH);
}
}
あっという間に完成したように見えますが、実際にはもっと長い時間を費やしました。特に配線に関してはの部分では悪戦苦闘 し、何度も何度も配線を見直し、接続をやり直すという作業の繰り返しでした。
電子工作のハードウェア部分は、「一発で動くことがまず無い」「原因の切り分けが難しい」「再現性が高いとは限らない」などの理由から苦手意識を持っていました。しかし、根気よく配線をチェックし続けた結果、最終的には正しく表示させることができました。この経験を通じて、電子工作に対する苦手意識を払拭することができたのは、大きな収穫だったと思います。
こうして、以下の2つの機能が完成しました。
- 20秒経過後にLEDを点灯する
- 緑ボタンを押すことでモードチェンジを行う
以下、実際のデモです。*2
しかし、実際に会場に持っていってそのまま動作できるのか、僕は不安でした。理由は、運用するにあたって懸念点を調べていたところ、以下の記事を発見したからです。
やらかし打線に
中:ブレッドボードのまま会場に持ってくるのやめろ
と、不動の4番に位置づけられていました。
ブレッドボードはプロトタイピングには便利ですが、接触不良や衝撃で配線が抜けるリスクがあります。舞台上で使用するには、より安定した回路にする必要があるというのはよくわかります。
しかし、問題は僕に電子工作の知識が不足していることです。回路図の読み方も分からず、購入した本もほぼ手つかずの状態でした。この時、RP人狼本番一週間前。なんとかならないかと調べたところ、どうやらユニバーサル基板の中でも、ブレッドボードと同じ回路をあらかじめ設計しているものがあり、これを用いればブレッドボードと同じように実装できる基板があることがわかりました。
「これなら自分でもできるかもしれない」と考え、早速ユニバーサル基板を購入しました。さらに、もう一度秋月電子通商へ足を運び、はんだごてスターターキットを購入。
これで、安定した回路作りに必要な道具が揃いました。
次のステップとして、僕は30年ぶりにはんだ付けに挑むことになったのです。
「はんだ付け討死編」へ続く。