// ======================================== // Manage an Arduboy's system EEPROM area // ======================================== /* ------------------------------------------------------------------------------ This sketch allows manipulation of the following values in system EEPROM: - The Unit Name. This is a 6 character field that can be read by sketches using the readUnitName() function. Also, the Arduboy2 class will display the Unit Name at the end of the boot logo sequence, if possible, during start up. - The Unit ID. This is a 16 bit value that can be read by sketches using the readUnitID() function. - The "Show Unit Name" flag. This flag indicates whether or not the Unit Name should be displayed at the end of the boot logo sequence in circumstances where it's possible to do so. - The "Show RGB LEDs with Boot Logo" flag. This flag indicates whether or not to flash the RGB LEDs while the boot logo is scrolling down. - The "Show Boot Logo" flag. This flag indicates whether or not to display the boot logo sequence during start up. This sketch also allows: - The entire System EEPROM area to be reset back to default values. - The entire User EEPROM area to be reset. This will clear the high scores and any other data saved by ALL sketches that have ever been installed. ------------------------------------------------------------------------------ */ // Version 2.2 /* ------------------------------------------------------------------------------ Copyright (c) 2018-2020, Scott Allen All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------ */ #include #include // The frame rate determines the button auto-repeat rate for unit name entry constexpr uint8_t frameRate = 10; // The unit ID auto-repeat rate is slowed, compared to the unit name rate, by // repeating only once per the defined number of frames constexpr unsigned int idRepeatFrames = 3; // Delay time before button auto-repeat starts, in milliseconds constexpr unsigned int repeatDelay = 700; // All the constant stings const char StrName[] PROGMEM = "NAME"; const char StrID[] PROGMEM = "ID"; const char StrFlags[] PROGMEM = "FLAGS"; const char StrYes[] PROGMEM = "YES"; const char StrNo[] PROGMEM = "NO"; const char StrSaveQ[] PROGMEM = "SAVE?"; const char StrSaved[] PROGMEM = "SAVED"; const char StrBtnChangeName[] PROGMEM = "UP:change Unit Name"; const char StrBtnChangeID[] PROGMEM = "DOWN:change Unit ID"; const char StrBtnFlags[] PROGMEM = "LEFT:flags"; const char StrBtnReset[] PROGMEM = "RIGHT:reset"; const char StrBtnMenu[] PROGMEM = "A:menu"; const char StrBtnSave[] PROGMEM = "B:save"; const char StrBtnYes[] PROGMEM = "A:yes"; const char StrBtnNo[] PROGMEM = "B:no"; const char StrShowLogoQ[] PROGMEM = "show boot logo?"; const char StrShowLEDsQ[] PROGMEM = "show boot LEDs?"; const char StrShowNameQ[] PROGMEM = "show unit name?"; const char StrBtnTestLogo[] PROGMEM = "UP+DOWN:test logo"; const char StrNoLogo1[] PROGMEM = "\"SHOW BOOT LOGO\""; const char StrNoLogo2[] PROGMEM = "flag is OFF"; const char StrBtnResetSys[] PROGMEM = "UP:reset system"; const char StrBtnResetUser[] PROGMEM = "Down:reset user"; const char StrEEPROM[] PROGMEM = "EEPROM"; const char StrResetSys1[] PROGMEM = "EEPROM reserved for"; const char StrResetSys2[] PROGMEM = "system use will be"; const char StrResetSys3[] PROGMEM = "reset to defaults!"; const char StrResetUser1[] PROGMEM = "EEPROM containing"; const char StrResetUser2[] PROGMEM = "ALL saved sketch data"; const char StrResetUser3[] PROGMEM = "will be erased!"; const char StrAreYouSureQ[] PROGMEM = "ARE YOU SURE?"; const char StrBtnResetYes[] PROGMEM = "YES:hold A, press B"; const char StrBtnResetNo[] PROGMEM = "NO:any D-pad button"; const char StrWriting[] PROGMEM = "WRITING..."; const char StrSystem[] PROGMEM = "SYSTEM"; const char StrUser[] PROGMEM = "USER"; const char StrReset[] PROGMEM = "RESET"; const char StrHex[] PROGMEM = "hex"; const char StrDecimal[] PROGMEM = "decimal"; // Defines for text and field locations #define MENU_BTN_CHANGE_NAME_X centerStr_P(StrBtnChangeName) #define MENU_BTN_CHANGE_NAME_Y 0 #define MENU_NAME_X centerStrLen(ARDUBOY_UNIT_NAME_LEN) #define MENU_NAME_Y (MENU_BTN_CHANGE_NAME_Y + charHeight + 3) #define MENU_BTN_CHANGE_ID_X centerStr_P(StrBtnChangeID) #define MENU_BTN_CHANGE_ID_Y 26 #define MENU_HEADING_HEX_X (centerStr_P(StrHex) - (WIDTH / 4)) #define MENU_HEADING_DECIMAL_X (centerStr_P(StrDecimal) + (WIDTH / 4)) #define MENU_HEADINGS_Y (MENU_BTN_CHANGE_ID_Y + charHeight + 1) #define MENU_ID_HEX_X (centerStrLen(5) - (WIDTH / 4)) #define MENU_ID_DECIMAL_X (centerStrLen(5) + (WIDTH / 4)) #define MENU_ID_Y (MENU_HEADINGS_Y + charHeight + 1) #define MENU_BTN_FLAGS_X 0 #define MENU_BTN_FLAGS_Y 56 #define MENU_BTN_RESET_X rightStr_P(StrBtnReset) #define MENU_BTN_RESET_Y MENU_BTN_FLAGS_Y #define NAME_TITLE_X centerStr_P(StrName) #define NAME_TITLE_Y 0 #define NAME_BTN_MENU_X 0 #define NAME_BTN_MENU_Y 0 #define NAME_BTN_SAVE_X rightStr_P(StrBtnSave) #define NAME_BTN_SAVE_Y NAME_BTN_MENU_Y #define NAME_LARGE_X centerStrLen(ARDUBOY_UNIT_NAME_LEN * 2) #define NAME_LARGE_Y 11 #define NAME_HEX_X 0 #define NAME_HEX_Y 40 #define NAME_DECIMAL_X 0 #define NAME_DECIMAL_Y 54 #define NAME_BTN_YES_X 0 #define NAME_BTN_YES_Y 0 #define NAME_BTN_NO_X rightStr_P(StrBtnNo) #define NAME_BTN_NO_Y NAME_BTN_YES_Y #define NAME_SAVE_Q_X (centerStr_P(StrSaveQ) - ((6 * charWidth) + (charWidth / 2))) #define NAME_SAVE_Q_Y (NAME_LARGE_Y + (charHeight / 2) + 3) #define NAME_SAVE_X (NAME_SAVE_Q_X + ((strlen_P(StrSaveQ) * charWidth) + charWidth)) #define NAME_SAVE_Y (NAME_LARGE_Y + 3) #define ID_TITLE_X centerStr_P(StrID) #define ID_TITLE_Y 0 #define ID_BTN_MENU_X 0 #define ID_BTN_MENU_Y 0 #define ID_BTN_SAVE_X rightStr_P(StrBtnSave) #define ID_BTN_SAVE_Y ID_BTN_MENU_Y #define ID_LARGE_X centerStrLen(5 * 2) #define ID_LARGE_Y 13 #define ID_2_DECIMAL_X 12 #define ID_2_DECIMAL_Y 38 #define ID_DECIMAL_X (WIDTH - (charWidth * 5 + 12)) #define ID_DECIMAL_Y ID_2_DECIMAL_Y #define ID_BINARY_X 0 #define ID_BINARY_Y 54 #define ID_BTN_YES_X 0 #define ID_BTN_YES_Y 0 #define ID_BTN_NO_X rightStr_P(StrBtnNo) #define ID_BTN_NO_Y ID_BTN_YES_Y #define ID_SAVE_Q_X (centerStr_P(StrSaveQ) - ((5 * charWidth) + (charWidth / 2))) #define ID_SAVE_Q_Y (ID_LARGE_Y + (charHeight / 2) + 1) #define ID_SAVE_X (ID_SAVE_Q_X + ((strlen_P(StrSaveQ) * charWidth) + charWidth)) #define ID_SAVE_Y (ID_LARGE_Y + 1) #define FLAGS_TITLE_X centerStr_P(StrFlags) #define FLAGS_TITLE_Y 0 #define FLAGS_BTN_MENU_X 0 #define FLAGS_BTN_MENU_Y 0 #define FLAGS_BTN_SAVE_X rightStr_P(StrBtnSave) #define FLAGS_BTN_SAVE_Y FLAGS_BTN_MENU_Y #define FLAGS_LOGO_Y 14 #define FLAGS_LEDS_Y 27 #define FLAGS_NAME_Y 40 #define FLAGS_Q_X (charWidth * 2) #define FLAGS_SET_X rightStr_P(StrYes) #define FLAGS_CURSOR_X 0 #define FLAGS_TEST_X centerStr_P(StrBtnTestLogo) #define FLAGS_TEST_Y 56 #define FLAGS_SAVED_X centerStr2_P(StrSaved) #define FLAGS_SAVED_Y ((HEIGHT / 2) - (char2Height / 2)) #define FLAGS_NO_LOGO_1_X centerStr_P(StrNoLogo1) #define FLAGS_NO_LOGO_1_Y ((HEIGHT / 2) - charHeight - 1) #define FLAGS_NO_LOGO_2_X centerStr_P(StrNoLogo2) #define FLAGS_NO_LOGO_2_Y (FLAGS_NO_LOGO_1_Y + charHeight + 2) #define RESET_BTN_MENU_X centerStr_P(StrBtnMenu) #define RESET_BTN_MENU_Y 0 #define RESET_BTN_SYS_X centerStr_P(StrBtnResetSys) #define RESET_BTN_SYS_Y 16 #define RESET_BTN_SYS_EEPROM_X centerStr_P(StrEEPROM) #define RESET_BTN_SYS_EEPROM_Y (RESET_BTN_SYS_Y + charHeight) #define RESET_BTN_USER_X centerStr_P(StrBtnResetUser) #define RESET_BTN_USER_Y 40 #define RESET_BTN_USER_EEPROM_X centerStr_P(StrEEPROM) #define RESET_BTN_USER_EEPROM_Y (RESET_BTN_USER_Y + charHeight) #define RESET_SYS_TEXT_1_X centerStr_P(StrResetSys1) #define RESET_SYS_TEXT_1_Y 0 #define RESET_SYS_TEXT_2_X centerStr_P(StrResetSys2) #define RESET_SYS_TEXT_2_Y (RESET_SYS_TEXT_1_Y + charHeight) #define RESET_SYS_TEXT_3_X centerStr_P(StrResetSys3) #define RESET_SYS_TEXT_3_Y (RESET_SYS_TEXT_2_Y + charHeight) #define RESET_SYS_SURE_Q_X centerStr_P(StrAreYouSureQ) #define RESET_SYS_SURE_Q_Y 32 #define RESET_SYS_BTN_YES_X centerStr_P(StrBtnResetYes) #define RESET_SYS_BTN_YES_Y 48 #define RESET_SYS_BTN_NO_X centerStr_P(StrBtnResetYes) #define RESET_SYS_BTN_NO_Y (RESET_SYS_BTN_YES_Y + charHeight) #define RESET_SYS_CONFIRMED_1_X centerStr2_P(StrSystem) #define RESET_SYS_CONFIRMED_1_Y 7 #define RESET_SYS_CONFIRMED_2_X centerStr2_P(StrEEPROM) #define RESET_SYS_CONFIRMED_2_Y (RESET_SYS_CONFIRMED_1_Y + char2Height + 2) #define RESET_SYS_CONFIRMED_3_X centerStr2_P(StrReset) #define RESET_SYS_CONFIRMED_3_Y (RESET_SYS_CONFIRMED_2_Y + char2Height + 2) #define RESET_USER_TEXT_1_X centerStr_P(StrResetUser1) #define RESET_USER_TEXT_1_Y 0 #define RESET_USER_TEXT_2_X centerStr_P(StrResetUser2) #define RESET_USER_TEXT_2_Y (RESET_USER_TEXT_1_Y + charHeight) #define RESET_USER_TEXT_3_X centerStr_P(StrResetUser3) #define RESET_USER_TEXT_3_Y (RESET_USER_TEXT_2_Y + charHeight) #define RESET_USER_SURE_Q_X centerStr_P(StrAreYouSureQ) #define RESET_USER_SURE_Q_Y 32 #define RESET_USER_BTN_YES_X centerStr_P(StrBtnResetYes) #define RESET_USER_BTN_YES_Y 48 #define RESET_USER_BTN_NO_X centerStr_P(StrBtnResetYes) #define RESET_USER_BTN_NO_Y (RESET_USER_BTN_YES_Y + charHeight) #define RESET_USER_CONFIRMED_1_X centerStr2_P(StrUser) #define RESET_USER_CONFIRMED_1_Y 7 #define RESET_USER_CONFIRMED_2_X centerStr2_P(StrEEPROM) #define RESET_USER_CONFIRMED_2_Y (RESET_USER_CONFIRMED_1_Y + char2Height + 2) #define RESET_USER_CONFIRMED_3_X centerStr2_P(StrReset) #define RESET_USER_CONFIRMED_3_Y (RESET_USER_CONFIRMED_2_Y + char2Height + 2) #define RESET_USER_WRITING_X centerStr2_P(StrWriting) #define RESET_USER_WRITING_Y ((HEIGHT / 2) - (char2Height / 2 - 1)) // EEPROM addresses constexpr unsigned int EEPROMstart = 0x0000; constexpr unsigned int EEPROMsize = 1024; constexpr unsigned int EEPROMend = EEPROMstart + EEPROMsize - 1; // Calculation of the number of frames to wait before button auto-repeat starts constexpr unsigned int delayFrames = repeatDelay / (1000 / frameRate); // The Arduino "magic" has trouble creating prototypes for functions called // by pointers, so they're declared here manually void stateMain(), stateName(), stateID(), stateFlags(), stateReset(); void stateSaveName(), stateSaveID(), stateResetSys(), stateResetUser(); void screenMain(), screenName(), screenID(), screenFlags(), screenReset(); void screenSaveName(), screenSaveID(), screenResetSys(), screenResetUser(); Arduboy2 arduboy; constexpr uint8_t charSpacing = arduboy.getCharacterSpacing(1); constexpr uint8_t char2Spacing = arduboy.getCharacterSpacing(2); constexpr uint8_t charWidth = arduboy.getCharacterWidth(1) + charSpacing; constexpr uint8_t char2Width = arduboy.getCharacterWidth(2) + char2Spacing; constexpr uint8_t charHeight = arduboy.getCharacterHeight(1) + arduboy.getLineSpacing(1); constexpr uint8_t char2Height = arduboy.getCharacterHeight(2) + arduboy.getLineSpacing(2); // The number of pixels for a small space between groups constexpr uint8_t smallSpace = 4; char unitName[ARDUBOY_UNIT_NAME_BUFFER_SIZE]; uint8_t nameIndex; uint16_t unitID; uint8_t idIndex; bool showLogoFlag; bool showLEDsFlag; bool showNameFlag; // Selected flag enum class SelectedFlag : uint8_t { selFlagLogo, selFlagLEDs, selFlagName }; SelectedFlag currentFlag; // Assign numbers for each state/screen enum class State : uint8_t { sMain, sName, sID, sFlags, sReset, sSaveName, sSaveID, sResetSys, sResetUser, sMAX = sResetUser }; State currentState; // Function pointer array for button handling void (*stateFunc[static_cast(State::sMAX) + 1])() = { stateMain, stateName, stateID, stateFlags, stateReset, stateSaveName, stateSaveID, stateResetSys, stateResetUser }; // Function pointer array for screen drawing void (*screenFunc[static_cast(State::sMAX) + 1])() = { screenMain, screenName, screenID, screenFlags, screenReset, screenSaveName, screenSaveID, screenResetSys, screenResetUser }; unsigned int delayCount = 0; bool repeating = false; // ============================= SETUP =================================== void setup() { arduboy.begin(); arduboy.setFrameRate(frameRate); setState(State::sMain); } // ======================================================================= // =========================== MAIN LOOP ================================= void loop() { if (!arduboy.nextFrame()) { return; } arduboy.pollButtons(); (*stateFunc[static_cast(currentState)])(); if ((delayCount != 0) && (--delayCount == 0)) { repeating = true; } } // ======================================================================= // ------------------------- Program States ------------------------------ // Set to the given state and display the screen for that state // Can be called with the current state to update the current screen void setState(State newState) { currentState = newState; stopButtonRepeat(); drawScreen(); } // STATE: Main selection screen void stateMain() { if (arduboy.justPressed(UP_BUTTON)) { setState(State::sName); } else if (arduboy.justPressed(DOWN_BUTTON)) { setState(State::sID); } else if (arduboy.justPressed(LEFT_BUTTON)) { setState(State::sFlags); } else if (arduboy.justPressed(RIGHT_BUTTON)) { setState(State::sReset); } } // STATE: Change unit name void stateName() { if (arduboy.justPressed(UP_BUTTON)) { nameCharInc(); startButtonDelay(); } else if (arduboy.justPressed(DOWN_BUTTON)) { nameCharDec(); startButtonDelay(); } else if (repeating && arduboy.pressed(UP_BUTTON)) { nameCharInc(); } else if (repeating && arduboy.pressed(DOWN_BUTTON)) { nameCharDec(); } else if (arduboy.justPressed(RIGHT_BUTTON)) { nameCursorRight(); } else if (arduboy.justPressed(LEFT_BUTTON)) { nameCursorLeft(); } else if (arduboy.justPressed(A_BUTTON)) { setState(State::sMain); } else if (arduboy.justPressed(B_BUTTON)) { setState(State::sSaveName); } else if (repeating) { stopButtonRepeat(); } } // STATE: Change unit ID void stateID() { if (arduboy.justPressed(UP_BUTTON)) { idDigitInc(); startButtonDelay(); } else if (arduboy.justPressed(DOWN_BUTTON)) { idDigitDec(); startButtonDelay(); } else if (repeating && arduboy.pressed(UP_BUTTON)) { if (arduboy.everyXFrames(idRepeatFrames)) { idDigitInc(); } } else if (repeating && arduboy.pressed(DOWN_BUTTON)) { if (arduboy.everyXFrames(idRepeatFrames)) { idDigitDec(); } } else if (arduboy.justPressed(RIGHT_BUTTON)) { idCursorRight(); } else if (arduboy.justPressed(LEFT_BUTTON)) { idCursorLeft(); } else if (arduboy.justPressed(A_BUTTON)) { setState(State::sMain); } else if (arduboy.justPressed(B_BUTTON)) { setState(State::sSaveID); } else if (repeating) { stopButtonRepeat(); } } // STATE: Set system flags void stateFlags() { if (arduboy.pressed(UP_BUTTON + DOWN_BUTTON)) { showNameFlag = arduboy.readShowUnitNameFlag(); showLEDsFlag = arduboy.readShowBootLogoLEDsFlag(); if ((showLogoFlag = arduboy.readShowBootLogoFlag()) == true) { arduboy.bootLogo(); } else { displayNoLogo(); } currentFlag = SelectedFlag::selFlagLogo; setState(State::sFlags); } else if (arduboy.justPressed(UP_BUTTON)) { flagsCursorUp(); } else if (arduboy.justPressed(DOWN_BUTTON)) { flagsCursorDown(); } else if (arduboy.justPressed(RIGHT_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) { flagToggle(); } else if (arduboy.justPressed(A_BUTTON)) { setState(State::sMain); } else if (arduboy.justPressed(B_BUTTON)) { saveFlags(); setState(State::sFlags); } } // STATE: Reset EEPROM areas void stateReset() { if (arduboy.justPressed(UP_BUTTON)) { setState(State::sResetSys); } else if (arduboy.justPressed(DOWN_BUTTON)) { setState(State::sResetUser); } else if (arduboy.justPressed(A_BUTTON)) { setState(State::sMain); } } // STATE: Prompt to save the unit name void stateSaveName() { if (arduboy.justPressed(A_BUTTON)) { arduboy.writeUnitName(unitName); setState(State::sMain); } else if (arduboy.justPressed(B_BUTTON)) { setState(State::sName); } } // STATE: Prompt to save the unit ID void stateSaveID() { if (arduboy.justPressed(A_BUTTON)) { arduboy.writeUnitID(unitID); setState(State::sMain); } else if (arduboy.justPressed(B_BUTTON)) { setState(State::sID); } } // STATE: Prompt to reset system EEPROM void stateResetSys() { if (arduboy.justPressed(B_BUTTON) && arduboy.pressed(A_BUTTON)) { resetSysEEPROM(); setState(State::sReset); } else if (arduboy.justPressed(UP_BUTTON) || arduboy.justPressed(DOWN_BUTTON) || arduboy.justPressed(RIGHT_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) { setState(State::sReset); } } // STATE: Prompt to reset user EEPROM void stateResetUser() { if (arduboy.justPressed(B_BUTTON) && arduboy.pressed(A_BUTTON)) { resetUserEEPROM(); setState(State::sReset); } else if (arduboy.justPressed(UP_BUTTON) || arduboy.justPressed(DOWN_BUTTON) || arduboy.justPressed(RIGHT_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) { setState(State::sReset); } } // ------------------------- Screen Display ------------------------------ // Display the screen for the current state void drawScreen() { arduboy.clear(); (*screenFunc[static_cast(currentState)])(); arduboy.display(); } // DISPLAY: Main screen void screenMain() { readEEPROM(); nameIndex = idIndex = 0; currentFlag = SelectedFlag::selFlagLogo; printStr_P(MENU_BTN_CHANGE_NAME_X, MENU_BTN_CHANGE_NAME_Y, StrBtnChangeName); printName(MENU_NAME_X, MENU_NAME_Y); printStr_P(MENU_BTN_CHANGE_ID_X, MENU_BTN_CHANGE_ID_Y, StrBtnChangeID); printStr_P(MENU_HEADING_HEX_X, MENU_HEADINGS_Y, StrHex); printStr_P(MENU_HEADING_DECIMAL_X, MENU_HEADINGS_Y, StrDecimal); printIDHex(MENU_ID_HEX_X, MENU_ID_Y); printIDDecimal(MENU_ID_DECIMAL_X, MENU_ID_Y); printStr_P(MENU_BTN_FLAGS_X, MENU_BTN_FLAGS_Y, StrBtnFlags); printStr_P(MENU_BTN_RESET_X, MENU_BTN_RESET_Y, StrBtnReset); } // DISPLAY: Change unit name void screenName() { printNameScreenCommon(); printStr_P(NAME_BTN_MENU_X, NAME_BTN_MENU_Y, StrBtnMenu); printStr_P(NAME_BTN_SAVE_X, NAME_BTN_SAVE_Y, StrBtnSave); printNameLarge(NAME_LARGE_X, NAME_LARGE_Y); printNameUnderline(NAME_LARGE_X, NAME_LARGE_Y); printNameCursors(); } // DISPLAY: Change unit ID void screenID() { printIDScreenCommon(); printStr_P(ID_BTN_MENU_X, ID_BTN_MENU_Y, StrBtnMenu); printStr_P(ID_BTN_SAVE_X, ID_BTN_SAVE_Y, StrBtnSave); printIDLarge(ID_LARGE_X, ID_LARGE_Y); printIDCursors(); } // DISPLAY: Set system flags void screenFlags() { printStr_P(FLAGS_BTN_MENU_X, FLAGS_BTN_MENU_Y, StrBtnMenu); printStr_P(FLAGS_BTN_SAVE_X, FLAGS_BTN_SAVE_Y, StrBtnSave); printStr_P(FLAGS_TITLE_X, FLAGS_TITLE_Y, StrFlags); printStr_P(FLAGS_Q_X, FLAGS_LOGO_Y, StrShowLogoQ); printStr_P(FLAGS_Q_X, FLAGS_LEDS_Y, StrShowLEDsQ); printStr_P(FLAGS_Q_X, FLAGS_NAME_Y, StrShowNameQ); printFlagSettings(); printStr_P(FLAGS_TEST_X, FLAGS_TEST_Y, StrBtnTestLogo); } // DISPLAY: Reset EEPROM areas void screenReset() { printStr_P(RESET_BTN_MENU_X, RESET_BTN_MENU_Y, StrBtnMenu); printStr_P(RESET_BTN_SYS_X, RESET_BTN_SYS_Y, StrBtnResetSys); printStr_P(RESET_BTN_SYS_EEPROM_X, RESET_BTN_SYS_EEPROM_Y, StrEEPROM); printStr_P(RESET_BTN_USER_X, RESET_BTN_USER_Y, StrBtnResetUser); printStr_P(RESET_BTN_USER_EEPROM_X, RESET_BTN_USER_EEPROM_Y, StrEEPROM); } // DISPLAY: Prompt to save the unit name void screenSaveName() { printNameScreenCommon(); printStr_P(NAME_BTN_YES_X, NAME_BTN_YES_Y, StrBtnYes); printStr_P(NAME_BTN_NO_X, NAME_BTN_NO_Y, StrBtnNo); printSavePrompt(NAME_SAVE_Q_X, NAME_SAVE_Q_Y); printNameLarge(NAME_SAVE_X, NAME_SAVE_Y); } // DISPLAY: Prompt to save the unit ID void screenSaveID() { printIDScreenCommon(); printStr_P(ID_BTN_YES_X, ID_BTN_YES_Y, StrBtnYes); printStr_P(ID_BTN_NO_X, ID_BTN_NO_Y, StrBtnNo); printSavePrompt(ID_SAVE_Q_X, ID_SAVE_Q_Y); printIDLarge(ID_SAVE_X, ID_SAVE_Y); } // DISPLAY: Prompt to reset the system EEPROM area void screenResetSys() { printStr_P(RESET_SYS_TEXT_1_X, RESET_SYS_TEXT_1_Y, StrResetSys1); printStr_P(RESET_SYS_TEXT_2_X, RESET_SYS_TEXT_2_Y, StrResetSys2); printStr_P(RESET_SYS_TEXT_3_X, RESET_SYS_TEXT_3_Y, StrResetSys3); printStr_P(RESET_SYS_SURE_Q_X, RESET_SYS_SURE_Q_Y, StrAreYouSureQ); printStr_P(RESET_SYS_BTN_YES_X, RESET_SYS_BTN_YES_Y, StrBtnResetYes); printStr_P(RESET_SYS_BTN_NO_X, RESET_SYS_BTN_NO_Y, StrBtnResetNo); } // DISPLAY: Prompt to reset the user EEPROM area void screenResetUser() { printStr_P(RESET_USER_TEXT_1_X, RESET_USER_TEXT_1_Y, StrResetUser1); printStr_P(RESET_USER_TEXT_2_X, RESET_USER_TEXT_2_Y, StrResetUser2); printStr_P(RESET_USER_TEXT_3_X, RESET_USER_TEXT_3_Y, StrResetUser3); printStr_P(RESET_USER_SURE_Q_X, RESET_USER_SURE_Q_Y, StrAreYouSureQ); printStr_P(RESET_USER_BTN_YES_X, RESET_USER_BTN_YES_Y, StrBtnResetYes); printStr_P(RESET_USER_BTN_NO_X, RESET_USER_BTN_NO_Y, StrBtnResetNo); } // Display a message indicating "Show boot logo" flag is off void displayNoLogo() { arduboy.clear(); printStr_P(FLAGS_NO_LOGO_1_X, FLAGS_NO_LOGO_1_Y, StrNoLogo1); printStr_P(FLAGS_NO_LOGO_2_X, FLAGS_NO_LOGO_2_Y, StrNoLogo2); arduboy.display(); arduboy.delayShort(2000); } // Save the system flags and overlay the "SAVED" message on the screen void saveFlags() { arduboy.writeShowUnitNameFlag(showNameFlag); arduboy.writeShowBootLogoFlag(showLogoFlag); arduboy.writeShowBootLogoLEDsFlag(showLEDsFlag); printStrLargeRev_P(FLAGS_SAVED_X, FLAGS_SAVED_Y, StrSaved); arduboy.display(); arduboy.delayShort(1500); } // Reset the system EEPROM area and display the confirmation message void resetSysEEPROM() { for (unsigned int i = EEPROMstart; i < EEPROM_STORAGE_SPACE_START; i++) { EEPROM.update(i, 0xFF); } arduboy.clear(); printStrLargeRev_P(RESET_SYS_CONFIRMED_1_X, RESET_SYS_CONFIRMED_1_Y, StrSystem); printStrLargeRev_P(RESET_SYS_CONFIRMED_2_X, RESET_SYS_CONFIRMED_2_Y, StrEEPROM); printStrLargeRev_P(RESET_SYS_CONFIRMED_3_X, RESET_SYS_CONFIRMED_3_Y, StrReset); arduboy.display(); arduboy.delayShort(2000); } // Reset the user EEPROM area and display the confirmation message void resetUserEEPROM() { arduboy.clear(); arduboy.setTextSize(2); printStr_P(RESET_USER_WRITING_X, RESET_USER_WRITING_Y, StrWriting); arduboy.setTextSize(1); arduboy.display(CLEAR_BUFFER); for (unsigned int i = EEPROM_STORAGE_SPACE_START; i <= EEPROMend; i++) { EEPROM.update(i, 0xFF); } printStrLargeRev_P(RESET_USER_CONFIRMED_1_X, RESET_USER_CONFIRMED_1_Y, StrUser); printStrLargeRev_P(RESET_USER_CONFIRMED_2_X, RESET_USER_CONFIRMED_2_Y, StrEEPROM); printStrLargeRev_P(RESET_USER_CONFIRMED_3_X, RESET_USER_CONFIRMED_3_Y, StrReset); arduboy.display(); arduboy.delayShort(2000); } // --------------------- Printing Functions ------------------------------ // Print the name entry screen common information void printNameScreenCommon() { printStr_P(NAME_TITLE_X, NAME_TITLE_Y, StrName); printNameHex(NAME_HEX_X, NAME_HEX_Y); printNameDecimal(NAME_DECIMAL_X, NAME_DECIMAL_Y); } // Print the ID entry screen common information void printIDScreenCommon() { printStr_P(ID_TITLE_X, ID_TITLE_Y, StrID); printIDDecimalBytes(ID_2_DECIMAL_X, ID_2_DECIMAL_Y); printIDDecimal(ID_DECIMAL_X, ID_DECIMAL_Y); printIDBinary(ID_BINARY_X, ID_BINARY_Y); } // Print the name screen cursors void printNameCursors() { arduboy.fillRect(NAME_LARGE_X + (nameIndex * char2Width), NAME_LARGE_Y + char2Height + 2, char2Width - char2Spacing, 2); arduboy.drawFastHLine(NAME_HEX_X + (nameIndex * (charWidth * 3 + smallSpace)), NAME_HEX_Y + charHeight + 1, charWidth * 3 - charSpacing); arduboy.drawFastHLine(NAME_DECIMAL_X + (nameIndex * (charWidth * 3 + smallSpace)), NAME_DECIMAL_Y + charHeight + 1, charWidth * 3 - charSpacing); } // Print the ID screen cursors void printIDCursors() { arduboy.fillRect(ID_LARGE_X + ((idIndex + 1) * char2Width), ID_LARGE_Y + char2Height, char2Width - char2Spacing, 2); arduboy.drawFastHLine(ID_2_DECIMAL_X + ((idIndex / 2) * (charWidth * 3 + smallSpace)), ID_2_DECIMAL_Y + charHeight + 1, charWidth * 3 - charSpacing); arduboy.drawFastHLine(ID_DECIMAL_X, ID_DECIMAL_Y + charHeight + 1, charWidth * 5 - 1); arduboy.drawFastHLine((ID_BINARY_X + charWidth + smallSpace) + (idIndex * (charWidth * 4 + smallSpace)), ID_BINARY_Y + charHeight + 1, charWidth * 4 - charSpacing); } // Print the values and cursor for the flags void printFlagSettings() { int cursorY; uint8_t cursorLen = strlen_P(StrYes) * charWidth - charSpacing; if (showLogoFlag) { printStr_P(FLAGS_SET_X, FLAGS_LOGO_Y, StrYes); } else { printStr_P(FLAGS_SET_X, FLAGS_LOGO_Y, StrNo); } if (showLEDsFlag) { printStr_P(FLAGS_SET_X, FLAGS_LEDS_Y, StrYes); } else { printStr_P(FLAGS_SET_X, FLAGS_LEDS_Y, StrNo); } if (showNameFlag) { printStr_P(FLAGS_SET_X, FLAGS_NAME_Y, StrYes); } else { printStr_P(FLAGS_SET_X, FLAGS_NAME_Y, StrNo); } switch (currentFlag) { case SelectedFlag::selFlagLEDs: cursorY = FLAGS_LEDS_Y; if (!showLEDsFlag) { cursorLen = strlen_P(StrNo) * charWidth - charSpacing; } break; case SelectedFlag::selFlagName: cursorY = FLAGS_NAME_Y; if (!showNameFlag) { cursorLen = strlen_P(StrNo) * charWidth - charSpacing; } break; default: // selFlagLogo cursorY = FLAGS_LOGO_Y; if (!showLogoFlag) { cursorLen = strlen_P(StrNo) * charWidth - charSpacing; } break; } arduboy.setCursor(FLAGS_CURSOR_X, cursorY); arduboy.print('\x10'); arduboy.drawFastHLine(FLAGS_SET_X, cursorY + charHeight, cursorLen); } // Print the unit name in normal size including an extent underline // at the given location void printName(int x, int y) { printStr(x, y, unitName); y += (charHeight + 1); for (uint8_t i = 0; i < ARDUBOY_UNIT_NAME_LEN; i++, x += charWidth) { arduboy.drawFastHLine(x, y, charWidth - charSpacing); } } // Print the unit name in large size at the given location void printNameLarge(int x, int y) { arduboy.setTextSize(2); printStr(x, y, unitName); arduboy.setTextSize(1); } // Add a line below the large name showing the current length // Coordinates are for the name itself void printNameUnderline(int x, int y) { int lWidth; if (unitName[0] != 0) { x -= 1; y += (char2Height + 6); lWidth = (strlen(unitName) * char2Width); arduboy.drawPixel(x, y); arduboy.drawPixel(x + lWidth - 1, y); arduboy.drawFastHLine(x, y + 1, lWidth); } } // Print the unit name in hex at the given location void printNameHex(int x, int y) { for (uint8_t i = 0; i < ARDUBOY_UNIT_NAME_LEN; i++) { printHex8(x, y, unitName[i]); x += charWidth * 3 + smallSpace; } } // Print the unit name in decimal at the given location void printNameDecimal(int x, int y) { for (uint8_t i = 0; i < ARDUBOY_UNIT_NAME_LEN; i++) { printDecimal8(x, y, unitName[i]); x += charWidth * 3 + smallSpace; } } // Print the unit ID in large size at the given location void printIDLarge(int x, int y) { arduboy.setTextSize(2); printIDHex(x, y); arduboy.setTextSize(1); } // Print the unit ID in normal size at the given location void printIDHex(int x, int y) { printHex16(x, y, unitID); } // Print the unit ID as 2 decimal bytes at the given location void printIDDecimalBytes(int x, int y) { printDecimal8(x, y, unitID >> 8); printDecimal8(x + charWidth * 3 + smallSpace, y, unitID & 0x00FF); } // Print the unit ID in decimal at the given location void printIDDecimal(int x, int y) { printDecimal16(x, y, unitID); } // print the unit ID in binary at the given location as 4 nybbles // with a leading 'b' void printIDBinary(int x, int y) { arduboy.setCursor(x, y); arduboy.print('b'); x += charWidth + smallSpace; for (char i = 3 * 4; i >= 0; i -= 4) { printBinaryNybble(x, y, static_cast(unitID >> i)); x += charWidth * 4 + smallSpace; } } // Print the save prompt in reverse at the given location void printSavePrompt(int x, int y) { arduboy.fillRect(x - 2, y - 2, strlen_P(StrSaveQ) * charWidth + 3, charHeight + 3); arduboy.setTextColor(BLACK); arduboy.setTextBackground(WHITE); printStr_P(x, y, StrSaveQ); arduboy.setTextColor(WHITE); arduboy.setTextBackground(BLACK); } // Print a string at the given location void printStr(int x, int y, char* str) { arduboy.setCursor(x, y); arduboy.print(str); } // Print a string in program memory at the given location void printStr_P(int x, int y, const char* str) { arduboy.setCursor(x, y); arduboy.print((__FlashStringHelper*)(str)); } // Print an 8 bit number in decimal, right justified with leading spaces void printDecimal8(int x, int y, uint8_t val) { printDecimalHelper(x, y, 2, 100, val); } // Print a 16 bit number in decimal, right justified with leading spaces void printDecimal16(int x, int y, uint16_t val) { printDecimalHelper(x, y, 4, 10000, val); } // Print a right justified decimal number, given width-1 and (width-1)^10 void printDecimalHelper(int x, int y, uint8_t width, uint16_t pwr10, uint16_t val) { arduboy.setCursor(x, y); while (width > 0) { if (val >= pwr10) { break; } arduboy.print(' '); pwr10 /= 10; width--; } arduboy.print(val); } // Print an 8 bit hex number with leading x and zeros void printHex8(int x, int y, uint8_t val) { arduboy.setCursor(x, y); arduboy.print('x'); if (val < 16) { arduboy.print('0'); } arduboy.print(val, HEX); } // Print a 16 bit hex number with leading x and zeros void printHex16(int x, int y, uint16_t val) { arduboy.setCursor(x, y); arduboy.print('x'); for (char i = 3 * 4; i >= 0; i -= 4) { arduboy.print((val >> i) & 0x000F, HEX); } } // Print a nybble in binary from the lowest 4 bits of the provided byte void printBinaryNybble(int x, int y, uint8_t val) { arduboy.setCursor(x, y); for (char i = 3; i >= 0; i--) { arduboy.print((val >> i) & 0x01); } } // Print a constant string in large size and reversed void printStrLargeRev_P(int x, int y, const char* string) { arduboy.fillRect(x - 4, y - 4, strlen_P(string) * char2Width + 6, char2Height + 6); arduboy.setTextColor(BLACK); arduboy.setTextBackground(WHITE); arduboy.setTextSize(2); printStr_P(x, y, string); arduboy.setTextSize(1); arduboy.setTextColor(WHITE); arduboy.setTextBackground(BLACK); } // ---------------- Control and Utility Functions ------------------------ // Get the current unit name and ID, and the system flags, from EEPROM void readEEPROM() { memset(unitName, 0, sizeof(unitName)); arduboy.readUnitName(unitName); unitID = arduboy.readUnitID(); showLogoFlag = arduboy.readShowBootLogoFlag(); showLEDsFlag = arduboy.readShowBootLogoLEDsFlag(); showNameFlag = arduboy.readShowUnitNameFlag(); } // Increment the name character at the cursor position void nameCharInc() { while (invalidChar(++unitName[nameIndex])) { } drawScreen(); } // Decrement the name character at the cursor position void nameCharDec() { while (invalidChar(--unitName[nameIndex])) { } drawScreen(); } // Return true if the given character is not allowed bool invalidChar(char c) { return (c == '\n') || (c == '\r') || (static_cast(c) == 0xFF); } // Move the name cursor right void nameCursorRight() { if (++nameIndex == ARDUBOY_UNIT_NAME_LEN) { nameIndex = 0; } drawScreen(); } // Move the name cursor left void nameCursorLeft() { if (nameIndex == 0) { nameIndex = ARDUBOY_UNIT_NAME_LEN - 1; } else { nameIndex--; } drawScreen(); } // Increment the ID digit at the cursor position void idDigitInc() { uint16_t mask = 0xF000 >> (idIndex * 4); uint16_t val = 0x1000 >> (idIndex * 4); unitID = (unitID & ~mask) | ((unitID + val) & mask); drawScreen(); } // Decrement the ID digit at the cursor position void idDigitDec() { uint16_t mask = 0xF000 >> (idIndex * 4); uint16_t val = 0x1000 >> (idIndex * 4); unitID = (unitID & ~mask) | ((unitID - val) & mask); drawScreen(); } // Move the ID cursor right void idCursorRight() { if (++idIndex == sizeof(unitID) * 2) { idIndex = 0; } drawScreen(); } // Move the ID cursor left void idCursorLeft() { if (idIndex == 0) { idIndex = sizeof(unitID) * 2 - 1; } else { idIndex--; } drawScreen(); } // Move the Flags cursor down void flagsCursorDown() { switch (currentFlag) { case SelectedFlag::selFlagLogo: currentFlag = SelectedFlag::selFlagLEDs; break; case SelectedFlag::selFlagLEDs: currentFlag = SelectedFlag::selFlagName; break; case SelectedFlag::selFlagName: currentFlag = SelectedFlag::selFlagLogo; break; } drawScreen(); } // Move the Flags cursor up void flagsCursorUp() { switch (currentFlag) { case SelectedFlag::selFlagName: currentFlag = SelectedFlag::selFlagLEDs; break; case SelectedFlag::selFlagLEDs: currentFlag = SelectedFlag::selFlagLogo; break; case SelectedFlag::selFlagLogo: currentFlag = SelectedFlag::selFlagName; break; } drawScreen(); } // Toggle the currently selected flag void flagToggle() { switch (currentFlag) { case SelectedFlag::selFlagLogo: showLogoFlag = !showLogoFlag; break; case SelectedFlag::selFlagLEDs: showLEDsFlag = !showLEDsFlag; break; case SelectedFlag::selFlagName: showNameFlag = !showNameFlag; break; } drawScreen(); } // Start the button auto-repeat delay void startButtonDelay() { delayCount = delayFrames; repeating = false; } // Stop the button auto-repeat or delay void stopButtonRepeat() { delayCount = 0; repeating = false; } // Calculate the X coordinate to center a string of the given length int centerStrLen(unsigned int len) { return (WIDTH / 2) - (len * charWidth / 2); } // Calculate the X coordinate to center a string located in program memory int centerStr_P(const char* str) { return (WIDTH / 2) - (strlen_P(str) * charWidth / 2); } // Calculate the X coordinate to center a size 2 string located in // program memory int centerStr2_P(const char* str) { return (WIDTH / 2) - (strlen_P(str) * char2Width / 2); } // Calculate the X coordinate to right justify a string in program memory int rightStr_P(const char* str) { return WIDTH - (strlen_P(str) * charWidth) + charSpacing; }