]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
- updated the CAT CW stuff, now maintaining a ring buffer.
authorc vw <dl1ycf@darc.de>
Tue, 6 Nov 2018 08:38:34 +0000 (09:38 +0100)
committerc vw <dl1ycf@darc.de>
Tue, 6 Nov 2018 08:38:34 +0000 (09:38 +0100)
  Several corrections such that hitting a "local" CW key aborts
  CAT CW correctly and continues with manual CW.

- moved choice of active level for CW buttons to GPIO menu

- added support for "CW letter spacing" in CW menu

- at many places corrected cast from a generic pointer to int. The correct
  way of passing an int when receiving a pointer is:

  void extern(void *p) {
    int state=(uintptr_t) p;
    call intern(state);
  }

  What is certainly wrong is "call intern((uintptr_t) p)" since this
  passes on many systems a 64-bit value where a 32-bit value is expected
  unless strong prototyping is used.

- cleaned up configure.c (at least: "gtk_widget_destroy(dialog)" in configure_gpio
  had to be deleted)

- use cw_breakin instead of vox_hang in CW-keyer

- improved GPIO side tone creation, no longer use softTone but do this within the keyer thread
  (but only if GPIO side tone is requested).

- Hide button names such as CWL_BUTTON from iambic.c

- Major overhaul of iambic keyer: see extensive comment in iambic.c

Makefile
Makefile.mac
cw_menu.c
gpio.c
gpio.h
iambic.c
iambic.h
new_protocol.c
new_protocol.h
property.c
rigctl.c

index d0686e959449a3d4ab86306df746d59b3fc1ef3f..544978351bfe2fb262bee89ca93ce055541088cb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -141,14 +141,9 @@ endif
 
 ifeq ($(LOCALCW_INCLUDE),LOCALCW)
 LOCALCW_OPTIONS=-D LOCALCW
-LOCALCW_SOURCES= \
-beep.c \
-iambic.c
-LOCALCW_HEADERS= \
-beep.h \
-iambic.h
-LOCALCW_OBJS= \
-iambic.o
+LOCALCW_SOURCES= iambic.c
+LOCALCW_HEADERS= iambic.h
+LOCALCW_OBJS   = iambic.o
 endif
 
 ifeq ($(GPIO_INCLUDE),GPIO)
index 50ae5c2969191a0e417ddea3e18a1cc5e2c7ac5d..edbb63fd1d1dc409d7a2385e4736513c780eb65a 100644 (file)
@@ -139,14 +139,9 @@ endif
 
 ifeq ($(LOCALCW_INCLUDE),LOCALCW)
 LOCALCW_OPTIONS=-D LOCALCW
-LOCALCW_SOURCES= \
-beep.c \
-iambic.c
-LOCALCW_HEADERS= \
-beep.h \
-iambic.h
-LOCALCW_OBJS= \
-iambic.o
+LOCALCW_SOURCES= iambic.c
+LOCALCW_HEADERS= iambic.h
+LOCALCW_OBJS=    iambic.o
 endif
 
 ifeq ($(GPIO_INCLUDE),GPIO)
index 21ddb9eb1d46a434da888b97304c305604f2973e..14ec79ade528c687062d83b8c6977c8b15df5392 100644 (file)
--- a/cw_menu.c
+++ b/cw_menu.c
@@ -33,6 +33,7 @@
 #include "receiver.h"
 #include "new_protocol.h"
 #include "old_protocol.h"
+#include "iambic.h"
 
 static GtkWidget *parent_window=NULL;
 
@@ -40,6 +41,14 @@ static GtkWidget *menu_b=NULL;
 
 static GtkWidget *dialog=NULL;
 
+void cw_changed() {
+// inform the local keyer about CW parameter changes
+// (only if LOCALCW is active).
+#ifdef LOCALCW
+  keyer_update();
+#endif
+}
+
 static void cleanup() {
   if(dialog!=NULL) {
     gtk_widget_destroy(dialog);
@@ -63,6 +72,11 @@ static void cw_keyer_internal_cb(GtkWidget *widget, gpointer data) {
   cw_changed();
 }
 
+static void cw_keyer_spacing_cb(GtkWidget *widget, gpointer data) {
+  cw_keyer_spacing=cw_keyer_spacing==1?0:1;
+  cw_changed();
+}
+
 static void cw_keyer_speed_value_changed_cb(GtkWidget *widget, gpointer data) {
   cw_keyer_speed=gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
   cw_changed();
@@ -235,11 +249,16 @@ void cw_menu(GtkWidget *parent) {
 
 #ifdef LOCALCW
   GtkWidget *cw_keyer_internal_b=gtk_check_button_new_with_label("CW Internal");
-  //gtk_widget_override_font(cw_keyer_internal_b, pango_font_description_from_string("Arial 18"));
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cw_keyer_internal_b), cw_keyer_internal);
   gtk_widget_show(cw_keyer_internal_b);
   gtk_grid_attach(GTK_GRID(grid),cw_keyer_internal_b,0,10,1,1);
   g_signal_connect(cw_keyer_internal_b,"toggled",G_CALLBACK(cw_keyer_internal_cb),NULL);
+
+  GtkWidget *cw_keyer_spacing_b=gtk_check_button_new_with_label("CW enforce letter spacing");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cw_keyer_spacing_b), cw_keyer_spacing);
+  gtk_widget_show(cw_keyer_spacing_b);
+  gtk_grid_attach(GTK_GRID(grid),cw_keyer_spacing_b,0,11,1,1);
+  g_signal_connect(cw_keyer_spacing_b,"toggled",G_CALLBACK(cw_keyer_spacing_cb),NULL);
 #endif
 
   gtk_container_add(GTK_CONTAINER(content),grid);
@@ -249,4 +268,3 @@ void cw_menu(GtkWidget *parent) {
   gtk_widget_show_all(dialog);
 
 }
-
diff --git a/gpio.c b/gpio.c
index 5828b8ff830ee475174f990d5a6494dbc1745144..b00a0c42d0f53c91dcccfdac1cf23791a05d024f 100644 (file)
--- a/gpio.c
+++ b/gpio.c
@@ -29,9 +29,6 @@
 #include <poll.h>
 #include <sched.h>
 #include <wiringPi.h>
-#ifdef LOCALCW
-#include <softTone.h>
-#endif
 #include <semaphore.h>
 
 #include "band.h"
