ランダムなコードを送り続けるモールス練習機を作る

子どもの夏休みの実験の準備で、Arduino Uno R3とLCD 1602 をつないでみた。かなり簡単に表示できることに気が付いて感動。

これはこれで、一段落ではあったけれど。 Arduinoでモールス符号って感じで、Lチカで遊んだな。と思い出す。これ、現在、打っているモールスの文字が表示できれば実はモールスの練習に使えるんじゃないの?と思い立つ。

Lチカに合わせて、打っているモールスをLCDに表示

とりあえずで、この前作ったLチカのコードにちょっとばかり追加。 追加個所は冒頭部分の、

/*
  Morse Blink /w SPEAKER and LCD Unit
*/

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display

int morseCode[26][4] = { // DOT=1 , DASH=2 , BLANK=0
    {1,2,0,0}, {2,1,1,1}, {2,1,2,1}, {2,1,1,0}, {1,0,0,0}, // A,B,C,D,E
    {1,1,2,1}, {2,2,1,0}, {1,1,1,1}, {1,1,0,0}, {1,2,2,2}, // F,G,H,I,J
    {2,1,2,0}, {1,2,1,1}, {2,2,0,0}, {2,1,0,0}, {2,2,2,0}, // K,L,N,M,O
    {1,2,2,1}, {2,2,1,2}, {1,2,1,0}, {1,1,1,0}, {2,0,0,0}, // P,Q,R,S,T
    {1,1,2,0}, {1,1,1,2}, {1,2,2,0}, {2,1,1,2}, {2,1,2,2}, // U,V,W,X,Y
    {2,2,1,1} }; // Z
 
int SPEAKER = 12;

これと、表示させる部分の

void keying (String Text, int wpm) {
  int i, j;
  int Tone = 400;
  int KeyingSpeed = (60 / (50 * (double)wpm)) * 1000;
    for ( i=0; i < Text.length(); i++) {
      lcd.setCursor(i,0);
      lcd.print(Text.substring(i,i+1));
        for ( j=0; j<4; j++) {

の部分、合計2か所、4行を追加するだけです。 たったこれだけの追加で、打っているコードをLCDに表記できるようになります。

Arduino Uno R3とLCD 1602 をつないでみるで実際にやってみたところのまんま。ユニットを初期化して、場所指定で文字を投げ込むだけですから。そりゃ、簡単にできるわけで。

ランダムなコードを送り続けるモールス練習機

ここまでできると、実は「送出文字をランダムにすればモールス練習機が作れるのでは?」という話か?しかも簡単に。 物理的な構成はそのままに、ソフトウェアだけ書き換えればいけそうな予感。

ランダムな文字列を取得する

ランダム文字列をArduino上で取得。 これはほかの実装とあまり大差なく、簡単に対応可能。 Arduinoのリファレンスには、こんな感じのサンプルが用意されています。

long randNumber;

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0)); // 未接続ピンのノイズを利用
}

void loop() {
  randNumber = random(300);
  Serial.println(randNumber);
  delay(50);
}

簡単ですっきり。 randomSeedで種の制御。ほかの言語だと種としてtimeとかを食わせてみるけれど。 Arduinoの場合、RTCはというと…ということで、ほかの実装のようなことはできない。 ということで、N/CのAnalogピンを読んで代用するというわけ。 言われればよくわかるけれど。これはコロンブスの卵的な発想ですね。 実際に使用する乱数(数値)はrandom関数で値を引き出します。 こっちはこっちで

void loop() {
  randNumber = random(300); // 0から299の乱数を生成
  Serial.println(randNumber);

  randNumber = random(10, 20); // 10から19の乱数を生成
  Serial.println(randNumber);

  delay(50);
}

こんな書き方になります。randomのカッコ内にrandom(最大値)、もしくはrandom(最小値,最大値)と記述します。 最大と最小を指定できるだけでもかなり省力化できます。 今回もランダム生成に対応する文字はA – Zの範囲だとすれば、65 – 91の間の乱数が発生すれば事足ります。

      KeyingString.concat((char)random(65,91));

こんな書き方をすれば、何かしらのアルファベットが出てくることになります。

LCDにどう表示するか?

練習機として使用するのであれば、打つタイミングで文字は表示されてほしいけど。 直前に打ったものも履歴で残ってほしいかも。 そう考えると、LCDがせっかく2段なので、1段目にライブな情報、2段目に直前の情報を表示してみましょう。 であれば、

void loop() {
...
    keying(KeyingString, 18);
    lcd.setCursor(0,1);
    lcd.print(KeyingString);
...
}

