LoginSignup
2
1

More than 1 year has passed since last update.

1-Key モールス符号キーボードをM5StickCでつくってみた

Posted at

この記事はM5Stack Advent Calendar 2022の17日目の記事です。

作ろうと思ったきっかけ

もともとはMONOISTの「モールス信号キーボード」の記事を読んで、「M5StickCだとボタンも大きいしハード追加もしで作れるし、モールス符号の勉強もできそう」と思ったのがきっかけです。(ちなみに筆者は電信級は持っていません)

作ったもの

BLEキーボードとして機能する1キーのモールス符号デコーダー(CW Decode Keyboard)です。

Bluetoothの接続状態は画面右上のマークで表示しています。ホストと接続する前は、マークが赤くなります。

no-connect.jpg

ホストと接続すると、マークが緑になります。

connected.jpg

ホスト(PC)からは"CW-Keyboard"という名前のBluetoothキーボードとして認識されます。

Pairing.png

サンプルコード

作成したコード(Arduino IDE用スケッチ)はgithubにアップしています。
https://github.com/tomorrow56/CW_BLEKeyboard

スケッチはM5StickCを使って動作させていますが、M5Unifiedライブラリを使用していますので、入力ピンの設定を変更すれば他のM5Stackシリーズでも動作するはずです。

コードの説明

モールス信号をデコードする部分の基本のアルゴリズムは MONOISTの記事に掲載されていたコード を使わせていただきました。
プログラムの見通しを良くするためパラメータの変数化と一部コードの修正を行っています。

BLEキーボードのインスタンス

M5Stack(ESP32)用のBluetoothライブラリは"NimBLE"を使用します。
インスタンスするときの引数で"deviceName","deviceManufacturer","batteryLevel"を設定します。

M5StickC_CW_BLEKeyboard.ino
// for BLE Keyboard
#include <BleKeyboard.h>
BleKeyboard bleKeyboard("CW-Keyboard", "M5Stack", 100);

モールス信号デコード用の各種パラメータの設定

M5StickCのボタンが押された長さから短点(・)と長点(-)の判定をするための各種パラメータです。
inputPinはM5StickCの液晶と同じ面にあるボタンA(G37)を指定しています。

M5StickC_CW_BLEKeyboard.ino
// for Morse Decode
unsigned long signal_len,t1,t2,t0;  //time for which button is pressed
String code = "";                   //string in which one alphabet is stored
const int spacePeriod = 2000;       //Waiting period until a space is entered
const int debouncingPeriod = 20;    //switch debouncing
const int longpressLimit = 500;     //Limit of long Press time
boolean connectDotDash = true;     //Code connect flag
int inputPin = G37;                 //M5StickC BtnA for input

setup関数

setup関数の最初に各種設定を行います。ボードタイプの判別は関数化(boardType())しています。

M5StickC_CW_BLEKeyboard.ino
void setup(void)
{
  auto cfg = M5.config();

#if defined ( ARDUINO )
  cfg.serial_baudrate = 115200;   // default=115200. if "Serial" is not needed, set it to 0.
#endif
  cfg.clear_display = true;  // default=true. clear the screen when begin.
  cfg.output_power  = false;  // default=true. use external port 5V output.
  cfg.internal_imu  = false;  // default=true. use internal IMU.
  cfg.internal_rtc  = false;  // default=true. use internal RTC.
  cfg.internal_spk  = false;  // default=true. use internal speaker.
  cfg.internal_mic  = false;  // default=true. use internal microphone.

  M5.begin(cfg);

  M5.Display.setBrightness(64);

  if (M5.Display.width() < M5.Display.height())
  { /// Landscape mode.
    M5.Display.setRotation(M5.Display.getRotation() ^ 1);
  }

  int textsize = M5.Display.height() / 160;
  if (textsize == 0)
  {
    textsize = 1;
  }
  M5.Display.setTextSize(textsize);

  // hardware model check
  char* boardName = boardType();
  M5.Display.startWrite();
  M5.Display.print("Core:");
  M5.Display.println(boardName);
  ESP_LOGI("setup", "core:%s", boardName);
  M5.Display.println("Morse Code Keyboard");

inputPinをプルアップ入力に設定し、BLEキーボードを開始します。

M5StickC_CW_BLEKeyboard.ino
  // Key setting internal pullup
  pinMode(inputPin, INPUT_PULLUP);

  // start BLE Keyboard
  Serial.println("Starting BLE work!");
  bleKeyboard.begin();
}

