diff --git a/gpiokeyer.c b/gpiokeyer.c new file mode 100644 index 0000000..9075073 --- /dev/null +++ b/gpiokeyer.c @@ -0,0 +1,482 @@ +// 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] + +/* + + 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. + +-------------------------------------------------------------------------------- +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. +-------------------------------------------------------------------------------- + + +--------------------------------------------------------------------------------- + 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 +*/ + +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void* keyer_thread(void *arg); +static pthread_t keyer_thread_id; + +// GPIO pins +#define KEYER_OUT_GPIO 26 +#define LEFT_PADDLE_GPIO 22 +#define RIGHT_PADDLE_GPIO 27 + +// Keyer modes +#define KEYER_STRAIGHT 0 +#define KEYER_MODE_A 1 +#define KEYER_MODE_B 2 +#define KEYER_ULTIMATIC 3 +#define NUM_KEYER_MODES 4 + +#define NSEC_PER_SEC (1000000000) + +enum { + CHECK = 0, + PREDOT, + PREDASH, + SENDDOT, + SENDDASH, + DOTDELAY, + DASHDELAY, + DOTHELD, + DASHHELD, + LETTERSPACE, + EXITLOOP +}; + +enum { + NONE = 0, + DOT = 1, + DASH = 2 +}; + +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 cw_keyer_speed = 20; +static int cw_keyer_weight = 55; +static int cw_keys_reversed = 0; +static int cw_keyer_mode = KEYER_MODE_B; +static int cw_keyer_spacing = 0; +static int cw_active_state = 0; +static sem_t cw_event; + +static int last_pressed = NONE; + +static int running, keyer_out = 0; + +static inline int kstate() { + return (*kdash<<1)&(*kdot); +} + +static int prev_state = 0; + +void keyer_update() { + 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; + } +} + +void keyer_event(int gpio, int level, uint32_t tick) { + int state = (cw_active_state == 0) ? (level == 0) : (level != 0); + int okdash = *kdash; + int okdot = *kdot; + int new_state; + + if (gpio == LEFT_PADDLE_GPIO) + kcwl = state; + else // RIGHT_PADDLE_GPIO + kcwr = state; + + if (*kdash > okdash) + last_pressed = DASH; + else if (*kdot > okdot) + last_pressed = DOT; + else { + new_state = kstate(); + last_pressed = new_state & prev_state; + } + + if (state || cw_keyer_mode == KEYER_STRAIGHT) + sem_post(&cw_event); +} + +// Added to support WiringPi, which uses a different type of callback. +void keyer_event_left() +{ + int level = digitalRead(LEFT_PADDLE_GPIO); + keyer_event(LEFT_PADDLE_GPIO, level, 0); + #if defined(DEBUG) + printf("Left Paddle Pressed\n"); + #endif +} + +// Added to support WiringPi, which uses a different type of callback. +void keyer_event_right() +{ + int level = digitalRead(RIGHT_PADDLE_GPIO); + keyer_event(RIGHT_PADDLE_GPIO, level, 0); + #if defined(DEBUG) + printf("Right Paddle Pressed\n"); + #endif +} + +void clear_memory() { + dot_memory = 0; + dash_memory = 0; +} + + +static inline void set_keyer_out(int state) { +// if (keyer_out != state) { + keyer_out = state; +// write(1, &buf[state], 1); // write output to stdout for Pi-HFIQ + +// if (state) +// beep_mute = 0; +// else +// beep_mute = 1; +// } +} + +static void* keyer_thread(void *arg) { + 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; + } + + if (*kdash) { // set dash memory + if (cw_keyer_mode == KEYER_ULTIMATIC) { + if (last_pressed == DASH) + dash_memory = 1; + } else + 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; + } + + if (*kdot) { // set dot memory + if (cw_keyer_mode == KEYER_ULTIMATIC) { + if (last_pressed == DOT) + dot_memory = 1; + } else + 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 + if (cw_keyer_mode == KEYER_ULTIMATIC) { + if (last_pressed == DASH) + dash_memory = 1; + } else + 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 + if (cw_keyer_mode == KEYER_ULTIMATIC) { + if (last_pressed == DOT) + dot_memory = 1; + } else + 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 ((cw_keyer_mode == KEYER_ULTIMATIC) && (last_pressed == DASH)) + key_state = PREDASH; + else 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 ((cw_keyer_mode == KEYER_ULTIMATIC) && (last_pressed == DOT)) + key_state = PREDOT; + else 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); + } + } +} + +int open_key_gpiokeyer(const char * name) +{ + int i; + + if (wiringPiSetupGpio () < 0) { + fprintf(stderr, "Unable to setup wiringPi: %s\n", strerror (errno)); + return -1; + } + + pinMode(RIGHT_PADDLE_GPIO, INPUT); + pullUpDnControl(RIGHT_PADDLE_GPIO, PUD_UP); + usleep(100000); + wiringPiISR(RIGHT_PADDLE_GPIO, INT_EDGE_BOTH, keyer_event_right); + + pinMode(LEFT_PADDLE_GPIO, INPUT); + pullUpDnControl(LEFT_PADDLE_GPIO, PUD_UP); + usleep(100000); + wiringPiISR(LEFT_PADDLE_GPIO, INT_EDGE_BOTH, keyer_event_left); + + pinMode(KEYER_OUT_GPIO, OUTPUT); + digitalWrite(KEYER_OUT_GPIO, 0); + + keyer_update(); + + i = sem_init(&cw_event, 0, 0); + running = 1; + i |= pthread_create(&keyer_thread_id, NULL, keyer_thread, NULL); + if(i < 0) { + fprintf(stderr,"pthread_create for keyer_thread failed %d\n", i); + return -1; + } + + return 0; +} + +void close_key_gpiokeyer(void) +{ + running = 0; + sem_post(&cw_event); + pthread_join(keyer_thread_id, 0); + sem_destroy(&cw_event); +} + +int is_key_down_gpiokeyer(void) +{ + static int retval; +// sem_wait(&cw_event); + retval = keyer_out; +// sem_post(&cw_event); + return retval; +} + +#endif diff --git a/is_key_down.c b/is_key_down.c index f8aae29..df6c8a3 100755 --- a/is_key_down.c +++ b/is_key_down.c @@ -59,12 +59,20 @@ static void close_key_enet(void); 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) +int open_key_gpiokeyer(const char * name); +void close_key_gpiokeyer(void); +int is_key_down_gpiokeyer(void); +#endif static enum { // The key access method None, // Return the internal state; default key is always up ParPort, // Use the parallel port SerPort, // Use the serial port Udp // Use UDP Ethernet +#if defined(ENABLE_GPIO_KEYER) + , GpioKeyer // Use Raspberry Pi GPIO keyer based on N1GP code +#endif } key_method = None; static int fd = -1; // File descriptor to read the parallel or serial port @@ -91,6 +99,12 @@ int quisk_open_key(const char * name) key_method = Udp; ret = open_key_enet(name); } +#if defined(ENABLE_GPIO_KEYER) + else if (!strncmp(name, "GPIO", 4)){ // Raspberry Pi GPIO keyer + key_method = GpioKeyer; + ret = open_key_gpiokeyer(name); + } +#endif else { ret = 5; } @@ -111,6 +125,11 @@ void quisk_close_key(void) case Udp: close_key_enet(); break; +#if defined(ENABLE_GPIO_KEYER) + case GpioKeyer: + close_key_gpiokeyer(); + break; +#endif } return; } @@ -126,6 +145,10 @@ int quisk_is_key_down(void) return is_key_down_pport(); case Udp: return is_key_down_enet(); +#if defined(ENABLE_GPIO_KEYER) + case GpioKeyer: + return is_key_down_gpiokeyer(); +#endif } return 0; } @@ -320,4 +343,5 @@ static int is_key_down_serport(void) return 0; } } + #endif diff --git a/quisk.h b/quisk.h index 9f4dd99..ae9eead 100755 --- a/quisk.h +++ b/quisk.h @@ -1,6 +1,7 @@ #define DEBUG_IO 0 #define DEBUG_MIC 0 +#define ENABLE_GPIO_KEYER 1 // Sound parameters // diff --git a/setup.py b/setup.py index 1df95e5..5680dd8 100755 --- a/setup.py +++ b/setup.py @@ -31,10 +31,10 @@ if sys.platform != "win32": print ("please install the package libpulse-dev") module1 = Extension ('quisk._quisk', - libraries = ['asound', 'portaudio', 'pulse', 'fftw3', 'm'], + 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'], + 'filter.c', 'extdemod.c', 'freedv.c', 'gpiokeyer.c'], ) module2 = Extension ('quisk.sdriqpkg.sdriq',