K3NG CW Keyer、受信送信5文字エコーバック練習機能を改造

K3NG CW KeyerのUコマンド(Receive / Send Echo Practice)を少々改造した。

変更内容

まず、Uコマンドの練習機能についてはこちら

概要は以下の通り。

  • 受信したコードをそのまま打鍵する
  • 一文字から始まって最大五文字
  • 以降、再び一文字から繰り返す(終りはなし - 終るにはコマンドモードから抜ける)
  • 出題される文字はアルファベット

そして、改造の内容は以下。

  • 出題される文字に数字と一部の記号を追加(追加した記号は「/?.,」の四文字)
  • 正答・誤答時の音を変更

数字等の追加は単純に文字の種類を増やしたというだけで、一般的な話。

音の変更は、今作っているハードウェアに合せたもの。K3NG CW Keyerでは音はPWM出力で圧電サウンダを鳴らすのが通常なのだと思う。しかし、これだと濁った音になるのでCWのサイドトーンとしてはいまいち。それを見越してであろう、「OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE」というオプションが用意されている。これを有効化すれば、音の出力がPWMではなく、単純なON/OFF信号になる。これを使って別の発振器を制御すれば良い。

ところがこの音出力機能は、打鍵のサイドトーンだけではなく他にも使われている。例えば、コマンドモードに入るときに「ピッ!」と音を出すとか。Uコマンドのエコーバック練習機能では、正答時には高い音(beep())、誤答時には低い音(boop())を鳴らして正誤を伝えるようになっている。

しかし、「OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE」を有効化していると、正答時にも誤答時にもサイドトーンと同じ音が「プー」と聞こえるだけで区別がつかない(「T」の符号と区別がつかないとも言える)。

そこで、正答・誤答の音はサイドトーンとは別の音を発するように改造する。と言っても、サイドトーンの発振回路しかないので、出力ラインをPWMで高速にスイッチングして別の音として聞こえるようにする。言ってみれば、PWM信号をサイドトーンの周波数で変調をかけたような感じか?

実際の動作の様子は下のビデオで。

K3NG CW Keyer、5文字エコー練習の拡張

なお、これはあくまでこの改造の動作デモンストレーション。(耳で聞いて手で打っているのではなくて、)RST Decoderでデコードして、パドルエミュレータを使ってPCで入力している。

変更個所

ソースの変更箇所は以下の通り(ファイルは二つ)。

keyer_features_and_options.h


--- D:/tmp/orig/k3ng_cw_keyer-master/k3ng_keyer/keyer_features_and_options.h	Mon May 28 15:52:59 2022
+++ D:/tmp/k3ng_cw_keyer-master/k3ng_keyer/keyer_features_and_options.h	Mon May 29 01:07:54 2022
@@ -47,3 +47,4 @@
 // #define FEATURE_ALPHABET_SEND_PRACTICE  // enables command mode S command - created by Ryan, KC2ZWM
-// #define FEATURE_COMMAND_MODE_PROGRESSIVE_5_CHAR_ECHO_PRACTICE // enables command mode U
+#define FEATURE_COMMAND_MODE_PROGRESSIVE_5_CHAR_ECHO_PRACTICE // enables command mode U
+#define OPTION_PROGRESSIVE_5_CHAR_INCLUDE_NUMBER
 // #define FEATURE_PTT_INTERLOCK 
@@ -106,3 +107,3 @@
 // #define OPTION_CMOS_SUPER_KEYER_IAMBIC_B_TIMING_ON_BY_DEFAULT
-// #define OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
+#define OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
 // #define FEATURE_SD_CARD_SUPPORT

「#define OPTION_PROGRESSIVE_5_CHAR_INCLUDE_NUMBER」は独自に追加したもの。

k3ng_keyer.ino

--- D:/tmp/orig/k3ng_cw_keyer-master/k3ng_keyer/k3ng_keyer.ino	Mon May 28 15:52:58 2022
+++ D:/tmp/k3ng_cw_keyer-master/k3ng_keyer/k3ng_keyer.ino	Tue May 29 01:30:52 2022
@@ -8162,2 +8163,71 @@
 #if defined(FEATURE_COMMAND_MODE_PROGRESSIVE_5_CHAR_ECHO_PRACTICE) && defined(FEATURE_COMMAND_MODE)