loop関数

入力したモールスコードをデコードして画面に表示する位置とサイズを指定します。

M5StickC_CW_BLEKeyboard.ino
void loop(void)
{
  vTaskDelay(1);
  int h = M5.Display.height() / 8;
  int w = M5.Display.width() / 12;

//  M5.update();

  M5.Display.setCursor(w*5, h*3);
  M5.Display.setTextSize(3);

ホストに接続されたら入力判別処理に入り、マークを緑にしてボタンが押されるのを待ちます。ボタンが押されたことを検出したら、t1に現在の時間を代入します。押されていなかった期間が"spacePeriod"より長い場合はスペースを入力します。

M5StickC_CW_BLEKeyboard.ino
  if(bleKeyboard.isConnected())
  {
    M5.Display.fillRect(w*11, h/2, w, h, TFT_GREEN);
    while (connectDotDash == true)
    {
      t0 = millis();
      // waiting for button press
      while (digitalRead(inputPin) == HIGH)
      {
      }
      // Enter a space if no key is pressed for a certain period of time
      if (millis()-t0 > spacePeriod)
      {
        bleKeyboard.print(' ');
        Serial.print(' ');
      }
      t1 = millis();                            //time at button press

次にボタンが離されるのを待ちます。ボタンが離されたら押されていた期間を"signal_len"に代入し、チャタリング防止期間(debouncingPeriod)より長い場合は、readio()関数を呼び出して、押された期間によって短点・長点を判定して"code"変数に追加します。

"longpressLimit"の期間でループを回し、この間にキーが押されたら、入力判別処理のループを繰り返します。
キーが押されなかったら、文字区切りと判断しループを抜けます。

M5StickC_CW_BLEKeyboard.ino
      // waiting for button released
      while (digitalRead(inputPin) == LOW)
      {
      }
      t2 = millis();                            //time at button release
      signal_len = t2 - t1;                     //time for which button is pressed
      if (signal_len > debouncingPeriod)        //to account for switch debouncing
      {
        code += readio();                       //function to read dot or dash
      }
      //limit of long press
      //if time between button press greater than 0.5sec, skip loop and go to next alphabet
      while ((millis() - t2) < longpressLimit)
      {     
        if (digitalRead(inputPin) == LOW)
        {
          connectDotDash = true;
          break;
        }else
        {
          connectDotDash = false;
        }
      }
    }

入力判別処理のループを抜けたら"convertor()"関数で"code"変数をデコードし、結果を"keyin"変数に代入し、LCD表示とホストへの送信を行います。

M5StickC_CW_BLEKeyboard.ino
    char keyin = convertor();       //function to decipher code into alphabet
    Serial.print(keyin);
    M5.Display.print(keyin);
    bleKeyboard.print(keyin);
    connectDotDash = true;
  }
  else
  {
    M5.Display.fillRect(w*11, h/2, w, h, TFT_RED);
  }
}

boardType()関数

M5Unifiedのボードタイプの判別部分を切り出した関数です。戻り値はボードタイプです。

M5StickC_CW_BLEKeyboard.ino
/**********
* hardware model check
**********/
char* boardType()
{
  char* name;
  switch (M5.getBoard())
  {
#if defined (CONFIG_IDF_TARGET_ESP32C3)
  case m5::board_t::board_M5StampC3:
    name = "StampC3";
    break;
  case m5::board_t::board_M5StampC3U:
    name = "StampC3U";
    break;
#else
  case m5::board_t::board_M5Stack:
    name = "Stack";
    break;
  case m5::board_t::board_M5StackCore2:
    name = "StackCore2";
    break;
  case m5::board_t::board_M5StickC:
    name = "StickC";
    break;
  case m5::board_t::board_M5StickCPlus:
    name = "StickCPlus";
    break;
  case m5::board_t::board_M5StackCoreInk:
    name = "CoreInk";
    break;
  case m5::board_t::board_M5Paper:
    name = "Paper";
    break;
  case m5::board_t::board_M5Tough:
    name = "Tough";
    break;
  case m5::board_t::board_M5Station:
    name = "Station";
    break;
  case m5::board_t::board_M5Atom:
    name = "ATOM";
    break;
  case m5::board_t::board_M5AtomPsram:
    name = "ATOM PSRAM";
    break;
  case m5::board_t::board_M5AtomU:
    name = "ATOM U";
    break;
  case m5::board_t::board_M5TimerCam:
    name = "TimerCamera";
    break;
  case m5::board_t::board_M5StampPico:
    name = "StampPico";
    break;
#endif
  default:
    name = "Who am I ?";
    break;
  }

  return name;
}

