diff --git a/src/main/application/debug_port.c b/src/main/application/debug_port.c index 86755d9..6cdbd8f 100644 --- a/src/main/application/debug_port.c +++ b/src/main/application/debug_port.c @@ -38,7 +38,7 @@ * @def RX_BUF_SIZE * @brief Size of the command receive buffer, in bytes. */ -#define RX_BUF_SIZE 64 +#define RX_BUF_SIZE 256 /** * @def CMD_STR_BUZZER @@ -147,6 +147,8 @@ static char s_rx_buf[ RX_BUF_SIZE ]; static size_t s_rx_count = 0; +bool s_immediate_autokey = false; + /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ /** @@ -236,6 +238,12 @@ static void exec_command_version( char const * const command ); */ static void exec_command_wpm( char const * const command ); +/** + * @fn exec_immediate_autokey_mode( void ) + * @brief Executes the special "immediate autokey" mode. + */ +static void exec_immediate_autokey_mode( void ); + /** * @fn print_invalid_command( char const * const ) * @brief Sends an "Invalid command" message for the specified command. @@ -330,6 +338,13 @@ void debug_port_usart_rx( void ) static void evaluate_rx_buf( void ) { + // Are we in immediate autokey mode? + if( s_immediate_autokey ) + { + exec_immediate_autokey_mode(); + return; + } + // If we receive the terminating character... if( s_rx_buf[ s_rx_count - 1 ] == TERMINATOR_CHAR ) { @@ -694,6 +709,20 @@ static void exec_command_keyer( char const * const command ) { // No subcommand - interpret as a status request. no action required } + else if( string_equals( command, CMD_STR_KEYER " immediate" ) ) + { + // Enter immediate autokey mode + s_immediate_autokey = true; + debug_port_print( CMD_STR_KEYER ": Now in immediate autokey mode. Send null character to exit." NEWLINE_STR ); + return; + } + else if( string_begins_with( command, CMD_STR_KEYER " key " ) ) + { + // Add remaining string to autokey buffer + size_t count = keyer_autokey_str( command + 10 ); + debug_port_printf( CMD_STR_KEYER ": \"%s\" (%u chars queued)" NEWLINE_STR, command + 10, count ); + return; + } else if( string_equals( command, CMD_STR_KEYER " output_active_low " ENABLE_STR ) ) { // Set output to active low @@ -898,6 +927,30 @@ static void exec_command_wpm( char const * const command ) } /* exec_command_wpm() */ +static void exec_immediate_autokey_mode( void ) +{ + // Loop through each available character + for( size_t idx = 0; idx < s_rx_count; idx++ ) + { + // If we receive the null character, exit autokey mode + if( s_rx_buf[ idx ] == 0 ) + { + debug_port_print( NEWLINE_STR CMD_STR_KEYER ": Exited immediate autokey mode." NEWLINE_STR ); + s_immediate_autokey = false; + keyer_panic(); + break; + } + + // Otherwise, queue the character + keyer_autokey_char( ( char )s_rx_buf[ idx ] ); + } + + // All characters were consumed + s_rx_count = 0; + +} /* exec_immediate_autokey_mode() */ + + static void print_invalid_command( char const * const command ) { debug_port_print( INVALID_COMMAND_STR "\"" ); diff --git a/src/main/application/keyer.c b/src/main/application/keyer.c index f7b6d76..f94d157 100644 --- a/src/main/application/keyer.c +++ b/src/main/application/keyer.c @@ -38,6 +38,8 @@ enum ELEMENT_UNKNOWN, /**< Element type is unknown (manual). */ ELEMENT_DOT, /**< Element is a dot. */ ELEMENT_DASH, /**< Element is a dash. */ + ELEMENT_LETTER_SPACE, /**< Element is a letter space. */ + ELEMENT_WORD_SPACE, /**< Element is a word space. */ ELEMENT_COUNT, /**< Number of valid elements. */ @@ -57,6 +59,7 @@ enum STATE_DOTS, /**< Keyer emits continuous dots. */ STATE_DASHES, /**< Keyer emits continuous dashes. */ STATE_INTERLEAVED, /**< Keyer emits continuous dots and dashes.*/ + STATE_AUTOKEY, /**< Keyer automatically keys characters. */ STATE_COUNT, /**< Number of valid states. */ }; @@ -69,7 +72,19 @@ enum */ #define NO_TIMEOUT 0 -static gpio_pin_t const KEYER_OUT_PIN = GPIO_PIN_A6; +/** + * @def AUTOKEY_BUF_SZ + * @brief Maximum number of elements queued in autokey buffer. + * @note This 4-kilobyte buffer takes up 1/4th of our RAM space. This seems justified since autokey is such a core + * part of the device's functionality. This could be reduced if required. + */ +#define AUTOKEY_BUF_SZ 4096 + +/** + * @def KEYER_OUT_PIN + * @brief The GPIO pin which is used for the keyer output. + */ +#define KEYER_OUT_PIN GPIO_PIN_A6 /* --------------------------------------------------- VARIABLES ---------------------------------------------------- */ @@ -77,41 +92,80 @@ static bool s_keyed = false; /**< Is the keyer hardware currently static bool s_panicked = false; /**< Was the keyer panic activated? */ static state_t s_state = STATE_OFF; /**< Currently active keyer state. */ -static element_t s_element = ELEMENT_NONE; /**< Element which started current lockout. */ -static tick_t s_element_start = 0; /**< Tick at which current element started. */ -static tick_t s_element_duration = NO_TIMEOUT;/**< Duration of current element. */ -static tick_t s_element_lockout = NO_TIMEOUT;/**< Lockout until next element. */ + +static element_t s_el = ELEMENT_NONE; /** The element currently being keyed. */ +static element_t s_lockout_el = ELEMENT_NONE;/**< Element which started lockout. */ +static tick_t s_el_stop_tick = 0; /** Tick at which current element must stop.*/ +static bool s_el_stop_tick_vld = false; /** Is `s_el_stop_tick` valid? */ +static tick_t s_el_start_tick = 0; /** Tick at which next element may start. */ +static bool s_el_start_tick_vld = false; /** Is `s_el_start_tick` valid? */ + +static element_t s_autokey_buf[ AUTOKEY_BUF_SZ ];/**< El buffer for autokey mode. */ +static size_t s_autokey_head = 0; /**< Head of autokey circular buffer. */ +static size_t s_autokey_tail = 0; /**< Tail of autokey circular buffer. */ static wpm_t s_ticks_wpm = 0; /**< WPM at which ticks were evaluated. */ static tick_t s_dot_ticks = 0; /**< Number of ticks per dot. */ static tick_t s_dash_ticks = 0; /**< Number of ticks per dash. */ static tick_t s_space_ticks = 0; /**< Number of ticks per element space. */ +static tick_t s_letter_space_ticks = 0; /**< Number of ticks per letter space. */ +static tick_t s_word_space_ticks = 0; /**< Number of ticks per word space. */ /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ /** - * @def is_timeout_elapsed( _tick, _duration ) - * @brief Returns `true` if the specified timeout has elapsed at the specified tick. + * @def is_tick_passed( _tick, _timeout ) + * @brief Returns `true` if `_tick` represents a tick greater than or equal to `_timeout`. */ -#define is_timeout_elapsed( _tick, _duration ) \ - ( sys_elapsed( ( _tick ), s_element_start ) > ( _duration ) ) +#define is_tick_passed( _tick, _timeout ) \ + ( sys_is_tick_gte( _tick, _timeout ) ) /** - * @def is_duration_elapsed( _tick ) - * @brief Returns `true` if the `s_element_duration` timeout has elapsed at the specified tick. + * @def is_stop_tick_passed( _tick ) + * @brief Returns `true` if `_tick` has passed `s_el_stop_tick`, or there is no valid stop tick. */ -#define is_duration_elapsed( _tick ) \ - is_timeout_elapsed( _tick, s_element_duration ) +#define is_stop_tick_passed( _tick ) \ + ( ! s_el_stop_tick_vld || is_tick_passed( _tick, s_el_stop_tick ) ) /** - * @def is_lockout_elapsed( _tick ) - * @brief Returns `true` if the `s_element_lockout` timeout has elapsed at the specified tick. + * @def is_start_tick_passed( _tick ) + * @brief Returns `true` if `_tick` has passed `s_el_start_tick`, or there is no valid start tick. */ -#define is_lockout_elapsed( _tick ) \ - is_timeout_elapsed( _tick, s_element_lockout ) +#define is_start_tick_passed( _tick ) \ + ( ! s_el_start_tick_vld || is_tick_passed( _tick, s_el_start_tick ) ) /* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ +/** + * @fn autokey_avail( void ) + * @brief Returns the number of available slots in the autokey circular buffer. + */ +static size_t autokey_avail( void ); + +/** + * @fn autokey_count( void ) + * @brief Returns the number of items in the autokey circular buffer. + */ +static size_t autokey_count( void ); + +/** + * @fn autokey_dequeue( element_t * ) + * @brief Dequeues an element from the autokey buffer. + */ +static bool autokey_dequeue( element_t * el ); + +/** + * @fn autokey_enqueue( void ) + * @brief Enqueues the specified element in the autokey buffer. + */ +static bool autokey_enqueue( element_t el ); + +/** + * @fn do_state_autokey( tick_t, bool ) + * @brief Executes the `STATE_AUTOKEY` state. + */ +static void do_state_autokey( tick_t tick, bool new_state ); + /** * @fn do_state_dashes( tick_t, bool ) * @brief Executes the `STATE_DASHES` state. @@ -142,6 +196,18 @@ static void do_state_off( tick_t tick, bool new_state ); */ static void do_state_on( tick_t tick, bool new_state ); +/** + * @fn element_duration( element_t ) + * @brief Returns the duration (in ticks) for the specified element. + */ +static tick_t element_duration( element_t el ); + +/** + * @fn element_is_keyed( element_t ) + * @brief Returns `true` if the specified element requires activating the key. + */ +static bool element_is_keyed( element_t el ); + /** * @fn get_keyed( void ) * @brief Returns `true` if the keyer hardware is keying. @@ -175,6 +241,439 @@ static void update_ticks( void ); /* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ +bool keyer_autokey_char( char c ) +{ + switch( c ) + { + case ' ': + // Space + return( autokey_enqueue( ELEMENT_WORD_SPACE ) ); + + case 'a': + case 'A': + // Letter A + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'b': + case 'B': + // Letter B + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'c': + case 'C': + // Letter C + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'd': + case 'D': + // Letter D + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'e': + case 'E': + // Letter E + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'f': + case 'F': + // Letter F + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'g': + case 'G': + // Letter G + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'h': + case 'H': + // Letter H + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'i': + case 'I': + // Letter I + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'j': + case 'J': + // Letter J + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'k': + case 'K': + // Letter K + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'l': + case 'L': + // Letter L + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'm': + case 'M': + // Letter M + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'n': + case 'N': + // Letter N + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'o': + case 'O': + // Letter O + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'p': + case 'P': + // Letter P + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'q': + case 'Q': + // Letter Q + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'r': + case 'R': + // Letter R + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 's': + case 'S': + // Letter S + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 't': + case 'T': + // Letter T + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'u': + case 'U': + // Letter U + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'v': + case 'V': + // Letter V + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'w': + case 'W': + // Letter W + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'x': + case 'X': + // Letter X + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'y': + case 'Y': + // Letter Y + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case 'z': + case 'Z': + // Letter Z + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '0': + // Number 0 + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '1': + // Number 1 + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '2': + // Number 2 + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '3': + // Number 3 + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '4': + // Number 4 + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '5': + // Number 5 + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '6': + // Number 6 + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '7': + // Number 7 + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '8': + // Number 8 + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '9': + // Number 9 + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '.': + // Period + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case ',': + // Comma + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '?': + // Question mark + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '\'': + // Single quote + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '!': + // Exclamation mark + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) ); + + case '-': + // Dash + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) ); + + case '/': + // Slash + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '=': + // Equals sign + return( autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '+': + // Plus sign + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '"': + // Double quote + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + case '_': + // Underscore + return( autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_DOT ) && + autokey_enqueue( ELEMENT_DASH ) && + autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + + default: + return( false ); + } + +} /* keyer_autokey_char() */ + + +size_t keyer_autokey_str( char const * str ) +{ + size_t count = 0; + while( * str ) + if( keyer_autokey_char( *( str++ ) ) ) + count++; + + return( count ); + +} /* keyer_autokey_str() */ + bool keyer_get_on( void ) { return( get_keyed() ); @@ -209,10 +708,12 @@ void keyer_init( void ) s_keyed = false; s_panicked = false; s_state = STATE_OFF; - s_element = ELEMENT_NONE; - s_element_start = 0; - s_element_duration = NO_TIMEOUT; - s_element_lockout = NO_TIMEOUT; + s_el = ELEMENT_NONE; + s_lockout_el = ELEMENT_NONE; + s_el_stop_tick = 0; + s_el_stop_tick_vld = false; + s_el_start_tick = 0; + s_el_start_tick_vld = false; s_ticks_wpm = 0; s_dot_ticks = 0; s_dash_ticks = 0; @@ -230,6 +731,7 @@ void keyer_init( void ) void keyer_panic( void ) { s_panicked = true; + s_autokey_tail = s_autokey_head; set_keyed( false ); } /* keyer_panic() */ @@ -298,25 +800,97 @@ void keyer_tick( tick_t tick ) case STATE_INTERLEAVED: do_state_interleaved( tick, new_state ); break; + case STATE_AUTOKEY: + do_state_autokey( tick, new_state ); + break; } } /* keyer_tick() */ +static size_t autokey_avail( void ) +{ + return( AUTOKEY_BUF_SZ - autokey_count() - 1 ); + +} /* autokey_avail() */ + + +static size_t autokey_count( void ) +{ + if( s_autokey_head >= s_autokey_tail ) + return( s_autokey_head - s_autokey_tail ); + else + return( AUTOKEY_BUF_SZ - s_autokey_tail + s_autokey_head ); + +} /* autokey_count() */ + + +static bool autokey_dequeue( element_t * el ) +{ + if( autokey_count() == 0 ) + return( false ); + + * el = s_autokey_buf[ s_autokey_tail ]; + increment_rollover( s_autokey_tail, AUTOKEY_BUF_SZ ); + return( true ); + +} /* autokey_dequeue() */ + + +static bool autokey_enqueue( element_t el ) +{ + if( autokey_avail() == 0 ) + return( false ); + + s_autokey_buf[ s_autokey_head ] = el; + increment_rollover( s_autokey_head, AUTOKEY_BUF_SZ ); + return( true ); + +} /* autokey_enqueue() */ + + +static void do_state_autokey( tick_t tick, bool new_state ) +{ + ( void )new_state; + + element_t el; + if( ! s_panicked && is_start_tick_passed( tick ) && autokey_dequeue( & el ) ) + { + // Activate keyer hardware (if required) + s_el = el; + tick_t el_dur = element_duration( s_el ); + s_el_stop_tick = tick + el_dur; + s_el_stop_tick_vld = true; + s_el_start_tick = tick + el_dur + s_space_ticks; + s_el_start_tick_vld = true; + if( element_is_keyed( s_el ) ) + set_keyed( true ); + } + else if( is_stop_tick_passed( tick ) && get_keyed() ) + { + // Deactivate keyer hardware + set_keyed( false ); + } + +} /* do_state_autokey() */ + + static void do_state_dashes( tick_t tick, bool new_state ) { ( void )new_state; - if( ! s_panicked && is_lockout_elapsed( tick ) && ! get_keyed() ) + if( ! s_panicked && is_start_tick_passed( tick ) && ! get_keyed() ) { // Activate keyer hardware + s_el = ELEMENT_DASH; + s_lockout_el = ELEMENT_DASH; + s_el_stop_tick = tick + s_dash_ticks; + s_el_stop_tick_vld = true; + s_el_start_tick = tick + s_dash_ticks + s_space_ticks; + s_el_start_tick_vld = true; set_keyed( true ); - s_element = ELEMENT_DASH; - s_element_start = tick; - s_element_duration = s_dash_ticks; - s_element_lockout = s_dash_ticks + s_space_ticks; } - else if( is_duration_elapsed( tick ) && get_keyed() ) + else if( is_stop_tick_passed( tick ) && get_keyed() ) { // Deactivate keyer hardware set_keyed( false ); @@ -329,16 +903,18 @@ static void do_state_dots( tick_t tick, bool new_state ) { ( void )new_state; - if( ! s_panicked && is_lockout_elapsed( tick ) && ! get_keyed() ) + if( ! s_panicked && is_start_tick_passed( tick ) && ! get_keyed() ) { // Activate keyer hardware + s_el = ELEMENT_DOT; + s_lockout_el = ELEMENT_DOT; + s_el_stop_tick = tick + s_dot_ticks; + s_el_stop_tick_vld = true; + s_el_start_tick = tick + s_dot_ticks + s_space_ticks; + s_el_start_tick_vld = true; set_keyed( true ); - s_element = ELEMENT_DOT; - s_element_start = tick; - s_element_duration = s_dot_ticks; - s_element_lockout = s_dot_ticks + s_space_ticks; } - else if( is_duration_elapsed( tick ) && get_keyed() ) + else if( is_stop_tick_passed( tick ) && get_keyed() ) { // Deactivate keyer hardware set_keyed( false ); @@ -351,15 +927,19 @@ static void do_state_interleaved( tick_t tick, bool new_state ) { ( void )new_state; - if( ! s_panicked && is_lockout_elapsed( tick ) && ! get_keyed() ) + if( ! s_panicked && is_start_tick_passed( tick ) && ! get_keyed() ) { + // Activate keyer hardware + s_el = ( s_lockout_el == ELEMENT_DOT ? ELEMENT_DASH : ELEMENT_DOT ); + s_lockout_el = s_el; + tick_t el_dur = element_duration( s_el ); + s_el_stop_tick = tick + el_dur; + s_el_stop_tick_vld = true; + s_el_start_tick = tick + el_dur + s_space_ticks; + s_el_start_tick_vld = true; set_keyed( true ); - s_element = ( s_element == ELEMENT_DOT ? ELEMENT_DASH : ELEMENT_DOT ); - s_element_start = tick; - s_element_duration = ( s_element == ELEMENT_DOT ? s_dot_ticks : s_dash_ticks ); - s_element_lockout = s_element_duration + s_space_ticks; } - else if( is_duration_elapsed( tick ) && get_keyed() ) + else if( is_stop_tick_passed( tick ) && get_keyed() ) { // Deactivate keyer hardware set_keyed( false ); @@ -373,18 +953,20 @@ static void do_state_off( tick_t tick, bool new_state ) ( void )new_state; // Deactivate keyer hardware, once allowed - if( get_keyed() && ( s_element_duration == NO_TIMEOUT || is_duration_elapsed( tick ) ) ) + if( get_keyed() && is_stop_tick_passed( tick ) ) { set_keyed( false ); } // Reset state, once allowed - if( s_element_start != 0 && ( s_element_lockout == NO_TIMEOUT || is_lockout_elapsed( tick ) ) ) + if( s_el != ELEMENT_NONE && is_start_tick_passed( tick ) ) { - s_element = ELEMENT_NONE; - s_element_start = 0; - s_element_duration = NO_TIMEOUT; - s_element_lockout = NO_TIMEOUT; + s_el = ELEMENT_NONE; + s_lockout_el = ELEMENT_NONE; + s_el_stop_tick = 0; + s_el_stop_tick_vld = false; + s_el_start_tick = 0; + s_el_start_tick_vld = false; } } /* do_state_off() */ @@ -392,19 +974,50 @@ static void do_state_off( tick_t tick, bool new_state ) static void do_state_on( tick_t tick, bool new_state ) { + ( void )tick; + // Activate unconditionally, unless panicked if( ! s_panicked && ( new_state || ! get_keyed() ) ) { + s_el = ELEMENT_UNKNOWN; + s_el_stop_tick = 0; + s_el_stop_tick_vld = false; + s_el_start_tick = 0; + s_el_start_tick_vld = false; set_keyed( true ); - s_element = ELEMENT_UNKNOWN; - s_element_start = tick; - s_element_duration = NO_TIMEOUT; - s_element_lockout = NO_TIMEOUT; } } /* do_state_on() */ +static tick_t element_duration( element_t el ) +{ + switch( el ) + { + case ELEMENT_UNKNOWN: + return( 0 ); + case ELEMENT_DOT: + return( s_dot_ticks ); + case ELEMENT_DASH: + return( s_dash_ticks ); + case ELEMENT_LETTER_SPACE: + return( s_letter_space_ticks - s_space_ticks ); + case ELEMENT_WORD_SPACE: + return( s_word_space_ticks - s_space_ticks ); + default: + return( 0 ); + } + +} /* element_duration() */ + + +static bool element_is_keyed( element_t el ) +{ + return( el == ELEMENT_DOT || el == ELEMENT_DASH ); + +} /* element_is_keyed() */ + + static bool get_keyed( void ) { return( s_keyed ); @@ -421,9 +1034,14 @@ static state_t get_next_state( void ) bool paddle_invert = keyer_get_paddle_invert(); // Determine next state - if( is_bit_set( inputs, INPUT_TYPE_STRAIGHT_KEY ) ) + if( autokey_count() != 0 ) + { + // Autokey has highest priority + return( STATE_AUTOKEY ); + } + else if( is_bit_set( inputs, INPUT_TYPE_STRAIGHT_KEY ) ) { - // Straight key supersedes all others + // Straight key supersedes all other inputs return( STATE_ON ); } else if( is_bit_set( inputs, INPUT_TYPE_PADDLE_LEFT ) && @@ -518,6 +1136,11 @@ static void update_ticks( void ) if( s_ticks_wpm == wpm ) return; - wpm_ticks( s_ticks_wpm = wpm, & s_dot_ticks, & s_dash_ticks, & s_space_ticks, NULL, NULL ); + wpm_ticks( s_ticks_wpm = wpm, + & s_dot_ticks, + & s_dash_ticks, + & s_space_ticks, + & s_letter_space_ticks, + & s_word_space_ticks ); } /* update_ticks() */ diff --git a/src/main/application/keyer.h b/src/main/application/keyer.h index ee020ff..ab51abd 100644 --- a/src/main/application/keyer.h +++ b/src/main/application/keyer.h @@ -12,6 +12,7 @@ /* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ #include +#include #include "utility/types.h" @@ -51,6 +52,20 @@ enum /* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ +/** + * @fn keyer_autokey_char( char ) + * @brief Adds the specified character to the keyer's autokey buffer. + * @returns `true` if the character was successfully queued. + */ +bool keyer_autokey_char( char c ); + +/** + * @fn keyer_autokey( char const * ) + * @brief Adds the specified string to the keyer's autokey buffer. + * @returns The number of characters that were successfully queued. + */ +size_t keyer_autokey_str( char const * str ); + /** * @fn keyer_get_on( void ) * @brief Returns `true` if the keyer is currently commanding the radio to transmit. @@ -84,7 +99,7 @@ void keyer_init( void ); /** * @fn keyer_panic( void ) - * @brief Immediately and unconditionally stops the keyer. + * @brief Immediately and unconditionally stops the keyer, and clears any pending autokey characters. */ void keyer_panic( void ); diff --git a/src/main/core/sys.c b/src/main/core/sys.c index 1933a42..eb266d6 100644 --- a/src/main/core/sys.c +++ b/src/main/core/sys.c @@ -135,6 +135,21 @@ bool sys_intrpt_enabled( void ) } /* sys_intrpt_enabled() */ +bool sys_is_tick_gt( tick_t a, tick_t b ) +{ + return( ( ( a > b ) && ( a - b <= ( TICK_MAX / 2 ) ) ) || + ( ( b > a ) && ( b - a > ( TICK_MAX / 2 ) ) ) ); + +} /* sys_is_tick_gt() */ + + +bool sys_is_tick_gte( tick_t a, tick_t b ) +{ + return( a == b || sys_is_tick_gt( a, b ) ); + +} /* sys_is_tick_gte() */ + + void sys_set_intrpt_enabled( bool enabled ) { if( enabled ) sei(); diff --git a/src/main/core/sys.h b/src/main/core/sys.h index 1189362..a949d40 100644 --- a/src/main/core/sys.h +++ b/src/main/core/sys.h @@ -128,6 +128,22 @@ void sys_init( void ); */ bool sys_intrpt_enabled( void ); +/** + * @fn sys_is_tick_gt( tick_t, tick_t ) + * @brief Returns `true` if `a` is later than `b`. + * @note This function is wraparound-aware, and will return the correct value even if the tick counter has reached + * its maximum value and rolled over. + */ +bool sys_is_tick_gt( tick_t a, tick_t b ); + +/** + * @fn sys_is_tick_gte( tick_t, tick_t ) + * @brief Returns `true` if `a` is later than `b`. + * @note This function is wraparound-aware, and will return the correct value even if the tick counter has reached + * its maximum value and rolled over. + */ +bool sys_is_tick_gte( tick_t a, tick_t b ); + /** * @fn sys_set_intrpt_enabled( bool ) * @brief Globally enables or disables interrupts. diff --git a/src/main/drivers/usart.c b/src/main/drivers/usart.c index d1dad2f..4dad214 100644 --- a/src/main/drivers/usart.c +++ b/src/main/drivers/usart.c @@ -56,18 +56,14 @@ _Static_assert( array_count( s_reg_tbl ) == USART_COUNT, "Invalid register table /** * @def RX_BUF_SIZE * @brief Buffer size for received data. - * @note `RX_BUF_SIZE` is significantly smaller than `TX_BUF_SIZE`, because it is intended that data will be - * immediately read from the driver's RX buffer into a function-specific buffer for further processing. */ -#define RX_BUF_SIZE 16 +#define RX_BUF_SIZE 8 /** * @def TX_BUF_SIZE * @brief Buffer size for transmitted data. - * @note `TX_BUF_SIZE` is significantly larger than `RX_BUF_SIZE`, because it needs to support queuing full-sized - * messages without requiring chunking. */ -#define TX_BUF_SIZE 256 +#define TX_BUF_SIZE 64 /** * @def TX_DELAY_US