こんな感じで、打ち終わったら、カーソルを2段目に移して打った文字を表示させればいいでしょう。

打つ文字列はどうしようか?

練習用として、ただただだらだらと打ち続けるというのもありかな? でも、そうなると聞き落とした時に復帰できなくなる。 一定の場所で区切られたほうがよさそう。 なら、一般的な電信の試験と同様に ランダムな5文字をひとくくり。 それを垂れ流すのはどうかな?

イメージとしては、こんな感じ。

P A R I S   T O K Y O          
O S A K A   O M I Y A          

OSAKA OMIYA PARIS TOKYOと打っているイメージ。 1行目に5文字1組を2組。 終わると、それらが2行目に移動。 これなら、結構簡単にスケッチできそう。

void loop() {
  String KeyingString;
  int i;
  for(i = 0; i < 11; i++) {
    if(i == 5){
       KeyingString.concat(" ");
    }else{
      KeyingString.concat((char)random(65,91));
    }
  }
    keying(KeyingString, 18);
    lcd.setCursor(0,1);
    lcd.print(KeyingString);
}

打つ文字を5文字+空白1文字+5文字で生成。 それをkeyingの関数に渡しちゃう。 打ち終わったら、2行目に渡した文字を表示。 これだけで完了。 これを実機に出してみましょう。 うん、上出来。 通電するといきなり始まるという部分さえ置いておけば、モールス練習機としては悪くない。

まとめ

Arduinoって、アイデアさえあれば、結構、「簡単に」「安価に」何でもできるんだね。 面白いもんです。

完成版のコード

できた。けど、なんかもっときれいに書く方法もありそうな気がするなぁ。

/*
  Morse Blink /w SPEAKER and LCD Unit
*/

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display

int morseCode[26][4] = { // DOT=1 , DASH=2 , BLANK=0
    {1,2,0,0}, {2,1,1,1}, {2,1,2,1}, {2,1,1,0}, {1,0,0,0}, // A,B,C,D,E
    {1,1,2,1}, {2,2,1,0}, {1,1,1,1}, {1,1,0,0}, {1,2,2,2}, // F,G,H,I,J
    {2,1,2,0}, {1,2,1,1}, {2,2,0,0}, {2,1,0,0}, {2,2,2,0}, // K,L,N,M,O
    {1,2,2,1}, {2,2,1,2}, {1,2,1,0}, {1,1,1,0}, {2,0,0,0}, // P,Q,R,S,T
    {1,1,2,0}, {1,1,1,2}, {1,2,2,0}, {2,1,1,2}, {2,1,2,2}, // U,V,W,X,Y
    {2,2,1,1} }; // Z
 
int SPEAKER = 12;
 
void setup() {
  lcd.init(); // LCD Initialize
  lcd.backlight();
  lcd.setCursor(0,0);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SPEAKER, OUTPUT);
  randomSeed(analogRead(0));
}
 
void keying (String Text, int wpm) {
  int i, j;
  int Tone = 400;
  int KeyingSpeed = (60 / (50 * (double)wpm)) * 1000;
  for ( i=0; i < Text.length(); i++) {
    lcd.setCursor(i,0);
    lcd.print(Text.substring(i,i+1));
    for ( j=0; j<4; j++) {
      switch (morseCode[(int)Text.charAt(i)-65][j]){
        case 1: // DOT
          digitalWrite(LED_BUILTIN, HIGH);
          tone(SPEAKER, Tone, KeyingSpeed);
          delay(KeyingSpeed);
          digitalWrite(LED_BUILTIN, LOW);
          delay(KeyingSpeed);
          break;
        case 2: // DASH
          digitalWrite(LED_BUILTIN, HIGH);
          tone(SPEAKER, Tone, KeyingSpeed*3);
          delay(KeyingSpeed*3);
          digitalWrite(LED_BUILTIN, LOW);
          delay(KeyingSpeed);
          break;
        default: // BLANK
          break;
      }
      delay(KeyingSpeed*2);
    }
  delay(KeyingSpeed*4); 
  }
  lcd.clear();
}



// the loop function runs over and over again forever
void loop() {
  String KeyingString;
  int i;
  for(i = 0; i < 11; i++) {
    if(i == 5){
       KeyingString.concat(" ");
    }else{
      KeyingString.concat((char)random(65,91));
    }
  }
    keying(KeyingString, 18);
    lcd.setCursor(0,1);
    lcd.print(KeyingString);
}

最終版コードはこちらからもダウンロードできます。

コメント