Menu programming in C

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Hi guys

I recently want to learn how to program a menu system in MCUs with c in general, what are the options/method/methaology available for doing that? Or what I can google to find out more.

I mean there must be a better way than using a lots of if then ( switch case) statement right?

Thanks a lot guys
 

ErnieM

Joined Apr 24, 2011
8,377
If you are using C then controlling program flow will always come down to a series of if/then statements or a switch block. However, nothing says you are forced to use raw naked statements without any overall structure.

It's going to come down to your specific project, what fits best into your hardware, or what are the inputs and what are the outputs.

Coincidentally I am doing the menu part of a new app here. My system includes a 4 line by 20 character alphanumeric display for output, and 5 push buttons for input arranged as so:
Rich (BB code):
      UP
LEFT ENTER RIGHT
      DOWN
My menus look like so:
Rich (BB code):
MENU TITLE:
  MENU ITEM
  ANOTHER MENU ITEM
 >LAST MENU ITEM
It's fairly flexible for not much code, but ultimately it comes down to a switch block to preform the decision that the buttons chose.
 

ErnieM

Joined Apr 24, 2011
8,377
First off, the variant of C code I use is for the C18 compiler for PIC18 devices. In this series constants and variables are quite different, as one is stored in RAM and one in program ROM and thus require different mechanisms for pointers. Thus a "const rom char" is a character string stored in program memory.

Your device (I seem to remember you don't use PICs) may not need the "rom" qualifier.

"UINT8" is an alias for an unsigned char.

As I define it a menu requires 3 pieces of information:

1) A title string
2) array of string pointers for each menu choice
3) a count of how many choices are in the menu.

A sample menu may look like so:

Rich (BB code):
const rom char* ProfileMenuTitle   =   "SELECT PROFILE:";
const rom char* ProfileMenuItems[] = { "Sn/Pb solder",
                                       "ROHS SOLDER",
                                       "CUSTOM PROFILE" };
UINT8 ProfileMenuItemCount = 3;
The interesting part of this definition is the ProfileMenuItems[] array, which has 3 elements each is a pointer to a string. Thus passing ProfileMenuItems passes the address of the first array pointer, and thus the entire array is accessible.

