# 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
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
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= \
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)
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 $@ $<
--- /dev/null
+/*
+
+ 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);
+}
--- /dev/null
+#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
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);
#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"
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
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;
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;
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;
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;
}
}
-#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);
}
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");
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() {
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);
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");
}
int gpio_init() {
fprintf(stderr,"encoder_init\n");
-#ifdef odroid
+#if defined odroid && !defined sx1509
VFO_ENCODER_A=88;
VFO_ENCODER_B=87;
#endif
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");
}
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) {
}
- 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);
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) {
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) {
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;
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);
}
static int lock_pressed(void *data) {
-fprintf(stderr,"lock_pressed\n");
lock_cb((GtkWidget *)NULL, (gpointer)NULL);
return 0;
}
}
}
+#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
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();
--- /dev/null
+/* 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, ¶m) == -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;
+}
+
--- /dev/null
+#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
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;
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;