From 87ca047c3d011caae526b20e03814aa0795bed4c Mon Sep 17 00:00:00 2001 From: Chris Vig Date: Thu, 28 Aug 2025 11:51:05 -0500 Subject: [PATCH] Add support for custom timing of individual Morse code elements. --- .vscode/c_cpp_properties.json | 1 + configuration.cmake | 7 +- src/executable.cmake | 4 + src/main/application/config.c | 16 +- src/main/application/config.h | 3 + src/main/application/debug_port.c | 59 ++- src/main/application/keyer.c | 662 ++++++++++++++---------------- src/main/application/storage.c | 1 + src/main/application/strings.c | 30 ++ src/main/application/strings.h | 14 + src/main/application/wpm.c | 79 +++- src/main/application/wpm.h | 103 ++++- 12 files changed, 567 insertions(+), 412 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 94ca3d1..a08eecb 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -13,6 +13,7 @@ "_DEBUG=1", "_BUILD_TYPE=\"Debug\"", "_CONFIG_DFLT_WPM=200", + "_CONFIG_DFLT_WPM_ELEMENT_SCALE=1.0f", "_CONFIG_DFLT_BUZZER_ENABLED=true", "_CONFIG_DFLT_BUZZER_FREQUENCY=700", "_CONFIG_DFLT_LED_STATUS_ENABLED=true", diff --git a/configuration.cmake b/configuration.cmake index b6d3d57..92a0a66 100644 --- a/configuration.cmake +++ b/configuration.cmake @@ -10,8 +10,10 @@ # -- Configuration Defaults -- # Define values -set(CONFIG_DFLT_WPM 200 - CACHE STRING "Default words per minute, in tenths of a WPM. (10 - 1000)") +set(CONFIG_DFLT_WPM 20.0f + CACHE STRING "Default words per minute. (1.0f - 100.0f)") +set(CONFIG_DFLT_WPM_ELEMENT_SCALE 1.0f + CACHE STRING "Default Morse code element scale. (0.1f - 10.f)") set(CONFIG_DFLT_BUZZER_ENABLED true CACHE STRING "Should the buzzer be enabled by default? (true / false)") set(CONFIG_DFLT_BUZZER_FREQUENCY 700 @@ -54,6 +56,7 @@ set(CONFIG_DFLT_KEYER_OUTPUT_ACTIVE_LOW true # Set compile definitions add_compile_definitions( _CONFIG_DFLT_WPM=${CONFIG_DFLT_WPM} + _CONFIG_DFLT_WPM_ELEMENT_SCALE=${CONFIG_DFLT_WPM_ELEMENT_SCALE} _CONFIG_DFLT_BUZZER_ENABLED=${CONFIG_DFLT_BUZZER_ENABLED} _CONFIG_DFLT_BUZZER_FREQUENCY=${CONFIG_DFLT_BUZZER_FREQUENCY} _CONFIG_DFLT_LED_STATUS_ENABLED=${CONFIG_DFLT_LED_STATUS_ENABLED} diff --git a/src/executable.cmake b/src/executable.cmake index 9d50fe2..e07ba7a 100644 --- a/src/executable.cmake +++ b/src/executable.cmake @@ -94,12 +94,16 @@ target_link_options( PRIVATE ${EXECUTABLE_LINK_OPTIONS} -Wl,-Map=${PROJECT_BUILD_DIR}/${EXECUTABLE_MAP_FILE} + -Wl,-u,vfprintf # Floating point support for printf + -Wl,-u,vfscanf # Floating point support for scanf ) # Add libraries target_link_libraries( ${EXECUTABLE_NAME} ${EXECUTABLE_LIBS} + printf_flt # Floating point support for printf + scanf_flt # Floating point support for scanf ) # Set custom filename for executable diff --git a/src/main/application/config.c b/src/main/application/config.c index 87ae25d..802b2c7 100644 --- a/src/main/application/config.c +++ b/src/main/application/config.c @@ -39,9 +39,6 @@ /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ -_Static_assert( _CONFIG_DFLT_WPM >= WPM_MINIMUM && - _CONFIG_DFLT_WPM <= WPM_MAXIMUM, - "Invalid default WPM!" ); _Static_assert( _CONFIG_DFLT_BUZZER_FREQUENCY >= BUZZER_MINIMUM_FREQUENCY && _CONFIG_DFLT_BUZZER_FREQUENCY <= BUZZER_MAXIMUM_FREQUENCY, "Invalid default buzzer frequency!" ); @@ -75,6 +72,8 @@ void config_default( config_t * config ) // Global settings config->wpm = _CONFIG_DFLT_WPM; + for( wpm_element_t el = 0; el < WPM_ELEMENT_COUNT; el++ ) + config->wpm_element_scale[ el ] = _CONFIG_DFLT_WPM_ELEMENT_SCALE; // Buzzer configuration config->buzzer_enabled = _CONFIG_DFLT_BUZZER_ENABLED; @@ -178,8 +177,15 @@ static void flush( tick_t tick ) static bool validate_config( config_t const * config ) { if( config->wpm < WPM_MINIMUM || - config->wpm > WPM_MAXIMUM || - config->buzzer_frequency < BUZZER_MINIMUM_FREQUENCY || + config->wpm > WPM_MAXIMUM ) + return false; + + for( wpm_element_t el = 0; el < WPM_ELEMENT_COUNT; el++ ) + if( config->wpm_element_scale[ el ] < WPM_ELEMENT_SCALE_MINIMUM || + config->wpm_element_scale[ el ] > WPM_ELEMENT_SCALE_MAXIMUM ) + return( false ); + + if( config->buzzer_frequency < BUZZER_MINIMUM_FREQUENCY || config->buzzer_frequency > BUZZER_MAXIMUM_FREQUENCY ) return( false ); diff --git a/src/main/application/config.h b/src/main/application/config.h index 4c367ab..937d926 100644 --- a/src/main/application/config.h +++ b/src/main/application/config.h @@ -32,6 +32,9 @@ typedef struct /** Global words per minute setting. */ wpm_t wpm; + /** Scale factor for each Morse code element. */ + wpm_element_scale_t wpm_element_scale[ WPM_ELEMENT_COUNT ]; + /** If set to `false`, the buzzer will be disabled and will not sound. */ bool buzzer_enabled; diff --git a/src/main/application/debug_port.c b/src/main/application/debug_port.c index 9a353a8..3b20917 100644 --- a/src/main/application/debug_port.c +++ b/src/main/application/debug_port.c @@ -799,7 +799,10 @@ static void exec_command_version( char const * const command ) static void exec_command_wpm( char const * const command ) { // Scanned variables - unsigned int wpm; + float wpm; + char element_str[ TOKEN_MAX_LEN ]; + wpm_element_t element; + float scale; int sscanf_count; // Parse subcommand @@ -807,20 +810,48 @@ static void exec_command_wpm( char const * const command ) { // No subcommand - interpret as a status request. No action required. } - else if( sscanf( command + 4, "%u %n", & wpm, & sscanf_count ) == 1 && + else if( sscanf( command + 4, "%f %n", & wpm, & sscanf_count ) == 1 && ( command + 4 )[ sscanf_count ] == NULL_CHAR ) { // Set WPM if( wpm < WPM_MINIMUM || wpm > WPM_MAXIMUM ) { - debug_port_printf( "Invalid WPM: \"%u\". Must be between " - stringize_value( WPM_MINIMUM ) " and " - stringize_value( WPM_MAXIMUM ) "." NEWLINE_STR, - wpm ); + debug_port_printf( "Invalid WPM: \"%.1f\". Must be between %.1f and %.1f." NEWLINE_STR, + wpm, WPM_MINIMUM, WPM_MAXIMUM ); return; } wpm_set( ( wpm_t )wpm ); } + else if( string_equals( command, CMD_STR_WPM " scale default" ) ) + { + // Reset element scales to defaults + wpm_element_scale_default(); + } + else if( string_begins_with( command, CMD_STR_WPM " scale " ) && + sscanf( command + 10, TOKEN_FMT_STR " %n", element_str, & sscanf_count ) == 1 && + ( command + 10 )[ sscanf_count ] == NULL_CHAR && + string_to_element( element_str, & element ) ) + { + // Report scale + debug_port_printf( CMD_STR_WPM " scale (%s): %.3f" NEWLINE_STR, + string_from_element( element ), + wpm_get_element_scale( element ) ); + return; + } + else if( string_begins_with( command, CMD_STR_WPM " scale " ) && + sscanf( command + 10, TOKEN_FMT_STR " %f %n", element_str, & scale, & sscanf_count ) == 2 && + ( command + 10 )[ sscanf_count ] == NULL_CHAR && + string_to_element( element_str, & element ) ) + { + // Set scale + if( scale < WPM_ELEMENT_SCALE_MINIMUM || scale > WPM_ELEMENT_SCALE_MAXIMUM ) + { + debug_port_printf( "Invalid scale: \"%.1f\". Must be between %.1f and %.1f." NEWLINE_STR, + scale, WPM_ELEMENT_SCALE_MINIMUM, WPM_ELEMENT_SCALE_MAXIMUM ); + return; + } + wpm_set_element_scale( element, scale ); + } else { // Unrecognized command? @@ -828,15 +859,17 @@ static void exec_command_wpm( char const * const command ) return; } + // Print status info - tick_t dot, dash; - wpm_ticks( wpm_get(), & dot, & dash, NULL, NULL, NULL ); - debug_port_printf( CMD_STR_WPM ": %u (%u.%u wpm - dot %lu ms, dash %lu ms)" NEWLINE_STR, + wpm_ticks_t ticks; + wpm_ticks( wpm_get(), ticks ); + debug_port_printf( CMD_STR_WPM ": %.1f (dot %lu ms, dash %lu ms, space %lu / %lu / %lu ms)" NEWLINE_STR, wpm_get(), - wpm_get() / 10, - wpm_get() % 10, - dot / TICKS_PER_MSEC, - dash / TICKS_PER_MSEC ); + ticks[ WPM_ELEMENT_DOT ] / TICKS_PER_MSEC, + ticks[ WPM_ELEMENT_DASH ] / TICKS_PER_MSEC, + ticks[ WPM_ELEMENT_ELEMENT_SPACE ] / TICKS_PER_MSEC, + ticks[ WPM_ELEMENT_LETTER_SPACE ] / TICKS_PER_MSEC, + ticks[ WPM_ELEMENT_WORD_SPACE ] / TICKS_PER_MSEC ); } /* exec_command_wpm() */ diff --git a/src/main/application/keyer.c b/src/main/application/keyer.c index 3ad6ccc..6c8f455 100644 --- a/src/main/application/keyer.c +++ b/src/main/application/keyer.c @@ -29,25 +29,6 @@ /* ----------------------------------------------------- TYPES ------------------------------------------------------ */ -/** - * @typedef element_t - * @brief Enumeration of Morse code elements. - */ -typedef uint8_t element_t; -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. */ - - ELEMENT_NONE /**< No element is active. */ - = ELEMENT_COUNT, -}; - /** * @typedef state_t * @brief Enumeration of states that the keyer module may be in. @@ -94,23 +75,19 @@ 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_el = ELEMENT_NONE; /** The element currently being keyed. */ -static element_t s_lockout_el = ELEMENT_NONE;/**< Element which started lockout. */ +static wpm_element_t s_el = WPM_ELEMENT_NONE;/** The element currently being keyed. */ +static wpm_element_t s_lockout_el = WPM_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 wpm_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. */ +static wpm_ticks_t s_ticks; /**< Number of ticks for each element type. */ +static tick_t s_ticks_tick = 0; /**< Tick at which s_ticks was updated. */ /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ @@ -150,16 +127,16 @@ static size_t autokey_avail( void ); static size_t autokey_count( void ); /** - * @fn autokey_dequeue( element_t * ) + * @fn autokey_dequeue( wpm_element_t * ) * @brief Dequeues an element from the autokey buffer. */ -static bool autokey_dequeue( element_t * el ); +static bool autokey_dequeue( wpm_element_t * el ); /** * @fn autokey_enqueue( void ) * @brief Enqueues the specified element in the autokey buffer. */ -static bool autokey_enqueue( element_t el ); +static bool autokey_enqueue( wpm_element_t el ); /** * @fn do_state_autokey( tick_t, bool ) @@ -197,18 +174,6 @@ 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. @@ -238,7 +203,7 @@ static void update_hardware( void ); * @brief Update cached element tick counts, if required. * @note This is an expensive calculation, so we only do it if the WPM changes. */ -static void update_ticks( void ); +static void update_ticks( tick_t tick ); /* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ @@ -248,414 +213,414 @@ bool keyer_autokey_char( char c ) { case ' ': // Space - return( autokey_enqueue( ELEMENT_WORD_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_WORD_SPACE ) ); case 'a': case 'A': // Letter A - return( autokey_enqueue( ELEMENT_DOT ) && - autokey_enqueue( ELEMENT_DASH ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); case 'e': case 'E': // Letter E - return( autokey_enqueue( ELEMENT_DOT ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); case 'i': case 'I': // Letter I - return( autokey_enqueue( ELEMENT_DOT ) && - autokey_enqueue( ELEMENT_DOT ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); case 'm': case 'M': // Letter M - return( autokey_enqueue( ELEMENT_DASH ) && - autokey_enqueue( ELEMENT_DASH ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); case 'n': case 'N': // Letter N - return( autokey_enqueue( ELEMENT_DASH ) && - autokey_enqueue( ELEMENT_DOT ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); case 't': case 'T': // Letter T - return( autokey_enqueue( ELEMENT_DASH ) && - autokey_enqueue( ELEMENT_LETTER_SPACE ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_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 ) ); + return( autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_DOT ) && + autokey_enqueue( WPM_ELEMENT_DASH ) && + autokey_enqueue( WPM_ELEMENT_LETTER_SPACE ) ); default: return( false ); @@ -709,16 +674,15 @@ void keyer_init( void ) s_keyed = false; s_panicked = false; s_state = STATE_OFF; - s_el = ELEMENT_NONE; - s_lockout_el = ELEMENT_NONE; + s_el = WPM_ELEMENT_NONE; + s_lockout_el = WPM_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; - s_space_ticks = 0; + for( wpm_element_t el = 0; el < WPM_ELEMENT_COUNT; el++ ) + s_ticks[ el ] = 0; + s_ticks_tick = 0; // Initialize GPIO gpio_set_dir( KEYER_OUT_PIN, GPIO_DIR_OUT ); @@ -726,6 +690,9 @@ void keyer_init( void ) // Default to key off set_keyed( false ); + // Update ticks + update_ticks( sys_get_tick() ); + } /* keyer_init() */ @@ -771,7 +738,7 @@ void keyer_set_paddle_mode( keyer_paddle_mode_t mode ) void keyer_tick( tick_t tick ) { // Update element tick counts if required - update_ticks(); + update_ticks( tick ); // Check the next state, and determine if it's different than the current state state_t next_state = get_next_state(); @@ -826,7 +793,7 @@ static size_t autokey_count( void ) } /* autokey_count() */ -static bool autokey_dequeue( element_t * el ) +static bool autokey_dequeue( wpm_element_t * el ) { if( autokey_count() == 0 ) return( false ); @@ -838,7 +805,7 @@ static bool autokey_dequeue( element_t * el ) } /* autokey_dequeue() */ -static bool autokey_enqueue( element_t el ) +static bool autokey_enqueue( wpm_element_t el ) { if( autokey_avail() == 0 ) return( false ); @@ -854,18 +821,35 @@ static void do_state_autokey( tick_t tick, bool new_state ) { ( void )new_state; - element_t el; + wpm_element_t el; if( ! s_panicked && is_start_tick_passed( tick ) && autokey_dequeue( & el ) ) { + // Get info for current state + bool prev_el_was_letter_space = ( s_el == WPM_ELEMENT_LETTER_SPACE ); + bool prev_lockout_el_was_keyed = wpm_element_is_keyed( s_lockout_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 ) ) + if( wpm_element_is_keyed( s_el ) ) + { + s_lockout_el = s_el; + s_el_stop_tick = tick + s_ticks[ s_el ]; + s_el_stop_tick_vld = true; + s_el_start_tick = tick + s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; + s_el_start_tick_vld = true; set_keyed( true ); + } + else + { + s_el_stop_tick = 0; + s_el_stop_tick_vld = false; + s_el_start_tick = tick + s_ticks[ s_el ] + - ( prev_lockout_el_was_keyed ? + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] : 0 ) + - ( prev_el_was_letter_space ? + ( s_ticks[ WPM_ELEMENT_LETTER_SPACE ] - s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ) : 0 ); + s_el_start_tick_vld = true; + } } else if( is_stop_tick_passed( tick ) && get_keyed() ) { @@ -883,11 +867,11 @@ static void do_state_dashes( tick_t tick, bool new_state ) 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 = WPM_ELEMENT_DASH; + s_lockout_el = s_el; + s_el_stop_tick = tick + s_ticks[ WPM_ELEMENT_DASH ]; s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_dash_ticks + s_space_ticks; + s_el_start_tick = tick + s_ticks[ WPM_ELEMENT_DASH ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; s_el_start_tick_vld = true; set_keyed( true ); } @@ -907,11 +891,11 @@ static void do_state_dots( tick_t tick, bool new_state ) 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 = WPM_ELEMENT_DOT; + s_lockout_el = s_el; + s_el_stop_tick = tick + s_ticks[ WPM_ELEMENT_DOT ]; s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_dot_ticks + s_space_ticks; + s_el_start_tick = tick + s_ticks[ WPM_ELEMENT_DOT ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; s_el_start_tick_vld = true; set_keyed( true ); } @@ -931,12 +915,11 @@ static void do_state_interleaved( tick_t tick, bool new_state ) 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_el = ( s_lockout_el == WPM_ELEMENT_DOT ? WPM_ELEMENT_DASH : WPM_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 = tick + s_ticks[ s_el ]; s_el_stop_tick_vld = true; - s_el_start_tick = tick + el_dur + s_space_ticks; + s_el_start_tick = tick + s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; s_el_start_tick_vld = true; set_keyed( true ); } @@ -960,10 +943,10 @@ static void do_state_off( tick_t tick, bool new_state ) } // Reset state, once allowed - if( s_el != ELEMENT_NONE && is_start_tick_passed( tick ) ) + if( s_el != WPM_ELEMENT_NONE && is_start_tick_passed( tick ) ) { - s_el = ELEMENT_NONE; - s_lockout_el = ELEMENT_NONE; + s_el = WPM_ELEMENT_NONE; + s_lockout_el = s_el; s_el_stop_tick = 0; s_el_stop_tick_vld = false; s_el_start_tick = 0; @@ -980,7 +963,8 @@ static void do_state_on( tick_t tick, bool new_state ) // Activate unconditionally, unless panicked if( ! s_panicked && ( new_state || ! get_keyed() ) ) { - s_el = ELEMENT_UNKNOWN; + s_el = WPM_ELEMENT_UNKNOWN; + s_lockout_el = s_el; s_el_stop_tick = 0; s_el_stop_tick_vld = false; s_el_start_tick = 0; @@ -991,34 +975,6 @@ static void do_state_on( tick_t tick, bool new_state ) } /* 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 ); @@ -1131,17 +1087,15 @@ static void update_hardware( void ) } /* update_hardware() */ -static void update_ticks( void ) +static void update_ticks( tick_t tick ) { - wpm_t wpm = wpm_get(); - if( s_ticks_wpm == wpm ) + // Limit this update to ~20 Hz, since it does floating point math + #define MIN_DELAY_MS 50 + if( s_ticks_tick != 0 && sys_elapsed( tick, s_ticks_tick ) < ( MIN_DELAY_MS * TICKS_PER_MSEC ) ) return; + #undef MIN_DELAY_MS - wpm_ticks( s_ticks_wpm = wpm, - & s_dot_ticks, - & s_dash_ticks, - & s_space_ticks, - & s_letter_space_ticks, - & s_word_space_ticks ); + wpm_ticks( wpm_get(), s_ticks ); + s_ticks_tick = tick; } /* update_ticks() */ diff --git a/src/main/application/storage.c b/src/main/application/storage.c index d2783f5..3e3e4cc 100644 --- a/src/main/application/storage.c +++ b/src/main/application/storage.c @@ -102,6 +102,7 @@ bool storage_get_config( config_version_t version, size_t size, void * config ) void storage_init( void ) { // Confirm we have the right layout version + // If not... welp? No other versions are known, so nothing to do but reset the entire storage. layout_version_t layout = ( layout_version_t )eeprom_read_byte( EEPROM_ADDR_LAYOUT_VERSION ); if( layout != LAYOUT_VERSION_CURRENT ) reinit_layout(); diff --git a/src/main/application/strings.c b/src/main/application/strings.c index f3cefcb..ae45dc9 100644 --- a/src/main/application/strings.c +++ b/src/main/application/strings.c @@ -24,6 +24,17 @@ static char const * const s_bool_tbl[] = }; _Static_assert( array_count( s_bool_tbl ) == 2, "Invalid string table!" ); +#define ELEMENT_PREFIX_LEN 12 +static char const * const s_element_tbl[] = +{ + stringize( WPM_ELEMENT_DOT ), + stringize( WPM_ELEMENT_DASH ), + stringize( WPM_ELEMENT_ELEMENT_SPACE ), + stringize( WPM_ELEMENT_LETTER_SPACE ), + stringize( WPM_ELEMENT_WORD_SPACE ), +}; +_Static_assert( array_count( s_element_tbl ) == WPM_ELEMENT_COUNT, "Invalid string table!" ); + #define INPUT_PIN_PREFIX_LEN 10 static char const * const s_input_pin_tbl[] = { @@ -104,6 +115,13 @@ char const * string_from_bool( bool b ) } /* string_from_bool() */ +char const * string_from_element( wpm_element_t el ) +{ + return( find_string( s_element_tbl, array_count( s_element_tbl ), ELEMENT_PREFIX_LEN, ( size_t )el ) ); + +} /* string_from_element() */ + + char const * string_from_input_pin( input_pin_t pin ) { return( find_string( s_input_pin_tbl, INPUT_PIN_COUNT, INPUT_PIN_PREFIX_LEN, ( size_t )pin ) ); @@ -151,6 +169,18 @@ bool string_to_bool( char const * str, bool * b ) } /* string_to_bool() */ +bool string_to_element( char const * str, wpm_element_t * el ) +{ + size_t result = find_value( s_element_tbl, array_count( s_element_tbl ), ELEMENT_PREFIX_LEN, str ); + if( result == WPM_ELEMENT_COUNT ) + return( false ); + + * el = ( wpm_element_t )result; + return( true ); + +} /* string_to_element() */ + + bool string_to_input_pin( char const * str, input_pin_t * pin ) { size_t result = find_value( s_input_pin_tbl, array_count( s_input_pin_tbl ), INPUT_PIN_PREFIX_LEN, str ); diff --git a/src/main/application/strings.h b/src/main/application/strings.h index 764c14b..302c854 100644 --- a/src/main/application/strings.h +++ b/src/main/application/strings.h @@ -17,6 +17,7 @@ #include "application/input.h" #include "application/keyer.h" #include "application/led.h" +#include "application/wpm.h" /* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ @@ -66,6 +67,12 @@ bool string_equals( char const * a, char const * b ); */ char const * string_from_bool( bool b ); +/** + * @fn string_from_element( wpm_element_t ) + * @brief Returns a string for the specified `wpm_element_t`. + */ +char const * string_from_element( wpm_element_t el ); + /** * @fn string_from_input_pin( input_pin_t ) * @brief Returns a string for the specified `input_pin_t`. @@ -103,6 +110,13 @@ bool string_is_empty( char const * str ); */ bool string_to_bool( char const * str, bool * b ); +/** + * @fn string_to_element( char const *, wpm_element_t * ) + * @brief Converts a string to a `wpm_element_t`, if possible. + * @returns `true` if the conversion succeeded. + */ +bool string_to_element( char const * str, wpm_element_t * el ); + /** * @fn string_to_input_pin( char const *, input_pin_t * ) * @brief Converts a string to an `input_pin_t`, if possible. diff --git a/src/main/application/wpm.c b/src/main/application/wpm.c index 36fe7d5..5d382a1 100644 --- a/src/main/application/wpm.c +++ b/src/main/application/wpm.c @@ -1,6 +1,6 @@ /** * @file src/main/application/wpm.c - * @brief Implementation for the words-per-minute calculation module. + * @brief Implementation for the Morse code utility module. * * @author Chris Vig (chris@invictus.so) * @date 2025-08-22 @@ -33,8 +33,34 @@ */ #define MSEC_PER_MIN ( ( uint32_t )MSEC_PER_SEC * ( uint32_t )SEC_PER_MIN ) +/* ----------------------------------------------------- MACROS ----------------------------------------------------- */ + +// Validation macros +#define validate_element( _element ) \ + assert_always( ( _element ) < WPM_ELEMENT_COUNT ) + /* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ +bool wpm_element_is_keyed( wpm_element_t el ) +{ + // Do not validate element for this function (allow checking WPM_ELEMENT_UNKNOWN, etc.) + + return( el == WPM_ELEMENT_DOT || el == WPM_ELEMENT_DASH ); + +} /* wpm_element_is_keyed() */ + + +void wpm_element_scale_default( void ) +{ + config_t config; + config_get( & config ); + for( wpm_element_t el = 0; el < WPM_ELEMENT_COUNT; el++ ) + config.wpm_element_scale[ el ] = WPM_ELEMENT_SCALE_DEFAULT; + config_set( & config ); + +} /* wpm_element_scale_default() */ + + wpm_t wpm_get( void ) { return( config()->wpm ); @@ -42,6 +68,15 @@ wpm_t wpm_get( void ) } /* wpm_get() */ +wpm_element_scale_t wpm_get_element_scale( wpm_element_t el ) +{ + validate_element( el ); + + return( config()->wpm_element_scale[ el ] ); + +} /* wpm_get_element_scale() */ + + void wpm_set( wpm_t wpm ) { config_t config; @@ -52,29 +87,37 @@ void wpm_set( wpm_t wpm ) } /* wpm_set() */ -void wpm_ticks( wpm_t wpm, - tick_t * dot, - tick_t * dash, - tick_t * element_space, - tick_t * letter_space, - tick_t * word_space ) +void wpm_set_element_scale( wpm_element_t el, wpm_element_scale_t scale ) +{ + validate_element( el ); + + config_t config; + config_get( & config ); + config.wpm_element_scale[ el ] = clamp( scale, WPM_ELEMENT_SCALE_MINIMUM, WPM_ELEMENT_SCALE_MAXIMUM ); + config_set( & config ); + +} /* wpm_set_element_scale() */ + +#include "debug_port.h" + +void wpm_ticks( wpm_t wpm, wpm_ticks_t ticks ) { if( wpm < WPM_MINIMUM || wpm > WPM_MAXIMUM ) return; // https://morsecode.world/international/timing/ - float unit_ms = ( float )MSEC_PER_MIN / ( ( ( float )wpm / ( float )WPM_T_SCALE ) * ( float )WORD_UNIT_LENGTH ); + float unit_ms = ( float )MSEC_PER_MIN / ( wpm * ( float )WORD_UNIT_LENGTH ); assert_always( unit_ms > 0.0f ); - if( dot ) - * dot = ( tick_t )roundf( 1.0f * unit_ms ) * TICKS_PER_MSEC; - if( dash ) - * dash = ( tick_t )roundf( 3.0f * unit_ms ) * TICKS_PER_MSEC; - if( element_space ) - * element_space = ( tick_t )roundf( 1.0f * unit_ms ) * TICKS_PER_MSEC; - if( letter_space ) - * letter_space = ( tick_t )roundf( 3.0f * unit_ms ) * TICKS_PER_MSEC; - if( word_space ) - * word_space = ( tick_t )roundf( 7.0f * unit_ms ) * TICKS_PER_MSEC; + ticks[ WPM_ELEMENT_DOT ] + = ( tick_t )roundf( 1.0f * unit_ms * wpm_get_element_scale( WPM_ELEMENT_DOT ) ) * TICKS_PER_MSEC; + ticks[ WPM_ELEMENT_DASH ] + = ( tick_t )roundf( 3.0f * unit_ms * wpm_get_element_scale( WPM_ELEMENT_DASH ) ) * TICKS_PER_MSEC; + ticks[ WPM_ELEMENT_ELEMENT_SPACE ] + = ( tick_t )roundf( 1.0f * unit_ms * wpm_get_element_scale( WPM_ELEMENT_ELEMENT_SPACE ) ) * TICKS_PER_MSEC; + ticks[ WPM_ELEMENT_LETTER_SPACE ] + = ( tick_t )roundf( 3.0f * unit_ms * wpm_get_element_scale( WPM_ELEMENT_LETTER_SPACE ) ) * TICKS_PER_MSEC; + ticks[ WPM_ELEMENT_WORD_SPACE ] + = ( tick_t )roundf( 7.0f * unit_ms * wpm_get_element_scale( WPM_ELEMENT_WORD_SPACE ) ) * TICKS_PER_MSEC; } /* wpm_ticks() */ diff --git a/src/main/application/wpm.h b/src/main/application/wpm.h index a96446d..90a9f56 100644 --- a/src/main/application/wpm.h +++ b/src/main/application/wpm.h @@ -1,6 +1,6 @@ /** * @file src/main/application/wpm.h - * @brief Header for the words-per-minute calculation module. + * @brief Header for the Morse code utility module. * * @author Chris Vig (chris@invictus.so) * @date 2025-08-22 @@ -12,48 +12,109 @@ /* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ +#include #include #include "utility/types.h" /* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ -/** - * @def WPM_T_SCALE - * @brief The integer value of `wpm_t` which represents a value of exactly 1 word per minute. - */ -#define WPM_T_SCALE 10 - /** * @def WPM_MAXIMUM * @brief The maximum permissible WPM value. */ -#define WPM_MAXIMUM 1000 +#define WPM_MAXIMUM 100.0f /** * @def WPM_MINIMUM * @brief The minimum permissible WPM value. */ -#define WPM_MINIMUM 10 +#define WPM_MINIMUM 1.0f + +/** + * @def WPM_ELEMENT_SCALE_MAXIMUM + * @brief THe maximum scale value for element durations. + */ +#define WPM_ELEMENT_SCALE_MAXIMUM 10.0f + +/** + * @def WPM_ELEMENT_SCALE_MINIMUM + * @brief The minimum scale value for element durations. + */ +#define WPM_ELEMENT_SCALE_MINIMUM 0.1f + +/** + * @def WPM_ELEMENT_SCALE_DEFAULT + * @brief The default scale value for element durations. + */ +#define WPM_ELEMENT_SCALE_DEFAULT _CONFIG_DFLT_WPM_ELEMENT_SCALE /* ------------------------------------------------------ TYPES ----------------------------------------------------- */ /** * @typedef wpm_t - * @brief Type representing words per minute (in one tenths). - * @note This is an integer value which represents tenths of a word per minute. For example, a value of 200 would - * represent 20 words per minute. 157 would represent 15.7 words per minute. + * @brief Type representing words per minute. + */ +typedef float wpm_t; + +/** + * @typedef wpm_element_t + * @brief Enumeration of supported Morse code elements. */ -typedef uint16_t wpm_t; +typedef uint8_t wpm_element_t; +enum +{ + WPM_ELEMENT_DOT, /**< Element is a dot. */ + WPM_ELEMENT_DASH, /**< Element is a dash. */ + WPM_ELEMENT_ELEMENT_SPACE, /**< Element is an inter-element space. */ + WPM_ELEMENT_LETTER_SPACE, /**< Element is a space between letters. */ + WPM_ELEMENT_WORD_SPACE, /**< Element is a space between words. */ + + WPM_ELEMENT_COUNT, /**< Number of valid element types. */ + + WPM_ELEMENT_NONE, /**< No valid element. */ + WPM_ELEMENT_UNKNOWN, /**< Unknown (manually keyed) element. */ +}; + +/** + * @typedef wpm_element_scale_t + * @brief Scaling factor for an element duration. + */ +typedef float wpm_element_scale_t; + +/** + * @typedef wpm_ticks_t + * @brief Typedef for an array of `tick_t` values representing the duration of each known Morse code element. + */ +typedef tick_t wpm_ticks_t[ WPM_ELEMENT_COUNT ]; /* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ +/** + * @fn wpm_element_is_keyed( wpm_element_t ) + * @brief Returns `true` if the specified element requires keying the radio. + */ +bool wpm_element_is_keyed( wpm_element_t el ); + +/** + * @fn wpm_element_scale_default( void ) + * @brief Resets all element scales to the default value. + * @note This modifies the application configuration. + */ +void wpm_element_scale_default( void ); + /** * @fn wpm_get( void ) * @brief Returns the current global WPM setting. */ wpm_t wpm_get( void ); +/** + * @fn wpm_get_element_scale( wpm_element_t ) + * @brief Returns the element scale for the specified element type. + */ +wpm_element_scale_t wpm_get_element_scale( wpm_element_t el ); + /** * @fn wpm_set( wpm_t ) * @brief Sets the global WPM setting. @@ -62,15 +123,17 @@ wpm_t wpm_get( void ); void wpm_set( wpm_t wpm ); /** - * @fn wpm_ticks( wpm_t, tick_t *, tick_t *, tick_t *, tick_t *, tick_t * ) + * @fn wpm_set_element_scale( wpm_element_t, wpm_element_scale_t ) + * @brief Sets the element scale for the specified element type. + * @note This modifies the application configuration. + */ +void wpm_set_element_scale( wpm_element_t el, wpm_element_scale_t scale ); + +/** + * @fn wpm_ticks( wpm_t, wpm_ticks_t ) * @brief Calculates the duration (in ticks) of various Morse code elements. * @note This function does floating-point math and should not be called frequently. */ -void wpm_ticks( wpm_t wpm, - tick_t * dot, - tick_t * dash, - tick_t * element_space, - tick_t * letter_space, - tick_t * word_space ); +void wpm_ticks( wpm_t wpm, wpm_ticks_t ticks ); #endif /* !defined( APPLICATION_WPM_H ) */