Arduinoで作っちゃいました ~ CW練習機(パート4) ~

2025年3月10日月曜日

t f B! P L

 Arduinoで作っちゃいました  CW練習機(パート4)

 

前回、エレキーの基本的な機能を作成しました。次に、エレキーの速度変更の機能追加をしようと思います。 

その前に、前回作成した基本機能を試してみて判ったのですが、IC-705の内蔵エレキーと比べて、何か使い勝手が悪いと感じました。大昔にロジックICで作ったエレキーと同じ機能が実現できているはずなのですが、なぜかCW練習機だとミスタッチが起こりやすいのです。
いろいろと調べたところ、最近のエレキーには、キーの先行入力を覚えておく機能(先行入力メモリー機能)があって、速度を上げてもミスタッチが起こりにくくなっていることがわかりました。このほか、多くのエレキーにはスクイーズ機能が備わっている事もわかりました。

 

1.    使用感の改善

リグ内蔵のエレキーには使用感を改善するために更に以下の「先行入力メモリー機能」が装備されているそうです。(名前は勝手につけたものです)

「単純なエレキー機能」

”N”(―・)を送出する場合、2個目の短点は、1個目の長点+ブランクが終る直前に、短点をOnにします(そしてOnが確認できてから離す(Offにする))

一方で、送信速度が上がってくると、On/Offのタイミングがシビアになってきます。そのため、人間は次の符号(この場合短点)を早めに打ってきます。これに対応するために、次の符号を早めに打った場合、(その符号送出開始前にOffにしても)先行入力の動作を記録して、前の符号に続けて自動送出するようになっていました。(先行入力メモリー機能)

「先行入力メモリー機能」

”N”(―・)を送出する場合、2個目の短点は、1個目の長点+ブランクを送出中に、短点をOnにします。エレキーはこの先行入力を記憶して、長点+ブランクに続いて短点を自動送出します(短点のOffは送出前でも良い)

この機能の有無が使用感に大きな違いとなって現れますので、この機能を追加しました。

 

合わせてスクイーズ機能(モードA)も追加します。
エレキーやパドルキーには色々な種類(方式)があるようで、特にエレキーでは、アイアンビック(スクイーズキー)が有名です。
スクイーズキーは、短点と長点を同時にOnにした場合に、短点と長点が交互に送出される機能です。

