From e440b07548c9c6ddfc358e89765c5bb47aa59637 Mon Sep 17 00:00:00 2001 From: Rob French Date: Sat, 29 Feb 2020 23:18:03 -0600 Subject: [PATCH] (1) Allowed use of the internal key state at the same time as the GPIO Keyer, in order to enable easy switching between CW and other modes. (2) Clean comments in the code. Remaining effort: - Cleanup GPIO Keyer for release. - Eventually, add PTT button and possibly separate straight key input to GPIO Keyer. --- gpiokeyer.c | 67 +++++++++++++++++++++++++++++++++++++-------------- is_key_down.c | 37 ++++++++++++++++++++++++++-- quisk.c | 33 +++++++++++++++++++++++++ quisk.h | 26 +++++++++++++++++++- setup.py | 16 ++++++++++-- 5 files changed, 156 insertions(+), 23 deletions(-) diff --git a/gpiokeyer.c b/gpiokeyer.c index 88f867c..627a2f2 100644 --- a/gpiokeyer.c +++ b/gpiokeyer.c @@ -1,22 +1,44 @@ -// NEED TO FIGURE OUT A SIMPLER WAY TO DO THIS... THIS IS JUST TO GET ENABLE_GPIO_KEYER! -#include // used by quisk.h -#include // Used by quisk.h -#include "quisk.h" - -#if defined(ENABLE_GPIO_KEYER) - -// gcc iambic.c -o iambic -l pigpio -lpthread -// or make, to run sudo ./iambic [options] +//#if defined(ENABLE_GPIO_KEYER) +/*********************************************************************** + * gpiokeyer.c + * by Robert A. French (KC4UPR) + * + * This file implements a General Purpose Input Output (GPIO) based + * Morse Code / Continuous Wave (CW) keyer for Quisk. This was derived + * from Richard (N1GP) Koch's Raspberry Pi iambic keyer code, available + * at: github.com/n1gp/iambic-keyer + * + * Modifications included: + * - Removing the GPIO sidetone functionality. Quisk will be creating + * both the sidetone and the actual quadrature CW signal to feed to + * the SDR. + * - Modifying the keying logic (state machine) slightly. I don't know + * if I improved it or not, but changes were based on my tracing thru + * the code and updating things that seemed reasonable. + * - Implementing GPIO key and T/R switching outputs, that could be + * used to drive both an external CW generator, as well a T/R + * switching with a configurable QSK/semi-QSK delay. + * - Making parameters configurable/updateable during runtime by calls + * from Quisk. + * - Making the GPIO pins configurable at startup. + * - Incorporating the keyer into Quisk's overall keying logic logic. + * + * Original N1GP changelog and licensing are below, as well as the notes + * from the even previous source, a Verilog keyer for Hermes Lite. + **********************************************************************/ /* + 3/1/2020, Rob French / KC4UPR, I converted the code for use as a + component of Quisk. 10/12/2016, Rick Koch / N1GP, I adapted Phil's verilog code from the openHPSDR Hermes iambic.v implementation to build and run on a raspberry PI 3. - 1/7/2017, N1GP, adapted to work with Jack Audio, much better timing. + 1/7/2017, N1GP, adapted to work with Jack Audio, much better + timing. --------------------------------------------------------------------------------- +------------------------------------------------------------------------ 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 @@ -29,12 +51,11 @@ 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. --------------------------------------------------------------------------------- +------------------------------------------------------------------------ - ---------------------------------------------------------------------------------- +------------------------------------------------------------------------ Copywrite (C) Phil Harman VK6PH May 2014 ---------------------------------------------------------------------------------- +------------------------------------------------------------------------ The code implements an Iambic CW keyer. The following features are supported: @@ -67,6 +88,11 @@ Boston, MA 02110-1301, USA. Speed calculation - Using standard PARIS timing, dot_period(mS) = 1200/WPM */ + +// NEED TO FIGURE OUT A SIMPLER WAY TO DO THIS... THIS IS JUST TO GET ENABLE_GPIO_KEYER! +//#include // used by quisk.h +//#include // Used by quisk.h +//#include "quisk.h" #define DEBUG @@ -128,8 +154,8 @@ static int key_state = 0; static int kdelay = 0; static int dot_delay = 0; static int dash_delay = 0; -static volatile int kcwl = 0; -static volatile int kcwr = 0; +static int kcwl = 0; +static int kcwr = 0; static int *kdot; static int *kdash; static int cw_keyer_speed = 20; @@ -441,6 +467,8 @@ static void* keyer_thread(void *arg) { #if defined(DEBUG) fprintf(stdout, "[GPIO Keyer] thread should be ending now\n"); #endif + + return arg; // did this to ditch compiler warnings... bad idea? } int open_key_gpiokeyer(const char * name) @@ -558,4 +586,7 @@ void quisk_set_gpio_keyer_enabled(int flag) cw_keyer_enabled = (flag == 0 ? 0 : 1); } -#endif +/*********************************************************************** + * EOF + **********************************************************************/ +//#endif diff --git a/is_key_down.c b/is_key_down.c index b955c28..5bce752 100755 --- a/is_key_down.c +++ b/is_key_down.c @@ -60,6 +60,10 @@ static int is_key_down_pport(void); static int is_key_down_serport(void); static int is_key_down_enet(void); #if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Headers for new functions to implement the GPIO Keyer. They + * are not defined as static here, because I implemented the functions + * in a separate source file (gpiokeyer.c). + */ int open_key_gpiokeyer(const char * name); void close_key_gpiokeyer(void); int is_key_down_gpiokeyer(void); @@ -71,6 +75,10 @@ static enum { // The key access method SerPort, // Use the serial port Udp // Use UDP Ethernet #if defined(ENABLE_GPIO_KEYER) + /* KC4UPR: Define a new hardware keyer type: a GPIO Keyer that + * implements straight, bug, and iambic keyer functionality, can be + * configured to use different pins on a Raspberry Pi. + */ , GpioKeyer // Use Raspberry Pi GPIO keyer based on N1GP code #endif } key_method = None; @@ -100,6 +108,22 @@ int quisk_open_key(const char * name) ret = open_key_enet(name); } #if defined(ENABLE_GPIO_KEYER) + /* KC4UPR: Check if the GPIO Keyer was requested, and if so, open + * it. It's name must start with "gpio". The overall format is: + * gpio:left,right,keyer_out,tr_switch + * where: + * left - GPIO input pin for the left paddle + * right - GPIO input pin for the right paddle + * keyer_out - GPIO output pin for the key (actual CW) signal + * (unlike the tr_switch line, this is only keyed + * for the actual dits and dahs) + * (set to zero to not use this) + * tr_switch - GPIO output pin for T/R switching (this is + * enabled any time keyer_out is enabled, plus a + * short, configurable delay after the last time + * the keyer output was set) + * (set to zero to not use this) + */ else if (!strncmp(name, "gpio", 4)){ // Raspberry Pi GPIO keyer key_method = GpioKeyer; ret = open_key_gpiokeyer(name); @@ -126,6 +150,8 @@ void quisk_close_key(void) close_key_enet(); break; #if defined(ENABLE_GPIO_KEYER) + /* KC4UPR: Self-explanatory--close the GPIO Keyer. + */ case GpioKeyer: close_key_gpiokeyer(); break; @@ -146,8 +172,16 @@ int quisk_is_key_down(void) case Udp: return is_key_down_enet(); #if defined(ENABLE_GPIO_KEYER) + /* KC4UPR: Check if the GPIO Keyer key is down. This also checks + * the state of the internal key. I included this to allow use of + * the quisk_set_key_down() function in conjunction with the GPIO + * Keyer, for example in the case of digimodes. This could probably + * also reasonably be applied to the other hardware key methods + * above, but I decided to try to restrict my changes to things + * explicitly involving the GPIO Keyer. + */ case GpioKeyer: - return is_key_down_gpiokeyer(); + return is_key_down_gpiokeyer() | key_is_down; #endif } return 0; @@ -343,5 +377,4 @@ static int is_key_down_serport(void) return 0; } } - #endif diff --git a/quisk.c b/quisk.c index f416024..efaa260 100755 --- a/quisk.c +++ b/quisk.c @@ -3045,6 +3045,15 @@ static PyObject * open_key(PyObject * self, PyObject * args) return PyInt_FromLong(quisk_open_key(name)); } +/* KC4UPR: Added this function to be able to close the current key from + * Python scripts. The intent was to be able to close the current key + * and open a new one, primarily to switch from hardware-generated CW to + * Fldigi-generated digital. This is a cumbersome way to do this, and + * I've moved away from it for the GPIO Keyer. However, this still + * seems like a reasonable function to have available to Python (like + * open_key() is), so I added it and didn't restrict it to cases where + * ENABLE_GPIO_KEYER is defined. + */ static PyObject * close_key(PyObject * self, PyObject * args) { if (!PyArg_ParseTuple (args, "")) @@ -5186,6 +5195,9 @@ static PyObject * set_sample_bytes(PyObject * self, PyObject * args) } #if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Set the GPIO Keyer mode from Python. Mode definitions are + * available in gpiokeyer.c. + */ static PyObject * set_gpio_keyer_mode(PyObject * self, PyObject * args) { int mode; @@ -5197,6 +5209,9 @@ static PyObject * set_gpio_keyer_mode(PyObject * self, PyObject * args) return Py_None; } +/* KC4UPR: Set the GPIO Keyer speed in WPM from Python. Valid range is + * 1-60 WPM. + */ static PyObject * set_gpio_keyer_speed(PyObject * self, PyObject * args) { int wpm; @@ -5208,6 +5223,9 @@ static PyObject * set_gpio_keyer_speed(PyObject * self, PyObject * args) return Py_None; } +/* KC4UPR: Set the GPIO Keyer dot-dash weight from Python. Valid range + * is 33-66. + */ static PyObject * set_gpio_keyer_weight(PyObject * self, PyObject * args) { int weight; @@ -5219,6 +5237,9 @@ static PyObject * set_gpio_keyer_weight(PyObject * self, PyObject * args) return Py_None; } +/* KC4UPR: Reverse the left/right paddle functions (dot/dash is by + * default). '0' to keep default, anything else to reverse. + */ static PyObject * set_gpio_keyer_reversed(PyObject * self, PyObject * args) { int rev; @@ -5230,6 +5251,9 @@ static PyObject * set_gpio_keyer_reversed(PyObject * self, PyObject * args) return Py_None; } +/* KC4UPR: Set strict character spacing mode. '0' to keep default, + * anything else to set strict spacing. + */ static PyObject * set_gpio_keyer_strict(PyObject * self, PyObject * args) { int strict; @@ -5241,6 +5265,11 @@ static PyObject * set_gpio_keyer_strict(PyObject * self, PyObject * args) return Py_None; } +/* KC4UPR: Enable (!= 0) or disable (== 0) the keyer. Note that if the + * keyer is disabled, then the left and right paddles will not generate + * a CW signal or switch T/R. However, set_key_down() can be used to + * set the internal key state. + */ static PyObject * set_gpio_keyer_enabled(PyObject * self, PyObject * args) { int enabled; @@ -5341,6 +5370,8 @@ static PyMethodDef QuiskMethods[] = { {"start_sound", start_sound, METH_VARARGS, "Start the soundcard."}, {"mixer_set", mixer_set, METH_VARARGS, "Set microphone mixer parameters such as volume."}, {"open_key", open_key, METH_VARARGS, "Open access to the state of the key (CW or PTT)."}, + /* KC4UPR: As described above, added close_key() as a Python-callable function. + */ {"close_key", close_key, METH_VARARGS, "Close the currently selected key."}, {"open_rx_udp", open_rx_udp, METH_VARARGS, "Open a UDP port for capture."}, {"close_rx_udp", close_rx_udp, METH_VARARGS, "Close the UDP port used for capture."}, @@ -5355,6 +5386,8 @@ static PyMethodDef QuiskMethods[] = { {"freedv_get_rx_char", quisk_freedv_get_rx_char, METH_VARARGS, "Get text characters received from freedv."}, {"freedv_set_options", (PyCFunction)quisk_freedv_set_options, METH_VARARGS|METH_KEYWORDS, "Set the freedv parameters."}, #if defined(ENABLE_GPIO_KEYER) + /* KC4UPR: Allow the other GPIO Keyer settings to be configured from Python. + */ {"set_gpio_keyer_mode", set_gpio_keyer_mode, METH_VARARGS, "Change the CW keyer mode."}, {"set_gpio_keyer_speed", set_gpio_keyer_speed, METH_VARARGS, "Change the CW keyer speed."}, {"set_gpio_keyer_weight", set_gpio_keyer_weight, METH_VARARGS, "Change the CW keyer symbol weight."}, diff --git a/quisk.h b/quisk.h index fd16fab..809c531 100755 --- a/quisk.h +++ b/quisk.h @@ -283,6 +283,15 @@ void quisk_check_freedv_mode(void); void quisk_calc_audio_graph(double, complex double *, double *, int, int); int QuiskDeltaMsec(int); #if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Define some additional functions for the GPIO Keyer. These + * allow setting various parameters for the keyer. + * mode - straight/bug, Iambic A, Iambic B + * speed - speed in words-per-minute (WPM), 1-60 + * weight - dot vs dash weight, in range 33-66 + * reverse - reverse left/right paddles (dot/dash by default) + * strict - enforce strict character spacing + * enabled - enable/disable the keyer + */ void quisk_set_gpio_keyer_mode(int); void quisk_set_gpio_keyer_speed(int); void quisk_set_gpio_keyer_weight(int); @@ -343,6 +352,11 @@ int import_quisk_api(void); // used to initialize Quisk_API #define quisk_is_key_down (*( int (*) (void) )Quisk_API[9]) #define quisk_sample_source4 (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write) )Quisk_API[10]) //#if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Provide API definitions for the GPIO Keyer functions. + * However... since this doesn't seem to be required for various other + * functions that are accessible from Python, I've commented it all out, + * since in truth I really don't know what it's for/how to use it. + */ //#define quisk_set_gpio_keyer_mode (*( void (*) (int) )Quisk_API[11]) //#define quisk_set_gpio_keyer_speed (*( void (*) (int) )Quisk_API[12]) //#define quisk_set_gpio_keyer_weight (*( void (*) (int) )Quisk_API[13]) @@ -362,9 +376,14 @@ void QuiskSleepMicrosec(int); void QuiskPrintTime(const char *, int); void quisk_sample_source(ty_sample_start, ty_sample_stop, ty_sample_read); void quisk_dvoice_freedv(ty_dvoice_codec_rx, ty_dvoice_codec_tx); -int quisk_is_key_down(void); +int quisk_is_key_down(void); void quisk_sample_source4(ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write); //#if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Provide API definitions for the GPIO Keyer functions. + * However... since this doesn't seem to be required for various other + * functions that are accessible from Python, I've commented it all out, + * since in truth I really don't know what it's for/how to use it. + */ //void quisk_set_gpio_keyer_mode(int); //void quisk_set_gpio_keyer_speed(int); //void quisk_set_gpio_keyer_weight(int); @@ -374,6 +393,11 @@ void quisk_sample_source4(ty_sample_start, ty_sample_stop, ty_sample_read, ty_sa //#endif //#if defined(ENABLE_GPIO_KEYER) +/* KC4UPR: Provide API definitions for the GPIO Keyer functions. + * However... since this doesn't seem to be required for various other + * functions that are accessible from Python, I've commented it all out, + * since in truth I really don't know what it's for/how to use it. + */ //#define QUISK_API_INIT { \ // &quisk_sound_state, &QuiskGetConfigInt, &QuiskGetConfigDouble, &QuiskGetConfigString, &QuiskTimeSec, \ // &QuiskSleepMicrosec, &QuiskPrintTime, &quisk_sample_source, &quisk_dvoice_freedv, &quisk_is_key_down, \ diff --git a/setup.py b/setup.py index 5680dd8..0f38ed2 100755 --- a/setup.py +++ b/setup.py @@ -16,6 +16,10 @@ fp.close() is_64bit = struct.calcsize("P") == 8 +# KC4UPR: Added some lists to grab extra sources and macros. +extra_sources = [] +extra_macros = [] + if sys.platform != "win32": try: import wx @@ -29,12 +33,20 @@ if sys.platform != "win32": print ("Please install the package portaudio19-dev") if not os.path.isdir("/usr/include/pulse"): print ("please install the package libpulse-dev") - + # KC4UPR: Added this section to build the GPIO Keyer, in the event + # that wiringPi.h is available. + if not os.path.isfile("/usr/include/wiringPi.h"): + print("No wiringPi.h available--not building GPIO Keyer") + else: + extra_sources.append('gpiokeyer.c') + extra_macros.append(('ENABLE_GPIO_KEYER', '1')) + module1 = Extension ('quisk._quisk', libraries = ['asound', 'portaudio', 'pulse', 'fftw3', 'm', 'pthread', 'rt', 'wiringPi'], sources = ['quisk.c', 'sound.c', 'sound_alsa.c', 'sound_portaudio.c', 'sound_pulseaudio.c', 'is_key_down.c', 'microphone.c', 'utility.c', - 'filter.c', 'extdemod.c', 'freedv.c', 'gpiokeyer.c'], + 'filter.c', 'extdemod.c', 'freedv.c'] + extra_sources, + define_macros = [] + extra_macros ) module2 = Extension ('quisk.sdriqpkg.sdriq',