@@ -1011,7 +1008,7 @@ static void cwAlert_left() {
     level=digitalRead(CWL_BUTTON);
     //fprintf(stderr,"cwl button : level=%d \n",level);
     //the second parameter of keyer_event ("state") is TRUE on key-down
-    keyer_event(CWL_BUTTON, CW_ACTIVE_LOW ? (level==0) : level);
+    keyer_event(1, CW_ACTIVE_LOW ? (level==0) : level);
 }
 
 static void cwAlert_right() {
@@ -1019,11 +1016,13 @@ static void cwAlert_right() {
     if (cw_keyer_internal != 0) return; // as quickly as possible
     level=digitalRead(CWR_BUTTON);
     //fprintf(stderr,"cwr button : level=%d \n",level);
-     keyer_event(CWR_BUTTON, CW_ACTIVE_LOW ? (level==0) : level);
+     keyer_event(0, CW_ACTIVE_LOW ? (level==0) : level);
 }
 
 //
-// Two functions added, might be useful somewhere
+// The following functions are an interface for
+// other parts to access CW gpio functions
+// (query left and right paddle, set sidetone output)
 //
 int gpio_left_cw_key() {
   int val=digitalRead(CWL_BUTTON);
@@ -1034,6 +1033,16 @@ int gpio_right_cw_key() {
   int val=digitalRead(CWR_BUTTON);
   return CW_ACTIVE_LOW? (val==0) : val;
 }
+
+int gpio_cw_sidetone_enabled() {
+  return ENABLE_GPIO_SIDETONE;
+}
+
+void gpio_cw_sidetone_set(int level) {
+  if (ENABLE_GPIO_SIDETONE) {
+    digitalWrite(SIDETONE_GPIO, level);
+  }
+}
 #endif
 
 int gpio_init() {
@@ -1167,7 +1176,12 @@ int gpio_init() {
     setup_cw_pin(CWR_BUTTON, cwAlert_right);
   }
   if (ENABLE_GPIO_SIDETONE) {
-    softToneCreate(SIDETONE_GPIO);
+//
+//  use this pin as an output pin and
+//  set its value to LOW
+//
+    pinMode(SIDETONE_GPIO, OUTPUT);
+    digitalWrite(SIDETONE_GPIO, 0);
   }
 #endif
 
@@ -1178,14 +1192,6 @@ void gpio_close() {
     running=0;
 }
 
-#ifdef LOCALCW
-void gpio_sidetone(int freq) {
-  if (ENABLE_GPIO_SIDETONE) {
-    softToneWrite (SIDETONE_GPIO, freq);
-  }
-}
-#endif
-
 int vfo_encoder_get_pos() {
   int pos=vfoEncoderPos;
 
@@ -1466,12 +1472,9 @@ static int e4_encoder_changed(void *data) {
 static gpointer rotary_encoder_thread(gpointer data) {
     int pos;
 
-    // ignore startup glitches
     sleep(2);
 
-    //g_mutex_lock(&m_running);
     running=1;
-    //g_mutex_unlock(&m_running);
     while(1) {
 
         pos=vfo_encoder_get_pos();
@@ -1514,17 +1517,12 @@ static gpointer rotary_encoder_thread(gpointer data) {
         mox_state = 0;
         lock_state = 0;
 #endif
-//fprintf(stderr,"gpio_thread: lock\n");
-        //g_mutex_lock(&m_running);
         if(running==0) {
-fprintf(stderr,"gpio_thread: unlock (running==0)\n");
-          //g_mutex_unlock(&m_running);
+          fprintf(stderr,"gpio_thread: quitting (running==0)\n");
           g_thread_exit(NULL);
         }
         usleep(100000);
 
-//fprintf(stderr,"gpio_thread: unlock (running==1)\n");
-        //g_mutex_unlock(&m_running);
     }
     return NULL;
 }
diff --git a/gpio.h b/gpio.h
index 1075d5d35adb23ff4ca3abc000f6bf250fa2c8df..e2253f3eaf75bf0ae89eedd2ff060188ec8c88f0 100644 (file)
--- a/gpio.h
+++ b/gpio.h
@@ -87,9 +87,10 @@ extern int SIDETONE_GPIO;
 extern int ENABLE_GPIO_SIDETONE;
 extern int ENABLE_CW_BUTTONS;
 extern int CW_ACTIVE_LOW;
-void gpio_sidetone(int freq);
+void gpio_cw_sidetone_set(int level);
 int  gpio_left_cw_key();
 int  gpio_right_cw_key();
+int  gpio_cw_sidetone_enabled();
 #endif
 
 void gpio_restore_state();
index 006574823d6e70443639e02e8d3edab037cfdc3b..70bdde6e1c2096a04a8b83146c468dcde470712d 100644 (file)
--- a/iambic.c
+++ b/iambic.c
 * Speed calculation -  Using standard PARIS timing, dot_period(mS) = 1200/WPM
 */
 
+/*
+ ***************************************************************************************************************
+ * Description of a major overhaul,
+ * Sep/Oct/Nov 2018, by DL1YCF Christoph van Wullen
+ ***************************************************************************************************************
+ *
+ * a) SOME MINOR TECHNICAL ISSUES:
+ * ===============================
+ *
+ * -keyer_close was actually unused. It is now called when local CW is no longer used
+ *      and "joins" (terminates) the keyer thread.
+ *
+ * - GPIO pin names are no longer used in iambic.c
+ *
+ * - cw_keyer_spacing can now be set/un-set in the CW menu (cw_menu.c)
+ *
+ * - changing the scheduling policy now becomes only effective for the keyer thread, because
+ *   sched_setscheduler() is called at the beginning and the end of the keyer thread with
+ *   pid argument 0 (even better: use gettid() return value).
+ *   NOTE: this is Linux-specific. Better switch to POSIX calls (e.g. pthread_setschedparam).
+ *   mlockall and munlockall still apply process-wide and are therefore executed in
+ *   keyer_init/keyer_close.
+ *
+ * b) SIDE TONE GENERATION
+ * =======================
+ *
+ * There is a possibility to generate a side tone on one of the GPIO ports. This is necessary at
+ * speeds greater than 20 wpm since there is a 50 msec delay (due to the LINUX sound system) between
+ * generating the side tone and when it finally appears in the head-phone. The GPIO side tone comes
+ * without a delay.
+ *
+ * The volume of the CW side tone in the standard audio channel is reduced to zero while producing
+ * a square wave on the GPIO pin. The idea is to low-pass this signal and combine it with the audio
+ * output (hardware mixer).
+ *
+ * In the previous version, the side tone was generate using the softTone functionality of wiringPI
+ * within the GPIO module. However, this has a drawback:
+ * one creates two high-priority threads (the tone generator and the keyer)
+ * both firing at high speed (once a milli-sec and faster). This made the frequency of the tone quite
+ * unstable. Instead, the side tone is now explicitly generated within the keyer (if GPIO side tone is activated)
+ * by periodically writing "1" and "0" to the GPIO output while waiting for the end of the just-being-sent
+ * dot or dash. Furthermore, the frequency of the side tone has been stabilized by "sleeping" UNTIL the
+ * pre-calculated output-switching time (and not FOR the calculated amount, this is, using TIMER_ABSTIME
+ * in clock_nanosleep).
+ *
+ * c) CW VOX
+ * =========
+ *
+ * Suppose you hit the paddle while in RX mode. In this case, the SDR automatically switches
+ * to TX, and remains so until a certain time (actually cw_keyer_hang_time, can be set in
+ * the CW menu) has passed since sending the last element (then, PTT is removed).
+ *
+ * - cw_keyer_spacing can now be set/un-set in the CW menu (cw_menu.c)
+ *
+ * - during a dot or dash when no GPIO side tone is produced, the keyer thread simply waits and
+ *   does no busy spinning.
+ *
+ * d) DOT/DASH MEMORY
+ * ==================
+ *
+ * For reasons explained below, it is necessary to have TWO such memories for both dot and dash,
+ * they are called dot_memory/dot_held and dash_memory/dash_held. Everything explained here and below
+ * likewise holds for dot/dash exchanged.
+ *
+ * dot_memory is set whenever the dot paddle is hit. The natural way to do this is in the interrupt
+ * service routine. So, keyer_event is the ONLY place where dot_memory is set. When the keyer wants to
+ * know whether the dot paddle has been hit in some interval in time, it has to RESET dot_memory at the
+ * beginning of the interval and can then read it out any time later.
+ *
+ * While the dot paddle may have been hit long ago, it is of interest whether is was still held at
+ * the beginning of the last dash. Therefore, the keyer can store the state of the dot paddle in the
+ * variable dot_held. This is done at the beginning of sending a dash. dot_held is separate from
+ * dot_memory because only dot_held (but not dot_memory) is cleared in iambic mode A when both
+ * paddles are released.
+ *
+ * e) IAMBIC MODES A AND B, AND SINGLE-LEVER PADDLES
+ * =================================================
+ *
+ * It seems that there are lively discussions about what is what, so I distilled out the
+ * following and added one clarification that becomes important when using this mode
+ * with single-lever paddles:
+ *
+ * Suppose you hit the dash paddle and squeeze immediately afterwards (both paddles held),
+ * then the keyer is supposed to produce a "dah dit dah dit dah dit .... " sequence. This is
+ * what "iambic" is all about.
+ *
+ * Now what happens if you release BOTH paddles just after the second "dah" starts to sound?
+ * In Mode-A you produce the letter "K" (dah dit dah), while in Mode-B, you produce the letter
+ * "C" (dah dit dah dit). This is consensus. In this implementation, I follow the plead of
+ * KO0B which I found on the internet and which essentially states that the "time window" for
+ * releasing both paddles should extend until the end of the delay following the second "dah".
+ * This is not only more convenient but also mimics the behaviour of the original Curtis
+ * mode A 8044 chip (KO0B says).
+ *
+ * This means that at the end of the delay following a dash, in Mode-B the condition for
+ * producing a dot is:
+ *
+ * -dot paddle has been held at the beginning of the last dash OR
+ * -dot paddle is just being held OR
+ * -dot paddle has been hit (and possibly already been released) during the time window
+ *     from the beginning of the dash until now.
+ *
+ * This implies that in dot_held, the state of the dot key at the beginning of a dash is
+ * stored, and that dot_memory is cleared at the beginning of a dash.
+ *
+ * To implement MODE-A, we clear dot_held at the end of the delay following the dash,
+ * when both keys are released at that point in time.
+ * We do not clear dot_memory in this case! Why?
+ *
+ * All the definitions on mode A/B I found relate to releasing keys that have been
+ * squeezed for some time. Nothing is said about what happens if a key has been
+ * recently hit. I am sometimes using a single-lever paddle, and at higher speeds,
+ * I have also observed that I use a standard (dual-lever) paddle this way, namely
+ * alternatingly hitting the paddle (shortly releasing the dash paddle when hitting
+ * the dot one, and then possibly re-activating the dash paddle).
+ *
+ * It seems consensus that using a single-lever paddle, there should be no difference
+ * between mode-A and mode-B.
+ *
+ * So imagine you want to produce the letter "N" (dah dit) this way. You will hit the dash key,
+ * then immediately afterwards the dot key, and possibly have both keys released 
+ * when the final dot of the letter should start. The final dot should not be suppressed
+ * even in mode-A.
+ * Thus: if both keys have been held at the beginning of a dash, and are relased
+ * during the dash or the delay following, this dash is the last element being produced in mode-A.
+ * BUT, if the dot key has been hit during the dash (or the delay following it), then a dot
+ * element will be produced both in mode-A and mode-B, it is not necessary to keep on holding
+ * the dot key.
+ *
+ * The present implementation will make both types happy: those doing pure squeeze and
+ * those releasing one paddle when hitting the other.
+ *
+ **************************************************************************************************************
+ */
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <sys/mman.h>
 
-#include <wiringPi.h>
-
+#ifdef GPIO
 #include "gpio.h"
+#endif
 #include "radio.h"
 #include "new_protocol.h"
 #include "iambic.h"
@@ -83,35 +218,21 @@ static void* keyer_thread(void *arg);
 static pthread_t keyer_thread_id;
 
 #define MY_PRIORITY (90)
-#define MAX_SAFE_STACK (8*1024)
 #define NSEC_PER_SEC   (1000000000)
 
-/*
-enum {
-    CHECK = 0,
-    PREDOT,
-    PREDASH,
-    SENDDOT,
-    SENDDASH,
-    DOTDELAY,
-    DASHDELAY,
-    DOTHELD,
-    DASHHELD,
-    LETTERSPACE,
-    EXITLOOP
-};
-*/
-
 static int dot_memory = 0;
 static int dash_memory = 0;
+static int dot_held = 0;
+static int dash_held = 0;
 int key_state = 0;
-static int kdelay = 0;
-static int dot_delay = 0;
-static int dash_delay = 0;
+static int dot_length = 0;
+static int dash_length = 0;
 static int kcwl = 0;
 static int kcwr = 0;
 int *kdot;
 int *kdash;
+int *kmemr;
+int *kmeml;
 static int running = 0;
 #ifdef __APPLE__
 static sem_t *cw_event;
@@ -128,50 +249,85 @@ extern int clock_nanosleep(clockid_t __clock_id, int __flags,
       __const struct timespec *__req,
       struct timespec *__rem);
 
-void stack_prefault(void) {
-        unsigned char dummy[MAX_SAFE_STACK];
-
-        memset(dummy, 0, MAX_SAFE_STACK);
-        return;
-}
+#ifndef GPIO
+//
+// It makes absolutely no sense to activate LOCALCW but not GPIO.
+// However, if this is done, the code should compile although it does
+// not do anything. Therefore we provide dummy functions related to
+// GPIO side tone generation. Without GPIO, keyer_event is never
+// called so the keyer_thread does nothing.
+//
+int gpio_cw_sidetone_enabled() { return 0; }
+void gpio_cw_sidetone_set(int level) {}
+#endif
 
 void keyer_update() {
-    if (!running)
-        keyer_init();
-
-    dot_delay = 1200 / cw_keyer_speed;
+    //
+    // This function will take notice of changes in the following variables
+    //
+    // cw_keyer_internal
+    // cw_keyer_speed
+    // cw_keyer_weight
+    // cw_keys_reversed
+    //
+    // that might occur asynchronously by changing settings in the CW menu.
+    // Changes to cw_letter_spacing are notices without calling keyer_update.
+    //
+    // The most important thing here is to start/stop the keyer thread.
+    //
+
+    dot_length = 1200 / cw_keyer_speed;
     // will be 3 * dot length at standard weight
-    dash_delay = (dot_delay * 3 * cw_keyer_weight) / 50;
+    dash_length = (dot_length * 3 * cw_keyer_weight) / 50;
 
     if (cw_keys_reversed) {
-        kdot = &kcwr;
+        kdot  = &kcwr;
         kdash = &kcwl;
+        kmemr = &dot_memory;
+        kmeml = &dash_memory;
     } else {
-        kdot = &kcwl;
+        kdot  = &kcwl;
         kdash = &kcwr;
+        kmeml = &dot_memory;
+        kmemr = &dash_memory;
     }
 
+    if (cw_keyer_internal == 0) {
+      if (!running) keyer_init();
+    } else {
+      keyer_close();
+    }
 }
 
-#ifdef GPIO
-void keyer_event(int gpio, int state) {
+//
+// This is called by the paddle interrupt service routine
+//
+// left=1: left  paddle triggered event
+// left=0: right paddle triggered event
+//
+// state=0: paddle has been released
+// state=1: paddle has been hit
+//
+void keyer_event(int left, int state) {
     if (state) {
-        // This is for aborting CAT CW messages if the key is hit.
+        // This is for aborting CAT CW messages if a key is hit.
        cw_key_hit = 1;
-        // we do PTT as soon as possible, but disable cwvox if
-       // PTT has been engaged manually
+        // do PTT already here, unless TX mode was activated before
         if (running && !cwvox && !mox) {
           g_idle_add(ext_mox_update, (gpointer)(long) 1);
-           cwvox=(int) cw_keyer_hang_time;
        }
+        cwvox=(int) cw_keyer_hang_time;
     }
-    if (gpio == CWL_BUTTON)
-        kcwl = state;
-        //fprintf(stderr,"L=%d\n",state);   
-    else // CWR_BUTTON
-        kcwr = state;
-
-    if (state || cw_keyer_mode == KEYER_STRAIGHT) {
+    if (left) {
+      // left paddle hit or released
+      kcwl = state;
+      if (state) *kmeml=1;  // trigger dot/dash memory
+    } else {
+      // right paddle hit or released
+      kcwr = state;
+      if (state) *kmemr=1;  // trigger dot/dash memory
+    }
+    if (state) {
 #ifdef __APPLE__
         sem_post(cw_event);
 #else
@@ -179,28 +335,19 @@ void keyer_event(int gpio, int state) {
 #endif
     }
 }
-#endif
-
-void clear_memory() {
-    dot_memory  = 0;
-    dash_memory = 0;
-}
 
 void set_keyer_out(int state) {
     if (keyer_out != state) {
-       // DL1YCF: Shouln't one activate PTT if not yet done?
-       //         At the moment, it is required to *manually* activate
-       //         MOX before starting local CW.
         keyer_out = state;
         if(protocol==NEW_PROTOCOL) schedule_high_priority(9);
-               //fprintf(stderr,"set_keyer_out keyer_out= %d\n", keyer_out);
         if (state) {
-           gpio_sidetone(cw_keyer_sidetone_frequency);
            cw_hold_key(1); // this starts a CW pulse in transmitter.c
         } else {
-           gpio_sidetone(0);
            cw_hold_key(0); // this stops a CW pulse in transmitter.c
         }
+    } else {
+       // We should not arrive here in a properly designed keyer
+        fprintf(stderr,"SET KEYER OUT: state unchanged: %d", state);
     }
 }
 
@@ -208,8 +355,21 @@ static void* keyer_thread(void *arg) {
     int pos;
     struct timespec loop_delay;
     int interval = 1000000; // 1 ms
+    int sidewait;
+    int sideloop;
+    int i;
+    int kdelay;
+    int old_volume;
+    struct sched_param param;
+
+    // In Linux, the new scheduling policy will be effective for the
+   //  calling thread only.
+    param.sched_priority = MY_PRIORITY;
+    if(sched_setscheduler((pid_t)0, SCHED_FIFO, &param) == -1) {
+            perror("sched_setscheduler failed");
+    }
 
-fprintf(stderr,"keyer_thread  state running= %d\n", running);
+    fprintf(stderr,"keyer_thread  state running= %d\n", running);
     while(running) {
 #ifdef __APPLE__
         sem_wait(cw_event);
@@ -219,172 +379,274 @@ fprintf(stderr,"keyer_thread  state running= %d\n", running);
 
         key_state = CHECK;
 
-       // If MOX still hanging, continue spinnning/checking and decrement cwvox
-
+        clock_gettime(CLOCK_MONOTONIC, &loop_delay);
         while (key_state != EXITLOOP || cwvox > 0) {
+         // re-trigger VOX if *not* busy-spinning
           if (cwvox > 0 && key_state != EXITLOOP && key_state != CHECK) cwvox=(int) cw_keyer_hang_time;
+
          switch (key_state) {
+
            case EXITLOOP:
-               if (cwvox >0) cwvox--;
+               // If we arrive here, cwvox is greater than zero, since key_state==EXITLOOP
+               // AND cwvox==0 leaves the outer "while" loop.
+               cwvox--;
+               // If CW-vox still hanging, continue "busy-spinning"
                 if (cwvox == 0) {
+                   // we have just reduced cwvox from 1 to 0.
                    g_idle_add(ext_mox_update,(gpointer)(long) 0);
                } else {
                    key_state=CHECK;
                }
                break;
+
             case CHECK: // check for key press
+               key_state = EXITLOOP;  // default next state
+               // Do not decrement cwvox until zero here, otherwise
+               // we won't enter the code 10 lines above that de-activates MOX.
                if (cwvox > 1) cwvox--;
                 if (cw_keyer_mode == KEYER_STRAIGHT) {       // Straight/External key or bug
                     if (*kdash) {                  // send manual dashes
-                        set_keyer_out(1);
-                        key_state = EXITLOOP;
-                    }
-                    else if (*kdot)                // and automatic dots
-                        key_state = PREDOT;
-                    else {
-                        set_keyer_out(0);
-                        key_state = EXITLOOP;
+                      set_keyer_out(1);
+                     clock_gettime(CLOCK_MONOTONIC, &loop_delay);
+                     // wait until dash is released
+                     if (gpio_cw_sidetone_enabled()) {
+                       // produce tone
+                       // Note. Using clock_nanosleep with ABSTIME is absolutely
+                       //       necessary to produce a stable frequency.
+                       //       You still may painfully recognize that LINUX
+                       //       e.g. on a RaspberryPi is not a real-time
+                       //       operating system.
+                       // Mute "normal" CW side tone, it will be reactivated
+                       // at the end of the following delay.
+                        old_volume=cw_keyer_sidetone_volume;
+                        cw_keyer_sidetone_volume=0;
+                        sidewait=500000000/cw_keyer_sidetone_frequency;
+                       for (;;) {
+                         gpio_cw_sidetone_set(1);
+                         loop_delay.tv_nsec += sidewait;
+                         while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                           loop_delay.tv_nsec -= NSEC_PER_SEC;
+                           loop_delay.tv_sec++;
+                         }
+                         clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                         gpio_cw_sidetone_set(0);
+                         loop_delay.tv_nsec += sidewait;
+                         while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                           loop_delay.tv_nsec -= NSEC_PER_SEC;
+                           loop_delay.tv_sec++;
+                         }
+                         if (!*kdash) break;
+                         clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                       }
+                     } else {
+                       // No-GPIO-sidetone case:
+                       // wait until dash is released. Check once a milli-sec
+                       for (;;) {
+                         loop_delay.tv_nsec += interval;
+                         while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                           loop_delay.tv_nsec -= NSEC_PER_SEC;
+                           loop_delay.tv_sec++;
+                         }
+                         if (!*kdash) break;
+                         clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                       }
+                      }
+                     // dash released.
+                      set_keyer_out(0);
+                     // wait at least 10ms before re-activating sidetone,
+                     // to allow the envelope of the side tone reaching zero
+                      if (gpio_cw_sidetone_enabled()) {
+                         loop_delay.tv_nsec += 10*interval;
+                         while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                           loop_delay.tv_nsec -= NSEC_PER_SEC;
+                           loop_delay.tv_sec++;
+                         }
+                         clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                         cw_keyer_sidetone_volume=old_volume;
+                      }
                     }
-                }
-                else {
-                    if (*kdot)
-                        key_state = PREDOT;
-                    else if (*kdash)
-                        key_state = PREDASH;
-                    else {
-                        set_keyer_out(0);
-                        key_state = EXITLOOP;
+                   if (*kdot) {
+                       // "bug" mode: dot key activates automatic dots
+                        key_state = SENDDOT;
                     }
+                   // end of KEYER_STRAIGHT case
+                } else {
+                   // Paddle
+                   // If both following if-statements are true, which one should win?
+                   // I think a "simultaneous squeeze" means a dot-dash sequence, since in 
+                   // a dash-dot sequence there is a larger time window to hit the dot.
+                    if (*kdash) key_state = SENDDASH;
+                    if (*kdot) key_state = SENDDOT;
                 }
-                break;
-            case PREDOT:                         // need to clear any pending dots or dashes
-                clear_memory();
-                key_state = SENDDOT;
-                break;
-            case PREDASH:
-                clear_memory();
-                key_state = SENDDASH;
-                break;
+               break;
 
-            // dot paddle  pressed so set keyer_out high for time dependant on speed
-            // also check if dash paddle is pressed during this time
-            case SENDDOT:
+           case SENDDOT:
+               dash_memory = 0;
+                dash_held = *kdash;
                 set_keyer_out(1);
-                if (kdelay == dot_delay) {
-                    kdelay = 0;
-                    set_keyer_out(0);
-                    key_state = DOTDELAY;        // add inter-character spacing of one dot length
-                }
-                else kdelay++;
-
-                // if Mode A and both paddels are relesed then clear dash memory
-                if (cw_keyer_mode == KEYER_MODE_A)
-                    if (!*kdot & !*kdash)
-                        dash_memory = 0;
-                    else if (*kdash)                   // set dash memory
-                        dash_memory = 1;
-                break;
-
-            // dash paddle pressed so set keyer_out high for time dependant on 3 x dot delay and weight
-            // also check if dot paddle is pressed during this time
-            case SENDDASH:
-                set_keyer_out(1);
-                if (kdelay == dash_delay) {
-                    kdelay = 0;
-                    set_keyer_out(0);
-                    key_state = DASHDELAY;       // add inter-character spacing of one dot length
-                }
-                else kdelay++;
-
-                // if Mode A and both padles are relesed then clear dot memory
-                if (cw_keyer_mode == KEYER_MODE_A)
-                    if (!*kdot & !*kdash)
-                        dot_memory = 0;
-                    else if (*kdot)                    // set dot memory
-                        dot_memory = 1;
+               clock_gettime(CLOCK_MONOTONIC, &loop_delay);
+               if (gpio_cw_sidetone_enabled()) {
+                  old_volume=cw_keyer_sidetone_volume;
+                  cw_keyer_sidetone_volume=0;
+                  sidewait=500000000/cw_keyer_sidetone_frequency;
+                  sideloop=(500000*dot_length)/sidewait;
+                  for (i=0; i<sideloop; i++) {
+                    gpio_cw_sidetone_set(1);
+                    loop_delay.tv_nsec += sidewait;
+                    while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                      loop_delay.tv_nsec -= NSEC_PER_SEC;
+                      loop_delay.tv_sec++;
+                    } 
+                    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                    gpio_cw_sidetone_set(0);
+                    loop_delay.tv_nsec += sidewait;
+                    while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                      loop_delay.tv_nsec -= NSEC_PER_SEC;
+                      loop_delay.tv_sec++;
+                   }
+                    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                  }
+               } else {
+                 // No-GPIO-sidetone case: just wait
+                  loop_delay.tv_nsec += 1000000 * dot_length;
+                  while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                    loop_delay.tv_nsec -= NSEC_PER_SEC;
+                    loop_delay.tv_sec++;
+                 }
+                  clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+               }
+                set_keyer_out(0);
+                key_state = DOTDELAY;       // add inter-character spacing of one dot length
+               kdelay=0;
                 break;
 
-            // add dot delay at end of the dot and check for dash memory, then check if paddle still held
             case DOTDELAY:
-                if (kdelay == dot_delay) {
-                    kdelay = 0;
-                    if(!*kdot && cw_keyer_mode == KEYER_STRAIGHT)   // just return if in bug mode
-                        key_state = EXITLOOP;
-                    else if (dash_memory)                 // dash has been set during the dot so service
-                        key_state = PREDASH;
-                    else key_state = DOTHELD;             // dot is still active so service
-                }
-                else kdelay++;
-
-                if (*kdash)                                 // set dash memory
-                    dash_memory = 1;
+                kdelay++;
+                if (kdelay > dot_length) {
+                 if (gpio_cw_sidetone_enabled()) {
+                   cw_keyer_sidetone_volume=old_volume;
+                 }
+                 if (cw_keyer_mode == KEYER_STRAIGHT) {
+                   // bug mode: continue sending dots or exit, depending on current dot key status
+                   key_state = EXITLOOP;
+                   if (*kdot) key_state=SENDDOT;
+                   // end of bug/straight case
+                  } else {
+//
+//                  DL1YCF:
+//                  This is my understanding where MODE A comes in:
+//                  If at the end of the delay, BOTH keys are
+//                  released, then do not start the next element.
+//                  However, if  the dash has been hit DURING the preceeding
+//                  dot, produce a dash in either case
+//
+                    if (cw_keyer_mode == KEYER_MODE_A && !*kdot && !*kdash) dash_held=0;
+
+                    if (dash_memory || *kdash || dash_held)
+                        key_state = SENDDASH;
+                    else if (*kdot)                                 // dot still held, so send a dot
+                       key_state = SENDDOT;
+                   else if (cw_keyer_spacing) {
+                        dot_memory = dash_memory = 0;
+                        key_state = LETTERSPACE;
+                       kdelay=0;
+                   } else
+                       key_state = EXITLOOP;
+                   // end of iambic case
+                  }
+                }   
                 break;
 
-            // add dot delay at end of the dash and check for dot memory, then check if paddle still held
-            case DASHDELAY:
-                if (kdelay == dot_delay) {
-                    kdelay = 0;
-
-                    if (dot_memory)                       // dot has been set during the dash so service
-                        key_state = PREDOT;
-                    else key_state = DASHHELD;            // dash is still active so service
-                }
-                else kdelay++;
-
-                if (*kdot)                                  // set dot memory
-                    dot_memory = 1;
-                break;
-
-            // check if dot paddle is still held, if so repeat the dot. Else check if Letter space is required
-            case DOTHELD:
-                if (*kdot)                                  // dot has been set during the dash so service
-                    key_state = PREDOT;
-                else if (*kdash)                            // has dash paddle been pressed
-                    key_state = PREDASH;
-                else if (cw_keyer_spacing) {    // Letter space enabled so clear any pending dots or dashes
-                    clear_memory();
-                    key_state = LETTERSPACE;
-                }
-                else key_state = EXITLOOP;
+           case SENDDASH:
+               dot_memory =  0;
+               dot_held = *kdot;  // remember if dot is still held at beginning of the dash
+                set_keyer_out(1);
+                clock_gettime(CLOCK_MONOTONIC, &loop_delay);
+               if (gpio_cw_sidetone_enabled()) {
+                  old_volume=cw_keyer_sidetone_volume;
+                  cw_keyer_sidetone_volume=0;
+                  sidewait=500000000/cw_keyer_sidetone_frequency;
+                  sideloop=(500000*dash_length)/sidewait;
+                  for (i=0; i<sideloop; i++) {
+                    gpio_cw_sidetone_set(1);
+                    loop_delay.tv_nsec += sidewait;
+                    while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                      loop_delay.tv_nsec -= NSEC_PER_SEC;
+                      loop_delay.tv_sec++;
+                    }
+                    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                    gpio_cw_sidetone_set(0);
+                    loop_delay.tv_nsec += sidewait;
+                    while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                      loop_delay.tv_nsec -= NSEC_PER_SEC;
+                      loop_delay.tv_sec++;
+                    }
+                    clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+                  }
+               } else {
+                 // No-GPIO-sidetone case: just wait
+                  loop_delay.tv_nsec += 1000000L * dash_length;
+                  while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
+                    loop_delay.tv_nsec -= NSEC_PER_SEC;
+                    loop_delay.tv_sec++;
+                  }
+                  clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &loop_delay, NULL);
+               }
+                set_keyer_out(0);
+                key_state = DASHDELAY;       // add inter-character spacing of one dot length
+               kdelay=0;
+               // do not fall through, update VOX at beginning of loop
                 break;
 
-            // check if dash paddle is still held, if so repeat the dash. Else check if Letter space is required
-            case DASHHELD:
-                if (*kdash)                   // dash has been set during the dot so service
-                    key_state = PREDASH;
-                else if (*kdot)               // has dot paddle been pressed
-                    key_state = PREDOT;
-                else if (cw_keyer_spacing) {    // Letter space enabled so clear any pending dots or dashes
-                    clear_memory();
-                    key_state = LETTERSPACE;
+            case DASHDELAY:
+               // we never arrive here in STRAIGHT/BUG mode
+                kdelay++;
+                if (kdelay > dot_length) {
+                 if (gpio_cw_sidetone_enabled()) {
+                   cw_keyer_sidetone_volume=old_volume;
+                 }
+//
+//                  DL1YCF:
+//                  This is my understanding where MODE A comes in:
+//                  If at the end of the dash delay, BOTH keys are
+//                  released, then do not start the next element.
+//                  However, if  the dot has been hit DURING the preceeding
+//                  dash, produce a dot in either case
+//
+                    if (cw_keyer_mode == KEYER_MODE_A && !*kdot && !*kdash) dot_held=0;
+                    if (dot_memory || *kdot || dot_held)
+                        key_state = SENDDOT;
+                    else if (*kdash)
+                       key_state = SENDDASH;
+                   else if (cw_keyer_spacing) {
+                        dot_memory = dash_memory = 0;
+                        key_state = LETTERSPACE;
+                       kdelay=0;
+                   } else key_state = EXITLOOP;
                 }
-                else key_state = EXITLOOP;
                 break;
 
-            // Add letter space (3 x dot delay) to end of character and check if a paddle is pressed during this time.
-            // Actually add 2 x dot_delay since we already have a dot delay at the end of the character.
             case LETTERSPACE:
-                if (kdelay == 2 * dot_delay) {
-                    kdelay = 0;
+                // Add letter space (3 x dot delay) to end of character and check if a paddle is pressed during this time.
+                // Actually add 2 x dot_length since we already have a dot delay at the end of the character.
+                kdelay++;
+                if (kdelay > 2 * dot_length) {
                     if (dot_memory)         // check if a dot or dash paddle was pressed during the delay.
-                        key_state = PREDOT;
+                        key_state = SENDDOT;
                     else if (dash_memory)
-                        key_state = PREDASH;
+                        key_state = SENDDASH;
                     else key_state = EXITLOOP;   // no memories set so restart
                 }
-                else kdelay++;
-
-                // save any key presses during the letter space delay
-                if (*kdot) dot_memory = 1;
-                if (*kdash) dash_memory = 1;
                 break;
 
             default:
+               fprintf(stderr,"KEYER THREAD: unknown state=%d",(int) key_state);
                 key_state = EXITLOOP;
-
             }
 
-            clock_gettime(CLOCK_MONOTONIC, &loop_delay);
+           // time stamp in loop_delay is either the last time stamp from the
+           // top of the loop, or the last time stamp from tone generation
+           // NOTE: we are using ABSTIME here to produce accurate delays.
             loop_delay.tv_nsec += interval;
             while (loop_delay.tv_nsec >= NSEC_PER_SEC) {
                 loop_delay.tv_nsec -= NSEC_PER_SEC;
@@ -394,28 +656,39 @@ fprintf(stderr,"keyer_thread  state running= %d\n", running);
         }
 
     }
-fprintf(stderr,"keyer_thread: EXIT\n");
+    fprintf(stderr,"keyer_thread: EXIT\n");
+    param.sched_priority = 0;
+    sched_setscheduler((pid_t) 0, SCHED_OTHER, &param);
 }
 
 void keyer_close() {
+    fprintf(stderr,".... closing keyer thread.\n");
     running=0;
+    // keyer thread may be sleeping, so wake it up
+#ifdef __APPLE__
+    sem_post(cw_event);
+#else
+    sem_post(&cw_event);
+#endif
+    pthread_join(keyer_thread_id, NULL);
+#ifdef __APPLE__
+    sem_close(cw_event);
+#else
+    sem_close(&cw_event);
+#endif
+    munlockall();
 }
 
 int keyer_init() {
     int rc;
-    struct sched_param param;
-
-    param.sched_priority = MY_PRIORITY;
-    if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
-            perror("sched_setscheduler failed");
-            running = 0;
-    }
 
+    fprintf(stderr,".... starting keyer thread.\n");
+    
     if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
             perror("mlockall failed");
-            running = 0;
     }
 
+    running = 1;
 #ifdef __APPLE__
     cw_event=sem_open("CW", O_CREAT, 0700, 0);
     rc = (cw_event == SEM_FAILED);
@@ -423,11 +696,9 @@ int keyer_init() {
     rc = sem_init(&cw_event, 0, 0);
 #endif
     rc |= pthread_create(&keyer_thread_id, NULL, keyer_thread, NULL);
-    running = 1;
     if(rc < 0) {
         fprintf(stderr,"pthread_create for keyer_thread failed %d\n", rc);
         exit(-1);
     }
-
     return 0;
 }
index bf29676c5095671770147cce9c144e1124968e7e..9eaba58cdf947895ec9372e6eed1e9ae7dfd618d 100644 (file)
--- a/iambic.h
+++ b/iambic.h
@@ -3,27 +3,18 @@
 
 enum {
     CHECK = 0,
-    PREDOT,
-    PREDASH,
     SENDDOT,
     SENDDASH,
     DOTDELAY,
     DASHDELAY,
-    DOTHELD,
-    DASHHELD,
     LETTERSPACE,
     EXITLOOP
 };
 
-extern int cwl_state;
-extern int cwr_state;
 extern int keyer_out;
-
 extern int key_state;
-extern int *kdot;
-extern int *kdash;
 
-void keyer_event(int gpio, int level);
+void keyer_event(int left, int state);
 void keyer_update();
 void keyer_close();
 int  keyer_init();
index a30d2eacd64ba2e6e8782924c013ee116df3e8d0..072ea09d2f34b13831c39f25e67bcbb9777f7f58 100644 (file)
@@ -287,14 +287,6 @@ void tuner_changed() {
 }
 */
 
-void cw_changed() {
-#ifdef LOCALCW
-    // update the iambic keyer params
-    if (cw_keyer_internal == 0)
-        keyer_update();
-#endif
-}
-
 void new_protocol_init(int pixels) {
     int i;
     int rc;
@@ -568,8 +560,6 @@ static void new_protocol_high_priority() {
         // set the ptt if we're not in breakin mode and mox is on
         if(cw_breakin == 0 && getMox()) high_priority_buffer_to_radio[4]|=0x02;
         high_priority_buffer_to_radio[5]|=(keyer_out) ? 0x01 : 0;
-        //high_priority_buffer_to_radio[5]|=(*kdot) ? 0x02 : 0;
-        //high_priority_buffer_to_radio[5]|=(*kdash) ? 0x04 : 0;
         high_priority_buffer_to_radio[5]|=(key_state==SENDDOT) ? 0x02 : 0;
         high_priority_buffer_to_radio[5]|=(key_state==SENDDASH) ? 0x04 : 0;
       }
index 659c7213bb1614c26658d99b2298244e968678f9..4a5bf5b592baf1eb5901f0e61cd0646ac956f1fe 100644 (file)
@@ -81,7 +81,6 @@ extern void new_protocol_run();
 extern void filter_board_changed();
 extern void pa_changed();
 extern void tuner_changed();
-extern void cw_changed();
 
 extern void setMox(int state);
 extern int getMox();
index e6bc40d88a7fc37617e6782971a52bd6f0fe2553..67abcbe77b62a3efd8ab8b06b25e888e59faf7a7 100644 (file)
@@ -80,7 +80,7 @@ void saveProperties(char* filename) {
     char line[512];
     char version[32];
 
-    fprintf(stderr,"saveProperties: %s\n",filename);
+    //fprintf(stderr,"saveProperties: %s\n",filename);
 
     if(!f) {
         fprintf(stderr,"can't open %s\n",filename);
index b285bcb1bc83158b876c0d809aa2618df7e05711..442611a6e1fca9129997d3207f787f10797435d8 100644 (file)
--- a/rigctl.c
+++ b/rigctl.c
@@ -297,6 +297,7 @@ int vfo_sm=0;   // VFO State Machine - this keeps track of
 
 static char cw_buf[30];
 static int  cw_busy=0;
+static int  cat_cw_seen=0;
 
 static long dotlen;
 static long dashlen;
@@ -317,45 +318,54 @@ extern int cw_key_up, cw_key_down, cw_not_ready;
 //
 void send_dash() {
   int TimeToGo;
-  if (cw_key_hit || cw_not_ready) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
+    // TimeToGo is invalid if local CW keying has set in
+    if (cw_key_hit || cw_not_ready) return;
     if (TimeToGo == 0) break;
     // sleep until 10 msec before ignition
     if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
     // sleep 1 msec
     usleep(1000L);
   }
+  // If local CW keying has set in, do not interfere
+  if (cw_key_hit || cw_not_ready) return;
   cw_key_down = dashsamples;
   cw_key_up   = dotsamples;
 }
 
 void send_dot() {
   int TimeToGo;
-  if (cw_key_hit || cw_not_ready) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
+    // TimeToGo is invalid if local CW keying has set in
+    if (cw_key_hit || cw_not_ready) return;
     if (TimeToGo == 0) break;
     // sleep until 10 msec before ignition
     if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
     // sleep 1 msec
     usleep(1000L);
   }
+  // If local CW keying has set in, do not interfere
+  if (cw_key_hit || cw_not_ready) return;
   cw_key_down = dotsamples;
   cw_key_up   = dotsamples;
 }
 
 void send_space(int len) {
   int TimeToGo;
-  if (cw_key_hit || cw_not_ready) return;
   for(;;) {
     TimeToGo=cw_key_up+cw_key_down;
+    // TimeToGo is invalid if local CW keying has set in
+    if (cw_key_hit || cw_not_ready) return;
     if (TimeToGo == 0) break;
     // sleep until 10 msec before ignition
     if (TimeToGo > 500) usleep((long)(TimeToGo-500)*20L);
     // sleep 1 msec
     usleep(1000L);
   }
+  // If local CW keying has set in, do not interfere
+  if (cw_key_hit || cw_not_ready) return;
   cw_key_up = len*dotsamples;
 }
 
@@ -481,16 +491,15 @@ void rigctl_send_cw_char(char cw_char) {
 //
 // This thread constantly looks whether CW data
 // is available, and produces CW in this case.
-// The buffer is copied to a local buffer and
-// immediately released, such that the next
-// chunk can already be prepeared. This way,
-// splitting a large CW text into words, and
-// sending each word with a separate KY command
-// produces perfectly readable CW.
 //
-// UPDATE: we maintain a ring buffer such that
-//         the contents of several KY commands
-//         can be buffered
+// A ring buffer is maintained such that the contents
+// of several KY commands can be buffered. This allows
+// sending a large CW text word-by-word (each word in a
+// separate KY command).
+//
+// If the contents of the last KY command do not fit into
+// the ring buffer, cw_busy is NOT reset. Eventually, there
+// is enough space in the ring buffer, then cw_busy is reset.
 //
 static gpointer rigctl_cw_thread(gpointer data)
 { 
@@ -504,15 +513,14 @@ static gpointer rigctl_cw_thread(gpointer data)
   int  num_buf=0;
   
   while (server_running) {
-    // wait for cw_buf become filled with data
-    // (periodically look every 100 msec)
+    // wait for CW data (periodically look every 100 msec)
     if (!cw_busy && num_buf ==0) {
       cw_key_hit=0;
       usleep(100000L);
       continue;
     }
 
-    // if new data available and space in buffer, copy it
+    // if new data is available and fits into the buffer, copy-in.
     // If there are several adjacent spaces, take only the first one.
     // This also swallows the "tails" of the KY commands which
     // (according to Kenwood) have to be padded with spaces up
@@ -527,7 +535,7 @@ static gpointer rigctl_cw_thread(gpointer data)
         num_buf++;
         if (write_buf - ring_buf == 128) write_buf=ring_buf;  // wrap around
       }
-      cw_busy=0; // mark buffer free again
+      cw_busy=0; // mark one-line buffer free again
     }
 
     // these values may have changed, so recompute them here
@@ -552,29 +560,29 @@ static gpointer rigctl_cw_thread(gpointer data)
            CAT_cw_is_active=0;
            continue;
        }
-       // some extra time to settle down, in order NOT to loose
-       // the first dit or dah
-        usleep(100000L);
     }
     // At this point, mox==1 and CAT_cw_active == 1
     if (cw_key_hit || cw_not_ready) {
        //
        // CW transmission has been aborted, either due to manually
        // removing MOX, changing the mode to non-CW, or because a CW key has been hit.
-       // Do not remove PTT if we abort CAT CW because a CW key has been hit.
+       // Do not remove PTT in the latter case
        CAT_cw_is_active=0;
-       // If an external CW key has been hit, we continue in TX mode
-       // doing CW manually. Otherwise, switch PTT off.
-       if (!cw_key_hit) {
+       // If a CW key has been hit, we continue in TX mode.
+       // Otherwise, switch PTT off.
+       if (!cw_key_hit && mox) {
          g_idle_add(ext_ptt_update ,(gpointer)0);
        }
-       // Stay for 1 sec swallowing incoming
-       // CAT CW commands. We need this long time since
-       // hamlib waits 0.5 secs after receiving a "KY1" message before trying to
-       // send the next bunch
-       cw_busy=-1;      // mark buffer purge situation
-       usleep(1000000L);
-       cw_busy=0;
+       // Let the CAT system swallow incoming CW commands by setting cw_busy to -1.
+       // Do so until no CAT CW message has arrived for 1 second
+       cw_busy=-1;
+       for (;;) {
+         cat_cw_seen=0;
+         usleep(1000000L);
+         if (cat_cw_seen) continue;
+         cw_busy=0;
+         break;
+       }
        write_buf=read_buf=ring_buf;
        num_buf=0;
     } else {
@@ -587,13 +595,16 @@ static gpointer rigctl_cw_thread(gpointer data)
       // Otherwise remove PTT and wait for next CAT CW command.
       if (cw_busy || num_buf > 0) continue;
       CAT_cw_is_active=0;
-      g_idle_add(ext_ptt_update ,(gpointer)0);
-      // wait up to 500 msec for MOX having gone
-      // otherwise there might be a race condition when sending
-      // the next character really soon
-      i=10;
-      while (mox && (i--) > 0) usleep(50000L);
+      if (!cw_key_hit) {
+        g_idle_add(ext_ptt_update ,(gpointer)0);
+        // wait up to 500 msec for MOX having gone
+        // otherwise there might be a race condition when sending
+        // the next character really soon
+        i=10;
+        while (mox && (i--) > 0) usleep(50000L);
+      }
     }
+    // end of while (server_running)
   }
   // We arrive here if the rigctl server shuts down.
   // This very rarely happens. But we should shut down the
@@ -601,8 +612,10 @@ static gpointer rigctl_cw_thread(gpointer data)
   // of a transmission
   rigctl_cw_thread_id = NULL;
   cw_busy=0;
-  CAT_cw_is_active=0;
-  g_idle_add(ext_ptt_update ,(gpointer)0);
+  if (CAT_cw_is_active) {
+    CAT_cw_is_active=0;
+    g_idle_add(ext_ptt_update ,(gpointer)0);
+  }
   return NULL;
 }
 
@@ -2204,6 +2217,7 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) {
                                           }  
         else if((strcmp(cmd_str,"KY")==0) && (zzid_flag == 0))
                                    { 
+                                       if (cw_busy < 0) cat_cw_seen=1;
                                        // DL1YCF:
                                        // Hamlib produces timeout errors if we are busy here for
                                        // seconds. Therefore we just move the data into a buffer