モードAは、短点(または長点)の送出中に両方ともOffとなった場合、Onになっているキーの信号送出して、終わります。
モードBは、短点(または長点)の送出中に両方ともOffとなった場合、Onになっているキーの信号送出した後、現在とは反対の符号を送出して終わります。
(A1Clubの解説(図解)が大変わかりやすいです。https://a1club.org/faq/faq-25.htm)
今回は、アイアンビック モードA相当 (長点・短点を交互に送出)も追加しました。

2.    プログラム

前回の基本機能に、1.の先行入力メモリー機能とアイアンビック モードAを追加した部分にコメントしました。

/*******************************
*  パート3 および パート4のエレキーの機能      *
 *******************************/

/// ハードウェアの接続定義 ///
#define DOT_PIN   3  // Paddle
#define DASH_PIN  2
#define BTN3_PIN 11  // ボタン3
#define BTN4_PIN 10  // ボタン4
#define SPK_PIN   8    // スピーカー

#define Off   0
#define On    1

// KEYやボタンの状態変化 //
#define BtnOnEdge 1
#define BtnOffEdge 2
#define NoChange 0

// EleKeyの状態 //
#define IDLE  0
#define DOT   1
#define DASH  2
#define BLANK 3

int eleKeySt = IDLE;
unsigned long start_time = 0;  // 時間監視用
unsigned long duration = 0;    // 送出時間 (ミリ秒)

//  WPMと送出時間(msec)
int WPM = 10;
int dot_length = 120;
int dash_length = 360;
int blank_length = 120;
//

// 先行入力のメモ
int Memo;
bool Memo_flag = false;

// スクイーズ操作用
int last_key = BLANK ;

extern int dotKeyStatus, dashKeyStatus;  // 後方参照

int getElekeyStatus() {
  int dotKeyEdge, dashKeyEdge ;

  // パドルの状態を更新 (チャタリング処理)
  dotKeyEdge = check_dotKey_status();   // 状態の更新
  dashKeyEdge = check_dashKey_status();  // 状態の更新

  /***** パート4での追加部分 (先行入力をメモに格納) *****/
  if (dotKeyEdge == true) {
    // if ((eleKeySt == DASH) || (eleKeySt == BLANK)) {  // 何かを送出中に来たトリガーはメモへ
    if (eleKeySt == DASH) {  // 何かを送出中に来たトリガーはメモへ (こちらの設定の方が打ちやすい)
      dotKeyEdge = false;
      Memo = DOT;
      Memo_flag = true;
    }
  }
  if (dashKeyEdge == true) {
    if ((eleKeySt == DOT) || (eleKeySt == BLANK)) {  // 何かを送出中に来たトリガーはメモへ
      dashKeyEdge = false;
      Memo = DASH;
      Memo_flag = true;
    }
  }
  /*****************************************/

  // 信号送出中か?
  if ((eleKeySt == DOT) || (eleKeySt == DASH)) {         // 信号送出中
    if (millis() - start_time <= duration) return (On);  // 信号送出の時間が経過していない
  }
  if (eleKeySt == BLANK) {                                // ブランク送出中
    if (millis() - start_time <= duration) return (Off);  // 信号送出の時間が経過していない
  }
  // IDLE状態または信号送出が完了した場合
  // 送出完了したのが、dotかdashの場合はBlankを送出する
  if ((eleKeySt == DOT) || (eleKeySt == DASH)) {
    last_key = eleKeySt ; // スクイーズ用に記憶
    eleKeySt = BLANK;
    duration = blank_length;
    start_time = millis();
    noTone(SPK_PIN);
    return (Off);
  }
  // 送出し終わったのがBlank
  if (eleKeySt == BLANK) {
    eleKeySt = IDLE;
    /*** パート4で追加(先行入力の送出) ***/
    if (Memo_flag == true) {  // 先行入力があれば、それを送出する
      Memo_flag = false;
      if (Memo == DOT) {
        eleKeySt = DOT;
        tone(SPK_PIN, Pitch);            // 音を出す
        digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
        duration = dot_length;
        start_time = millis();
        return (On);
      } else {
        eleKeySt = DASH;
        tone(SPK_PIN, Pitch);            // 音を出す
        digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
        duration = dash_length;
        start_time = millis();
        return (On);
      }
    }
    /****************/
  }
  // 次に送出する符号
  if ((dotKeyStatus == On) && (dashKeyStatus == Off)) {  // dotKey On
    eleKeySt = DOT;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dot_length;
    start_time = millis();
    return (On);
  }
  if ((dotKeyStatus == On) && (dashKeyStatus == On) && (last_key == DOT)) {  // スクイーズ操作
    eleKeySt = DASH;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dash_length;
    start_time = millis();
    return (On);
  }
  if ((dashKeyStatus == On) && (dotKeyStatus == Off)) {  // Key On Edge
    eleKeySt = DASH;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dash_length;
    start_time = millis();
    return (On);
  }
  if ((dotKeyStatus == On) && (dashKeyStatus == On) && (last_key == DASH)) {  // スクイーズ操作
    eleKeySt = DOT;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dot_length;
    start_time = millis();
    return (On);
  }

  // IDLE中

  // ボタン3 (WPM up)
  if (check_Btn3_status() == BtnOnEdge) {  // Btn On Edge
    setWPM(+1);                            // 速度(WPM)を上げる
  }
  // ボタン4 (WPM down)
  if (check_Btn4_status() == BtnOnEdge) {  // Btn On Edge
    setWPM(-1);                            // 速度(WPM)を下げる
  }

  return (Off);  // IDLE中
}


3.    速度変更機能の追加

エレキーの速度変更用にボタン3、ボタン4を追加しました。パート2の音程(Pitch)変更と同様の内容になります。またWPMを変更したらLCDに設定したWPMを表示するようにしました。(Pitchも同様に修正)
1WPM
は、50 dot/分に相当しますので、dotOn時間(ミリ秒)は、
60
*1000/(50) = 120ミリ秒で計算しました。


/*****   Definition of WPM ********************************************/
/* int WPM = 10;  // default                                          */
/* 10 WPM of "Paris_" is 500 dots/minute                              */
/* unsigned long dot_length = 60 sec / 500 * 1000 ; // milli second   */
/*               = 120ms/dot                                          */
/**********************************************************************/
// 速度(WPM)変更 (5-50)
void setWPM(int n) {
  char s[16] ;
  WPM = WPM + n;
  if (WPM < 5) WPM = 5;
  if (WPM > 50) WPM = 50;
  dot_length = 60000 / (50 * WPM);
  dash_length = dot_length * 3;
  blank_length = dot_length;
  tone(SPK_PIN, Pitch, 100);  // 速度を変更したら、ピッと鳴らす
  sprintf(s," WPM=%d ", WPM) ;
  LCD_print(s) ;
}


4.    まとめ

エレキーの機能を2回に分けて作成しました。CW練習機のエレキーとして実用レベルに近づいたと思いました。

以下に、今回完成したCW練習機のメインプログラム、CW_Reader、EleKeyの各プログラムファイルを自分記録として残します。(2025年3月11日版)

メインプログラム

/////////////////////////////
// Blogger CW練習機 (その2) //
//  20250306 by JL1BDL     //
/////////////////////////////

/// ハードウェアの接続定義 ///
#define KEY_PIN   4  // 電鍵(Key)を接続
#define BTN1_PIN  9  // ボタン1
#define BTN2_PIN 12  // ボタン2
#define SPK_PIN   8  // スピーカー

/**** パート3 (エレキー) ****/
#define DOT_PIN   3  // Paddle
#define DASH_PIN  2  
#define BTN3_PIN 11  // ボタン3
#define BTN4_PIN 10  // ボタン4

#define Off 0
#define On 1

// KEYやボタンの状態変化 //
#define BtnOnEdge 1
#define BtnOffEdge 2
#define NoChange 0

/// 共通変数 ///
int Pitch = 600;  // 音程(Pitch)のデフォルト値 600Hz

/******** パート2 ************/
// LCDライブラリには種類がたくさんあるようなので、
// ライブラリ管理から、「LiquidCrystal_I2C」で検索してインストール (現時点で Ver1.1.2)

/// LCD1602用ライブラリを使用するための宣言
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// LCD制御のためのインスタンス生成
LiquidCrystal_I2C lcd(0x27, 16, 2);  // address 0x27, 16 x 2

/****************************/

void setup() {

  Serial.begin(115200);
  Serial.println("Start!");

  // 各ピンの設定
  pinMode(KEY_PIN, INPUT_PULLUP);  // straight key
  pinMode(BTN1_PIN, INPUT_PULLUP);
  pinMode(BTN2_PIN, INPUT_PULLUP);
  // 追加: スピーカーの音と合わせてLEDも点滅させる事にした
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);  // turn off

  /**** パート3 (エレキー) ****/
  pinMode(DOT_PIN, INPUT_PULLUP);  // straight key
  pinMode(DASH_PIN, INPUT_PULLUP);  // straight key
  pinMode(BTN3_PIN, INPUT_PULLUP);
  pinMode(BTN4_PIN, INPUT_PULLUP);


  /*** パート2 ***/
  // LCD 1602の準備 (ライブラリによる)
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print(F("Morse Keyer Part II" )) ; //  19文字なので、" II"は表示されない
  delay(2000);  // 2秒間表示
  lcd.clear() ;
  //
}

