2025年12月9日火曜日

『モールス信号クイズ』for UIAPduinoをつくってみた。

 

環境

  • マイコン:CH32V003
  • 開発環境:Arduino IDE
  • 操作ボタン:タクトスイッチ=内部プルアップ入力
  • その他:ブザー


12月7日の【電波文化祭】出展に向けて、UIAPduino を使った何かしらの作例を展示したいと思いました。

そこで急遽、ChatGPT に相談して作ったのが、この 『モールス信号クイズ』 です。

青(初級)・オレンジ(中級)・赤(上級)のボタンを押すと、5つの単語をモールス信号に変換してブザーから鳴らすだけの、シンプルな装置です。

ChatGPT に仕様を説明したところ、まずは Arduino UNO 版のソースコードを一発で出力してくれました。

それが問題なく動いたので、UIAPduino 用にピン番号だけ書き換えたのが、下に載せたコードです。

コミュニティのメンバーにモールス信号の内容も確認してもらえたので、正しく動作しているはず。




ところが、せっかく作ったものの……当日の【電波文化祭】は 大盛況!

会場中がにぎやかで、あちこちでモールス信号の音も飛び交っていたため、このブザー音だけでクイズとして遊んでもらうのは難しい状況 でした。これは残念。

ただ、マイコンボードに興味のある方には
「こういうの、ChatGPT でサッと作れるんですよ!」
としっかりアピールできたので、展示としては意味があったと思います。

ChatGPT 曰く、

「このコードは、読むだけでモールス装置の仕組みが理解できる構成になっています」

たしかに、case 'P': return ".--."; のように、モールス信号がそのまま見える形で記述されていて、なかなか面白い仕上がりになっています。

せっかく生成してもらったので、ソースコードを置いておきますね。

ではでは!


完成コード(コピペでOK)

注意: このスケッチ単体だけを1つの .ino に貼り付けてください(重複定義エラー対策)。


/**********************************************************************
 *  Morse Quiz Device for UIAPduino
 *  ---------------------------------
 *  ● LED と ブザー でモールス信号を送信する装置
 *  ● A/B/C の3つのボタンで難易度別の問題を再生
 *    - A → 初級(動物)
 *    - B → 中級(電子工作)
 *    - C → 上級(実用CW・高速)
 *
 *  移植しやすいよう、Arduino 標準関数のみ使用。
 *  このコードを読むだけでモールス装置の仕組みが理解できる構成。
 **********************************************************************/

// ------------------------------------------------------------
// ピン定義
// ------------------------------------------------------------

// モールスの光を出すLED(UIAPduinoの2番は内蔵LEDに繋がっている)
const int LED_PIN     = 2;

// ブザー(アクティブブザー推奨:音がすぐ出るタイプ)
const int BUZZER_PIN  = 8;

// A/B/C の3ボタン
// ボタンは GND に落とし、INPUT_PULLUP で内部プルアップを使う。
// → 押していない:HIGH
// → 押した:LOW
const int BUTTON_A_PIN = 5;
const int BUTTON_B_PIN = 3;
const int BUTTON_C_PIN = 4;


// ------------------------------------------------------------
// クイズ再生中フラグ
// ・ボタンを連打されても途中で割り込まないために使う。
// ------------------------------------------------------------
bool isPlaying = false;


// ------------------------------------------------------------
// 5単語とドット長(速度)をまとめた構造体
// ------------------------------------------------------------
struct Quiz {
  const char* words[5];    // 5つの単語
  unsigned int dotMs;      // ドット(・)の長さ(ミリ秒)
};


// ------------------------------------------------------------
// 初級:子ども向け(ゆっくり)
// dot = 180ms → 聞き取りやすい速度
// ------------------------------------------------------------
Quiz quizA = {
  { "CAT", "DOG", "FROG", "LION", "BEAR" },
  180
};


// ------------------------------------------------------------
// 中級:電子工作(普通の速度)
// dot = 120ms → 標準的なモールス速度
// ------------------------------------------------------------
Quiz quizB = {
  { "LED", "BOARD", "PROBE", "RESIN", "CABLE" },
  120
};


// ------------------------------------------------------------
// 上級:実用CW(高速)
// dot = 60ms → CW経験者がニヤリとする速さ
// ------------------------------------------------------------
Quiz quizC = {
  { "CQDX", "QRZ", "QTH", "RST", "ANT" },
  60
};


// ------------------------------------------------------------
// 文字 → モールスパターン変換
// 使用する文字のみ対応(A〜Z と 0〜9)
// ------------------------------------------------------------
const char* getMorsePattern(char c) {
  switch (c) {
    case 'A': return ".-";
    case 'B': return "-...";
    case 'C': return "-.-.";
    case 'D': return "-..";
    case 'E': return ".";
    case 'F': return "..-.";
    case 'G': return "--.";
    case 'H': return "....";
    case 'I': return "..";
    case 'J': return ".---";
    case 'K': return "-.-";
    case 'L': return ".-..";
    case 'M': return "--";
    case 'N': return "-.";
    case 'O': return "---";
    case 'P': return ".--.";
    case 'Q': return "--.-";
    case 'R': return ".-.";
    case 'S': return "...";
    case 'T': return "-";
    case 'U': return "..-";
    case 'V': return "...-";
    case 'W': return ".--";
    case 'X': return "-..-";
    case 'Y': return "-.--";
    case 'Z': return "--..";

    // 数字(コールサイン対応)
    case '0': return "-----";
    case '1': return ".----";
    case '2': return "..---";
    case '3': return "...--";
    case '4': return "....-";
    case '5': return ".....";
    case '6': return "-....";
    case '7': return "--...";
    case '8': return "---..";
    case '9': return "----.";
  }
  return ""; // 未対応文字は空
}