char readio()関数

短点・長点を判定する関数です。MONOISTに掲載のコードをそのまま使わせてもらっています。

M5StickC_CW_BLEKeyboard.ino
char readio()
{
  if (signal_len < 200 && signal_len > 20)
  {
    return '.';                        //if button press less than 0.2sec, it is a dot
  }
  else //if (signal_len >= 300)
  {
    return '-';                        //if button press more than 0.2sec, it is a dash
  }
}

convertor()関数

モールス符号を英数字・記号に変換する関数です。MONOISTに掲載のコード(基本の英数字)をベースに英文モールス符号で規定されている記号を一通り追加しました。ただし、“ダイアクリティカルマーク”付きアルファベットは対応していません。
ここで定義されていないモールス符号が入力された場合は、ホストに改行コードを送信します。

M5StickC_CW_BLEKeyboard.ino
char convertor()
{
  static String letters[] = {
    ".-",   // A (1)
    "-...", // B
    "-.-.", // C
    "-..",  // D
    ".",    // E
    "..-.", // F
    "--.",  // G
    "....", // H
    "..",   // I
    ".---", // J
    "-.-",  // K
    ".-..", // L
    "--",   // M
    "-.",   // N
    "---",  // O
    ".--.", // P
    "--.-", // Q
    ".-.",  // R
    "...",  // S
    "-",    // T
    "..-",  // U
    "...-", // V
    ".--",  // W
    "-..-", // X
    "-.--", // Y
    "--..", // Z (26)
    "-----",  // 0
    ".----",  // 1
    "..---",  // 2
    "...--",  // 3
    "....-",  // 4
    ".....",  // 5
    "-....",  // 6
    "--...",  // 7
    "---..",  // 8
    "----.",  // 9 (36)
    "E"       // end code
  };

  int i = 0; 
  char c;
  if (code == ".-.-.-")
  {
    c = '.';        //period
  }
  else if (code == "--..--")
  {
    c = ',';        //comma
  }
  else if (code == "..--..")
  {
    c = '?';        //question
  }
  else if (code == ".----.")
  {
    c = 0x27;        //Apostrophe (')
  }
  else if (code == "-.-.--")
  {
    c = '!';        //Exclamation
  }
  else if (code == "-..-.")
  {
    c = '/';        //slash
  }
  else if (code == "-.--.")
  {
    c = '(';        //Open parenthesis
  }
  else if (code == "-.--.-")
  {
    c = ')';        //Close parenthesis
  }
    else if (code == ".-...")
  {
    c = '&';        //ampersand
  }
  else if (code == "---...")
  {
    c = ':';        //colon
  }
  else if (code == "-.-.-.")
  {
    c = ';';        //semicolon ';'
  }
  else if (code == "-...-")
  {
    c = '=';        //double dash
  }
  else if (code == ".-.-.")
  {
    c = '+';        //plus
  }
  else if (code == "-....-")
  {
    c = '-';        //hyphen
  }
  else if (code == "..--.-")
  {
    c = '_';        //underscore
  }
  else if (code == ".-..-.")
  {
    c = '?';        //Question
  }
  else if (code == "...-..-")
  {
    c = '$';        //Dollar
  }
  else if (code == ".--.-.")
  {
    c = '@';        //at mark
  }
  else if (code == "...-.-")
  {
    c = 0x0a;  //end of work, send "LF"
  }
  else
  {
    while (letters[i] != "E")  //loop for comparing input code with letters array
    {
      if (letters[i] == code)
      {
        if (i>25)
          c = '0'+(i-26);
        else
          c = 'A'+i;
        break;
      }
      i++;
    }
    if (letters[i] == "E")
    {
      c = 0x0a;  //if input code doesn't match any letter, send "LF"
//      Serial.println("");  //if input code doesn't match any letter, error
    }
  }
  code = "";                            //reset code to blank string
  return c;
}

参考にしたサイト

以下のサイトの情報を参考にさせていただきました。どうもありがとうございます。

M5Unified

CW Decorder

Morse Code

NimBLE Keyboard

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1