October 16, 2011
by claesz
|
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;
}
|