// ------------------------------------------------------------
// 1つの信号(・または-)を送信
// dot = dotMs, dash = dotMs × 3
// LED と ブザーを同時にONにする
// ------------------------------------------------------------
void sendSymbol(char symbol, unsigned int dotMs) {
  unsigned int onTime;

  if (symbol == '.') {
    onTime = dotMs;            // dot(短点)
  } else if (symbol == '-') {
    onTime = dotMs * 3;        // dash(長点)
  } else {
    return; // パターン外
  }

  // 信号 ON
  digitalWrite(LED_PIN, HIGH);
  tone(BUZZER_PIN, 1000);      // 1000Hzのビープ音
  delay(onTime);

  // 信号 OFF
  digitalWrite(LED_PIN, LOW);
  noTone(BUZZER_PIN);

  // シンボル間スペース(1単位)
  delay(dotMs);
}


// ------------------------------------------------------------
// 1文字分のモールス信号を送信
// 文字間スペース:合計3単位(ここでは追加2単位)
// ------------------------------------------------------------
void sendLetter(char letter, unsigned int dotMs) {
  // スペースは単語の区切り → 追加の長めの休止
  if (letter == ' ') {
    delay(dotMs * 4);
    return;
  }

  // 小文字を大文字に変換
  char upper = letter;
  if (upper >= 'a' && upper <= 'z') {
    upper = upper - 'a' + 'A';
  }

  // モールスパターン取得
  const char* pattern = getMorsePattern(upper);
  if (!pattern || pattern[0] == '\0') {
    return; // 未定義文字は無視
  }

  // パターンを順に送信(例:"--." → '-', '-', '.')
  for (int i = 0; pattern[i] != '\0'; i++) {
    sendSymbol(pattern[i], dotMs);
  }

  // 文字間スペース(残り2単位)
  delay(dotMs * 2);
}


// ------------------------------------------------------------
// 単語全体を送信
// ------------------------------------------------------------
void sendWord(const char* word, unsigned int dotMs) {
  for (int i = 0; word[i] != '\0'; i++) {
    sendLetter(word[i], dotMs);
  }
}


// ------------------------------------------------------------
// クイズ1問分(5単語)を順に送信
// ------------------------------------------------------------
void playQuiz(const Quiz& q) {
  isPlaying = true;

  // ★ スタート合図:短いビープ1回
  digitalWrite(LED_PIN, HIGH);
  tone(BUZZER_PIN, 1500);
  delay(150);
  digitalWrite(LED_PIN, LOW);
  noTone(BUZZER_PIN);
  delay(300);

  // 5単語連続で送信
  for (int i = 0; i < 5; i++) {
    sendWord(q.words[i], q.dotMs);

    // 単語間スペース(約7単位)
    if (i < 4) {
      delay(q.dotMs * 7);
    }
  }

  // ★ 終了合図:ピッピッ
  delay(400);
  for (int k = 0; k < 2; k++) {
    digitalWrite(LED_PIN, HIGH);
    tone(BUZZER_PIN, 1800);
    delay(120);
    digitalWrite(LED_PIN, LOW);
    noTone(BUZZER_PIN);
    delay(150);
  }

  isPlaying = false;
}


// ------------------------------------------------------------
// ボタン押下判定(簡易デバウンス付き)
// ------------------------------------------------------------
bool isButtonPressed(int pin) {
  // ボタンは押していないと HIGH(プルアップ)
  if (digitalRead(pin) == LOW) {
    delay(20); // チャタリング軽減
    if (digitalRead(pin) == LOW) {

      // 押しっぱなしで誤作動しないよう、
      // 離されるまで待つ
      while (digitalRead(pin) == LOW) {
        delay(5);
      }
      return true;
    }
  }
  return false;
}


// ------------------------------------------------------------
// 初期化処理
// ------------------------------------------------------------
void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  pinMode(BUTTON_A_PIN, INPUT_PULLUP);
  pinMode(BUTTON_B_PIN, INPUT_PULLUP);
  pinMode(BUTTON_C_PIN, INPUT_PULLUP);

  digitalWrite(LED_PIN, LOW);
  noTone(BUZZER_PIN);
}


// ------------------------------------------------------------
// メインループ
// ・クイズ再生中は他のボタンを無視する
// ------------------------------------------------------------
void loop() {
  if (!isPlaying) {

    if (isButtonPressed(BUTTON_A_PIN)) {
      playQuiz(quizA);  // 初級
    }

    else if (isButtonPressed(BUTTON_B_PIN)) {
      playQuiz(quizB);  // 中級
    }

    else if (isButtonPressed(BUTTON_C_PIN)) {
      playQuiz(quizC);  // 上級(高速)
    }
  }
}

0 件のコメント:

コメントを投稿

人気の投稿