//// Mainの処理部 ////
char buf[5];
void loop() {  
  // 本体はこれだけ
  readCW(buf);
  LCD_print(buf);
}


/******** パート1のプログラムの名前を変更 *********/
int keySt = Off ;

int getKeyStatus() {    // パート1 のメインloopの名前を変更
  int key;
  // 電鍵(Key)の状態が変化したら、Toneをon/offする
  key = check_Key_status();
  if (key == BtnOnEdge) {  // Key On Edge
    tone(SPK_PIN, Pitch);  // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    keySt = On ;
  }
  if (key == BtnOffEdge) {  // Key Off Edge
    noTone(SPK_PIN);        // 音を止める
    digitalWrite(LED_BUILTIN, LOW);  // LED Off (追加)
    keySt = Off ;
  }
  // ボタン1 (音程up)
  if (check_Btn1_status() == BtnOnEdge) {  // Btn On Edge
    setTone(+50);                          // 音程(Pitch)を上げる
  }
  // ボタン2 (音程down)
  if (check_Btn2_status() == BtnOnEdge) {  // Btn On Edge
    setTone(-50);                          // 音程(Pitch)を下げる
  }

  return(keySt) ;  // パート2で追加

}

// 音程変更 (300-1200Hz)
void setTone(int n) {
  char s[16] ;
  Pitch = Pitch + n;
  if (Pitch < 300) Pitch = 300;
  if (Pitch > 1200) Pitch = 1200;
  tone(SPK_PIN, Pitch, 100);  // 音程を変更したら、ピッと鳴らす
  sprintf(s, "Pitch= %d ", Pitch) ;
  LCD_print(s) ;
}

///////////////////////////////////////////////////////
// 以下は、電鍵(Key)、ボタン1,、ボタン2の入力チェック   //
//  On/Offではなく、on/offの切り替え(Edge)を返す       //
//      BtnOnEdge =1, BtnOffEdge= 2, NoChange = 0    //
///////////////////////////////////////////////////////

int threshold = 3;  // thresholdは、全ボタン共通とした

/// 電鍵(KEY) ///
static int KeyCnt = 0;
static int KeyStatus = Off;  // normally off