+
+#ifdef OPTION_PROGRESSIVE_5_CHAR_INCLUDE_NUMBER
+char random_cw_code() {
+	byte c;
+	
+	c = (char)random(55,95);
+
+	if (c <= 64) {	// number
+		c -= 7;				// c: 48(0) to 57(9)
+	}
+	else {
+		switch (c) {
+			case 91 :
+				c = '/';
+				break;
+			case 92 :
+				c = '?';
+				break;
+			case 93 :
+				c = ',';
+				break;
+			case 94 :
+				c = '.';
+				break;
+			default:		// alphabet
+				break;
+		}
+	}
+
+	return(c);
+}
+#endif
+
+#ifdef OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
+#define FC5_FAIL				-1
+#define FC5_CORRECT_5		0
+#define FC5_SUCCESS			1
+
+void beep_fc5(char fc5) {
+	unsigned int tone_freq;
+	char i, n;
+
+	n = 4;
+	
+	switch (fc5) {
+		case FC5_CORRECT_5:
+				tone_freq = 600;
+				n = 8;
+				break;
+		case FC5_SUCCESS:
+				tone_freq = 1200;
+				break;
+		case FC5_FAIL:
+		default:
+				tone_freq = 300;
+				break;
+	}
+
+	for (i = 0; i < n; i++) {
+		tone(sidetone_line,	tone_freq);
+		delay(50);
+		noTone(sidetone_line);
+		if (fc5 == FC5_CORRECT_5) {
+			tone_freq += 80;
+		}
+	}
+}
+#endif
+              
 void command_progressive_5_char_echo_practice() {
@@ -8177,4 +8247,4 @@
   byte progressive_step_counter;
-  byte practice_mode;
-  char word_buffer[10];
+  // byte practice_mode;
+  // char word_buffer[10];
 
@@ -8201,3 +8271,3 @@
       } else {
-        lcd_center_print_timed("RX / TX 5 Char", 0, default_display_msg_delay);
+        lcd_center_print_timed("RX-TX 5 Char", 0, default_display_msg_delay);
         if (LCD_ROWS > 1){
@@ -8237,2 +8307,9 @@
     //   case ECHO_PROGRESSIVE_5:
+#if defined(OPTION_PROGRESSIVE_5_CHAR_INCLUDE_NUMBER)
+        cw_to_send_to_user = random_cw_code();
+        cw_to_send_to_user.concat(random_cw_code());
+        cw_to_send_to_user.concat(random_cw_code());
+        cw_to_send_to_user.concat(random_cw_code());
+        cw_to_send_to_user.concat(random_cw_code());
+#else
         cw_to_send_to_user = (char)random(65,91);
@@ -8242,2 +8319,3 @@
         cw_to_send_to_user.concat((char)random(65,91));
+#endif	// defined(OPTION_PROGRESSIVE_5_CHAR_INCLUDE_NUMBER)
         progressive_step_counter = 1;
@@ -8278,2 +8356,5 @@
 
+      send_char(' ',0);
+      send_char(' ',0);
+            
       // send the CW to the user
@@ -8338,3 +8419,4 @@
         // do we have all the characters from the user? - if so, get out of user_send_loop
-        if ((user_sent_cw.length() >= cw_to_send_to_user.length()) || ((progressive_step_counter < 255) && (user_sent_cw.length() == progressive_step_counter))) {
+        //if ((user_sent_cw.length() >= cw_to_send_to_user.length()) || ((progressive_step_counter < 255) && (user_sent_cw.length() == progressive_step_counter))) {
+        if ((user_sent_cw.length() >= cw_to_send_to_user.length()) || (user_sent_cw.length() == progressive_step_counter)) {
           user_send_loop = 0;
@@ -8355,3 +8437,4 @@
       if (loop1 && loop2) {
-        if (progressive_step_counter < 255) {                                             // we're in progressive mode
+//        if (progressive_step_counter < 255) {                                             // we're in progressive mode
+        if (1) {
           if (user_sent_cw.substring(0,progressive_step_counter) == cw_to_send_to_user.substring(0,progressive_step_counter)) {    // we get here if the character entered is correct
@@ -8360,3 +8443,7 @@
               if (wrong_answer_led) digitalWrite(wrong_answer_led, LOW);                  // clear the wrong answer LED
+#ifdef OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
+              beep_fc5(FC5_SUCCESS);
+#else
               beep();                                                                     // and beep
+#endif
               #ifdef FEATURE_DISPLAY
@@ -8365,4 +8452,6 @@
               #endif                                                                      // FEATURE_DISPLAY
+              /*
               send_char(' ',0);
               send_char(' ',0);
+              */
               progressive_step_counter++;
@@ -8377,5 +8466,9 @@
               #endif                                                                      // FEATURE_DISPLAY
+#ifdef OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
+              beep_fc5(FC5_CORRECT_5);
+#else
               unsigned int NEWtone               =  400;                                  // the initial tone freuency for the tone sequence
               unsigned int TONEduration          =   50;                                  // define the duration of each tone element in the tone sequence to drive a speaker
-              for (int k=0; k<6; k++) {                                                   // a loop to generate some increasing tones
+              //for (int k=0; k<6; k++) {                                                   // a loop to generate some increasing tones
+              for (byte k=0; k<6; k++) {                                                   // a loop to generate some increasing tones
                 tone(sidetone_line,NEWtone);                                              // generate a tone on the speaker pin
@@ -8383,6 +8476,10 @@
                 noTone(sidetone_line);                                                    // turn off the tone
-                NEWtone = NEWtone*1.25;                                                   // calculate a new value for the tone frequency
+                //NEWtone = NEWtone*1.25;                                                   // calculate a new value for the tone frequency
+                NEWtone += (NEWtone >> 2);                                                   // calculate a new value for the tone frequency
               }                                                                           // end for
+#endif
+              /*
               send_char(' ',0);
               send_char(' ',0);
+              */
               if (correct_answer_led) digitalWrite(correct_answer_led, LOW);              // clear the correct answer LED
@@ -8392,7 +8489,18 @@
             if (correct_answer_led) digitalWrite(correct_answer_led, LOW);                // clear the correct answer LED
+#ifdef OPTION_SIDETONE_DIGITAL_OUTPUT_NO_SQUARE_WAVE
+            beep_fc5(FC5_FAIL);
+#else
             boop();
+#endif
+            #ifdef FEATURE_DISPLAY
+            	lcd_center_print_timed("Fail", 0, default_display_msg_delay);
+            #endif
+            
+            /*
             send_char(' ',0);
             send_char(' ',0);
+            */
           }
         } else {                                                                          // I don't think we ever get here, unless the progressive_step_counter is more than 255, but it is reset as Loop1 starts
+/* NEVER COME HERE
           if (user_sent_cw == cw_to_send_to_user) {                                       // correct answer
@@ -8407,2 +8515,3 @@
           }
+*/
         }                                                                                 // if (progressive_step_counter < 255)

関数を二つ追加。一つは出題文字生成のrandom_cw_code()。アルファベットに加えて、数字と記号(「/?.,」の四文字)を出題する。単純な構成なので記号を増やしたり減らしたりは簡単にできるはず。もう一つの関数は、正答・誤答・五文字連続正答時のブザー音を発生するbeep_fc5()。それに、これらの関数追加に伴う改造と、不要コードの整理(元々が他から持ってきたコードのようで要らないものがいくつかあった)。

「if (progressive_step_counter < 255) 」の判定は削除(常に真)。progressive_step_counterは出題文字数。初期値は1で1文字正解するごとにカウントアップする。5文字正解後に回る大外の「while (loop1) {}」の頭で「progressive_step_counter = 1;」初期化しているのでprogressive_step_counterが6を超えることはない。そのため、このif文は不要。当然、else{}も不要。

また、正答時にはLCDに「Success!」と表示するようになっているので、それを真似て誤答時に「Fail」と表示するようにした。これに伴い、「Fail」の文字領域を確保するために”RX / TX 5 Char”を”RX-TX 5 Char”に変更した。これをやらないとコンパイラが警告を発する。

どうやら、文字列リテラルはグローバル変数領域(RAM)に確保されるみたい。変更しない文字列なのでROM領域に置けばいいのに。どちらに置くかのスイッチとかあるのかな?


【追加改造】

また少し改造した。今回は、成功数と失敗数の表示機能追加。5文字連続正解した際に、下の写真のように表示する。 二行目の左が5文...