NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Menus and sub menus

October 16, 2011
by claesz
claesz's Avatar

I presume a lot of people have posted menu codes on this forum before, but I thought I'd share it anyhow, just to get some feedback on possible improvements since I'm not that good a C programmer.

If you want to test it out, the setup is the basic NK LCD setup with the addition of a push button on PC4 (make sure to set default to off (short pin to port, middle pin to 5v and long pin to gnd)), a green LED on PC3 and a red LED on PC2. For now the menu just allows you to enter sub menus, turn on/off green LED, red LED and return to main menu. But it is set up so you should be able to expand endlessly (provided you have enough memory) with sub menus in any hierarchy. Basically a menu entry can either have an action code or a sub menu link. You can define any action you want in the switch in main().

A few notes: I have planned this to allow more than 3 selections on each menu/sub menu, so if you scroll past "page 1" it will show you page 2 etc. I haven't implemented this completely yet. For now you are limited to three items on each menu (title+3 lines). Also, if you find any unused variables they are probably part of "the bigger schemes" and can safely be removed unless you want to play around with the code and update it to work with unlimited menu entries.

Code tested on a ATMega 328p, but I think it should work on a 168 (CPU def is for 168 by mistake). Make sure to either change the code to specify PROGMEM or change the makefile by removing "-j .text".

Hope the code isn't too basic to share it here. If anyone have suggestions for improvements (and there should be plenty of room for that), I would very much appreciate the input.

// initialload.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

// PIN DEFINITIONS:
//
// PC2 -- RED LCD
// PC3 -- GREEN LCD
// PC4 -- Switch to start stop scrolling

// Global menu data
int curMenu = 0; // menu currently shown
int curItem = 0; // item currently marked (line 0 for menutitle, so starts on one)
int cursorCount = 0;
int menuCount = 0;
char userin[5];

int numbMenus = 5;
int numbItems = 10;
char menutitle[5][20];
char menuitem[5][5][20];
int menulink[5][5]; // a link to a submenu OR
int menuactn[5][5]; // menu actions - turn on LED or whatever

// LED as output
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

void initMenu() {
    strcpy(menutitle[0], "MAIN MENU");
    strcpy(menuitem[0][0], "Green LED");
    menulink[0][0] = 1; // link to menutitle[1]; 
    menuactn[0][0] = 0; // No action - just a sub menu 
    strcpy(menuitem[0][1], "Red LED");
    menulink[0][1] = 2;
    menuactn[0][1] = 0;
    strcpy(menuitem[0][2], "\0"); // Need to initialize the end str of array

    strcpy(menutitle[1], "Green LED");
    strcpy(menuitem[1][0], "Turn on");
    menulink[1][0] = 0; // no sub menu, just action
    menuactn[1][0] = 1;
    strcpy(menuitem[1][1], "Turn off");
    menulink[1][1] = 0;
    menuactn[1][1] = 2;
    strcpy(menuitem[1][2], "Return to main");
    menulink[1][2] = 999;
    menuactn[1][2] = 0;
    strcpy(menuitem[1][3], "\0"); // Need to initialize the end str of array

    strcpy(menutitle[2], "Red LED");
    strcpy(menuitem[2][0], "Turn on");
    menulink[2][0] = 0; // no sub menu, just action 
    menuactn[2][0] = 3;
    strcpy(menuitem[2][1], "Turn off");
    menulink[2][1] = 0;
    menuactn[2][1] = 4;
    strcpy(menuitem[2][2], "Return to main");
    menulink[2][2] = 999; // code to trigger return to main menu.  Can't use 0 as that is considered no action here
    menuactn[2][2] = 0; 
    strcpy(menuitem[2][3], "\0"); // Need to initialize the end str of array    
    // END of menu data
}

void showMenu() {
    lcd_clear_and_home();
    fprintf_P(&lcd_stream, PSTR("%s"), menutitle[curMenu]);
    //print menu content
    while ((cursorCount < 5) && (menuitem[curMenu][menuCount][0] != '\0')) {
        lcd_goto_position(cursorCount+1, 0); // +1 to leave first line for menu title
        if (curItem == menuCount) { 
            // item currently indicated by cursor
            fprintf_P(&lcd_stream, PSTR("->%s"), menuitem[curMenu][menuCount]);
        } else {
            fprintf_P(&lcd_stream, PSTR("  %s"), menuitem[curMenu][menuCount]);
        }
        cursorCount++; 
        menuCount++;
    }
}

int clicklen() {
    int timeCount = 0;
    while (timeCount < 500) { // half a second click = long
        if (!(PINC & (1<<PC4))) {
            // click stopped
            return 0;
        }
        delay_ms(1);
        timeCount++;
    }
        // still wait for user to let go, our it will enter new menu scrolling
    while (PINC & (1<<PC4)) { } // do nothing
    return 1; // click lasted for more than 500ms
}

int clicks() {
    delay_ms(50);  // just to give button time to settle
    //Started by btn click, no check for how long
    if (clicklen() == 1) {
    // long click
        // return selection
        return curItem;
    } else {
        // short click
        curItem++; // add one to curr item
        if (menuitem[curMenu][curItem][0] == '\0') { curItem = 0; }
    }
    return 999; // we can't use 0 since that is actually a menu item. So 999 indicates short click;

}

//-- MAIN--
int main() {
    int selection; // return action from click function
    lcd_init();

    DDRC &= ~(1<<PC4); // Set PC4 for input
    DDRC |= (1<<PC3);   // Set PC3 for output
    DDRC |= (1<<PC2);   // Set PC2 for output

    initMenu(); // initialize menu by adding menu data to menu globals

    // print menu
    while(1) {
        showMenu();
        while(1) {  
            if (PINC & (1<<PC4)) { 
                selection = clicks();
                if (selection == 999) {
                    // no selection, just flipping though menu
                    cursorCount = 0;
                    menuCount = 0;
                    showMenu(); // updates menu
                } else {
                    // a selection was made
                    break; // proceed
                }
            }  
        }

        // handle user input
        if (menuactn[curMenu][selection]) {
            // has an action
            switch (menuactn[curMenu][selection]) {
                case 1: 
                    // action 1
                    PORTC |= (1<<PC3);  // Turn on green LCD
                    break;
                case 2: 
                    // action 2
                    PORTC &= ~(1<<PC3); // Turn on green LCD
                    break;
                case 3:
                    PORTC |= (1<<PC2);  // Turn on red LCD
                    break;              
                case 4:
                    PORTC &= ~(1<<PC2); // Turn on green LCD
                    break;              
            }
        } else {
            // no action so must have sub menu
            switch (menulink[curMenu][selection]) {
                case 999: 
                    curMenu = 0; // return to main menu
                    break;
                default: 
                    curMenu = menulink[curMenu][selection];
                    break;
            }
        }
        cursorCount = 0;
        menuCount = 0;  
    }
    return 0;
}
October 16, 2011
by claesz
claesz's Avatar

Oh, forgot to mention that the menu is operated by a single push button. Short click to scroll down, long click to select.

Post a Reply

Please log in to post a reply.

Did you know that you can connect to certain car computers via the OBD-II port with a microcontroller? Learn more...