/////////////////////////////
// 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 ////////////////////
/**************************************************/
/**** 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が重要 (当てはまる符号がないときにループする)
}
}
}
0 件のコメント:
コメントを投稿