diff --git a/gpiokeyer.c b/gpiokeyer.c index 627a2f2..a48349d 100644 --- a/gpiokeyer.c +++ b/gpiokeyer.c @@ -1,4 +1,3 @@ -//#if defined(ENABLE_GPIO_KEYER) /*********************************************************************** * gpiokeyer.c * by Robert A. French (KC4UPR) @@ -89,12 +88,7 @@ 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 +//#define DEBUG #include #include @@ -112,27 +106,31 @@ Boston, MA 02110-1301, USA. #include #include -static void* keyer_thread(void *arg); -static pthread_t keyer_thread_id; - -// GPIO pins -//#define LEFT_PADDLE_GPIO 22 -//#define RIGHT_PADDLE_GPIO 27 -//#define KEY_OUT_GPIO 23 -//#define PTT_OUT_GPIO 24 -static int left_paddle_gpio = 22; -static int right_paddle_gpio = 27; -static int keyer_out_gpio = 23; -static int tr_switch_gpio = 24; - -// Keyer modes -#define KEYER_STRAIGHT 0 -#define KEYER_MODE_A 1 -#define KEYER_MODE_B 2 -#define NUM_KEYER_MODES 3 +/*********************************************************************** + * Constants + **********************************************************************/ #define NSEC_PER_SEC (1000000000) +/*********************************************************************** + * Types + **********************************************************************/ + +/* KC4UPR: I added an enumeration for the keyer modes, instead of a + * macro. Probably superfluous. + */ +enum { + KEYER_STRAIGHT = 0, + KEYER_MODE_A, + KEYER_MODE_B, + NUM_KEYER_MODES +}; + +/* KC4UPR: Enumeration of the states in the iambic keyer state machine. + * I added the HANGTIME state, which is used to differentiate between + * the end of the last CW symbol, and the end of the transmission delay + * for QSK/semi-QSK operations. + */ enum { CHECK = 0, PREDOT, @@ -148,44 +146,81 @@ enum { EXITLOOP }; +/*********************************************************************** + * Function Prototypes + * + * These are only prototypes for the internal functions... the functions + * that are callable from quisk.c and/or Python are all declared in + * quisk.h, and then defined at the end of this file. + **********************************************************************/ + +static void keyer_update(void); +static void keyer_event(int, int, uint32_t); +static void keyer_event_left(void); +static void keyer_event_right(void); +static void clear_memory(void); +static void set_keyer_out(int); +static void* keyer_thread(void *); + +/*********************************************************************** + * Variables + **********************************************************************/ + +/* GPIO Pins + * + * KC4UPR: Converted the GPIO pin definitions from macros to variables, + * so that we can set them dynamically at runtime. + */ +static int left_paddle_gpio = 22; +static int right_paddle_gpio = 27; +static int keyer_out_gpio = 23; +static int tr_switch_gpio = 24; + +/* Keyer Configuration + */ +static int cw_keyer_mode = KEYER_MODE_A; +static int cw_keyer_speed = 20; +static int cw_keyer_weight = 55; +static int cw_keys_reversed = 0; +static int cw_keyer_spacing = 0; +static int cw_keyer_enabled = 1; +static int cw_hangtime_msec = 250; // not currently (re-)configurable +static int cw_active_state = 0; // not currently (re-)configurable + +/* Current Paddle State + */ +static int kcwl = 0; // left paddle +static int kcwr = 0; // right paddle +static int *kdot; // dots - point to either left or right +static int *kdash; // dashes - point to either right or left + +/* Internal Keyer State + */ 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_A; -static int cw_keyer_spacing = 0; -static int cw_active_state = 0; +static int running, keyer_out = 0; + +/* Thread Variables + */ +static pthread_t keyer_thread_id; static sem_t cw_event; #if defined(DEBUG) static int cw_event_value; #endif -static int cw_hangtime_msec = 250; -static int running, keyer_out = 0; -static int cw_keyer_enabled = 1; +/*********************************************************************** + * Internal functions + **********************************************************************/ -// Function prototypes - -void keyer_update(void); -void keyer_event(int, int, uint32_t); -void keyer_event_left(void); -void keyer_event_right(void); -void clear_memory(void); -static void set_keyer_out(int); -static void* keyer_thread(void*); - -///// - -void keyer_update() { +/* KC4UPR: Update the keyer, i.e. calculate its internal values based + * on current settings. This is called once at keyer open, and then + * again any time certain settings are updated. + */ +static 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; @@ -198,11 +233,19 @@ void keyer_update() { kdash = &kcwr; } - // need to actually dynamically set this at some point... + /* NOTE: need to actually dynamically set this at some point... + * Currently, based on the code below, there is a hardcoded 250ms + * semi-QSK delay. ~250ms after the end of the last symbol, the + * T/R switch will be disabled. + */ cw_hangtime_msec = (int)(0.25f * 1000.0f); } -void keyer_event(int gpio, int level, uint32_t tick) { +/* KC4UPR: The "actual" event handler. Updates the value of the + * currently depressed/released paddle. + */ +static void keyer_event(int gpio, int level, uint32_t tick) { + // cw_active_state is for the logic level (active high vs low). int state = (cw_active_state == 0) ? (level == 0) : (level != 0); if (gpio == left_paddle_gpio) @@ -210,46 +253,89 @@ void keyer_event(int gpio, int level, uint32_t tick) { else // RIGHT_PADDLE_GPIO kcwr = state; - if (state) // || cw_keyer_mode == KEYER_STRAIGHT) + /* Post (increment) the semaphore. Why? Because the keyer thread + * may be waiting on it, so this lets the keyer thread begin running + * the state machine if it was currently between sequences. + */ + if (state) sem_post(&cw_event); } -// Added to support WiringPi, which uses a different type of callback. -void keyer_event_left() +/* KC4UPR: Helper function added to support WiringPi instead of PiGPIO. + * PiGPIO supports using the same callback for multiple pins, because + * the pin is one of the parameters to the interrupt handler. However, + * I needed to use WiringPi instead of PiGPIO due to PiGPIO not working + * with my soundcard "hat" for the Raspberry Pi. + */ +static void keyer_event_left() { int level = digitalRead(left_paddle_gpio); keyer_event(left_paddle_gpio, level, 0); #if defined(DEBUG) + /* KC4UPR: I added current value of the semaphore because I was + * curious as to how many times this ISR is getting called. For + * whatever reason, despite my debounce circuit on this pin, + * this is getting called a lot... + */ sem_getvalue(&cw_event, &cw_event_value); - fprintf(stdout, "left paddle pressed - %d\n", cw_event_value); + fprintf(stdout, "[GPIO Keyer] left paddle pressed; semaphore value: %d\n", cw_event_value); #endif } -// Added to support WiringPi, which uses a different type of callback. -void keyer_event_right() +/* KC4UPR: Helper function added to support WiringPi instead of PiGPIO. + * PiGPIO supports using the same callback for multiple pins, because + * the pin is one of the parameters to the interrupt handler. However, + * I needed to use WiringPi instead of PiGPIO due to PiGPIO not working + * with my soundcard "hat" for the Raspberry Pi. + */ +static void keyer_event_right() { int level = digitalRead(right_paddle_gpio); keyer_event(right_paddle_gpio, level, 0); #if defined(DEBUG) + /* KC4UPR: I added current value of the semaphore because I was + * curious as to how many times this ISR is getting called. For + * whatever reason, despite my debounce circuit on this pin, + * this is getting called a lot... + */ sem_getvalue(&cw_event, &cw_event_value); - fprintf(stdout, "right paddle pressed - %d\n", cw_event_value); + fprintf(stdout, "[GPIO Keyer] right paddle pressed; semaphore value: %d\n", cw_event_value); #endif } -void clear_memory() { +/* KC4UPR: Clear the dot and dash memory of the iambic keyer. + */ +static void clear_memory() { dot_memory = 0; dash_memory = 0; } - +/* KC4UPR: Set the keyer output (which will be read by the function + * is_key_down_gpiokeyer(), when called by Quisk). Also writes the + * digital output GPIO pins, if they are being used. + */ static void set_keyer_out(int state) { - if (state && tr_switch_gpio) { + /* First, write to the hardware T/R switch, if we are using it, and + * if the specific keyer state is key_down. A logical AND is used + * here instead of bitwise, because tr_switch_gpio could have + * multiple valid values. + */ + if (state && tr_switch_gpio) digitalWrite(tr_switch_gpio, 1 & cw_keyer_enabled); - } + + /* Set the key state. This is what will be read by Quisk. + */ keyer_out = state; - digitalWrite(keyer_out_gpio, keyer_out & cw_keyer_enabled); + + /* Finally, write to the hardware CW key line, if it was specified. + */ + if (keyer_out_gpio) + digitalWrite(keyer_out_gpio, keyer_out & cw_keyer_enabled); } +/* KC4UPR: Keyer thread, which processes the key state and moves + * through the state machines. + */ static void* keyer_thread(void *arg) { struct timespec loop_delay; int interval = 1000000; // 1 ms @@ -257,13 +343,22 @@ static void* keyer_thread(void *arg) { while (running) { #if defined(DEBUG) + /* KC4UPR: Writing out the current semaphore value because + * I was curious at the impact of bouncing on how often the + * interrupt handler was getting called. + */ sem_getvalue(&cw_event, &cw_event_value); - fprintf(stdout, "waiting - %d\n", cw_event_value); + fprintf(stdout, "[GPIO Keyer] waiting; semaphore value: %d\n", cw_event_value); #endif sem_wait(&cw_event); key_state = CHECK; while (key_state != EXITLOOP) { + /* Anytime the keyer output is set (i.e. we are transmitting + * a symbol), reset the elapsed hangtime. Once we complete + * sending the current symbol (dit/dah/straight key out), we + * will start the hangtime counter for the T/R switch. + */ if (keyer_out) hangtime_elapsed = 0; @@ -274,13 +369,21 @@ static void* keyer_thread(void *arg) { if (tr_switch_gpio) { #if defined(DEBUG) sem_getvalue(&cw_event, &cw_event_value); - fprintf(stdout, "hangtime complete, %d msec - %d\n", cw_hangtime_msec, cw_event_value); + fprintf(stdout, "[GPIO Keyer] hangtime complete, %d msec; semaphore value: %d\n", cw_hangtime_msec, cw_event_value); #endif digitalWrite(tr_switch_gpio, 0); key_state = EXITLOOP; } } - // INTENTIONALLY FALLING THROUGH TO 'CHECK' STATE (no 'break') + /* KC4UPR: The HANGTIME state is intentionally first in + * the switch() statement, and it intentionally does NOT + * have a break at the end of it. I want it to fall- + * through into the CHECK state logic, so it can look to + * see if there are any switch actuations, which would + * then interrupt the HANGTIME state and start the state + * machine over. This is probably terrible coding + * practice... + */ case CHECK: // check for key press if (cw_keyer_mode == KEYER_STRAIGHT) { // Straight/External key or bug @@ -296,8 +399,6 @@ static void* keyer_thread(void *arg) { if (keyer_out) set_keyer_out(0); } else { - // NOTE: not working right - //set_keyer_out(0); key_state = EXITLOOP; } } @@ -307,8 +408,6 @@ static void* keyer_thread(void *arg) { else if (*kdash) key_state = PREDASH; else if (key_state != HANGTIME) { - // Do we really need to do this? Aren't these covered in other states? - //set_keyer_out(0); key_state = EXITLOOP; } } @@ -449,6 +548,11 @@ static void* keyer_thread(void *arg) { key_state = EXITLOOP; } + /* If the key out state is 0 (not sending), increment the + * hangtime elapsed timer. This will continue incrementing + * while we are in the HANGTIME state, until either the + * timer completes, or keyer out goes to 1. + */ if (!keyer_out) hangtime_elapsed++; @@ -471,41 +575,87 @@ static void* keyer_thread(void *arg) { return arg; // did this to ditch compiler warnings... bad idea? } +/*********************************************************************** + * is_key_down.c functions + * + * These functions are called from is_key_down.c, and implement the + * method-specific open/close/set functions for the keyer. + **********************************************************************/ + +/* KC4UPR: Open the GPIO keyer. Its 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) + * NOTE: The pin numbers to be used are GPIO pin numbers, NOT WiringPi + * pin numbers even though WiringPi is used!!! + */ int open_key_gpiokeyer(const char * name) { int i; + /* KC4UPR: Read the GPIO keyer name, and parse out the pins for + * left paddle, right paddle, keyer out, and T/R switch. The last + * two can be zero if that functionality is not desired, but they + * must be present in the name string. + */ if (sscanf(name, "gpio:%d,%d,%d,%d", &left_paddle_gpio, &right_paddle_gpio, &keyer_out_gpio, &tr_switch_gpio) < 4) { - fprintf(stderr, "Insufficient parameters for GPIO Keyer: %s\n", name); + fprintf(stderr, "[GPIO Keyer] insufficient parameters: %s\n", name); return -1; } #if defined(DEBUG) - fprintf(stdout, "GPIO Keyer selected:\n - left paddle GPIO: %d\n - right paddle GPIO: %d\n - keyer out GPIO: %d\n - T/R switch GPIO: %d\n", + fprintf(stdout, "[GPIO Keyer] configuration:\n - left paddle GPIO: %d\n - right paddle GPIO: %d\n - keyer out GPIO: %d\n - T/R switch GPIO: %d\n", left_paddle_gpio, right_paddle_gpio, keyer_out_gpio, tr_switch_gpio); #endif - + /* KC4UPR: Setup WiringPi. Note that I have specified using the + * GPIO pin names rather than the WiringPi pin names. + */ if (wiringPiSetupGpio () < 0) { - fprintf(stderr, "Unable to setup wiringPi: %s\n", strerror (errno)); + fprintf(stderr, "[GPIO Keyer] unable to setup wiringPi: %s\n", strerror(errno)); return -1; } + /* KC4UPR: Setup the right paddle pin for input. An interrupt will + * be triggered on both key down and key up. + */ pinMode(right_paddle_gpio, INPUT); pullUpDnControl(right_paddle_gpio, PUD_UP); usleep(100000); wiringPiISR(right_paddle_gpio, INT_EDGE_BOTH, keyer_event_right); + /* KC4UPR: Setup the left paddle pin for input. An interrupt will + * be triggered on both key down and key up. + */ pinMode(left_paddle_gpio, INPUT); pullUpDnControl(left_paddle_gpio, PUD_UP); usleep(100000); wiringPiISR(left_paddle_gpio, INT_EDGE_BOTH, keyer_event_left); + /* KC4UPR: Setup the keyer output pin as an output pin, IF the + * value is not 0. If the pin value is 0, then this output is + * disabled. + */ if (keyer_out_gpio) { pinMode(keyer_out_gpio, OUTPUT); digitalWrite(keyer_out_gpio, 0); } - + + /* KC4UPR: Setup the T/R switch pin as an output pin, IF the + * value is not 0. If the pin value is 0, then this output is + * disabled. + */ if (tr_switch_gpio) { pinMode(tr_switch_gpio, OUTPUT); digitalWrite(tr_switch_gpio, 0); @@ -517,17 +667,26 @@ int open_key_gpiokeyer(const char * name) 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); + fprintf(stderr,"[GPIO Keyer] pthread_create for keyer_thread failed %d\n", i); return -1; } return 0; } +/* KC4UPR: Close the GPIO keyer. This sets the running flag for the + * keyer thread to false, posts the semaphore (to ensure the keyer + * thread gets off of a semaphore wait), and then joins the thread to + * close it. + * + * NOTE: If Quisk is not closed after closing the GPIO keyer, the + * interrupt handlers for the GPIO pins remain present. I think I can + * possibly fix this by assigning a NULL ISR??? + */ void close_key_gpiokeyer(void) { #if defined(DEBUG) - fprintf(stdout, "[GPIO Keyer] closing key\n"); + fprintf(stdout, "[GPIO Keyer] closing keyer\n"); #endif running = 0; sem_post(&cw_event); @@ -538,6 +697,9 @@ void close_key_gpiokeyer(void) #endif } +/* KC4UPR: Return the state of the keyer. This is a logic AND of the + * current keyer_out state, and the cw_keyer_enabled signal. + */ int is_key_down_gpiokeyer(void) { static int retval; @@ -545,48 +707,94 @@ int is_key_down_gpiokeyer(void) return retval; } +/*********************************************************************** + * Externally callable functions + * + * These functions are callable from Quisk proper, as well as from any + * Quisk Python modules. They are declared in quisk.h, and are used to + * (re-)configure various keyer parameters. + **********************************************************************/ + +/* KC4UPR: Set the keyer mode. Keyer modes are defined as an + * enumeration in this file. Current enums are: + * KEYER_STRAIGHT = 0 + * KEYER_MODE_A = 1 + * KEYER_MODE_B = 2 + * If the requested mode is out of bounds, nothing happens... + */ void quisk_set_gpio_keyer_mode(int mode) { #if defined(DEBUG) - fprintf(stdout, "MODE CHANGE\n"); + fprintf(stdout, "[GPIO Keyer] mode change: %d - %s\n", mode, + ((mode>-1)&&(mode -1) && (mode < NUM_KEYER_MODES)) cw_keyer_mode = mode; } +/* KC4UPR: Set the keyer speed in WPM. Valid from 1-60 WPM. + */ void quisk_set_gpio_keyer_speed(int wpm) { #if defined(DEBUG) - fprintf(stdout, "SPEED CHANGE\n"); + fprintf(stdout, "[GPIO Keyer] speed change: %d - %s\n", wpm, + ((wpm>0)&&(wpm<61))?"success":"failed "); #endif - cw_keyer_speed = wpm; - keyer_update(); + if ((wpm > 0) && (wpm < 61)) { + cw_keyer_speed = wpm; + keyer_update(); + } } +/* KC4UPR: Set the keyer weight in percent (?). Valid from 33-66. + */ void quisk_set_gpio_keyer_weight(int weight) { - if ((weight > 32) && (weight < 67)) + #if defined(DEBUG) + fprintf(stdout, "[GPIO Keyer] weight change: %d - %s\n", weight, + ((weight>32)&&(weight<67))?"success":"failed "); + #endif + if ((weight > 32) && (weight < 67)) { cw_keyer_weight = weight; - keyer_update(); + keyer_update(); + } } +/* KC4UPR: Reverse the paddles. Default: left = dit, right = dah. + */ void quisk_set_gpio_keyer_reversed(int flag) { + #if defined(DEBUG) + fprintf(stdout, "[GPIO Keyer] reverse paddles: %s\n", + flag==0?"false":"true "); + #endif cw_keys_reversed = (flag == 0 ? 0 : 1); keyer_update(); } +/* KC4UPR: Set strict character spacing. Default: off. + */ void quisk_set_gpio_keyer_strict(int flag) { + #if defined(DEBUG) + fprintf(stdout, "[GPIO Keyer] strict spacing: %s\n", + flag==0?"false":"true "); + #endif cw_keyer_spacing = (flag == 0 ? 0 : 1); } +/* KC4UPR: Enabled/disable the keyer. If true, then regardless of + * state, nothing will be output. + */ void quisk_set_gpio_keyer_enabled(int flag) { + #if defined(DEBUG) + fprintf(stdout, "[GPIO Keyer] enabled: %s\n", + flag==0?"false":"true "); + #endif cw_keyer_enabled = (flag == 0 ? 0 : 1); } /*********************************************************************** * EOF **********************************************************************/ -//#endif diff --git a/is_key_down.c b/is_key_down.c index 5bce752..c94fb2d 100755 --- a/is_key_down.c +++ b/is_key_down.c @@ -109,7 +109,7 @@ int quisk_open_key(const char * 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: + * it. Its 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 @@ -123,6 +123,8 @@ int quisk_open_key(const char * name) * short, configurable delay after the last time * the keyer output was set) * (set to zero to not use this) + * NOTE: The pin numbers to be used are GPIO pin numbers, NOT + * WiringPi pin numbers even though WiringPi is used!!! */ else if (!strncmp(name, "gpio", 4)){ // Raspberry Pi GPIO keyer key_method = GpioKeyer; diff --git a/quisk.h b/quisk.h index 809c531..cdc5b2a 100755 --- a/quisk.h +++ b/quisk.h @@ -1,7 +1,6 @@ #define DEBUG_IO 0 #define DEBUG_MIC 0 -#define ENABLE_GPIO_KEYER 1 // Sound parameters //