To display this menu we call a function (we'll define this next):
Rich (BB code):
UINT8 Top = 0, Sel = 0;
SelectMenu( ProfileMenuTitle, 
            ProfileMenuItems,  
            ProfileMenuItemCount, 
            &Top, 
            &Sel);
Sel will contain the index number of the menu choice when this function returns. Top is used when the number of items in the menu exceed what can be displayed (mine scroll up and down as need be).

So let's see how this function may supply a menu function select block:
Rich (BB code):
UINT8 Top = 0, Sel = 0;
SelectMenu( ProfileMenuTitle, 
            ProfileMenuItems,  
            ProfileMenuItemCount, 
            &Top, 
            &Sel);
switch (Sel)
     {
     case 0:
         LeadSolderFunction();
         break;
     case 1:
         ROHSSolderFunction();
         break;
     case 2:
         CustomSolderFunction();
         break;
     default:
         // handle bad returns?
         break;
     }
In the next post I put the code behind SelectMenu().
 

spinnaker

Joined Oct 29, 2009
7,830
It is really going to depend on what hardware is connected to the mcu. If you have a large lcd and keyboard then you can display a menu of several choices and have the user enter a key.

If all you have is a single line lcd and a few switches then you would have one button to cycle through the choices and another to select the value.

There are probably thousands of ways you can go but they pretty much all come down to displaying something to the user and then waiting for input.
 

ErnieM

Joined Apr 24, 2011
8,377
A few of the functions, variables, and symbols that will follow need a definition:
Rich (BB code):
Keys: global variable to hold debounced button state. (it's set inside an ISR)

BUTTON_UP, BUTTON_DOWN, BUTTON_ENTER: Value of Keys when these buttons pressed

WriteCmdXLCD(CLEAR_SCREEN); Clears the LCD screen

PrintRomAt(X, Y, pTitle);   Writes the printed string on the LCD
                            starting at line X, character Y. 
                            (O,O is top left)

#define MENU_LINES    3        // line count of items on the menu
#define MENU_LINE1    1        // line count of the menu title itself
LCD functions are from Microchip's XLCD functions, though they have been modified to eliminate the need to call BusyXLCD() before each call. Delays are now based on TimeDelay.c functions and work across a broad range of PICs.

Now let's flesh out SelectMenu(). The function clears the display, updates the count for the cursor. It is a simple function as all the cursor checks are contained in the DrawMenuItems() function.
Rich (BB code):
void SelectMenu(
    const rom char* pTitle,        // menu title string pointer
    const rom char* psItems[],     // list of string pointers to item names
    UINT8 nItems,                  // count of items in the list    integer count
    UINT8* pTop,                   // item at top of list        zero based
    UINT8* pSel)                   // currently selected Item    zero based
{           
    // begin with a fresh slate
    WriteCmdXLCD(CLEAR_SCREEN); 
    PrintRomAt(0, 0, pTitle);

    // draw menu items
    DrawMenuItems(psItems, nItems, pTop, pSel);

    while (Keys);         // wait for previous key to be released
    while(1)
    {
        while (!Keys);    // wait for new key to be pressed
        switch (Keys)
        {
        case BUTTON_UP:   // dec the cursor & redraw
            (*pSel)--;
            break;
        case BUTTON_DOWN: // inc the cursor & redraw
            (*pSel)++;
            break;
        case BUTTON_ENTER:
          // user made selection,
            // variables have been updated
            // so just return
            return;
        }
    DrawMenuItems(psItems, nItems, pTop, pSel);
    while (Keys);    // wait for previous key to be released
    }
}
DrawMenuItems() is a bit more involved as it manages and validates both pSel and pTop. The function also works with very long menus by scrolling the items that can be viewed as needed. Short menu cursor will wrap around the ends, though not on the long lists.

All items on the menu get redrawn every time the selected item changes. This looks fine on my LCD, but may be glitchy on a slower system as I'm chuggin' 12 million instructions every second.

Rich (BB code):
void DrawMenuItems(
    const rom char* psItems[],    // list of string pointers to item names
    UINT8 nItems,            // count of items in the list    integer count
    UINT8* pTop,            // item at top of list        zero based
    UINT8* pSel)            // currently selected Item    zero based
{
    UINT8 I, I2;
    UINT8 Addr;

    #define Top *pTop        // aliase for clarity
    #define Cursor *pSel    


    // validate the Cursor: neither beyond or before the list
    // check if cursor too large
    if (Cursor == nItems) 
    {
      // cursor at just beyond the end of the list
      // was incremented there. 
      // short lists can loop,
      // long lists just remain there 
      if (nItems <= MENU_LINES)
        // short list
        Cursor = 0;
      else
        // long list
          Cursor = nItems - 1;     
    }

    // check if cursor too small
    if (Cursor > nItems) 
    {
      // when decremented below zero Cursor gets huge 
        // as this is what unsigned numbers do
      // short lists can loop,
      // long lists just remain there 
      if (nItems <= MENU_LINES)
        // short list
        Cursor = nItems - 1;
      else
        // long list
          Cursor = 0;         
    }

    // check for short lists (Items <= LINES)
    if ( (nItems <= MENU_LINES) & (Top != 0) )
    {
        // short lists always have Top = 0
        Top = 0;
    }

    // check if selected item cursor is above top
    if (Cursor < Top ) 
    {
        Top = Cursor;
    }

    // check if item below bottom
    while (Cursor >= Top + MENU_LINES)
    {
        // keep chuggin till we've skipped down enough
        Top+=1;
    }

    // undraw any old menu items & cursor
    for (I=0; I < MENU_LINES; I++)
    {
    PrintRomAt( (I+MENU_LINE1), 0, CLEAR_LINE);
        if ( (I+Top) < nItems) 
        {
          PrintRomAt( (I+MENU_LINE1), 2, psItems[I+Top]);
        }
    }
     //    new draw cursor
    PrintRomAt( (Cursor-Top + MENU_LINE1), 1, ">");        
}
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Your device (I seem to remember you don't use PICs) may not need the "rom" qualifier.
Hi ErnieM,

You surprised me that you remember I don't use PICs, yes I am more familiar with AVR, but I was told that PICs is more popular in the industry, so I am learning PICs now.

And, thanks for your time and effort to answer my question, really appreciate it!!

now I need to stick my bum in front of my computer (as my girl friend put it :)) and take a good look at your codes.

Thanks again!
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
It is really going to depend on what hardware is connected to the mcu...
Hi spinnaker,

I am hoping to learn some decent character menu interface, not too fancy but also not too simple. It's very hard to tell exactly what I mean as written communication is not something I am good at.

Not to a particular hardware at this stage, as I just want to know how it is generally done, it's generally routine/method etc.

As for hardware, I have a nokia 5110 84 * 48 graphic LCD module and LCD1602 module.
 

THE_RB

Joined Feb 11, 2008
5,438
I think it's very important with menu systems etc to sit down first and write it out on paper, as a flow chart type of thing.

That way you can really nut out how the menu works, what controls it needs, how the display changes etc. Normally on a software team that job is done by the "designer" and is done first before the coder has to make it happen in code.

Good "design" of the operation of the software is important and should be done first. It makes it a LOT easier to write the code when you know exactly what the menu sequences are and what is shown on the screen.

And of course you can always add menu items later in code etc, but at least you should get the bulk of the menu (and the operation of the device) designed on paper first before coding.

Another thing that can help is to draw a grid on your display area (on paper) showing how many lines and characters you can get, and photocopy that in bulk. Then you can write on it all the different menu displays and see how they fit, which helps in the "design" phase.

With your 84*48 display you can get 14 chars across by 6 lines down (assuming a standard 5*7 pixel font displayed at 6*8 spacing).
 
Top