]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
added keyer support from Rick Koch, N1GP
authorJohn Melton - G0ORX/N6LYT <john.d.melton@googlemail.com>
Sat, 10 Dec 2016 12:25:08 +0000 (12:25 +0000)
committerJohn Melton - G0ORX/N6LYT <john.d.melton@googlemail.com>
Sat, 10 Dec 2016 12:25:08 +0000 (12:25 +0000)
Makefile
beep.c [new file with mode: 0644]
beep.h [new file with mode: 0644]
cw_menu.c
gpio.c
gpio.h
iambic.c [new file with mode: 0644]
iambic.h [new file with mode: 0644]
radio.c
radio.h

index 96b582e0a6e8156bf0dba4906576741fb8cd297a..f88bbf5277719ac17aa2b55bdb25b8a78521f48c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,12 @@ PSK_INCLUDE=PSK
 # uncomment the line to below include support for FreeDV codec2
 FREEDV_INCLUDE=FREEDV
 
+# uncomment the line to below include support for sx1509 i2c expander
+#SX1509_INCLUDE=sx1509
+
+# uncomment the line to below include support local CW keyer
+LOCALCW_INCLUDE=LOCALCW
+
 #uncomment the line below for the platform being compiled on
 UNAME_N=raspberrypi
 #UNAME_N=odroid
@@ -69,6 +75,19 @@ freedv.o \
 freedv_menu.o
 endif
 
+ifeq ($(LOCALCW_INCLUDE),LOCALCW)
+LOCALCW_OPTIONS=-D LOCALCW
+LOCALCW_SOURCES= \
+beep.c \
+iambic.c
+LOCALCW_HEADERS= \
+beep.h \
+iambic.h
+LOCALCW_OBJS= \
+beep.o \
+iambic.o
+endif
+
 #required for MRAA GPIO
 #MRAA_INCLUDE=MRAA
 
@@ -89,6 +108,10 @@ else
   ifeq ($(UNAME_N),odroid)
   GPIO_LIBS=-lwiringPi
   endif
+  ifeq ($(SX1509_INCLUDE),sx1509)
+  GPIO_OPTIONS+=-D sx1509
+  GPIO_LIBS+=-lsx1509
+  endif
   GPIO_SOURCES= \
   gpio.c
   GPIO_HEADERS= \
@@ -105,7 +128,7 @@ GTKLIBS=`pkg-config --libs gtk+-3.0`
 
 AUDIO_LIBS=-lasound
 
-OPTIONS=-g -D $(UNAME_N) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) $(PSK_OPTIONS) $(SHORT_FRAMES) -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3
+OPTIONS=-g -D $(UNAME_N) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) $(FREEDV_OPTIONS) $(LOCALCW_OPTIONS) $(PSK_OPTIONS) $(SHORT_FRAMES) -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(DEBUG_OPTION) -O3
 
 LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS)
 INCLUDES=$(GTKINCLUDES)
@@ -286,13 +309,13 @@ wdsp_init.o \
 button_text.o \
 vox.o
 
-all: prebuild $(PROGRAM) $(HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES)
+all: prebuild $(PROGRAM) $(HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES)
 
 prebuild:
        rm -f version.o
 
