From 62fd2316ed288d11b8108f520f778625c7de049d Mon Sep 17 00:00:00 2001 From: John Melton - G0ORX/N6LYT Date: Sat, 10 Dec 2016 12:25:08 +0000 Subject: [PATCH] added keyer support from Rick Koch, N1GP --- Makefile | 31 ++++- beep.c | 393 ++++++++++++++++++++++++++++++++++++++++++++++++++++ beep.h | 11 ++ cw_menu.c | 6 + gpio.c | 328 ++++++++++++++++++++++++++++++++++++------- gpio.h | 2 + iambic.c | 405 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ iambic.h | 12 ++ radio.c | 1 + radio.h | 1 + 10 files changed, 1139 insertions(+), 51 deletions(-) create mode 100644 beep.c create mode 100644 beep.h create mode 100644 iambic.c create mode 100644 iambic.h diff --git a/Makefile b/Makefile index 96b582e..f88bbf5 100644 --- 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 diff --git a/cw_menu.c b/cw_menu.c index d1c90d8..5350e89 100644 --- 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 08102b4..63f426c 100644 --- a/gpio.c +++ b/gpio.c @@ -11,9 +11,13 @@ #include #include #include +#include #ifdef raspberrypi #include #endif +#ifdef sx1509 +#include +#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<) 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 4ed7155..6a07e3f 100644 --- 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +} + diff --git a/iambic.h b/iambic.h new file mode 100644 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 c442e75..5bb28ec 100644 --- 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 af02e6a..7cd4bda 100644 --- 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; -- 2.45.2