diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 90449b2..607c443 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -8,7 +8,7 @@ # -- Executable Configuration -- -# Note that for the main executable, we use the project forced to lowercase +# Note that for the main executable, we use the project name forced to lowercase string(TOLOWER "${CMAKE_PROJECT_NAME}" EXECUTABLE_NAME) set(EXECUTABLE_SOURCE application/buzzer.c application/buzzer.h @@ -22,6 +22,8 @@ set(EXECUTABLE_SOURCE application/buzzer.c application/keyer.h application/led.c application/led.h + application/storage.c + application/storage.h application/wpm.c application/wpm.h core/main.c @@ -29,11 +31,15 @@ set(EXECUTABLE_SOURCE application/buzzer.c core/sys.h core/version.c core/version.h + drivers/eeprom.c + drivers/eeprom.h drivers/gpio.c drivers/gpio.h drivers/usart.c drivers/usart.h utility/constants.h + utility/crc.c + utility/crc.h utility/debug.c utility/debug.h utility/types.h diff --git a/src/main/application/config.c b/src/main/application/config.c index 6e4a585..20c9eab 100644 --- a/src/main/application/config.c +++ b/src/main/application/config.c @@ -16,9 +16,26 @@ #include "application/input.h" #include "application/keyer.h" #include "application/led.h" +#include "application/storage.h" +#include "core/sys.h" #include "utility/debug.h" +#include "utility/types.h" #include "utility/utility.h" +/* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ + +/** + * @def CONFIG_VERSION_CURRENT + * @brief The currently active configuration version. + */ +#define CONFIG_VERSION_CURRENT ( 1 ) + +/** + * @def MINIMUM_SAVE_PERIOD + * @brief Minimum elapsed time between saving config to storage. + */ +#define MINIMUM_SAVE_PERIOD ( 5 * TICKS_PER_SEC ) + /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ _Static_assert( _CONFIG_DFLT_WPM >= WPM_MINIMUM && @@ -31,9 +48,17 @@ _Static_assert( _CONFIG_DFLT_BUZZER_FREQUENCY >= BUZZER_MINIMUM_FREQUENCY && /* --------------------------------------------------- VARIABLES ---------------------------------------------------- */ static config_t s_config; /**< Currently active app configuration. */ +static bool s_modified = false; /**< Has the configuration been modified? */ +static tick_t s_save_tick = 0; /**< Tick config was last saved. */ /* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ +/** + * @fn flush( tick_t ) + * @brief Writes the configuration to non-volatile storage and updates state. + */ +static void flush( tick_t tick ); + /** * @fn validate_config( config_t const * ) * @brief Returns `true` if all fields in the specified configuration struct are valid. @@ -83,17 +108,31 @@ void config_default( config_t * config ) } /* config_default() */ +void config_flush( void ) +{ + flush( sys_get_tick() ); + +} /* config_flush() */ + + void config_get( config_t * config ) { - memcpy( config, & s_config, sizeof( config_t ) ); + * config = s_config; } /* config_get() */ void config_init( void ) { - // Set configuration to defaults, for now - config_default( & s_config ); + // Try to get configuration from storage, and restore defaults if that fails. + // In this case, any existing configuration will be lost the next time config is saved. + config_t config; + if( ! storage_get_config( CONFIG_VERSION_CURRENT, sizeof( config_t ), & config ) || + ! validate_config( & config ) ) + config_default( & config ); + + // Set local copy + s_config = config; } /* config_init() */ @@ -110,12 +149,31 @@ bool config_set( config_t const * config ) if( ! validate_config( config ) ) return( false ); - memcpy( & s_config, config, sizeof( config_t ) ); + s_config = * config; + s_modified = true; return( true ); } /* config_set() */ +void config_tick( tick_t tick ) +{ + if( ! s_modified || sys_elapsed( tick, s_save_tick ) < MINIMUM_SAVE_PERIOD ) + return; + flush( tick ); + +} /* config_tick() */ + + +static void flush( tick_t tick ) +{ + storage_set_config( CONFIG_VERSION_CURRENT, sizeof( config_t ), & s_config ); + s_modified = false; + s_save_tick = tick; + +} /* flush() */ + + static bool validate_config( config_t const * config ) { if( config->wpm < WPM_MINIMUM || diff --git a/src/main/application/config.h b/src/main/application/config.h index 2b8d658..bb338bb 100644 --- a/src/main/application/config.h +++ b/src/main/application/config.h @@ -57,6 +57,12 @@ typedef struct } config_t; +/** + * @typedef config_version_t + * @brief Typedef for the configuration version number. + */ +typedef uint8_t config_version_t; + /* ----------------------------------------------------- MACROS ----------------------------------------------------- */ /** @@ -74,6 +80,12 @@ typedef struct */ void config_default( config_t * config ); +/** + * @fn config_flush( void ) + * @brief Immediately writes the current configuration to storage. + */ +void config_flush( void ); + /** * @fn config_get( config_t * ) * @brief Copies the current application configuration into the specified struct. @@ -100,4 +112,10 @@ config_t const * config_read_only( void ); */ bool config_set( config_t const * config ); +/** + * @fn config_tick( tick_t ) + * @brief Performs periodic processing at the specified tick. + */ +void config_tick( tick_t tick ); + #endif /* !defined( APPLICATION_CONFIG_H ) */ \ No newline at end of file diff --git a/src/main/application/debug_port.c b/src/main/application/debug_port.c index 449ab98..86755d9 100644 --- a/src/main/application/debug_port.c +++ b/src/main/application/debug_port.c @@ -19,8 +19,10 @@ #include "application/keyer.h" #include "core/sys.h" #include "core/version.h" +#include "drivers/eeprom.h" #include "drivers/usart.h" #include "utility/constants.h" +#include "utility/crc.h" #include "utility/types.h" /* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ @@ -38,6 +40,60 @@ */ #define RX_BUF_SIZE 64 +/** + * @def CMD_STR_BUZZER + * @brief The string for the `buzzer` command. + */ +#define CMD_STR_BUZZER "Buzzer" + +/** + * @def CMD_STR_CONFIG + * @brief The string for the `config` command. + */ +#define CMD_STR_CONFIG "Config" + +/** + * @def CMD_STR_EEPROM + * @brief The string for the `eeprom` command. + */ +#define CMD_STR_EEPROM "EEPROM" + +/** + * @def CMD_STR_HELP + * @brief The string for the `help` command. + */ +#define CMD_STR_HELP "Help" + +/** + * @def CMD_STR_KEYER + * @brief The string for the `keyer` command. + */ +#define CMD_STR_KEYER "Keyer" + +/** + * @def CMD_STR_PANIC + * @brief The string for the `panic` command. + */ +#define CMD_STR_PANIC "Panic" + +/** + * @def CMD_STR_TICK + * @brief The string for the `tick` command. + */ +#define CMD_STR_TICK "Tick" + +/** + * @def CMD_STR_VERSION + * @brief The string for the `version` command. + */ +#define CMD_STR_VERSION "Version" + +/** + * @def CMD_STR_WPM + * @brief The string for the `wpm` command. + */ +#define CMD_STR_WPM "WPM" + /** * @def ENABLE_STR * @brief The token to enable an option. @@ -109,91 +165,83 @@ static size_t s_rx_count = 0; static void evaluate_rx_buf( void ); /** - * @fn exec_command( char const * command ) + * @fn exec_command( char const * const command ) * @brief Executes the specified command, including sending any required response. */ static void exec_command( char const * const command ); /** - * @fn exec_command_buzzer( char const * command ) + * @fn exec_command_buzzer( char const * const command ) * @brief Executes the `buzzer` command. */ static void exec_command_buzzer( char const * const command ); /** - * @fn exec_command_config( char const * command ) + * @fn exec_command_config( char const * const command ) * @brief Executes the `config` command. */ static void exec_command_config( char const * const command ); /** - * @fn exec_command_help( char const * command ) + * @fn exec_command_eeprom( char const * const command ) + * @brief Executes the `eeprom` command. + */ +static void exec_command_eeprom( char const * const command ); + +/** + * @fn exec_command_help( char const * const command ) * @brief Executes the `help` command. */ static void exec_command_help( char const * const command ); /** - * @fn exec_command_input( char const * command ) + * @fn exec_command_input( char const * const command ) * @brief Executes the `input` command. */ static void exec_command_input( char const * const command ); /** - * @fn exec_command_keyer( char const * command ) + * @fn exec_command_keyer( char const * const command ) * @brief Executes the `keyer` command. */ static void exec_command_keyer( char const * const command ); /** - * @fn exec_command_led( char const * command ) + * @fn exec_command_led( char const * const command ) * @brief Executes the `led` command. */ static void exec_command_led( char const * const command ); /** - * @fn exec_command_panic( char const * command ) + * @fn exec_command_panic( char const * const command ) * @brief Executes the `panic` command. */ static void exec_command_panic( char const * const command ); /** - * @fn exec_command_tick( char const * command ) + * @fn exec_command_tick( char const * const command ) * @brief Executes the `tick` command. */ static void exec_command_tick( char const * const command ); /** - * @fn exec_command_version( char const * command ) + * @fn exec_command_version( char const * const command ) * @brief Executes the `version` command. */ static void exec_command_version( char const * const command ); /** - * @fn exec_command_wpm( char const * command ) + * @fn exec_command_wpm( char const * const command ) * @brief Executes the `wpm` command. */ static void exec_command_wpm( char const * const command ); /** - * @fn print_invalid_command( char const * ) + * @fn print_invalid_command( char const * const ) * @brief Sends an "Invalid command" message for the specified command. */ static void print_invalid_command( char const * const command ); -/** - * @fn read_long( char const *, long * ) - * @brief Attempts to read a signed long integer from the specified buffer. - * @returns `true` if reading the value succeeded and all data was consumed. - */ -static bool read_long( char const * buf, long * value ) FUNC_MAY_BE_UNUSED; - -/** - * @fn read_ulong( char const *, unsigned long * ) - * @brief Attempts to read an unsigned long integer from the specified buffer. - * @returns `true` if reading the value succeeded and all data was consumed. - */ -static bool read_ulong( char const * buf, unsigned long * value ) FUNC_MAY_BE_UNUSED; - /** * @fn string_begins_with( char const *, char const * ) * @brief Returns `true` if `str` begins with `token`. @@ -314,25 +362,27 @@ static void evaluate_rx_buf( void ) static void exec_command( char const * const command ) { // Handle known commands, starting with panic because I'm paranoid - if( string_equals( command, "stop" ) || string_equals( command, "panic" ) ) + if( string_begins_with( command, CMD_STR_PANIC ) ) exec_command_panic( command ); - else if( string_begins_with( command, "buzzer" ) ) + else if( string_begins_with( command, CMD_STR_BUZZER ) ) exec_command_buzzer( command ); - else if( string_begins_with( command, "config" ) ) + else if( string_begins_with( command, CMD_STR_CONFIG ) ) exec_command_config( command ); - else if( string_begins_with( command, "help" ) ) + else if( string_begins_with( command, CMD_STR_EEPROM ) ) + exec_command_eeprom( command ); + else if( string_begins_with( command, CMD_STR_HELP ) ) exec_command_help( command ); else if( string_begins_with( command, "input" ) ) exec_command_input( command ); - else if( string_begins_with( command, "keyer" ) ) + else if( string_begins_with( command, CMD_STR_KEYER ) ) exec_command_keyer( command ); else if( string_begins_with( command, "led" ) ) exec_command_led( command ); - else if( string_begins_with( command, "tick" ) ) + else if( string_begins_with( command, CMD_STR_TICK ) ) exec_command_tick( command ); - else if( string_begins_with( command, "version" ) ) + else if( string_begins_with( command, CMD_STR_VERSION ) ) exec_command_version( command ); - else if( string_begins_with( command, "wpm" ) ) + else if( string_begins_with( command, CMD_STR_WPM ) ) exec_command_wpm( command ); // Handle unrecognized commands @@ -344,42 +394,38 @@ static void exec_command( char const * const command ) static void exec_command_buzzer( char const * const command ) { - // Drop "buzzer" token - char const * c = command + 6; + // Scanned variables + unsigned int freq; + int sscanf_count; - if( string_is_empty( c ) ) + // Parse subcommand + if( string_equals( command, CMD_STR_BUZZER ) ) { // No subcommand - interpret as a status request. no action required } - else if( string_equals( c, " " ENABLE_STR ) ) + else if( string_equals( command, CMD_STR_BUZZER " " ENABLE_STR ) ) { // Turn buzzer on buzzer_set_enabled( true ); } - else if( string_equals( c, " " DISABLE_STR ) ) + else if( string_equals( command, CMD_STR_BUZZER " " DISABLE_STR ) ) { // Turn buzzer oiff buzzer_set_enabled( false ); } - else if( string_begins_with( c, " frequency " ) ) + else if( string_begins_with( command, CMD_STR_BUZZER " frequency " ) && + sscanf( command + 17, "%u %n", & freq, & sscanf_count ) == 1 && + ( command + 17 )[ sscanf_count ] == NULL_CHAR ) { - // Drop " frequency " - c += 11; - unsigned long freq; - if( read_ulong( c, & freq ) && - freq >= BUZZER_MINIMUM_FREQUENCY && - freq <= BUZZER_MAXIMUM_FREQUENCY ) - { - buzzer_set_frequency( ( buzzer_freq_t )freq ); - } - else + if( freq < BUZZER_MINIMUM_FREQUENCY || freq > BUZZER_MAXIMUM_FREQUENCY ) { - debug_port_print( "Invalid frequency: \"" ); - debug_port_print( c ); - debug_port_print( "\". Must be between " stringize_value( BUZZER_MINIMUM_FREQUENCY ) - " and " stringize_value( BUZZER_MAXIMUM_FREQUENCY ) " Hz." NEWLINE_STR ); + debug_port_printf( "Invalid frequency: \"%u\". Must be between " + stringize_value( BUZZER_MINIMUM_FREQUENCY ) " and " + stringize_value( BUZZER_MAXIMUM_FREQUENCY ) " Haz. " NEWLINE_STR, + freq ); return; } + buzzer_set_frequency( ( buzzer_freq_t )freq ); } else { @@ -389,7 +435,7 @@ static void exec_command_buzzer( char const * const command ) } // Send buzzer status - debug_port_printf( "Buzzer: %s (%u Hz)" NEWLINE_STR, + debug_port_printf( CMD_STR_BUZZER ": %s (%u Hz)" NEWLINE_STR, buzzer_get_enabled() ? ENABLED_STR : DISABLED_STR, buzzer_get_frequency() ); @@ -398,10 +444,16 @@ static void exec_command_buzzer( char const * const command ) static void exec_command_config( char const * const command ) { - // Drop "config" token - char const * c = command + 6; - - if( string_equals( c, " default" ) ) + // Parse subcommand + if( string_equals( command, CMD_STR_CONFIG " crc" ) ) + { + // Print config CRC + config_t config; + config_get( & config ); + crc16_t crc = crc_calc_crc16( & config, sizeof( config_t ) ); + debug_port_printf( CMD_STR_CONFIG " CRC16: 0x%04X" NEWLINE_STR, crc ); + } + else if( string_equals( command, CMD_STR_CONFIG " default" ) ) { // Restore default configuration config_t config; @@ -409,14 +461,104 @@ static void exec_command_config( char const * const command ) config_set( & config ); // Send response - debug_port_print( "Default configuration restored." NEWLINE_STR ); + debug_port_print( CMD_STR_CONFIG ": Default restored." NEWLINE_STR ); + return; + } + else if( string_equals( command, CMD_STR_CONFIG " flush" ) ) + { + // Flush config + config_flush(); + debug_port_print( CMD_STR_CONFIG ": Flushed." NEWLINE_STR ); + } + else if( string_equals( command, CMD_STR_CONFIG " size" ) ) + { + // Report configuration size + debug_port_printf( CMD_STR_CONFIG " size: %u bytes." NEWLINE_STR, sizeof( config_t ) ); } else + { + // Unknown subcommand? print_invalid_command( command ); + return; + } } /* exec_command_config() */ +static void exec_command_eeprom( char const * const command ) +{ + // Scanned variables + unsigned int addr; + unsigned int data; + int sscanf_count; + + // Parse subcommand + if( string_begins_with( command, CMD_STR_EEPROM " erase_byte " ) && + sscanf( command + 18, "%u %n", & addr, & sscanf_count ) == 1 && + ( command + 18 )[ sscanf_count ] == NULL_CHAR ) + { + // Erase byte at address + if( addr >= EEPROM_COUNT ) + { + debug_port_printf( "Invalid address: \"%u\". Must be less than " + stringize_value( EEPROM_COUNT ) "." NEWLINE_STR, + addr ); + return; + } + eeprom_erase_byte( ( eeprom_addr_t )addr ); + debug_port_printf( CMD_STR_EEPROM " Erase 0x%04X" NEWLINE_STR, addr ); + return; + } + else if( string_begins_with( command, CMD_STR_EEPROM " read_byte " ) && + sscanf( command + 17, "%u %n", & addr, & sscanf_count ) == 1 && + ( command + 17 )[ sscanf_count ] == NULL_CHAR ) + { + // Read byte at address + if( addr >= EEPROM_COUNT ) + { + debug_port_printf( "Invalid address: \"%u\". Must be less than " + stringize_value( EEPROM_COUNT ) "." NEWLINE_STR, + addr ); + return; + } + byte_t byte; + eeprom_read( ( eeprom_addr_t )addr, & byte, 1 ); + debug_port_printf( CMD_STR_EEPROM " Read 0x%04X: 0x%02X" NEWLINE_STR, addr, byte ); + return; + } + else if( string_begins_with( command, CMD_STR_EEPROM " write_byte " ) && + sscanf( command + 18, "%u %u %n", & addr, & data, & sscanf_count ) == 2 && + ( command + 18 )[ sscanf_count ] == NULL_CHAR ) + { + // Write byte at address + if( addr >= EEPROM_COUNT ) + { + debug_port_printf( "Invalid address: \"%u\". Must be less than " + stringize_value( EEPROM_COUNT ) "." NEWLINE_STR, + addr ); + return; + } + if( data >= UINT8_MAX ) + { + debug_port_printf( "Invalid byte: \"%u\". Must be less than " + stringize_value( UINT8_MAX ) "." NEWLINE_STR, + data ); + } + byte_t byte = ( byte_t )data; + eeprom_write( ( eeprom_addr_t )addr, & byte, 1 ); + debug_port_printf( CMD_STR_EEPROM " Write 0x%04X: 0x%02X" NEWLINE_STR, addr, byte ); + return; + } + else + { + // Unrecognized command? + print_invalid_command( command ); + return; + } + +} /* exec_command_eeprom() */ + + static void exec_command_help( char const * const command ) { ( void )command; @@ -427,6 +569,10 @@ static void exec_command_help( char const * const command ) static void exec_command_input( char const * const command ) { + // + // TODO: UPDATE TO NEW STYLE FOR THESE FUNCTIONS + // + // Local constants static char const * const s_pin_tbl[] = { @@ -534,6 +680,7 @@ static void exec_command_input( char const * const command ) static void exec_command_keyer( char const * const command ) { + // String table static char const * s_paddle_mode_tbl[] = { stringize( KEYER_PADDLE_MODE_IAMBIC ), @@ -542,44 +689,42 @@ static void exec_command_keyer( char const * const command ) }; _Static_assert( array_count( s_paddle_mode_tbl ) == KEYER_PADDLE_MODE_COUNT, "Invalid string table!" ); - // Drop "keyer" prefix - char const * c = command + 5; - - if( string_is_empty( c ) ) + // Parse subcommand + if( string_equals( command, CMD_STR_KEYER ) ) { // No subcommand - interpret as a status request. no action required } - else if( string_equals( c, " output_active_low " ENABLE_STR ) ) + else if( string_equals( command, CMD_STR_KEYER " output_active_low " ENABLE_STR ) ) { // Set output to active low keyer_set_output_active_low( true ); } - else if( string_equals( c, " output_active_low " DISABLE_STR ) ) + else if( string_equals( command, CMD_STR_KEYER " output_active_low " DISABLE_STR ) ) { // Set output to active high keyer_set_output_active_low( false ); } - else if( string_equals( c, " " stringize( KEYER_PADDLE_MODE_IAMBIC ) ) ) + else if( string_equals( command, CMD_STR_KEYER " " stringize( KEYER_PADDLE_MODE_IAMBIC ) ) ) { // Set to iambic mode keyer_set_paddle_mode( KEYER_PADDLE_MODE_IAMBIC ); } - else if( string_equals( c, " " stringize( KEYER_PADDLE_MODE_ULTIMATIC ) ) ) + else if( string_equals( command, CMD_STR_KEYER " " stringize( KEYER_PADDLE_MODE_ULTIMATIC ) ) ) { // Set to ultimatic mode keyer_set_paddle_mode( KEYER_PADDLE_MODE_ULTIMATIC ); } - else if( string_equals( c, " " stringize( KEYER_PADDLE_MODE_ULTIMATIC_ALTERNATE ) ) ) + else if( string_equals( command, CMD_STR_KEYER " " stringize( KEYER_PADDLE_MODE_ULTIMATIC_ALTERNATE ) ) ) { // Set to ultimatic alternate mode keyer_set_paddle_mode( KEYER_PADDLE_MODE_ULTIMATIC_ALTERNATE ); } - else if( string_equals( c, " paddle_invert " ENABLE_STR ) ) + else if( string_equals( command, CMD_STR_KEYER " paddle_invert " ENABLE_STR ) ) { // Set invert paddles to true keyer_set_paddle_invert( true ); } - else if( string_equals( c, " paddle_invert " DISABLE_STR ) ) + else if( string_equals( command, CMD_STR_KEYER " paddle_invert " DISABLE_STR ) ) { // Set invert paddles to false keyer_set_paddle_invert( false ); @@ -592,7 +737,7 @@ static void exec_command_keyer( char const * const command ) } // Print status info - debug_port_printf( "Keyer: %s (%s - %s)" NEWLINE_STR, + debug_port_printf( CMD_STR_KEYER ": %s (%s - %s)" NEWLINE_STR, keyer_get_on() ? ON_STR : OFF_STR, s_paddle_mode_tbl[ keyer_get_paddle_mode() ], keyer_get_paddle_invert() ? "inverted" : "normal" ); @@ -602,6 +747,10 @@ static void exec_command_keyer( char const * const command ) static void exec_command_led( char const * const command ) { + // + // TODO: UPDATE TO NEW STYLE FOR THESE FUNCTIONS + // + // Local constants static char const * s_led_tbl[] = { @@ -632,7 +781,7 @@ static void exec_command_led( char const * const command ) } if( led == LED_COUNT ) { - print_invalid_command( c ); + print_invalid_command( command ); return; } @@ -669,6 +818,7 @@ static void exec_command_led( char const * const command ) static void exec_command_panic( char const * const command ) { ( void )command; + keyer_panic(); debug_port_print( "Stopped keyer." NEWLINE_STR ); @@ -678,7 +828,8 @@ static void exec_command_panic( char const * const command ) static void exec_command_tick( char const * const command ) { ( void )command; - debug_port_printf( "Tick: %lu" NEWLINE_STR, sys_get_tick() ); + + debug_port_printf( CMD_STR_TICK ": %lu" NEWLINE_STR, sys_get_tick() ); } /* exec_command_tick() */ @@ -704,31 +855,32 @@ static void exec_command_version( char const * const command ) static void exec_command_wpm( char const * const command ) { - // Drop "wpm" prefix - char const * c = command + 3; + // Scanned variables + unsigned int wpm; + int sscanf_count; - unsigned long wpm; - if( string_is_empty( c ) ) + // Parse subcommand + if( string_equals( command, CMD_STR_WPM ) ) { - // No subcommand - interpret as a status request. no action required + // No subcommand - interpret as a status request. No action required. } - else if( ( * c ) == ' ' && read_ulong( c + 1, & wpm ) ) + else if( sscanf( command + 4, "%u %n", & wpm, & sscanf_count ) == 1 && + ( command + 4 )[ sscanf_count ] == NULL_CHAR ) { - if( wpm >= WPM_MINIMUM && wpm <= WPM_MAXIMUM ) - { - wpm_set( ( wpm_t )wpm ); - } - else + // Set WPM + if( wpm < WPM_MINIMUM || wpm > WPM_MAXIMUM ) { - debug_port_print( "Invalid WPM: \"" ); - debug_port_print( c + 1 ); - debug_port_print( "\". Must be between " stringize_value( WPM_MINIMUM ) - " and " stringize_value( WPM_MAXIMUM ) "." NEWLINE_STR ); + debug_port_printf( "Invalid WPM: \"%u\". Must be between " + stringize_value( WPM_MINIMUM ) " and " + stringize_value( WPM_MAXIMUM ) "." NEWLINE_STR, + wpm ); return; } + wpm_set( ( wpm_t )wpm ); } else { + // Unrecognized command? print_invalid_command( command ); return; } @@ -736,7 +888,7 @@ static void exec_command_wpm( char const * const command ) // Print status info tick_t dot, dash; wpm_ticks( wpm_get(), & dot, & dash, NULL, NULL, NULL ); - debug_port_printf( "WPM: %u (%u.%u wpm - dot %lu ms, dash %lu ms)" NEWLINE_STR, + debug_port_printf( CMD_STR_WPM ": %u (%u.%u wpm - dot %lu ms, dash %lu ms)" NEWLINE_STR, wpm_get(), wpm_get() / 10, wpm_get() % 10, @@ -755,32 +907,6 @@ static void print_invalid_command( char const * const command ) } /* print_invalid_command() */ -static bool read_long( char const * buf, long * value ) -{ - char * end; - long parsed_value = strtol( buf, & end, 10 ); - if( end == buf || * end != NULL_CHAR ) - return( false ); - - *value = parsed_value; - return( true ); - -} /* read_long() */ - - -static bool read_ulong( char const * buf, unsigned long * value ) -{ - char * end; - unsigned long parsed_value = strtoul( buf, & end, 10 ); - if( end == buf || * end != NULL_CHAR ) - return( false ); - - *value = parsed_value; - return( true ); - -} /* read_ulong() */ - - static bool string_begins_with( char const * str, char const * token ) { return( ! strncasecmp( str, token, strlen( token ) ) ); diff --git a/src/main/application/storage.c b/src/main/application/storage.c new file mode 100644 index 0000000..f319316 --- /dev/null +++ b/src/main/application/storage.c @@ -0,0 +1,128 @@ +/** + * @file src/main/application/storage.c + * @brief Implementation for the application-specific storage module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-26 + */ + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include +#include + +#include "application/storage.h" +#include "drivers/eeprom.h" +#include "utility/crc.h" +#include "utility/types.h" + +/* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ + +// Same for all layout verions +#define EEPROM_ADDR_RESERVED_START 0x0000 +#define EEPROM_ADDR_LAYOUT_VERSION ( EEPROM_ADDR_RESERVED_START ) +#define EEPROM_ADDR_RESERVED_END EEPROM_ADDR_RESERVED_START + 0x0100 + +// EEPROM addresses for version 1 +#define EEPROM_ADDR_V1_CONFIG_BASE EEPROM_ADDR_RESERVED_END +#define EEPROM_ADDR_V1_CONFIG_VERSION ( EEPROM_ADDR_V1_CONFIG_BASE ) +#define EEPROM_ADDR_V1_CONFIG_SIZE ( EEPROM_ADDR_V1_CONFIG_VERSION + sizeof( config_version_t ) ) +#define EEPROM_ADDR_V1_CONFIG_CRC16 ( EEPROM_ADDR_V1_CONFIG_SIZE + sizeof( size_t ) ) +#define EEPROM_ADDR_V1_CONFIG_DATA ( EEPROM_ADDR_V1_CONFIG_CRC16 + sizeof( crc16_t ) ) +#define EEPROM_ADDR_V1_CONFIG_END EEPROM_ADDR_V1_CONFIG_BASE + 0x0100 +_Static_assert( EEPROM_ADDR_V1_CONFIG_DATA + sizeof( config_t ) < EEPROM_ADDR_V1_CONFIG_END, + "Not enough space allocated!" ); + +// EEPROM addresses for current version +#define EEPROM_ADDR_CONFIG_VERSION EEPROM_ADDR_V1_CONFIG_VERSION +#define EEPROM_ADDR_CONFIG_SIZE EEPROM_ADDR_V1_CONFIG_SIZE +#define EEPROM_ADDR_CONFIG_CRC16 EEPROM_ADDR_V1_CONFIG_CRC16 +#define EEPROM_ADDR_CONFIG_DATA EEPROM_ADDR_V1_CONFIG_DATA + +/* ----------------------------------------------------- TYPES ------------------------------------------------------ */ + +/** + * @typedef layout_version_t + * @brief Enumeration of supported storage layout versions. + */ +typedef uint8_t layout_version_t; +enum +{ + LAYOUT_VERSION_INVALID_0 = 0x00, + LAYOUT_VERSION_INVALID_1 = 0xFF, + + LAYOUT_VERSION_1 = 0x01, + + LAYOUT_VERSION_CURRENT + = LAYOUT_VERSION_1, +}; + +/* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ + +/** + * @fn reinit_layout( void ) + * @brief Re-initializes the storage layout. + */ +static void reinit_layout( void ); + +/* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ + +bool storage_get_config( config_version_t version, size_t size, void * config ) +{ + // Validate version + config_version_t check_version; + eeprom_read( EEPROM_ADDR_CONFIG_VERSION, & check_version, sizeof( config_version_t ) ); + if( check_version != version ) + return( false ); + + // Validate size + size_t check_size; + eeprom_read( EEPROM_ADDR_CONFIG_SIZE, & check_size, sizeof( size_t ) ); + if( check_size != size ) + return( false ); + + // Read config + eeprom_read( EEPROM_ADDR_CONFIG_DATA, config, size ); + + // Validate CRC + crc16_t check_crc; + eeprom_read( EEPROM_ADDR_CONFIG_CRC16, & check_crc, sizeof( crc16_t ) ); + if( check_crc != crc_calc_crc16( config, sizeof( config_t ) ) ) + { + memset( config, 0, size ); + return( false ); + } + + return( true ); + +} /* storage_get_config() */ + + +void storage_init( void ) +{ + // Confirm we have the right layout version + layout_version_t layout = ( layout_version_t )eeprom_read_byte( EEPROM_ADDR_LAYOUT_VERSION ); + if( layout != LAYOUT_VERSION_CURRENT ) + reinit_layout(); + +} /* storage_init() */ + + +void storage_set_config( config_version_t version, size_t size, void * config ) +{ + crc16_t crc = crc_calc_crc16( config, size ); + + eeprom_write( EEPROM_ADDR_CONFIG_VERSION, & version, sizeof( config_version_t ) ); + eeprom_write( EEPROM_ADDR_CONFIG_SIZE, & size, sizeof( size_t ) ); + eeprom_write( EEPROM_ADDR_CONFIG_CRC16, & crc, sizeof( crc16_t ) ); + eeprom_write( EEPROM_ADDR_CONFIG_DATA, config, size ); + +} /* storage_set_config() */ + + +static void reinit_layout( void ) +{ + // Write layout byte to required address + eeprom_write_byte( EEPROM_ADDR_LAYOUT_VERSION, ( byte_t )LAYOUT_VERSION_CURRENT ); + +} /* reinit_layout() */ diff --git a/src/main/application/storage.h b/src/main/application/storage.h new file mode 100644 index 0000000..ae97a59 --- /dev/null +++ b/src/main/application/storage.h @@ -0,0 +1,42 @@ +/** + * @file src/main/application/storage.h + * @brief Header for the application-specific storage module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-26 + */ + +#if !defined( APPLICATION_STORAGE_H ) +#define APPLICATION_STORAGE_H + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include +#include + +#include "application/config.h" + +/* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ + +/** + * @fn storage_get_config( config_t * ) + * @brief Gets the stored application configuration from storage. + * @returns `true` if the configuration was successfully retrieved. + * @note This will return `false` if the version number does not match the specified value, if the stored size is + * invalid, or if the CRC does not match. + */ +bool storage_get_config( config_version_t version, size_t size, void * config ); + +/** + * @fn storage_init( void ) + * @brief Initializes the storage module. + */ +void storage_init( void ); + +/** + * @fn storage_set_config( config_t const * ) + * @brief Stores the specified configuration. + */ +void storage_set_config( config_version_t version, size_t size, void * config ); + +#endif /* !defined( APPLICATION_STORAGE_H ) */ diff --git a/src/main/core/main.c b/src/main/core/main.c index fb7a48b..113c511 100644 --- a/src/main/core/main.c +++ b/src/main/core/main.c @@ -21,7 +21,9 @@ #include "application/input.h" #include "application/keyer.h" #include "application/led.h" +#include "application/storage.h" #include "core/sys.h" +#include "drivers/eeprom.h" #include "drivers/gpio.h" #include "drivers/usart.h" #include "utility/debug.h" @@ -193,8 +195,9 @@ static void handle_usart_tx_complete( usart_t usart ) static void init( void ) { // Initialize all system modules - config_init(); sys_init(); + storage_init(); + config_init(); led_init(); input_init(); buzzer_init(); @@ -248,6 +251,7 @@ static void periodic_50ms( tick_t tick ) static void periodic_1s( tick_t tick ) { // Periodic processing for modules with 1 Hz tick rates + config_tick( tick ); debug_port_tick( tick ); // Toggle the status LED. We're still alive! diff --git a/src/main/drivers/eeprom.c b/src/main/drivers/eeprom.c new file mode 100644 index 0000000..b952ad9 --- /dev/null +++ b/src/main/drivers/eeprom.c @@ -0,0 +1,339 @@ +/** + * @file src/main/drivers/eeprom.c + * @brief Implementation for the EEPROM driver module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-25 + */ + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include +#include +#include + +#include "core/sys.h" +#include "drivers/eeprom.h" +#include "utility/debug.h" +#include "utility/types.h" +#include "utility/utility.h" + +/* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ + +/** + * @def WRITE_BUF_SZ + * @brief The size for the write buffer. + * @note The largest object we currently write is the config structure, which is ~22 bytes (at time of this writing). + * Set the buffer size to 64 bytes as an initial approximation. + */ +#define WRITE_BUF_SZ 64 + +/** + * @def WRITE_DELAY_MS + * @brief Time to delay when waiting for space in write buffer. + * @note This is a guess and could be tuned / modified. + */ +#define WRITE_DELAY_MS 2 + +/* --------------------------------------------------- VARIABLES ---------------------------------------------------- */ + +static struct +{ + eeprom_addr_t address; /**< Address to write to. */ + byte_t data; /**< Data to write. */ +} +s_write_buf[ WRITE_BUF_SZ ]; /**< Circular write buffer for EEPROM. */ +static volatile size_t s_write_head = 0; /**< Head position for circular buffer. */ +static volatile size_t s_write_tail = 0; /**< Tail position for circular buffer. */ + +/* ----------------------------------------------------- MACROS ----------------------------------------------------- */ + +/** + * @def validate_addr( _addr ) + * @brief Validates the specified `eeprom_addr_t`. + */ +#define validate_addr( _addr ) \ + assert_always( ( _addr ) < EEPROM_COUNT ) + +/* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ + +/** + * @fn get_data( void ) + * @brief Immediately returns the content of the EEDR data register. + */ +static byte_t get_data( void ); + +/** + * @fn ready_isr( void ) + * @brief Runs the ISR for the "EEPROM ready" interrupt. + */ +static void ready_isr( void ); + +/** + * @fn set_address( eeprom_addr_t ) + * @brief Sets the content of the EEAR address register. + */ +static void set_address( eeprom_addr_t addr ); + +/** + * @fn set_data( byte_t byte ) + * @brief Sets the content of the EEDR data register. + */ +static void set_data( byte_t byte ); + +/** + * @fn set_eeprom_interrupt_enabled( bool ) + * @brief Enables or disables the EEPROM ready interrupt. + */ +static void set_eeprom_interrupt_enabled( bool enabled ); + +/** + * @fn set_write_mode_erase_only( void ) + * @brief Sets the write mode to erase only. + */ +static void set_write_mode_erase_only( void ); + +/** + * @fn set_write_mode_erase_write( void ) + * @brief Sets the write mode to erase and write atomically. + */ +static void set_write_mode_erase_write( void ); + +/** + * @fn strobe_read_enable( void ) + * @brief Strobes the EERE bit to start an EEPROM read. + */ +static void strobe_read_enable( void ); + +/** + * @fn strobe_write_enable( void ) + * @brief Strobes the EEMPE and EEPE bits to start an EEPROM write. + */ +static void strobe_write_enable( void ); + +/** + * @fn wait_ready( void ) + * @brief Synchronously waits for the `EEPE` bit in `EECR` to clear, indicating that EEPROM is ready to read or write. + */ +static void wait_ready( void ); + +/** + * @fn write_buf_avail( void ) + * @brief Returns the number of bytes available in the write buffer. + */ +static size_t write_buf_avail( void ); + +/** + * @fn write_buf_count( void ) + * @brief Returns the number of bytes currently in the write buffer. + */ +static size_t write_buf_count( void ); + +/* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ + +void eeprom_erase_byte( eeprom_addr_t addr ) +{ + validate_addr( addr ); + + wait_ready(); + set_write_mode_erase_only(); + set_address( addr ); + + strobe_write_enable(); + +} /* eeprom_erase_byte() */ + + +void eeprom_read( eeprom_addr_t addr, void * buf, size_t size ) +{ + validate_addr( addr ); + assert_always( addr + size <= EEPROM_COUNT ); + + for( size_t idx = 0; idx < size; idx++ ) + ( ( uint8_t * )buf )[ idx ] = eeprom_read_byte( addr + idx ); + +} /* eeprom_read() */ + + +byte_t eeprom_read_byte( eeprom_addr_t addr ) +{ + validate_addr( addr ); + + wait_ready(); + set_address( addr ); + strobe_read_enable(); + + return( get_data() ); + +} /* eeprom_read_byte() */ + + +void eeprom_write( eeprom_addr_t addr, void const * buf, size_t size ) +{ + validate_addr( addr ); + assert_always( addr + size <= EEPROM_COUNT ); + + byte_t const * ptr = ( byte_t * )buf; + + // Get interrupt flag + bool intrpt_en = sys_intrpt_enabled(); + + // Loop as long as there's still data + while( size ) + { + // Wait until we have room available in the write buffer. + // If there's no room, re-enable interrupts and then wait long enough for a couple of bytes to write. + sys_cli(); + size_t avail = write_buf_avail(); + if( avail == 0 ) + { + sys_sei(); + _delay_ms( WRITE_DELAY_MS ); + continue; + } + + // Write as many bytes as will fit into buffer + while( size && avail ) + { + s_write_buf[ s_write_head ].address = addr++; + s_write_buf[ s_write_head ].data = *( ptr++ ); + increment_rollover( s_write_head, WRITE_BUF_SZ ); + size--; + avail--; + } + + // Enable interrupt to start writing + set_eeprom_interrupt_enabled( true ); + } + + // Restore original state of interrupt flag + sys_set_intrpt_enabled( intrpt_en ); + +} /* eeprom_write() */ + + +void eeprom_write_byte( eeprom_addr_t addr, byte_t byte ) +{ + validate_addr( addr ); + + wait_ready(); + set_write_mode_erase_write(); + set_address( addr ); + set_data( byte ); + + strobe_write_enable(); + +} /* eeprom_write_byte() */ + + +static byte_t get_data( void ) +{ + return( EEDR ); + +} /* get_data() */ + + +static void ready_isr( void ) +{ + // If no data is left, disable the interrupt and return + if( write_buf_count() == 0 ) + { + set_eeprom_interrupt_enabled( false ); + return; + } + + // Write the next byte + set_write_mode_erase_write(); + set_address( s_write_buf[ s_write_tail ].address ); + set_data( s_write_buf[ s_write_tail ].data ); + increment_rollover( s_write_tail, WRITE_BUF_SZ ); + strobe_write_enable(); + +} /* ready_isr() */ + + +static void set_address( eeprom_addr_t addr ) +{ + EEAR = addr; + +} /* set_address() */ + + +static void set_data( byte_t byte ) +{ + EEDR = byte; + +} /* set_data() */ + + +static void set_eeprom_interrupt_enabled( bool enabled ) +{ + assign_bit( EECR, EERIE, enabled ); + +} /* set_eeprom_interrupt_enabled() */ + + +static void set_write_mode_erase_only( void ) +{ + set_bit( EECR, EEPM0 ); + clear_bit( EECR, EEPM1 ); + +} /* set_write_mode_erase_only() */ + + +static void set_write_mode_erase_write( void ) +{ + clear_bit( EECR, EEPM0 ); + clear_bit( EECR, EEPM1 ); + +} /* set_write_mode_erase_write() */ + + +static void strobe_read_enable( void ) +{ + set_bit( EECR, EERE ); + +} /* strobe_read_enable() */ + + +static void strobe_write_enable( void ) +{ + // The write to EEPE must happen within 4 clock cycles of the write to EEMPE. + // Clear interrupt flag while writing bits to prevent this from being interrupted. + sys_cli(); + set_bit( EECR, EEMPE ); // enable writes + set_bit( EECR, EEPE ); // command write + sys_sei(); + +} /* strobe_write_enable() */ + + +static void wait_ready( void ) +{ + wait_bit_clear( EECR, EEPE ); + +} /* wait_ready() */ + + +static size_t write_buf_avail( void ) +{ + return( WRITE_BUF_SZ - write_buf_count() - 1 ); + +} /* write_buf_avail() */ + + +static size_t write_buf_count( void ) +{ + if( s_write_head >= s_write_tail ) + return( s_write_head - s_write_tail ); + else + return( WRITE_BUF_SZ - s_write_tail + s_write_head ); + +} /* write_buf_count() */ + + +ISR( EE_READY_vect ) +{ + ready_isr(); + +} /* ISR( EE_READY_vect ) */ diff --git a/src/main/drivers/eeprom.h b/src/main/drivers/eeprom.h new file mode 100644 index 0000000..875eaa8 --- /dev/null +++ b/src/main/drivers/eeprom.h @@ -0,0 +1,69 @@ +/** + * @file src/main/drivers/eeprom.h + * @brief Header for the EEPROM driver module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-25 + */ + +#if !defined( DRIVERS_EEPROM_H ) +#define DRIVERS_EEPROM_H + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include + +#include "utility/types.h" + +/* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ + +/** + * @def EEPROM_COUNT + * @brief The number of bytes of EEPROM memory available. + */ +#define EEPROM_COUNT 4096 + +/* ----------------------------------------------------- TYPES ------------------------------------------------------ */ + +/** + * @typedef eeprom_addr_t + * @brief Typedef representing an EEPROM byte address. + */ +typedef uint16_t eeprom_addr_t; + +/* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ + +/** + * @fn eeprom_erase_byte( eeprom_addr_t addr ) + * @brief Synchronously erases the byte at the specified EEPROM address. + * @note This function blocks until the EEPROM is ready to write data. + */ +void eeprom_erase_byte( eeprom_addr_t addr ); + +/** + * @fn eeprom_read( eeprom_addr_t, byte_t *, size_t ) + * @brief Reads the specified number of bytes from the specified EEPROM address into a buffer. + */ +void eeprom_read( eeprom_addr_t addr, void * buf, size_t size ); + +/** + * @fn eeprom_read_byte( eeprom_addr_t addr ) + * @brief Returns the byte at the specified EEPROM address. + * @note This function blocks until the EEPROM is ready to read data. + */ +byte_t eeprom_read_byte( eeprom_addr_t addr ); + +/** + * @fn eeprom_write( eeprom_addr_t, byte_t const *, size_t ) + * @brief Writes the specified data buffer to the specified EEPROM address. + */ +void eeprom_write( eeprom_addr_t addr, void const * buf, size_t size ); + +/** + * @fn eeprom_write_byte( eeprom_addr_t addr, byte_t byte ) + * @brief Synchronously writes the byte at the specified EEPROM address. + * @note This function blocks until the EEPROM is ready to write data. + */ +void eeprom_write_byte( eeprom_addr_t addr, byte_t byte ); + +#endif /* !defined( DRIVERS_EEPROM_H ) */ diff --git a/src/main/drivers/usart.c b/src/main/drivers/usart.c index cf64ed6..d1dad2f 100644 --- a/src/main/drivers/usart.c +++ b/src/main/drivers/usart.c @@ -397,10 +397,6 @@ size_t usart_tx( usart_t usart, byte_t const * data, size_t size, usart_wait_mod validate_usart( usart ); validate_wait_mode( wait_mode ); - // Validate size - if( size == 0 ) - return( true ); - // Get interrupt flag bool intrpt_en = sys_intrpt_enabled(); diff --git a/src/main/utility/crc.c b/src/main/utility/crc.c new file mode 100644 index 0000000..b511714 --- /dev/null +++ b/src/main/utility/crc.c @@ -0,0 +1,58 @@ +/** + * @file src/main/utility/crc.c + * @brief Implementation for the CRC module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-26 + */ + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include +#include + +#include + +#include "utility/crc.h" +#include "utility/types.h" + +/* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ + +/** + * @def CRC8_SEED + * @brief The seed value for 8-bit CRCs. + */ +#define CRC8_SEED 0xFF + +/** + * @def CRC16_SEED + * @brief The seed value for 16-bit CRCs. + */ +#define CRC16_SEED 0xFFFF + +/* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ + +crc8_t crc_calc_crc8( void const * buf, size_t size ) +{ + byte_t const * ptr = ( byte_t const * )buf; + + crc8_t crc = CRC8_SEED; + while( size-- ) + crc = _crc8_ccitt_update( crc, *( ptr++ ) ); + + return( crc ); + +} /* crc_calc_crc8() */ + + +crc16_t crc_calc_crc16( void const * buf, size_t size ) +{ + byte_t const * ptr = ( byte_t const * )buf; + + crc16_t crc = CRC16_SEED; + while( size-- ) + crc = _crc16_update( crc, *( ptr++ ) ); + + return( crc ); + +} /* crc_calc_crc16() */ diff --git a/src/main/utility/crc.h b/src/main/utility/crc.h new file mode 100644 index 0000000..7a465a8 --- /dev/null +++ b/src/main/utility/crc.h @@ -0,0 +1,45 @@ +/** + * @file src/main/utility/crc.h + * @brief Header for the CRC module. + * + * @author Chris Vig (chris@invictus.so) + * @date 2025-08-26 + */ + +#if !defined( UTILITY_CRC_H ) +#define UTILITY_CRC_H + +/* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ + +#include +#include + +/* ----------------------------------------------------- TYPES ------------------------------------------------------ */ + +/** + * @typedef crc8_t + * @brief Typedef for an 8-bit CRC. + */ +typedef uint8_t crc8_t; + +/** + * @typedef crc16_t + * @brief Typedef for a 16-bit CRC. + */ +typedef uint16_t crc16_t; + +/* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ + +/** + * @fn crc_calc_crc8( void const *, size_t ) + * @brief Calculates an 8-bit CRC over the specified buffer. + */ +crc8_t crc_calc_crc8( void const * buf, size_t size ); + +/** + * @fn crc_calc_crc16( void const *, size_t ) + * @brief Calculates a 16-bit CRC over the specified buffer. + */ +crc16_t crc_calc_crc16( void const * buf, size_t size ); + +#endif /* !defined( UTILITY_CRC_H ) */