-$(PROGRAM): $(OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(GPIO_OBJS) $(PSK_OBJS)
-       $(LINK) -o $(PROGRAM) $(OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(PSK_OBJS) $(LIBS)
+$(PROGRAM): $(OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(GPIO_OBJS) $(PSK_OBJS)
+       $(LINK) -o $(PROGRAM) $(OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(LIBS)
 
 .c.o:
        $(COMPILE) -c -o $@ $<
diff --git a/beep.c b/beep.c
new file mode 100644 (file)
index 0000000..284fd47
--- /dev/null
+++ b/beep.c
@@ -0,0 +1,393 @@
+/*
+
+    10/16/2016, Rick Koch / N1GP, added to give an alternative sidetone
+                by using the RPi's audio output.
+
+--------------------------------------------------------------------------------
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+You should have received a copy of the GNU Library General Public
+License along with this library; if not, write to the
+Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+Boston, MA  02110-1301, USA.
+--------------------------------------------------------------------------------
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sched.h>
+#include <pthread.h>
+#include <signal.h>
+#include <semaphore.h>
+#include <math.h>
+#include <alsa/asoundlib.h>
+
+double beep_freq = 800;                                 /* sinusoidal wave frequency in Hz */
+
+static char *device = "hw:0";
+static snd_pcm_format_t format = SND_PCM_FORMAT_S16;    /* sample format */
+static unsigned int rate = 48000;                       /* stream rate */
+static unsigned int channels = 2;                       /* count of channels */
+static unsigned int buffer_time = 36000;                /* ring buffer length in us */
+static unsigned int period_time = 6000;                 /* period time in us */
+static int period_event = 0;                            /* produce poll event after each period */
+static snd_pcm_sframes_t buffer_size;
+static snd_pcm_sframes_t period_size;
+static snd_output_t *output = NULL;
+
+static void* beep_thread(void *arg);
+static pthread_t beep_thread_id;
+
+static snd_pcm_t *handle;
+static signed short *samples;
+static snd_pcm_channel_area_t *areas;
+
+static void generate_sine(const snd_pcm_channel_area_t *areas, 
+                          snd_pcm_uframes_t offset,
+                          int count, double *_phase)
+{
+        static double max_phase = 2. * M_PI;
+        double phase = *_phase;
+        double step = max_phase*beep_freq/(double)rate;
+        unsigned char *samples[channels];
+        int steps[channels];
+        unsigned int chn;
+        int format_bits = snd_pcm_format_width(format);
+        unsigned int maxval = (1 << (format_bits - 1)) - 1;
+        int bps = format_bits / 8;  /* bytes per sample */
+        int phys_bps = snd_pcm_format_physical_width(format) / 8;
+
+        /* verify and prepare the contents of areas */
+        for (chn = 0; chn < channels; chn++) {
+                if ((areas[chn].first % 8) != 0) {
+                        printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first);
+                        exit(EXIT_FAILURE);
+                }
+                samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8));
+                if ((areas[chn].step % 16) != 0) {
+                        printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step);
+                        exit(EXIT_FAILURE);
+                }
+                steps[chn] = areas[chn].step / 8;
+                samples[chn] += offset * steps[chn];
+        }
+        /* fill the channel areas */
+        while (count-- > 0) {
+                union {
+                        float f;
+                        int i;
+                } fval;
+                int res, i;
+
+                res = sin(phase) * maxval;
+                res ^= 1U << (format_bits - 1);
+                for (chn = 0; chn < channels; chn++) {
+                        /* Generate data in native endian format */
+                            for (i = 0; i < bps; i++)
+                                *(samples[chn] + i) = (res >>  i * 8) & 0xff;
+                        samples[chn] += steps[chn];
+                }
+                phase += step;
+                if (phase >= max_phase)
+                        phase -= max_phase;
+        }
+        *_phase = phase;
+}
+
+static int xrun_recovery(snd_pcm_t *handle, int err)
+{
+        if (err == -EPIPE) {    /* under-run */
+                err = snd_pcm_prepare(handle);
+                if (err < 0)
+                        printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
+                return 0;
+        } else if (err == -ESTRPIPE) {
+                while ((err = snd_pcm_resume(handle)) == -EAGAIN)
+                        sleep(1);       /* wait until the suspend flag is released */
+                if (err < 0) {
+                        err = snd_pcm_prepare(handle);
+                        if (err < 0)
+                                printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
+                }
+                return 0;
+        }
+        return err;
+}
+
+static int write_loop(snd_pcm_t *handle,
+                      signed short *samples,
+                      snd_pcm_channel_area_t *areas)
+{
+        double phase = 0;
+        signed short *ptr;
+        int err, cptr;
+
+        while (1) {
+                generate_sine(areas, 0, period_size, &phase);
+                ptr = samples;
+                cptr = period_size;
+                while (cptr > 0) {
+                        err = snd_pcm_writei(handle, ptr, cptr);
+                        if (err == -EAGAIN)
+                                continue;
+                        if (err < 0) {
+                                if (xrun_recovery(handle, err) < 0) {
+                                        printf("Write error: %s\n", snd_strerror(err));
+                                        exit(EXIT_FAILURE);
+                                }
+                                break;  /* skip one period */
+                        }
+                        ptr += err * channels;
+                        cptr -= err;
+                }
+        }
+}
+
+static int set_hwparams(snd_pcm_t *handle,
+                        snd_pcm_hw_params_t *params,
+                        snd_pcm_access_t access)
+{
+        unsigned int rrate;
+        snd_pcm_uframes_t size;
+        int err, dir;
+        /* choose all parameters */
+        err = snd_pcm_hw_params_any(handle, params);
+        if (err < 0) {
+                printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* set the interleaved read/write format */
+        err = snd_pcm_hw_params_set_access(handle, params, access);
+        if (err < 0) {
+                printf("Access type not available for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* set the sample format */
+        err = snd_pcm_hw_params_set_format(handle, params, format);
+        if (err < 0) {
+                printf("Sample format not available for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* set the count of channels */
+        err = snd_pcm_hw_params_set_channels(handle, params, channels);
+        if (err < 0) {
+                printf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err));
+                return err;
+        }
+        /* set the stream rate */
+        rrate = rate;
+        err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
+        if (err < 0) {
+                printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err));
+                return err;
+        }
+        if (rrate != rate) {
+                printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
+                return -EINVAL;
+        }
+        /* set the buffer time */
+        err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
+        if (err < 0) {
+                printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
+                return err;
+        }
+        err = snd_pcm_hw_params_get_buffer_size(params, &size);
+        if (err < 0) {
+                printf("Unable to get buffer size for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        buffer_size = size;
+        /* set the period time */
+        err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
+        if (err < 0) {
+                printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
+                return err;
+        }
+        err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
+        if (err < 0) {
+                printf("Unable to get period size for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        period_size = size;
+        /* write the parameters to device */
+        err = snd_pcm_hw_params(handle, params);
+        if (err < 0) {
+                printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        return 0;
+}
+
+static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams)
+{
+        int err;
+        /* get the current swparams */
+        err = snd_pcm_sw_params_current(handle, swparams);
+        if (err < 0) {
+                printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* start the transfer when the buffer is almost full: */
+        /* (buffer_size / avail_min) * avail_min */
+        err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);
+        if (err < 0) {
+                printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* allow the transfer when at least period_size samples can be processed */
+        /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
+        err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size);
+        if (err < 0) {
+                printf("Unable to set avail min for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        /* enable period events when requested */
+        if (period_event) {
+                err = snd_pcm_sw_params_set_period_event(handle, swparams, 1);
+                if (err < 0) {
+                        printf("Unable to set period event: %s\n", snd_strerror(err));
+                        return err;
+                }
+        }
+        /* write the parameters to the playback device */
+        err = snd_pcm_sw_params(handle, swparams);
+        if (err < 0) {
+                printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
+                return err;
+        }
+        return 0;
+}
+
+void beep_mute(int mute)
+{
+    int err;
+    snd_ctl_elem_id_t *id;
+    snd_hctl_elem_t *elem;
+    snd_ctl_elem_value_t *control;
+    snd_hctl_t *hctl;
+
+    err = snd_hctl_open(&hctl, device, 0);
+    err = snd_hctl_load(hctl);
+    snd_ctl_elem_id_alloca(&id);
+    snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+    snd_ctl_elem_id_set_name(id, "PCM Playback Switch");
+
+    elem = snd_hctl_find_elem(hctl, id);
+
+    snd_ctl_elem_value_alloca(&control);
+    snd_ctl_elem_value_set_id(control, id);   
+
+    snd_ctl_elem_value_set_integer(control, 0, mute);
+    err = snd_hctl_elem_write(elem, control);
+    snd_hctl_close(hctl);
+    if (err) fprintf(stderr, "ERROR beep_mute()\n");
+}
+
+void beep_vol(long volume)
+{
+    long min, max, output;
+    snd_mixer_selem_id_t *sid;
+    snd_mixer_t *mhandle;
+    const char *selem_name = "PCM";
+    int do_once = 1;
+    snd_mixer_elem_t* elem;
+
+    beep_mute(1);
+    if (volume > 100) volume = 100; // sounds raspy any higher
+    if (volume < 0) volume = 0;
+    snd_mixer_open(&mhandle, 0);
+    snd_mixer_attach(mhandle, device);
+    snd_mixer_selem_register(mhandle, NULL, NULL);
+    snd_mixer_load(mhandle);
+    snd_mixer_selem_id_alloca(&sid);
+    snd_mixer_selem_id_set_index(sid, 0);
+    snd_mixer_selem_id_set_name(sid, selem_name);
+    elem = snd_mixer_find_selem(mhandle, sid);
+    snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
+    min = -2000; // PI's audio mixer range is broken
+    output = (((max - min) * volume) / 100) + min;
+    snd_mixer_selem_set_playback_volume_all(elem, output);
+
+    beep_mute(0);
+    snd_mixer_close(mhandle);
+}
+
+static void* beep_thread(void *arg) {
+        int err;
+        snd_pcm_hw_params_t *hwparams;
+        snd_pcm_sw_params_t *swparams;
+        unsigned int chn;
+        snd_pcm_hw_params_alloca(&hwparams);
+        snd_pcm_sw_params_alloca(&swparams);
+
+        err = snd_output_stdio_attach(&output, stdout, 0);
+        if (err < 0) {
+                printf("Output failed: %s\n", snd_strerror(err));
+                return 0;
+        }
+
+        if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+                printf("Playback open error: %s\n", snd_strerror(err));
+                return 0;
+        }
+        
+        if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+                printf("Setting of hwparams failed: %s\n", snd_strerror(err));
+                exit(EXIT_FAILURE);
+        }
+        if ((err = set_swparams(handle, swparams)) < 0) {
+                printf("Setting of swparams failed: %s\n", snd_strerror(err));
+                exit(EXIT_FAILURE);
+        }
+
+        samples = malloc((period_size * channels * snd_pcm_format_physical_width(format)) / 8);
+        if (samples == NULL) {
+                printf("No enough memory\n");
+                exit(EXIT_FAILURE);
+        }
+        
+        areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
+        if (areas == NULL) {
+                printf("No enough memory\n");
+                exit(EXIT_FAILURE);
+        }
+        for (chn = 0; chn < channels; chn++) {
+                areas[chn].addr = samples;
+                areas[chn].first = chn * snd_pcm_format_physical_width(format);
+                areas[chn].step = channels * snd_pcm_format_physical_width(format);
+        }
+
+        write_loop(handle, samples, areas);
+
+        free(areas);
+        free(samples);
+        snd_pcm_close(handle);
+        return 0;
+}
+
+void beep_init() {
+    int i = pthread_create(&beep_thread_id, NULL, beep_thread, NULL);
+    if(i < 0) {
+        fprintf(stderr,"pthread_create for beep_thread failed %d\n", i);
+        exit(-1);
+    }
+}
+
+void beep_close() {
+//    beep_vol(0);
+    beep_mute(1);
+    pthread_cancel(beep_thread_id);
+}
diff --git a/beep.h b/beep.h
new file mode 100644 (file)
index 0000000..63a3370
--- /dev/null
+++ b/beep.h
@@ -0,0 +1,11 @@
+#ifndef _BEEP_H
+#define _BEEP_H
+
+extern double beep_freq;
+
+void beep_vol(long volume);
+void beep_mute(int mute);
+void beep_init();
+void beep_close();
+
+#endif
index d1c90d87097b1c0d6c8aabb0c6605060c833e705..5350e89563ab16740997515e160a5a595311c1bc 100644 (file)
--- a/cw_menu.c
+++ b/cw_menu.c
@@ -213,6 +213,12 @@ void cw_menu(GtkWidget *parent) {
   gtk_grid_attach(GTK_GRID(grid),cw_keyer_weight_b,1,9,1,1);
   g_signal_connect(cw_keyer_weight_b,"value_changed",G_CALLBACK(cw_keyer_weight_value_changed_cb),NULL);
 
+  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);
 
   gtk_container_add(GTK_CONTAINER(content),grid);
 
diff --git a/gpio.c b/gpio.c
index 08102b43f38c45fc9789b6dc7abdd5240ef1abee..63f426c860c384582880932279f2a3cee19a8b24 100644 (file)
--- a/gpio.c
+++ b/gpio.c
 #include <sched.h>
 #include <pthread.h>
 #include <wiringPi.h>
+#include <semaphore.h>
 #ifdef raspberrypi
 #include <pigpio.h>
 #endif
+#ifdef sx1509
+#include <SparkFunSX1509_C.h>
+#endif
 
 #include "band.h"
 #include "channel.h"
@@ -39,7 +43,7 @@ int ENABLE_VFO_ENCODER=1;
 int ENABLE_VFO_PULLUP=1;
 int VFO_ENCODER_A=17;
 int VFO_ENCODER_B=18;
-#ifdef odroid
+#if defined odroid && !defined sx1509
 int VFO_ENCODER_A_PIN=0;
 int VFO_ENCODER_B_PIN=1;
 #endif
@@ -47,7 +51,12 @@ int ENABLE_AF_ENCODER=1;
 int ENABLE_AF_PULLUP=0;
 int AF_ENCODER_A=20;
 int AF_ENCODER_B=26;
+#ifdef sx1509
 int AF_FUNCTION=25;
+#else
+int AF_FUNCTION=2; //RRK, was 25 now taken by waveshare LCD TS, disable i2c
+int LOCK_BUTTON=2; //temporarily in flux upstream
+#endif
 int ENABLE_RF_ENCODER=1;
 int ENABLE_RF_PULLUP=0;
 int RF_ENCODER_A=16;
@@ -57,7 +66,11 @@ int ENABLE_AGC_ENCODER=1;
 int ENABLE_AGC_PULLUP=0;
 int AGC_ENCODER_A=4;
 int AGC_ENCODER_B=21;
+#if defined sx1509
 int AGC_FUNCTION=7;
+#else
+int AGC_FUNCTION=3; //RRK, was 7 now taken by waveshare LCD TS, disable i2c
+#endif
 int ENABLE_BAND_BUTTON=1;
 int BAND_BUTTON=13;
 int ENABLE_BANDSTACK_BUTTON=1;
@@ -75,7 +88,68 @@ int MOX_BUTTON=27;
 int ENABLE_FUNCTION_BUTTON=1;
 int FUNCTION_BUTTON=22;
 int ENABLE_LOCK_BUTTON=1;
-int LOCK_BUTTON=25;
+int ENABLE_CW_BUTTONS=1;
+// make sure to disable UART0 for next 2 gpios
+int CWL_BUTTON=15;
+int CWR_BUTTON=14;
+
+#ifdef sx1509
+/* Hardware Hookup:
+
+Leaves a spare gpio and an extra unused button (x1)
+
+SX1509 Breakout ------ Odroid ------------ Component
+      GND -------------- GND (1)
+      3V3 -------------- 3.3V(6)
+      SDA -------------- SDA (3)
+      SCL -------------- SCL (5)
+      INT -------------- #88 (11)
+       0 --------------------------------- TN S1 E1 (row 1)
+       1 --------------------------------- S2 S3 E2 (row 2)
+       2 --------------------------------- S4 S5 E3 (row 3)
+       3 --------------------------------- S6 FN x1 (row 4)
+       4 --------------------------------- VFO_ENCODER_A
+       5 --------------------------------- VFO_ENCODER_B
+       6 --------------------------------- AF_ENCODER_A
+       7 --------------------------------- AF_ENCODER_B
+       8 --------------------------------- TN S2 S4 S6 (col 1)
+       9 --------------------------------- S1 S3 S5 FN (col 2)
+       10 -------------------------------- E1 E2 E3 x1 (col 3)
+       11 -------------------------------- RF_ENCODER_A
+       12 -------------------------------- RF_ENCODER_B
+       13 -------------------------------- AGC_ENCODER_A
+       14 -------------------------------- AGC_ENCODER_B
+       15 -------------------------------- spare_gpio
+
+Alternate to allow 5 extra buttons
+
+       0 --------------------------------- TN S1 x1 x2 (row 1)
+       1 --------------------------------- S2 S3 E1 x4 (row 2)
+       2 --------------------------------- S4 S5 E2 x5 (row 3)
+       3 --------------------------------- S6 x3 E3 FN (row 4)
+       4 --------------------------------- VFO_ENCODER_A
+       5 --------------------------------- VFO_ENCODER_B
+       6 --------------------------------- AF_ENCODER_A
+       7 --------------------------------- AF_ENCODER_B
+       8 --------------------------------- TN S2 S4 S6 (col 1)
+       9 --------------------------------- S1 S3 S5 x3 (col 2)
+       10 -------------------------------- x1 E1 E2 E3 (col 3)
+       11 -------------------------------- x2 x4 x5 FN (col 4)
+       12 -------------------------------- RF_ENCODER_A
+       13 -------------------------------- RF_ENCODER_B
+       14 -------------------------------- AGC_ENCODER_A
+       15 -------------------------------- AGC_ENCODER_B
+
+x1-x5 (spare buttons)
+
+*/
+const uint8_t SX1509_ADDRESS=0x3E;
+struct SX1509* pSX1509;
+
+//#ifdef odroid
+int SX1509_INT_PIN=0;
+//#endif
+#endif
 
 static volatile int vfoEncoderPos;
 static volatile int afEncoderPos;
@@ -170,6 +244,11 @@ static void lockAlert(int gpio, int level, uint32_t tick) {
     lock_state=(level==0);
 }
 
+static void cwAlert(int gpio, int level, uint32_t tick) {
+    if (cw_keyer_internal == 0)
+       keyer_event(gpio, cw_active_level == 0 ? level : (level==0));
+}
+
 static void vfoEncoderPulse(int gpio, int level, unsigned int tick) {
    static int levA=0, levB=0, lastGpio = -1;
 
@@ -253,7 +332,67 @@ static void agcEncoderPulse(int gpio, int level, uint32_t tick)
    }
 }
 
-#ifdef odroid
+#ifdef sx1509
+#define SX1509_ENCODER_MASK 0xF0F0
+
+#define BTN_ROWS 4 // Number of rows in the button matrix
+#define BTN_COLS 4 // Number of columns in the button matrix
+
+// btnMap maps row/column combinations to button states:
+volatile int *btnArray[BTN_ROWS][BTN_COLS] = {
+  { &mox_state, &band_state, NULL, NULL},
+  { &bandstack_state, &mode_state, &afFunction, NULL},
+  { &filter_state, &noise_state, &rfFunction, NULL},
+  { &agc_state, NULL, &agcFunction, &function_state}
+};
+
+void sx1509_interrupt(void) {
+
+   static int lastBtnPress = 255;
+   static uint64_t lastBtnPressTime = 0;
+
+   // read and clear encoder interrupts
+   uint16_t encInterrupt = SX1509_interruptSource(pSX1509, true);
+
+
+   if (encInterrupt & SX1509_ENCODER_MASK) {
+      if (encInterrupt & (1<<VFO_ENCODER_A))
+        vfoEncoderPulse(VFO_ENCODER_A, SX1509_digitalRead(pSX1509, VFO_ENCODER_A), 0);
+      if (encInterrupt & (1<<VFO_ENCODER_B))
+        vfoEncoderPulse(VFO_ENCODER_B, SX1509_digitalRead(pSX1509, VFO_ENCODER_B), 0);
+      if (encInterrupt & (1<<AF_ENCODER_A))
+        afEncoderPulse(AF_ENCODER_A, SX1509_digitalRead(pSX1509, AF_ENCODER_A), 0);
+      if (encInterrupt & (1<<AF_ENCODER_B))
+        afEncoderPulse(AF_ENCODER_B, SX1509_digitalRead(pSX1509, AF_ENCODER_B), 0);
+      if (encInterrupt & (1<<RF_ENCODER_A))
+        rfEncoderPulse(RF_ENCODER_A, SX1509_digitalRead(pSX1509, RF_ENCODER_A), 0);
+      if (encInterrupt & (1<<RF_ENCODER_B))
+        rfEncoderPulse(RF_ENCODER_B, SX1509_digitalRead(pSX1509, RF_ENCODER_B), 0);
+      if (encInterrupt & (1<<AGC_ENCODER_A))
+        agcEncoderPulse(AGC_ENCODER_A, SX1509_digitalRead(pSX1509, AGC_ENCODER_A), 0);
+      if (encInterrupt & (1<<AGC_ENCODER_B))
+        agcEncoderPulse(AGC_ENCODER_B, SX1509_digitalRead(pSX1509, AGC_ENCODER_B), 0);
+   }
+
+   uint16_t btnData = SX1509_readKeypad(pSX1509);
+
+   if (btnData) {
+      uint8_t row = SX1509_getRow(pSX1509, btnData);
+      uint8_t col = SX1509_getCol(pSX1509, btnData);
+
+      if ((btnData != lastBtnPress) ||
+          (lastBtnPressTime < millis() - 100)) //100ms
+      {
+         lastBtnPress = btnData;
+         lastBtnPressTime = millis();
+         if (btnArray[row][col] != NULL)
+            *btnArray[row][col] = 1;
+      }
+   }
+}
+#endif
+
+#if defined odroid && !defined sx1509
 void interruptB(void) {
    vfoEncoderPulse(VFO_ENCODER_B,digitalRead(VFO_ENCODER_B_PIN),0);
 }
@@ -274,7 +413,7 @@ void gpio_restore_state() {
   if(value) VFO_ENCODER_A=atoi(value);
   value=getProperty("VFO_ENCODER_B");
   if(value) VFO_ENCODER_B=atoi(value);
-#ifdef odroid
+#if defined odroid && !defined sx1509
   value=getProperty("VFO_ENCODER_A_PIN");
   if(value) VFO_ENCODER_A_PIN=atoi(value);
   value=getProperty("VFO_ENCODER_B_PIN");
@@ -338,8 +477,16 @@ void gpio_restore_state() {
   if(value) MOX_BUTTON=atoi(value);
   value=getProperty("ENABLE_LOCK_BUTTON");
   if(value) ENABLE_LOCK_BUTTON=atoi(value);
+#ifndef sx1509
   value=getProperty("LOCK_BUTTON");
   if(value) LOCK_BUTTON=atoi(value);
+#endif
+  value=getProperty("ENABLE_CW_BUTTONS");
+  if(value) ENABLE_CW_BUTTONS=atoi(value);
+  value=getProperty("CWL_BUTTON");
+  if(value) CWL_BUTTON=atoi(value);
+  value=getProperty("CWR_BUTTON");
+  if(value) CWR_BUTTON=atoi(value);
 }
 
 void gpio_save_state() {
@@ -353,7 +500,7 @@ void gpio_save_state() {
   setProperty("VFO_ENCODER_A",value);
   sprintf(value,"%d",VFO_ENCODER_B);
   setProperty("VFO_ENCODER_B",value);
-#ifdef odroid
+#if defined odroid && !defined sx1509
   sprintf(value,"%d",VFO_ENCODER_A_PIN);
   setProperty("VFO_ENCODER_A_PIN",value);
   sprintf(value,"%d",VFO_ENCODER_B_PIN);
@@ -417,8 +564,16 @@ void gpio_save_state() {
   setProperty("MOX_BUTTON",value);
   sprintf(value,"%d",ENABLE_LOCK_BUTTON);
   setProperty("ENABLE_LOCK_BUTTON",value);
+#ifndef sx1509
   sprintf(value,"%d",LOCK_BUTTON);
   setProperty("LOCK_BUTTON",value);
+#endif
+  sprintf(value,"%d",ENABLE_CW_BUTTONS);
+  setProperty("ENABLE_CW_BUTTONS",value);
+  sprintf(value,"%d",CWL_BUTTON);
+  setProperty("CWL_BUTTON",value);
+  sprintf(value,"%d",CWR_BUTTON);
+  setProperty("CWR_BUTTON",value);
 
   saveProperties("gpio.props");
 }
@@ -427,7 +582,7 @@ void gpio_save_state() {
 int gpio_init() {
 fprintf(stderr,"encoder_init\n");
 
-#ifdef odroid
+#if defined odroid && !defined sx1509
   VFO_ENCODER_A=88;
   VFO_ENCODER_B=87;
 #endif
@@ -435,6 +590,17 @@ fprintf(stderr,"encoder_init\n");
   gpio_restore_state();
 #ifdef raspberrypi
 
+#define BUTTON_STEADY_TIME_US 5000
+
+  void setup_button(int button, gpioAlertFunc_t pAlert) {
+    gpioSetMode(button, PI_INPUT);
+    gpioSetPullUpDown(button,PI_PUD_UP);
+    // give time to settle to avoid false triggers
+    usleep(10000);
+    gpioSetAlertFunc(button, pAlert);
+    gpioGlitchFilter(button, BUTTON_STEADY_TIME_US);
+  }
+
     fprintf(stderr,"encoder_init: VFO_ENCODER_A=%d VFO_ENCODER_B=%d\n",VFO_ENCODER_A,VFO_ENCODER_B);
 
     fprintf(stderr,"gpioInitialize\n");
@@ -444,9 +610,7 @@ fprintf(stderr,"encoder_init\n");
     }
 
   if(ENABLE_FUNCTION_BUTTON) {
-    gpioSetMode(FUNCTION_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(FUNCTION_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(FUNCTION_BUTTON, functionAlert);
+    setup_button(FUNCTION_BUTTON, functionAlert);
   }
 
   if(ENABLE_VFO_ENCODER) {
@@ -473,10 +637,8 @@ fprintf(stderr,"encoder_init\n");
   }
 
 
-    gpioSetMode(AF_FUNCTION, PI_INPUT);
-    gpioSetPullUpDown(AF_FUNCTION,PI_PUD_UP);
-    gpioSetAlertFunc(AF_FUNCTION, afFunctionAlert);
-    afFunction=0;
+  setup_button(AF_FUNCTION, afFunctionAlert);
+  afFunction=0;
 
   if(ENABLE_AF_ENCODER) {
     gpioSetMode(AF_ENCODER_A, PI_INPUT);
@@ -493,9 +655,7 @@ fprintf(stderr,"encoder_init\n");
     afEncoderPos=0;
   }
 
-    gpioSetMode(RF_FUNCTION, PI_INPUT);
-    gpioSetPullUpDown(RF_FUNCTION,PI_PUD_UP);
-    gpioSetAlertFunc(RF_FUNCTION, rfFunctionAlert);
+    setup_button(RF_FUNCTION, rfFunctionAlert);
     rfFunction=0;
 
   if(ENABLE_RF_ENCODER) {
@@ -513,9 +673,7 @@ fprintf(stderr,"encoder_init\n");
     rfEncoderPos=0;
   }
 
-  gpioSetMode(AGC_FUNCTION, PI_INPUT);
-  gpioSetPullUpDown(AGC_FUNCTION,PI_PUD_UP);
-  gpioSetAlertFunc(AGC_FUNCTION, agcFunctionAlert);
+  setup_button(AGC_FUNCTION, agcFunctionAlert);
   agcFunction=0;
 
   if(ENABLE_AGC_ENCODER) {
@@ -535,56 +693,119 @@ fprintf(stderr,"encoder_init\n");
 
 
   if(ENABLE_BAND_BUTTON) {
-    gpioSetMode(BAND_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(BAND_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(BAND_BUTTON, bandAlert);
+    setup_button(BAND_BUTTON, bandAlert);
   }
  
   if(ENABLE_BANDSTACK_BUTTON) {
-    gpioSetMode(BANDSTACK_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(BANDSTACK_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(BANDSTACK_BUTTON, bandstackAlert);
+    setup_button(BANDSTACK_BUTTON, bandstackAlert);
   }
  
   if(ENABLE_MODE_BUTTON) {
-    gpioSetMode(MODE_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(MODE_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(MODE_BUTTON, modeAlert);
+    setup_button(MODE_BUTTON, modeAlert);
   }
  
   if(ENABLE_FILTER_BUTTON) {
-    gpioSetMode(FILTER_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(FILTER_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(FILTER_BUTTON, filterAlert);
+    setup_button(FILTER_BUTTON, filterAlert);
   }
  
   if(ENABLE_NOISE_BUTTON) {
-    gpioSetMode(NOISE_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(NOISE_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(NOISE_BUTTON, noiseAlert);
+    setup_button(NOISE_BUTTON, noiseAlert);
   }
  
   if(ENABLE_AGC_BUTTON) {
-    gpioSetMode(AGC_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(AGC_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(AGC_BUTTON, agcAlert);
+    setup_button(AGC_BUTTON, agcAlert);
   }
  
   if(ENABLE_MOX_BUTTON) {
-    gpioSetMode(MOX_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(MOX_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(MOX_BUTTON, moxAlert);
+    setup_button(MOX_BUTTON, moxAlert);
   }
 
+#ifndef sx1509
   if(ENABLE_LOCK_BUTTON) {
-    gpioSetMode(LOCK_BUTTON, PI_INPUT);
-    gpioSetPullUpDown(LOCK_BUTTON,PI_PUD_UP);
-    gpioSetAlertFunc(LOCK_BUTTON, lockAlert);
+    setup_button(LOCK_BUTTON, lockAlert);
+  }
+#endif
+
+  if(ENABLE_CW_BUTTONS) {
+    gpioSetMode(CWL_BUTTON, PI_INPUT);
+    gpioSetAlertFunc(CWL_BUTTON, cwAlert);
+    gpioSetMode(CWR_BUTTON, PI_INPUT);
+    gpioSetAlertFunc(CWR_BUTTON, cwAlert);
+    gpioGlitchFilter(CWL_BUTTON, 5000);
+    gpioGlitchFilter(CWR_BUTTON, 5000);
   }
  
 #endif
 
-#ifdef odroid
+#ifdef sx1509
+  // override default (PI) values
+  VFO_ENCODER_A=4;
+  VFO_ENCODER_B=5;
+  AF_ENCODER_A=6;
+  AF_ENCODER_B=7;
+  RF_ENCODER_A=12;
+  RF_ENCODER_B=13;
+  AGC_ENCODER_A=14;
+  AGC_ENCODER_B=15;
+
+  fprintf(stderr,"sx1509 encoder_init: VFO_ENCODER_A=%d VFO_ENCODER_B=%d\n",VFO_ENCODER_A,VFO_ENCODER_B);
+
+  pSX1509 = newSX1509();
+
+  // Call SX1509_begin(<address>) to initialize the SX1509. If it
+  // successfully communicates, it'll return 1. 255 for soft reset
+  if (!SX1509_begin(pSX1509, SX1509_ADDRESS, 255))
+  {
+    printf("Failed to communicate to sx1509 at %x.\n", SX1509_ADDRESS);
+    return 1;
+  }
+
+  fprintf(stderr,"wiringPiSetup\n");
+  if (wiringPiSetup () < 0) {
+    printf ("Unable to setup wiringPi: %s\n", strerror (errno));
+    return 1;
+  }
+
+  // Initialize the buttons
+  // Sleep time off (0). 16ms scan time, 8ms debounce:
+  SX1509_keypad(pSX1509, BTN_ROWS, BTN_COLS, 0, 16, 8);
+
+  // Initialize the encoders
+  SX1509_pinMode(pSX1509, VFO_ENCODER_A, INPUT_PULLUP);
+  SX1509_pinMode(pSX1509, VFO_ENCODER_B, INPUT_PULLUP);
+  SX1509_enableInterrupt(pSX1509, VFO_ENCODER_A, CHANGE);
+  SX1509_enableInterrupt(pSX1509, VFO_ENCODER_B, CHANGE);
+  vfoEncoderPos=0;
+  SX1509_pinMode(pSX1509, AF_ENCODER_A, INPUT_PULLUP);
+  SX1509_pinMode(pSX1509, AF_ENCODER_B, INPUT_PULLUP);
+  SX1509_enableInterrupt(pSX1509, AF_ENCODER_A, CHANGE);
+  SX1509_enableInterrupt(pSX1509, AF_ENCODER_B, CHANGE);
+  afEncoderPos=0;
+  SX1509_pinMode(pSX1509, RF_ENCODER_A, INPUT_PULLUP);
+  SX1509_pinMode(pSX1509, RF_ENCODER_B, INPUT_PULLUP);
+  SX1509_enableInterrupt(pSX1509, RF_ENCODER_A, CHANGE);
+  SX1509_enableInterrupt(pSX1509, RF_ENCODER_B, CHANGE);
+  rfEncoderPos=0;
+  SX1509_pinMode(pSX1509, AGC_ENCODER_A, INPUT_PULLUP);
+  SX1509_pinMode(pSX1509, AGC_ENCODER_B, INPUT_PULLUP);
+  SX1509_enableInterrupt(pSX1509, AGC_ENCODER_A, CHANGE);
+  SX1509_enableInterrupt(pSX1509, AGC_ENCODER_B, CHANGE);
+  agcEncoderPos=0;
+
+  afFunction=0;
+  rfFunction=0;
+  agcFunction=0;
+
+  pinMode(SX1509_INT_PIN, INPUT);
+  pullUpDnControl(SX1509_INT_PIN, PUD_UP);
+
+  if ( wiringPiISR (SX1509_INT_PIN, INT_EDGE_FALLING, &sx1509_interrupt) < 0 ) {
+    printf ("Unable to setup ISR: %s\n", strerror (errno));
+    return 1;
+  }
+#endif
+
+#if defined odroid && !defined sx1509
 
     //VFO_ENCODER_A=ODROID_VFO_ENCODER_A;
     //VFO_ENCODER_B=ODROID_VFO_ENCODER_B;
@@ -637,7 +858,7 @@ fprintf(stderr,"encoder_init\n");
 
 void gpio_close() {
     running=0;
-#ifdef odroid
+#if defined odroid && !defined sx1509
     FILE *fp;
     fp = popen("echo 97 > /sys/class/gpio/unexport\n", "r");
     pclose(fp);
@@ -849,7 +1070,6 @@ static int mox_pressed(void *data) {
 }
 
 static int lock_pressed(void *data) {
-fprintf(stderr,"lock_pressed\n");
   lock_cb((GtkWidget *)NULL, (gpointer)NULL);
   return 0;
 }
@@ -992,11 +1212,25 @@ static void* rotary_encoder_thread(void *arg) {
             }
         }
 
+#ifdef sx1509
+        // buttons only generate interrupt when
+        // pushed on, so clear them now
+        function_state = 0;
+        band_state = 0;
+        bandstack_state = 0;
+        mode_state = 0;
+        filter_state = 0;
+        noise_state = 0;
+        agc_state = 0;
+        mox_state = 0;
+        lock_state = 0;
+#endif
+
 #ifdef raspberrypi
           if(running) gpioDelay(100000); // 10 per second
 #endif
 #ifdef odroid
-          if(runnig) usleep(100000);
+          if(running) usleep(100000);
 #endif
     }
 #ifdef raspberrypi
diff --git a/gpio.h b/gpio.h
index 4ed7155d569852d2f4a8252f30b3d727221e658d..6a07e3f5d2fa5a3f86e8e38d3a2fe3dd0abf472c 100644 (file)
--- a/gpio.h
+++ b/gpio.h
@@ -54,6 +54,8 @@ extern int ENABLE_MOX_BUTTON;
 extern int MOX_BUTTON;
 extern int ENABLE_FUNCTION_BUTTON;
 extern int FUNCTION_BUTTON;
+extern int CWL_BUTTON;
+extern int CWR_BUTTON;
 
 void gpio_restore_state();
 void gpio_save_state();
diff --git a/iambic.c b/iambic.c
new file mode 100644 (file)
index 0000000..d8084a6
--- /dev/null
+++ b/iambic.c
@@ -0,0 +1,405 @@
+/* Copyright (C)
+* 2015 - John Melton, G0ORX/N6LYT
+*
+* 10/12/2016, Rick Koch / N1GP adapted Phil's verilog code from
+*             the openHPSDR Hermes iambic.v implementation to work
+*             with John's pihpsdr project. This allows one to work
+*             CW solely from the pihpsdr unit remotely.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+*
+* ---------------------------------------------------------------------------------
+*         Copywrite (C) Phil Harman VK6PH May 2014
+* ---------------------------------------------------------------------------------
+*
+* The code implements an Iambic CW keyer.  The following features are supported:
+*
+*         * Variable speed control from 1 to 60 WPM
+*         * Dot and Dash memory
+*         * Straight, Bug, Iambic Mode A or B Modes
+*         * Variable character weighting
+*         * Automatic Letter spacing
+*         * Paddle swap
+*
+* Dot and Dash memory works by registering an alternative paddle closure whilst a paddle is pressed.
+* The alternate paddle closure can occur at any time during a paddle closure and is not limited to being
+* half way through the current dot or dash. This feature could be added if required.
+*
+* In Straight mode, closing the DASH paddle will result in the output following the input state.  This enables a
+* straight morse key or external Iambic keyer to be connected.
+*
+* In Bug mode closing the dot paddle will send repeated dots.
+*
+* The difference between Iambic Mode A and B lies in what the keyer does when both paddles are released. In Mode A the
+* keyer completes the element being sent when both paddles are released. In Mode B the keyer sends an additional
+* element opposite to the one being sent when the paddles are released.
+*
+* This only effects letters and characters like C, period or AR.
+*
+* Automatic Letter Space works as follows: When enabled, if you pause for more than one dot time between a dot or dash
+* the keyer will interpret this as a letter-space and will not send the next dot or dash until the letter-space time has been met.
+* The normal letter-space is 3 dot periods. The keyer has a paddle event memory so that you can enter dots or dashes during the
+* inter-letter space and the keyer will send them as they were entered.
+*
+* Speed calculation -  Using standard PARIS timing, dot_period(mS) = 1200/WPM
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sched.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <time.h>
+#include <sys/mman.h>
+
+#include <wiringPi.h>
+#include <softTone.h>
+
+#include "gpio.h"
+#include "radio.h"
+#include "new_protocol.h"
+#include "iambic.h"
+#include "beep.h"
+
+static void* keyer_thread(void *arg);
+static pthread_t keyer_thread_id;
+
+// set to 0 to use the PI's hw:0 audio out for sidetone
+#define SIDETONE_GPIO 0 // this is in wiringPi notation
+
+#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 key_state = 0;
+static int kdelay = 0;
+static int dot_delay = 0;
+static int dash_delay = 0;
+static int kcwl = 0;
+static int kcwr = 0;
+static int *kdot;
+static int *kdash;
+static int running = 0;
+static sem_t cw_event;
+
+int keyer_out = 0;
+
+// using clock_nanosleep of librt
+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;
+}
+
+void keyer_update() {
+    if (!running)
+        keyer_init();
+
+    dot_delay = 1200 / cw_keyer_speed;
+    // will be 3 * dot length at standard weight
+    dash_delay = (dot_delay * 3 * cw_keyer_weight) / 50;
+
+    if (cw_keys_reversed) {
+        kdot = &kcwr;
+        kdash = &kcwl;
+    } else {
+        kdot = &kcwl;
+        kdash = &kcwr;
+    }
+    beep_vol(cw_keyer_sidetone_volume);
+    beep_freq = cw_keyer_sidetone_frequency;
+}
+
+void keyer_event(int gpio, int level) {
+    int state = (level == 0);
+
+    if (gpio == CWL_BUTTON)
+        kcwl = state;
+    else  // CWR_BUTTON
+        kcwr = state;
+
+    if (state || cw_keyer_mode == KEYER_STRAIGHT)
+        sem_post(&cw_event);
+}
+
+void clear_memory() {
+    dot_memory  = 0;
+    dash_memory = 0;
+}
+
+void set_keyer_out(int state) {
+    if (keyer_out != state) {
+        keyer_out = state;
+        if(protocol==NEW_PROTOCOL) schedule_high_priority(9);
+        if (state)
+            if (SIDETONE_GPIO)
+                softToneWrite (SIDETONE_GPIO, cw_keyer_sidetone_frequency);
+            else
+                beep_mute(1);
+        else
+            if (SIDETONE_GPIO)
+                softToneWrite (SIDETONE_GPIO, 0);
+            else
+                beep_mute(0);
+    }
+}
+
+static void* keyer_thread(void *arg) {
+    int pos;
+    struct timespec loop_delay;
+    int interval = 1000000; // 1 ms
+
+    while(running) {
+        sem_wait(&cw_event);
+        key_state = CHECK;
+
+        while (key_state != EXITLOOP) {
+            switch(key_state) {
+            case CHECK: // check for key press
+                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;
+                    }
+                }
+                else {
+                    if (*kdot)
+                        key_state = PREDOT;
+                    else if (*kdash)
+                        key_state = PREDASH;
+                    else {
+                        set_keyer_out(0);
+                        key_state = EXITLOOP;
+                    }
+                }
+                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;
+
+            // 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:
+                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;
+                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;
+                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;
+                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;
+                }
+                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;
+                    if (dot_memory)         // check if a dot or dash paddle was pressed during the delay.
+                        key_state = PREDOT;
+                    else if (dash_memory)
+                        key_state = PREDASH;
+                    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:
+                key_state = EXITLOOP;
+
+            }
+
+            clock_gettime(CLOCK_MONOTONIC, &loop_delay);
+            loop_delay.tv_nsec += 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);
+        }
+    }
+}
+
+void keyer_close() {
+    running=0;
+    beep_close();
+}
+
+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;
+    }
+
+    if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
+            perror("mlockall failed");
+            running = 0;
+    }
+
+    stack_prefault();
+
+    fprintf(stderr,"keyer_init\n");
+
+    if (wiringPiSetup () < 0) {
+        fprintf (stderr, "Unable to setup wiringPi: %s\n", strerror (errno));
+        return 1;
+    }
+
+    if (SIDETONE_GPIO)
+        softToneCreate(SIDETONE_GPIO);
+    else {
+        beep_init();
+        beep_vol(cw_keyer_sidetone_volume);
+    }
+
+    rc = sem_init(&cw_event, 0, 0);
+    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;
+}
+
diff --git a/iambic.h b/iambic.h
new file mode 100644 (file)
index 0000000..3585fd6
--- /dev/null
+++ b/iambic.h
@@ -0,0 +1,12 @@
+#ifndef _IAMBIC_H
+#define _IAMBIC_H
+
+extern int cwl_state;
+extern int cwr_state;
+extern int keyer_out;
+
+void keyer_event(int gpio, int level);
+void keyer_update();
+void keyer_close();
+
+#endif
diff --git a/radio.c b/radio.c
index c442e75d9d110038227a6e4dc5b8d1c480b5bd68..5bb28ec3054dabe51382cd0c79a27cdb8c154276 100644 (file)
--- a/radio.c
+++ b/radio.c
@@ -159,6 +159,7 @@ int cw_keyer_ptt_delay=20; // 0-255ms
 int cw_keyer_hang_time=300; // ms
 int cw_keyer_sidetone_frequency=400; // Hz
 int cw_breakin=1; // 0=disabled 1=enabled
+int cw_active_level=1; // 0=active_low 1=active_high
 
 int vfo_encoder_divisor=15;
 
diff --git a/radio.h b/radio.h
index af02e6ae3acca6e4f7fa7bea37a73073b8603fa0..7cd4bdadd2fe666ef152da53b21048ebc5a82e94 100644 (file)
--- a/radio.h
+++ b/radio.h
@@ -171,6 +171,7 @@ extern int cw_keyer_ptt_delay;
 extern int cw_keyer_hang_time;
 extern int cw_keyer_sidetone_frequency;
 extern int cw_breakin;
+extern int cw_active_level;
 
 extern int vfo_encoder_divisor;