User Interface (Part 4)
Once the structure of the menus has been translated into a vector of menu items, let’s talk about the core portion of the code which will read information from the rotary encoder and convert the information into appropriate display.
The following routine must be called from the loop() routine:
void mnuDriver(void) { int16_t lastCounts; int16_t counts = 0; boolean firstCount = true; uint8_t menuIndex = 1; if (ENC.buttonState() == BTN_DOWN) { // Read push button status (1 true) while (ENC.buttonState() != BTN_UP) ; // wait button release while (menuIndex != 0) { LCD.printString(vMnuItems[menuIndex].caption, 1); // Display menu caption on first line if (vMnuItems[menuIndex].menuType == MNU_TYP_PARAM) counts = getParameter(menuIndex); // Get the current value of the selected mnuItem else counts = vMnuItems[menuIndex].lastIndex; ENC.setCounter(counts, vMnuItems[menuIndex].min, vMnuItems[menuIndex].max); // Set encoder counter firstCount = true; do { lastCounts = counts; counts = ENC.getCounter(); // Get counter value from encoder if ((lastCounts != counts) || firstCount) { // If counter has changed since last display firstCount = false; if (vMnuItems[menuIndex].menuType == MNU_TYP_PARAM) displayParameterValue(menuIndex, counts); // Display the mnuItem value corresponding to the number of counts else LCD.printString(vMnuItems[vMnuItems[menuIndex].nextMenuIndex + counts].caption, 2); // Display menu caption } } while (ENC.buttonState() != BTN_DOWN); // While button is not depressed while (ENC.buttonState() != BTN_UP); // Wait for button release // Check what has been clicked if (vMnuItems[menuIndex].menuType == MNU_TYP_PARAM) { // If this was a mnuItem setParameter(menuIndex, counts); // Record mnuItem value menuIndex = vMnuItems[menuIndex].nextMenuIndex; // Get back to root menu level } else { uint8_t selectedSubMenuIndex = (vMnuItems[menuIndex].nextMenuIndex + counts); switch(vMnuItems[selectedSubMenuIndex].menuType) { // Depending on selected sub menu type case MNU_TYP_PARAM: case MNU_TYP_ITEM: vMnuItems[menuIndex].lastIndex = counts; menuIndex = selectedSubMenuIndex; // Get down to sub menu level break; case MNU_TYP_RETURN: vMnuItems[menuIndex].lastIndex = 0; menuIndex = vMnuItems[selectedSubMenuIndex].nextMenuIndex; // Get back to root menu level break; case MNU_TYP_EXIT: vMnuItems[menuIndex].lastIndex = 0; menuIndex = 0; // Exit loop break; } } } // "Blank" display LCD.printString(MSG_FIRMWARE, 1); LCD.printString(" ", 2); } }
Note:
- ENC.xxx and LCD.xxx stands for my PlainENC and PlainLCD libraries. Please check this pageif you are interested in the code.The mnuDriver() routine calls the getParameter() external function, the displayParameterValue() and setParameter() external routines which must be prepared according to the application.Below is an example of use of these routines, as they are written for an alarm clock application:
void displayParameterValue(uint8_t menuIndex, uint8_t valueIndex){ switch (menuIndex) { case 6: // Hours case 7: // Minutes case 8: // Seconds case 10: // Day case 11: // Month case 12: // Year case 14: // Hours Alarm case 15: // Minutes Alarm LCD.printInteger(valueIndex, 2); break; case 16: // Alarm state LCD.printString(vOnOff[valueIndex], 2); break; } }
uint8_t getParameter(uint8_t menuIndex) { uint8_t value = 0; switch (menuIndex) { case 6: // Hours case 7: // Minutes case 8: // Seconds case 10: // Day case 11: // Month case 12: // Year value = bcdTobin(RTC_readRegister(menuIndexToMemoryAddress(menuIndex))); break; case 14: // Hours Alarm value = _alarmHours; break; case 15: // Minutes Alarm value = _alarmMinutes; break; case 16: // Minutes Alarm value = _alarmPreset; break; } return(value); }
void setParameter(uint8_t menuIndex, uint8_t value) { switch (menuIndex) { case 6: // Hours case 7: // Minutes case 8: // Seconds case 10: // Day case 11: // Month case 12: // Year RTC_writeRegister(menuIndexToMemoryAddress(menuIndex), binTobcd(value)); break; case 14: // Hours Alarm _alarmHours = value; break; case 15: // Minutes Alarm _alarmMinutes = value; break; case 16: // Alarm state _alarmPreset = value; break; } }
In other words, these routines and this function are dispatching information according to their unique menu item index. They do that externally in order to keep the core routine reasonably compact and readable.