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

2025年3月9日日曜日

t f B! P L

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

 

前回に続いて、パート2では、電鍵から入力されたCW符号の読み取り部分を作成します。

 

1.    全体の構成

CW練習機(パート2)の構成は以下になります。

プログラムの構成

 


 

2.    メイン処理部

メインの処理部はこれだけです。

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

 

 3.    表示部

 先ず、CW符号を読みとる処理が返してきた文字を表示する部分です。

一番簡単なのはSerial経由でPC側に表示させるものですが、これだとCW練習機を使用する際に必ずPCが必要となります。そのため、今回はArduino講習会でFQA松本さんが頒布してくださったI2C接続のLCD1602(16文字x2行の表示機)を使用する事にします。

LCDのライブラリは沢山ありますが、今回は「LiquidCrystalI2C」を使いました。(このライブラリ導入とHello Worldは完了している前提で話を進めます)

ハードウェアの接続は以下のようにしました。(Arduino Pro Miniの例)

 

機能追加

LCDライブラリは、setCursor(x,y)で表示位置を設定し、print()で文字を表示しますが、行の右端を超える部分(17文字目以降)は表示されません。(折り返して次の行に表示されない)
また、16文字x2行の2行目の終りに達した時に、全体が上にスクロールするか、上の行がクリアされて続きを表示する機能がありません。
そのため、このようなコンソール型の表示をする機能追加をしました。(2行目の終りに達した時は、上の行をクリアして表示を続けるタイプにしました)

//////////////////////////////
//  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(' ');
  }
}

(注)関数名(サブルーチン名)は同じですが、引数の型でどれが呼ばれたかが区別されています。
  今回は作成しませんでしたが、LCD_print(int x); LCD_print(float x);なども準備すると便利と思います。

 

4.    CW符号の読み取り部

 CW符号の読み取り部分のプログラムは、いくつかの機能に分割して作成します。


4.1 電鍵(Key)読み取り部

パート1で作成したloop()を修正します。
関数の名前をloop()からgetKeyStatus()とし、電鍵(Key)On/Off状態を返す return文を最後に追加します。
(今回、スピーカーの音に合わせて内蔵LEDも点滅させる処理を追加しました)

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

主な機能は以下になります。
==================================
getKeyStatus()を繰り返し呼びながら、

1) Key OnからKey Offになるまでの時間を計測して、短点、長点の判定をして、短点・長点データを連結していきます。(短点’1’、長点’2’の文字列)

2) Key OffからKey OnになるまでのBlankの時間を計測して、文字区切りの判定をします。

3) 文字区切りが来たら、文字区切りまでの短点・長点の符号を次の文字判定(CW_decode())へ渡します。

4) 短点・長点の時間計測と判定の中で、速度追従処理をします。
================================== 

モールス符号の基本は短点のOn時間を1として、長点は3、符号間のブランクは1、文字区切りは3、単語区切りは7以上です。
しかしながら、人間が打つ限り、長さのブレや癖、速度の変化が出ます。従って人間の電鍵入力を読みとる部分は、試しながら
パラメータを調整してみました。
(ソースコードはお世辞にもきれいとは言えませんので省略します。)

4.3 文字判定

短点・長点データを、二分探索アルゴリズムで高速探索して文字符号に変換します。
データが符号と一致しない場合は、”_”を返します。

プログラム抜粋 (前提: 短点・長点データは rcvdCodeというグローバル変数に入っている)


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

// 注意: 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 },            // ヌ
 ・・・中略・・・
  { "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が重要  (当てはまる符号がないときにループする)
    }
  }
}

アルゴリズムの説明

二分探索は、探索するキー(データ)を左から右に昇順に並べます。
・左手を下限、右手を上限として、入力データを左手と右手の中央のデータと比較します。
・入力データと一致すれば、それが目的とする探索データとなります。
・もし、中央のデータが入力データよりも大きければ、探索するデータは中央より下(左側)にあると分かるので、上限(右手)を中央の一つ下として、再度探索していきます。
・最終的に、左手>=右手となった場合はマッチするデータが無いと判断します。

(コラム) 本題とは少し逸れますが、
モールス符号の最も要素が多い符号は[HH](訂正)の短点8個になります。そのため、短点を1、長点を0とした8bit(右詰め、または左詰め)データとして、256個の配列から直接検索した方が早いと思われる方もいるかもしれません。しかしながら、例えば、右詰めの場合、"T"と"M", "O", "0[ゼロ]"はいずれも(‘0000 0000’)になり判別できませんので、別に長さのデータを付加する必要があります。また、将来的に連結して送信された文字(例えば “QSL”(11要素))を一単語として読みとる、など機能追加への対応に柔軟性がありません。
このような観点から、短点・長点データは、柔軟性・拡張性のある文字列データとしました。

 

5.    プログラムファイルの分割

CW符号の読み取り部分は、比較的長いプログラムになるのと、機能的にまとまっていますので、プログラムファイルを”CW_Reader.ino”に分割しました。 

以下に、メイン側のプログラムと、CW読み取り部分の全プログラムを掲載します。

メイン側


/////////////////////////////
// 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  // スピーカー

#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

  /*** パート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) {
  Pitch = Pitch + n;
  if (Pitch < 300) Pitch = 300;
  if (Pitch > 1200) Pitch = 1200;
  tone(SPK_PIN, Pitch, 100);  // 音程を変更したら、ピッと鳴らす
}

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

int threshold = 10;  // 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.ino


/**************************************************/
/**** 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();  // メインルーチン側で定義されている

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

void waitForKeyOff() {
  while (PIN_Status == On) {
    PIN_Status = getKeyStatus();
  }
}

int waitForKeyOnWithTimeout(unsigned long startTime, int timeout) {
  while (PIN_Status == Off) {
    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が重要  (当てはまる符号がないときにループする)
    }
  }
}


6.    まとめ

今回は、ハードはLCD表示機を追加し、ソフトはCW読み取り機能を追加するところまで完成しました。

次は、エレキーの機能追加をした過程を2回に分けて説明したいと思います。

 

このブログを検索

ブログ アーカイブ

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

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

QooQ