User Interface (Part 4)

Part 1234, 5

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.

Leave a Reply

You must be logged in to post a comment.