int check_Key_status() {
  if (digitalRead(KEY_PIN) == HIGH) {  // switch off
    KeyCnt--;
    if (KeyCnt < 0) {
      KeyCnt = 0;
      if (KeyStatus == On) {
        KeyStatus = Off;
        return (BtnOffEdge);  // Key Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    KeyCnt++;
    if (KeyCnt > threshold) {
      KeyCnt = threshold;
      if (KeyStatus == Off) {
        KeyStatus = On;
        return (BtnOnEdge);  // Key On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/// ボタン1(Btn1) ///
static int Btn1Cnt = 0;
static int Btn1Status = Off;  // normally off

int check_Btn1_status() {
  if (digitalRead(BTN1_PIN) == HIGH) {  // switch off
    Btn1Cnt--;
    if (Btn1Cnt < 0) {
      Btn1Cnt = 0;
      if (Btn1Status == On) {
        Btn1Status = Off;
        return (BtnOffEdge);  // Btn1 Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    Btn1Cnt++;
    if (Btn1Cnt > threshold) {
      Btn1Cnt = threshold;
      if (Btn1Status == Off) {
        Btn1Status = On;
        return (BtnOnEdge);  // Btn1 On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/// ボタン2(Btn2) ///
static int Btn2Cnt = 0;
static int Btn2Status = Off;  // normally off

int check_Btn2_status() {
  if (digitalRead(BTN2_PIN) == HIGH) {  // switch off
    Btn2Cnt--;
    if (Btn2Cnt < 0) {
      Btn2Cnt = 0;
      if (Btn2Status == On) {
        Btn2Status = Off;
        return (BtnOffEdge);  // Btn2 Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    Btn2Cnt++;
    if (Btn2Cnt > threshold) {
      Btn2Cnt = threshold;
      if (Btn2Status == Off) {
        Btn2Status = On;
        return (BtnOnEdge);  // Btn2 On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/****** (パート2) LCDライブラリの機能追加  ******/

//////////////////////////////
//  LCD1602のスクロール処理  //
//////////////////////////////

#define MaxCol 16
#define MaxLine 2

static int cur_x = 0;
static int cur_y = 0;

void LCD_print(char c) {
  lcd.setCursor(cur_x, cur_y);
  lcd.print(c);
  cur_x++;
  if (cur_x >= MaxCol) {
    cur_x = 0;
    cur_y++;
    if (cur_y >= MaxLine) {
      cur_y = 0;
    }
    for (int i = 0 ; i < MaxCol; i++) {
      lcd.setCursor(i,cur_y) ;
      lcd.print(" ") ;
    }
  }
}
void LCD_print(char* s) {
  for (int i = 0; i < strlen(s); i++) {
    LCD_print(s[i]);
  }
}
void LCD_println(char c) {
  lcd.setCursor(cur_x, cur_y);
  lcd.print(c);
  cur_x = 0;
  cur_y++;
  if (cur_y >= MaxLine) {
    cur_y = 0;
  }
    for (int i = 0 ; i < MaxCol; i++) {
      lcd.setCursor(i,cur_y) ;
      lcd.print(" ") ;
    }
}
void LCD_println(char* s) {
  for (int i = 0; i < strlen(s) - 1; i++) {
    LCD_print(s[i]);
  }
  for (int i = cur_x; i < MaxCol ; i++) {
    LCD_print(' ');
  }
}

/////////// EOF ////////////////////

CW_Reader


/**************************************************/
/**** Morse Code Reader (decoder)              ****/
/**************************************************/

#include <string.h>  // strcmpを使うので

#define Off 0
#define On 1

int PIN_Status = Off;  // Status of KEY 

/*************************************/
/*** Key のOn/Off状態変化の待ち処理  ***/
/*************************************/

extern int getKeyStatus();  // メインルーチン側で定義されている
extern int getElekeyStatus() ;

void waitForKeyOn() {
  while (PIN_Status == Off) {
    PIN_Status = getKeyStatus() | getElekeyStatus();  // キーの状態
  }
}

void waitForKeyOff() {
  while (PIN_Status == On) {
    PIN_Status = getKeyStatus() | getElekeyStatus();  // キーの状態
    // PIN_Status = getElekeyStatus();  // キーの状態
    // PIN_Status = getKeyStatus();
  }
}

int waitForKeyOnWithTimeout(unsigned long startTime, int timeout) {
  while (PIN_Status == Off) {
    PIN_Status = getKeyStatus() | getElekeyStatus();  // キーの状態
    // PIN_Status = getElekeyStatus();  // キーの状態
    // PIN_Status = getKeyStatus();           // キーの状態
    if ((millis() - startTime) > timeout)  // PIN_Status = Offのまま時間が経過
      return (Off);
  }
  return (On);
}

/***********************************/
/* モールス符号の判別               */
/***********************************/

unsigned long t_OnPeriod = 0;
unsigned long t_OffPeriod = 0;
unsigned long L1 = 240;  // dot + blank == 2 period (240 milli Sec. at WPM=10)
//unsigned long L2 = 480;  // dash+ blank == 4 period
unsigned long t_OnStart = 0;
unsigned long t_OffStart = 0;
bool space_is_sent = true;

char rcvdCode[10];  // モールスコードのバッファ
int rcvdCodePtr = 0;

/////////////////////////////////////

///  1文字受信の処理  ///

void readCW(char* buf) {
  //  モールスコードのバッファをクリア
  rcvdCodePtr = 0;
  rcvdCode[rcvdCodePtr] = 0x00;

  // 文字の入力開始待ち
  // 時間が経過してもKeyOnにならない場合は、単語区切りと判定 (スペースを返す)

  PIN_Status = waitForKeyOnWithTimeout(t_OffStart, L1 * 3);  // 次のKey Onまでの時間で単語区切りを判定 // 実際の通信用に短めで判定
  if (PIN_Status == Off) {                                   // 単語区切り
    t_OnStart = 0;
    t_OffStart = 0;
    if (space_is_sent == false) {  // Spaceが続けて出力されるのを抑制する
      // 単語区切り
      strcpy(buf, " ");
      space_is_sent = true;
      return;
    }
  }

  // 短点・長点および文字区切りの判定 //

  while (1) {
    t_OnPeriod = 0;
    while (t_OnPeriod < 20) {  // Dotの時間が20ミリ秒未満はノイズと判定している (これは逆にデコード率を下げる効果もあるので要調整)
      // Key On 待ち
      waitForKeyOn();
      // Key on になった
      t_OnStart = millis();  // Key on start time
      //  Key off 待ち
      waitForKeyOff();
      //  Key offになった
      t_OffStart = millis();                // Key off start time
      t_OnPeriod = t_OffStart - t_OnStart;  // Key On duration
    }
    //  Dot か Dash かを判定する
    // if (t_OnPeriod / L1 < 0.5) {  // dot  正確には0.5
    if (t_OnPeriod < L1) {  // dotは(dot+blank)以下をdotと判定する(かなり荒っぽいがこれが良いようだ)
      rcvdCode[rcvdCodePtr] = '1';
      rcvdCodePtr++;
    } else {  // dash (dotより長ければすべてdashとする)
      rcvdCode[rcvdCodePtr] = '2';
      rcvdCodePtr++;
    }

    //  Key Onを待ちながら、Key offのタイムアウト時間をモニター  ==> 文字区切りの判定へ
    t_OffPeriod = 0;
    while (1) {
      PIN_Status = waitForKeyOnWithTimeout(t_OffStart, L1);  //
      if (PIN_Status == Off) break;                          // timeout => 文字区切り
      t_OnStart = millis();
      t_OffPeriod = t_OnStart - t_OffStart;
      // if (t_OffPeriod >= L1/3) { // <= 整数演算で精度が落ちる
      if (t_OffPeriod * 3 >= L1) {  // (dot+blank)の1/3以上の長さのKey Off後であれば、Key Onと判定
        break;                      // Key On
      } else {                      // 短すぎるOnは無視 (ノイズ)
        PIN_Status = Off;
        t_OffPeriod = 0;
        continue;
      }
    }

    /* 速度追従の考え方
     * 人間の癖で、dotやdashのデューティー比は、1:1や1:3ではない。
     * しかし、1.3:0.7や、0.9:1.1など、dot+spaceは一定のリズムになっている。(dashも同様)
     * そのため、文字区切りでない限りは、dot+space (=L1とした)を基準に速度追従をすると
     * デコードしやすくなる
     */

    if (PIN_Status == Off) {  // timeout (t_Off=0) [文字区切りで、参考にするBlankが無い場合はOn時間だけで計算]
      // if (t_OnPeriod / L1 < 0.75) {        // <=整数演算で精度が落ちる
      if (t_OnPeriod * 4 < L1 * 3) {          // dot
        L1 = (L1 * 9 + t_OnPeriod * 2) / 10;  // 受信速度の変化に追従する
      } else {
        L1 = (L1 * 9 + t_OnPeriod * 2 / 3) / 10;  // 長点は短点の長さに戻してから
      }
    } else {                                            // On時間とOff時間の両方で速度追従の計算をする
      if (t_OnPeriod * 4 < L1 * 3) {                    // dot
        L1 = (L1 * 9 + t_OnPeriod + t_OffPeriod) / 10;  // 受信速度の変化に追従する
      } else {
        L1 = (L1 * 9 + t_OnPeriod / 3 + t_OffPeriod) / 10;
      }
    }
    if (L1 < 50) L1 = 50;  // max_WPM = 48 WPM ?

    //  一文字の終わりか?
    if (PIN_Status == Off) {  // Key Offのままで time out --> 1文字終了==> 文字区切りか、単語区切り
      break;                  // 1文字の判定開始へ
    }
    if (rcvdCodePtr > 8) break;  // 9要素以上のコードは強制的に終了
  }                              // while end // 1文字受信

  // 文字の判定
  rcvdCode[rcvdCodePtr] = 0x00;  // 最後に終端文字を入れて
  CW_decode(buf);                // コードから文字への変換をする (結果はbufに)

  space_is_sent = false;  // 非スペース文字を出力したので、単語区切りがあった場合はスペースを出力可とする
}


/***************************************/
/**   モールスコードの文字判定処理     **/
/***************************************/

// 注意: Arduino IDEはコード体系がUTF-8であり、カナが3byteになる
//       そのため、デコード後の出力文字はByteデータで定義 (ASCIIカナ)

//  辞書データには和文が入っていますが、CW練習機では使用していません。

/** 逆引き辞書 **/
struct Dic3 {
  char* Mcode;  // モールス符号 1:短点、2:長点
  char* C;      // Alphabet
  char* KANA;   // カナ (ターミナル出力用 UTF-8)
  char K[3];    // KANA (OLED表示用 ASCIIカナ)
};

int maxCode = 79;
char* NoChar = "_";  //

Dic3 deCode[] = {
  { "1", "E", "ヘ", 0xCD, 0x00, 0x00 },               // ヘ
  { "11", "I", "゙", 0xDE, 0x00, 0x00 },              // ゙
  { "111", "S", "ラ", 0xD7, 0x00, 0x00 },             // ラ
  { "1111", "H", "ヌ", 0xC7, 0x00, 0x00 },            // ヌ
  { "11111", "5", NoChar, 0x35, 0x00, 0x00 },        //
  { "111111", "^", NoChar, 0x01, 0x00, 0x00 },       //
  { "11111111", "[HH]", NoChar, 0x01, 0x00, 0x00 },  //
  { "11112", "4", "", 0x34, 0x00, 0x00 },            //
  { "1112", "V", "ク", 0xB8, 0x00, 0x00 },            // ク
  { "11121", "[R]", ">>", 0x3E, 0x3E, 0x00 },        // ラタ ='>>' 和文終わり
  { "111212", "[VA]", NoChar, 0x01, 0x00, 0x00 },    //
  { "11122", "3", NoChar, 0x33, 0x00, 0x00 },        //
  { "112", "U", "ウ", 0xB3, 0x00, 0x00 },             // ウ
  { "1121", "F", "チ", 0xC1, 0x00, 0x00 },            // チ
  { "11211", NoChar, "ト", 0xC4, 0x00, 0x00 },        // ト
  { "11212", NoChar, "ミ", 0xD0, 0x00, 0x00 },        // ミ
  { "112121", "UR", NoChar, 0x01, 0x00, 0x00 },      //  コード外の連続文字のテスト
  { "1122", NoChar, "ノ", 0xC9, 0x00, 0x00 },         // ノ
  { "11221", NoChar, "゚", 0xDF, 0x00, 0x00 },        // ゚
  { "112211", NoChar, NoChar, 0x01, 0x00, 0x00 },    //
  { "112212", "_", NoChar, 0x01, 0x00, 0x00 },       //
  { "11222", "2", NoChar, 0x32, 0x00, 0x00 },        //
  { "12", "A", "イ", 0xB2, 0x00, 0x00 },              // イ
  { "121", "R", "ナ", 0xC5, 0x00, 0x00 },             // ナ
  { "1211", "L", "カ", 0xB6, 0x00, 0x00 },            // カ
  { "12111", "[AS]", "オ", 0xB5, 0x00, 0x00 },        // オ
  { "12112", NoChar, "イ", 0xB2, 0x00, 0x00 },        // イ
  { "121121", "\"", "」", 0xA3, 0x00, 0x00 },         // 」
  { "1212", NoChar, "ロ", 0xDB, 0x00, 0x00 },         // ロ
  { "12121", "[AR]", "ン", 0xDD, 0x00, 0x00 },        // ン
  { "121211", NoChar, "・", 0xA5, 0x00, 0x00 },       // ・
  { "121212", ".", "、", 0xA4, 0x00, 0x00 },          // 、
  { "12122", NoChar, "テ", 0xC3, 0x00, 0x00 },        // テ
  { "122", "W", "ヤ", 0xD4, 0x00, 0x00 },             // ヤ
  { "1221", "P", "ツ", 0xC2, 0x00, 0x00 },            // ツ
  { "12211", NoChar, "エ", 0xB4, 0x00, 0x00 },        // エ
  { "12212", NoChar, "ー", 0xB0, 0x00, 0x00 },        // ー
  { "122121", "@", NoChar, 0x01, 0x00, 0x00 },       //
  { "1222", "J", "ヲ", 0xA6, 0x00, 0x00 },            // ヲ
  { "12221", NoChar, "セ", 0xBE, 0x00, 0x00 },        // セ
  { "12222", "1", NoChar, 0x31, 0x00, 0x00 },        //
  { "122221", "\'", NoChar, 0x01, 0x00, 0x00 },      //
  { "2", "T", "ム", 0xD1, 0x00, 0x00 },               // ム
  { "21", "N", "タ", 0xC0, 0x00, 0x00 },              // タ
  { "211", "D", "ホ", 0xCE, 0x00, 0x00 },             // ホ
  { "2111", "B", "ハ", 0xCA, 0x00, 0x00 },            // ハ
  { "21111", "6", NoChar, 0x36, 0x00, 0x00 },        //
  { "211112", "-", NoChar, 0x01, 0x00, 0x00 },       //
  { "21112", "=", "メ", 0xD2, 0x00, 0x00 },           // メ
  { "2112", "X", "マ", 0xCF, 0x00, 0x00 },            // マ
  { "21121", "/", "モ", 0xD3, 0x00, 0x00 },           // モ
  { "21122", NoChar, "ユ", 0xD5, 0x00, 0x00 },        // ユ
  { "211222", "<<", "<<", 0x3C, 0x3C, 0x00 },        // ホレ ='<<' 和文開始
  { "212", "K", "ワ", 0xDC, 0x00, 0x00 },             // ワ
  { "2121", "C", "ニ", 0xC6, 0x00, 0x00 },            // ニ
  { "21211", NoChar, "キ", 0xB7, 0x00, 0x00 },        // キ
  { "2121112", "CU", NoChar, 0x01, 0x00, 0x00 },     //    コード外の連続文字のテスト
  { "21212", NoChar, "サ", 0xBB, 0x00, 0x00 },        // サ
  { "2122", "Y", "ケ", 0xB9, 0x00, 0x00 },            // ケ
  { "21221", "(", "ル", 0xD9, 0x00, 0x00 },           // ル
  { "212212", ")", "「", 0xA2, 0x00, 0x00 },          // 「
  { "21222", NoChar, "エ", 0xB4, 0x00, 0x00 },        // エ
  { "22", "M", "ヨ", 0xD6, 0x00, 0x00 },              // ヨ
  { "221", "G", "リ", 0xD8, 0x00, 0x00 },             // リ
  { "2211", "Z", "フ", 0xCC, 0x00, 0x00 },            // フ
  { "22111", "7", NoChar, 0x37, 0x00, 0x00 },        //
  { "22112", NoChar, "ヒ", 0xCB, 0x00, 0x00 },        // ヒ
  { "221122", ",", NoChar, 0x01, 0x00, 0x00 },       //
  { "2212", "Q", "ネ", 0xC8, 0x00, 0x00 },            // ネ
  { "22121", NoChar, "シ", 0xBC, 0x00, 0x00 },        // シ
  { "22122", NoChar, "ア", 0xB1, 0x00, 0x00 },        // ア
  { "222", "O", "レ", 0xDA, 0x00, 0x00 },             // レ
  { "2221", NoChar, "ソ", 0xBF, 0x00, 0x00 },         // ソ
  { "22211", "8", NoChar, 0x38, 0x00, 0x00 },        //
  { "222111", ":", NoChar, 0x01, 0x00, 0x00 },       //
  { "22212", NoChar, "ス", 0xBD, 0x00, 0x00 },        // ス
  { "2222", NoChar, "コ", 0xBA, 0x00, 0x00 },         // コ
  { "22221", "9", NoChar, 0x39, 0x00, 0x00 },        //
  { "22222", "0", NoChar, 0x30, 0x00, 0x00 }         //
};

int lptr;  // left pointer (lower side)
int rptr;  // right pointer (higher side)
int indexPtr;

void CW_decode(char* buf) {  // デコードした文字(列)はbufにコピーして返す
  lptr = 0;
  rptr = maxCode;

  while (1) {            // 二分探索でコードを判定する
    if (lptr >= rptr) {  // 二分探索をしたが、辞書データの間でマッチしなかった場合
      // not found
      strcpy(buf, "_");  // '_' アンダーバーを返す
      return;
    }
    indexPtr = (lptr + rptr) / 2;
    if (strcmp(rcvdCode, deCode[indexPtr].Mcode) == 0) { // マッチした場合
      strcpy(buf, deCode[indexPtr].C);
      return ;
    }
    /*** まだ一致していないので、探索を続ける ***/
    if (strcmp(rcvdCode, deCode[indexPtr].Mcode) < 0) {  // 大きいか、小さい場合
      rptr = indexPtr;
    } else {
      lptr = indexPtr + 1;  // ここの+1が重要  (当てはまる符号がないときにループする)
    }
  }
}

EleKey 

/*******************************
 *  パート3 および パート4のエレキーの機能      *
 *******************************/

/// ハードウェアの接続定義 ///
#define DOT_PIN   3  // Paddle
#define DASH_PIN  2
#define BTN3_PIN 11  // ボタン3
#define BTN4_PIN 10  // ボタン4
#define SPK_PIN   8    // スピーカー

#define Off   0
#define On    1

// KEYやボタンの状態変化 //
#define BtnOnEdge 1
#define BtnOffEdge 2
#define NoChange 0

// EleKeyの状態 //
#define IDLE  0
#define DOT   1
#define DASH  2
#define BLANK 3

int eleKeySt = IDLE;
unsigned long start_time = 0;  // 時間監視用
unsigned long duration = 0;    // 送出時間 (ミリ秒)

//  WPMと送出時間(msec)
int WPM = 10;
int dot_length = 120;
int dash_length = 360;
int blank_length = 120;
//

// 先行入力のメモ
int Memo;
bool Memo_flag = false;

// スクイーズ操作用
int last_key = BLANK ;

extern int dotKeyStatus, dashKeyStatus;  // 後方参照

int getElekeyStatus() {
  int dotKeyEdge, dashKeyEdge ;

  // パドルの状態を更新 (チャタリング処理)
  dotKeyEdge = check_dotKey_status();   // 状態の更新
  dashKeyEdge = check_dashKey_status();  // 状態の更新

  /***** パート4での追加部分 (先行入力をメモに格納) *****/
  if (dotKeyEdge == true) {
    // if ((eleKeySt == DASH) || (eleKeySt == BLANK)) {  // 何かを送出中に来たトリガーはメモへ
    if (eleKeySt == DASH) {  // 何かを送出中に来たトリガーはメモへ (こちらの設定の方が打ちやすい)
      dotKeyEdge = false;
      Memo = DOT;
      Memo_flag = true;
    }
  }
  if (dashKeyEdge == true) {
    if ((eleKeySt == DOT) || (eleKeySt == BLANK)) {  // 何かを送出中に来たトリガーはメモへ
      dashKeyEdge = false;
      Memo = DASH;
      Memo_flag = true;
    }
  }
  /*****************************************/

  // 信号送出中か?
  if ((eleKeySt == DOT) || (eleKeySt == DASH)) {         // 信号送出中
    if (millis() - start_time <= duration) return (On);  // 信号送出の時間が経過していない
  }
  if (eleKeySt == BLANK) {                                // ブランク送出中
    if (millis() - start_time <= duration) return (Off);  // 信号送出の時間が経過していない
  }
  // IDLE状態または信号送出が完了した場合
  // 送出完了したのが、dotかdashの場合はBlankを送出する
  if ((eleKeySt == DOT) || (eleKeySt == DASH)) {
    last_key = eleKeySt ; // スクイーズ用に記憶
    eleKeySt = BLANK;
    duration = blank_length;
    start_time = millis();
    noTone(SPK_PIN);
    return (Off);
  }
  // 送出し終わったのがBlank
  if (eleKeySt == BLANK) {
    eleKeySt = IDLE;
    /*** パート4で追加(先行入力の送出) ***/
    if (Memo_flag == true) {  // 先行入力があれば、それを送出する
      Memo_flag = false;
      if (Memo == DOT) {
        eleKeySt = DOT;
        tone(SPK_PIN, Pitch);            // 音を出す
        digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
        duration = dot_length;
        start_time = millis();
        return (On);
      } else {
        eleKeySt = DASH;
        tone(SPK_PIN, Pitch);            // 音を出す
        digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
        duration = dash_length;
        start_time = millis();
        return (On);
      }
    }
    /****************/
  }
  // 次に送出する符号
  if ((dotKeyStatus == On) && (dashKeyStatus == Off)) {  // dotKey On
    eleKeySt = DOT;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dot_length;
    start_time = millis();
    return (On);
  }
  if ((dotKeyStatus == On) && (dashKeyStatus == On) && (last_key == DOT)) {  // スクイーズ操作
    eleKeySt = DASH;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dash_length;
    start_time = millis();
    return (On);
  }
  if ((dashKeyStatus == On) && (dotKeyStatus == Off)) {  // Key On Edge
    eleKeySt = DASH;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dash_length;
    start_time = millis();
    return (On);
  }
  if ((dotKeyStatus == On) && (dashKeyStatus == On) && (last_key == DASH)) {  // スクイーズ操作
    eleKeySt = DOT;
    tone(SPK_PIN, Pitch);            // 音を出す
    digitalWrite(LED_BUILTIN, LOW);  // LED On (追加)
    duration = dot_length;
    start_time = millis();
    return (On);
  }

  // IDLE中

  // ボタン3 (WPM up)
  if (check_Btn3_status() == BtnOnEdge) {  // Btn On Edge
    setWPM(+1);                            // 速度(WPM)を上げる
  }
  // ボタン4 (WPM down)
  if (check_Btn4_status() == BtnOnEdge) {  // Btn On Edge
    setWPM(-1);                            // 速度(WPM)を下げる
  }

  return (Off);  // IDLE中
}

///////////////////////////////////////////////////////
// 以下は、パドル(dotKey, dashKey)、ボタン3,、ボタン4の入力チェック   //
//  On/Offではなく、on/offの切り替え(Edge)を返す       //
//      BtnOnEdge =1, BtnOffEdge= 2, NoChange = 0    //
///////////////////////////////////////////////////////

extern int threshold;  // thresholdは、全ボタン共通とした

/// パドル (dotKey) ///
static int dotKeyCnt = 0;
static int dotKeyStatus = Off;  // normally off

int check_dotKey_status() {
  if (digitalRead(DOT_PIN) == HIGH) {  // switch off
    dotKeyCnt--;
    if (dotKeyCnt < 0) {
      dotKeyCnt = 0;
      if (dotKeyStatus == On) {
        dotKeyStatus = Off;
        return (BtnOffEdge);  // Key Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    dotKeyCnt++;
    if (dotKeyCnt > threshold) {
      dotKeyCnt = threshold;
      if (dotKeyStatus == Off) {
        dotKeyStatus = On;
        return (BtnOnEdge);  // Key On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/// パドル (dashKey) ///
static int dashKeyCnt = 0;
static int dashKeyStatus = Off;  // normally off

int check_dashKey_status() {
  if (digitalRead(DASH_PIN) == HIGH) {  // switch off
    dashKeyCnt--;
    if (dashKeyCnt < 0) {
      dashKeyCnt = 0;
      if (dashKeyStatus == On) {
        dashKeyStatus = Off;
        return (BtnOffEdge);  // Key Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    dashKeyCnt++;
    if (dashKeyCnt > threshold) {
      dashKeyCnt = threshold;
      if (dashKeyStatus == Off) {
        dashKeyStatus = On;
        return (BtnOnEdge);  // Key On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/// ボタン3(Btn3) ///
static int Btn3Cnt = 0;
static int Btn3Status = Off;  // normally off

int check_Btn3_status() {
  if (digitalRead(BTN3_PIN) == HIGH) {  // switch off
    Btn3Cnt--;
    if (Btn3Cnt < 0) {
      Btn3Cnt = 0;
      if (Btn3Status == On) {
        Btn3Status = Off;
        return (BtnOffEdge);  // Btn3 Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    Btn3Cnt++;
    if (Btn3Cnt > threshold) {
      Btn3Cnt = threshold;
      if (Btn3Status == Off) {
        Btn3Status = On;
        return (BtnOnEdge);  // Btn3 On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/// ボタン4(Btn4) ///
static int Btn4Cnt = 0;
static int Btn4Status = Off;  // normally off

int check_Btn4_status() {
  if (digitalRead(BTN4_PIN) == HIGH) {  // switch off
    Btn4Cnt--;
    if (Btn4Cnt < 0) {
      Btn4Cnt = 0;
      if (Btn4Status == On) {
        Btn4Status = Off;
        return (BtnOffEdge);  // Btn4 Off Edge
      } else {
        return (NoChange);  // no change
      }
    }
  } else {  // switch on
    Btn4Cnt++;
    if (Btn4Cnt > threshold) {
      Btn4Cnt = threshold;
      if (Btn4Status == Off) {
        Btn4Status = On;
        return (BtnOnEdge);  // Btn4 On Edge
      } else {
        return (NoChange);  // no change
      }
    }
  }
}

/*****   Definition of WPM ********************************************/
/* int WPM = 10;  // default                                          */
/* 10 WPM of "Paris_" is 500 dots/minute                              */
/* unsigned long dot_length = 60 sec / 500 * 1000 ; // milli second   */
/*               = 120ms/dot                                          */
/**********************************************************************/
// 速度(WPM)変更 (5-50)
void setWPM(int n) {
  char s[16] ;
  WPM = WPM + n;
  if (WPM < 5) WPM = 5;
  if (WPM > 50) WPM = 50;
  dot_length = 60000 / (50 * WPM);
  dash_length = dot_length * 3;
  blank_length = dot_length;
  tone(SPK_PIN, Pitch, 100);  // 速度を変更したら、ピッと鳴らす
  sprintf(s," WPM=%d ", WPM) ;
  LCD_print(s) ;
}

このブログを検索

ブログ アーカイブ

CW Decoderの製作講習会を開催しました

 先日、「マイコンで作る 本格的CW デコーダ」を掲載しましたが、これを、船橋市アマチュア無線クラブ(FARC: JJ1YNA)で発表したところ、大画面化したものが欲しいとのご要望をいただきました。 そこで、OLEDをタッチパネル付きのTFT液晶に変更して大画面化、操作性を改善し...

QooQ