Menu programming in C

Discussion in 'Programmer's Corner' started by bug13, Jan 4, 2013.

  1. bug13

    Thread Starter Senior Member

    Feb 13, 2012
    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
  2. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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:
    Code ( (Unknown Language)):
    2.       UP
    4.       DOWN
    My menus look like so:
    Code ( (Unknown Language)):
    1. MENU TITLE:
    2.   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.
    bug13 likes this.
  3. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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:

    Code ( (Unknown Language)):
    1. const rom char* ProfileMenuTitle   =   "SELECT PROFILE:";
    2. const rom char* ProfileMenuItems[] = { "Sn/Pb solder",
    3.                                        "ROHS SOLDER",
    4.                                        "CUSTOM PROFILE" };
    5. 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):
    Code ( (Unknown Language)):
    2. UINT8 Top = 0, Sel = 0;
    3. SelectMenu( ProfileMenuTitle,
    4.             ProfileMenuItems,  
    5.             ProfileMenuItemCount,
    6.             &Top,
    7.             &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:
    Code ( (Unknown Language)):
    2. UINT8 Top = 0, Sel = 0;
    3. SelectMenu( ProfileMenuTitle,
    4.             ProfileMenuItems,  
    5.             ProfileMenuItemCount,
    6.             &Top,
    7.             &Sel);
    8. switch (Sel)
    9.      {
    10.      case 0:
    11.          LeadSolderFunction();
    12.          break;
    13.      case 1:
    14.          ROHSSolderFunction();
    15.          break;
    16.      case 2:
    17.          CustomSolderFunction();
    18.          break;
    19.      default:
    20.          // handle bad returns?
    21.          break;
    22.      }
    In the next post I put the code behind SelectMenu().
    bug13 likes this.
  4. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    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.
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    A few of the functions, variables, and symbols that will follow need a definition:
    Code ( (Unknown Language)):
    1. Keys: global variable to hold debounced button state. (it's set inside an ISR)
    3. BUTTON_UP, BUTTON_DOWN, BUTTON_ENTER: Value of Keys when these buttons pressed
    5. WriteCmdXLCD(CLEAR_SCREEN); Clears the LCD screen
    7. PrintRomAt(X, Y, pTitle);   Writes the printed string on the LCD
    8.                             starting at line X, character Y.
    9.                             (O,O is top left)
    11. #define MENU_LINES    3        // line count of items on the menu
    12. #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.
    Code ( (Unknown Language)):
    1. void SelectMenu(
    2.     const rom char* pTitle,        // menu title string pointer
    3.     const rom char* psItems[],     // list of string pointers to item names
    4.     UINT8 nItems,                  // count of items in the list    integer count
    5.     UINT8* pTop,                   // item at top of list        zero based
    6.     UINT8* pSel)                   // currently selected Item    zero based
    7. {          
    8.     // begin with a fresh slate
    9.     WriteCmdXLCD(CLEAR_SCREEN);
    10.     PrintRomAt(0, 0, pTitle);
    12.     // draw menu items
    13.     DrawMenuItems(psItems, nItems, pTop, pSel);
    15.     while (Keys);         // wait for previous key to be released
    16.     while(1)
    17.     {
    18.         while (!Keys);    // wait for new key to be pressed
    19.         switch (Keys)
    20.         {
    21.         case BUTTON_UP:   // dec the cursor & redraw
    22.             (*pSel)--;
    23.             break;
    24.         case BUTTON_DOWN: // inc the cursor & redraw
    25.             (*pSel)++;
    26.             break;
    27.         case BUTTON_ENTER:
    28.           // user made selection,
    29.             // variables have been updated
    30.             // so just return
    31.             return;
    32.         }
    33.     DrawMenuItems(psItems, nItems, pTop, pSel);
    34.     while (Keys);    // wait for previous key to be released
    35.     }
    36. }
    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.

    Code ( (Unknown Language)):
    1. void DrawMenuItems(
    2.     const rom char* psItems[],    // list of string pointers to item names
    3.     UINT8 nItems,            // count of items in the list    integer count
    4.     UINT8* pTop,            // item at top of list        zero based
    5.     UINT8* pSel)            // currently selected Item    zero based
    6. {
    7.     UINT8 I, I2;
    8.     UINT8 Addr;
    10.     #define Top *pTop        // aliase for clarity
    11.     #define Cursor *pSel    
    14.     // validate the Cursor: neither beyond or before the list
    15.     // check if cursor too large
    16.     if (Cursor == nItems)
    17.     {
    18.       // cursor at just beyond the end of the list
    19.       // was incremented there.
    20.       // short lists can loop,
    21.       // long lists just remain there
    22.       if (nItems <= MENU_LINES)
    23.         // short list
    24.         Cursor = 0;
    25.       else
    26.         // long list
    27.           Cursor = nItems - 1;    
    28.     }
    30.     // check if cursor too small
    31.     if (Cursor > nItems)
    32.     {
    33.       // when decremented below zero Cursor gets huge
    34.         // as this is what unsigned numbers do
    35.       // short lists can loop,
    36.       // long lists just remain there
    37.       if (nItems <= MENU_LINES)
    38.         // short list
    39.         Cursor = nItems - 1;
    40.       else
    41.         // long list
    42.           Cursor = 0;        
    43.     }
    45.     // check for short lists (Items <= LINES)
    46.     if ( (nItems <= MENU_LINES) & (Top != 0) )
    47.     {
    48.         // short lists always have Top = 0
    49.         Top = 0;
    50.     }
    52.     // check if selected item cursor is above top
    53.     if (Cursor < Top )
    54.     {
    55.         Top = Cursor;
    56.     }
    58.     // check if item below bottom
    59.     while (Cursor >= Top + MENU_LINES)
    60.     {
    61.         // keep chuggin till we've skipped down enough
    62.         Top+=1;
    63.     }
    65.     // undraw any old menu items & cursor
    66.     for (I=0; I < MENU_LINES; I++)
    67.     {
    68.     PrintRomAt( (I+MENU_LINE1), 0, CLEAR_LINE);
    69.         if ( (I+Top) < nItems)
    70.         {
    71.           PrintRomAt( (I+MENU_LINE1), 2, psItems[I+Top]);
    72.         }
    73.     }
    74.      //    new draw cursor
    75.     PrintRomAt( (Cursor-Top + MENU_LINE1), 1, ">");        
    76. }
    bug13 likes this.
  6. bug13

    Thread Starter Senior Member

    Feb 13, 2012
    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!
  7. bug13

    Thread Starter Senior Member

    Feb 13, 2012
    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.
  8. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    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).
    bug13 likes this.