CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

menu structure with 4x20 LCD

 
Post new topic   Reply to topic    CCS Forum Index -> Code Library
View previous topic :: View next topic  
Author Message
PrinceNai



Joined: 31 Oct 2016
Posts: 480
Location: Montenegro

View user's profile Send private message

menu structure with 4x20 LCD
PostPosted: Sat Jun 29, 2019 9:57 am     Reply with quote

With great help from the members of this forum I managed to translate the code found here: https://www.youtube.com/watch?v=PFzNBtnfJ6Y to CCS. I do recommend watching those tutorials, because the author really explains it well. It is a menu structure used with 4x20 LCD. It uses three buttons, up, down and enter to browse the menu. Here is the code:

30.6.2019 added "floating header", where the header of the selected menu group always stays on top. Added some more functions behind menus.

menu_structure.c
Code:

//#define FLOATING_HEADER 0                  // menu header disappears with browsing
#define FLOATING_HEADER 1                    // menu header always stays on top
void strromcpy(char *dest, rom char *source);                                                   
void LcdWriteStringRom (int8 x, int8 y, rom int8 *LCD_Data, int8 clear_line);       
void show_menu (void);
void browse_menu (void);
void start(void);
void dummy(void);                                               
void ClearMinus (void);
void Blank (void);
void ExitMenu(void);
void DisplaySelectedOption(void);
// functions for menus and sub-menus. Note they all start with 1 and the function for last one is always return up, so it is not needed
void Main1(void);
void Main2(void);
void Main3(void);                                     
//
void Sub101(void);
void Sub102(void);         
//
void Sub201(void);                         
void Sub202(void);
void Sub203(void);
void Sub204(void);
void Sub205(void);
void Sub206(void);
//

int8 ROM_String_Length = 0;
char ROM_String_Copy[21]; 
#define ROW_LENGTH 20                     // uncomment for your display size, TBD for displays other than 4x20
//#define ROW_LENGTH 16                           

int8 line_cnt_g = 1;                      // first line on LCD is 1, different than tutorial
int8 from_g = 0;                          // declare global copies of variables from show_menu to be able to see tham all the time with debugger                               
int8 till_g = 0;                                                   
int8 temp_g = 0;                          // calculate the span of addresses for the current menu             
int8 Intermediate_g = 0;                                                                                 
int8 Position = 0;
int8 Exit = 0;
int8 FloatingHeaderAddress = 0;           // this is the address of the first entry of the current menu, i.e. header
                                          // that can be used for a "non disappearing header" style of the menu                                           
                                                                                                                                                               
// menu options texts                           
rom char menu_000[] = " [MAIN MENU -0]";     // 0                       
rom char menu_001[] = " main_001-1";         // 1
rom char menu_002[] = " main_002-2";         // 2               
rom char menu_003[] = " main_003-3";         // 3
rom char menu_004[] = " EXIT MENU-4";        // 4
// sub-menu 1 text                                             
rom char menu_100[] = " [SUB-MENU1 -5]";     // 5                               
rom char menu_101[] = " sub_101-6";          // 6
rom char menu_102[] = " sub_102-7";          // 7
rom char menu_103[] = " SUB-MENU1 EXIT -8";  // 8
// sub-menu 2 text                                                     
rom char menu_200[] = " [SUB-MENU2 -9]";     // 9
rom char menu_201[] = " sub_201-10";         // 10
rom char menu_202[] = " sub_202-11";         // 11                                           
rom char menu_203[] = " sub_203-12";         // 12
rom char menu_204[] = " sub_204-13";         // 13               
rom char menu_205[] = " sub_205-14";         // 14                                   
rom char menu_206[] = " sub_206-15";         // 15                                               
rom char menu_207[] = " SUB-MENU2 EXIT";     // 16
// ******************** END MENU TEXT *****************************************
                                                                                                                                                                           
static unsigned int8 selected = 1;           // indicates selected menu item. Start with 1, because we don't want to pick the header
                                                                                                                     
// every element of the array must have all elements defined in the prototype: text, number of elements, up, down, enter and function (can be null)
MenuEntry Menu[] = {                         // at least one function must be defined, else it doesn't compile
   {menu_000, 5, 0, 0, 0, 0},                // text 0; 5 options, up button menu_000, down button menu_000, enter menu_000, no function attached - non browsable
   {menu_001, 5, 1, 2, 6, 0},                // text 1; 5 options, up button menu_001, down button menu_002, enter sub-menu_101, no function attached
   {menu_002, 5, 1, 3, 10, 0},               // text 2; 5 options, up button menu_001, down button menu_003, enter sub-menu_201, no function attached
   {menu_003, 5, 2, 4, 3, 0},                // text 3; 5 options, up button menu_002, down button menu_004, enter menu_003, no function attached
   {menu_004, 5, 3, 4, 4, ExitMenu},         // text 4; 5 options, up button menu_003, down button menu_004 {no roll-over}, enter menu_004, execute ExitMenu function
                                                                                                                                                                                                                                                                     
// sub-menu for menu_001                                                                               
   {menu_100, 4, 0, 0, 0, 0},                // text 5; 4 options, non browsable
   {menu_101, 4, 6, 7, 6, dummy},            // text 6; 4 options, up button stay in menu_101, down button menu_102, enter menu_101, execute dummy function
   {menu_102, 4, 6, 8, 7, Sub102},           // text 7; 4 options, up button menu_101, down button menu_103, enter menu_102, no function attached
   {menu_103, 4, 7, 8, 1, Blank},            // text 8; 4 options, up button menu_102, down button stay in menu_103, enter menu_001, execute blank function   
                                                                                                                                   
// sub-menu for menu_002                                                                           
   {menu_200, 8, 0, 0, 0, 0},                // text 9;  main menu, non-browsable           
   {menu_201, 8, 10, 11, 10, Sub201},        // text 10; main menu, 8 options, up button menu_201, down button menu_202, enter menu_201, no function attached
   {menu_202, 8, 10, 12, 11, Sub202},        // text 11; main menu, 8 options, up button menu_201, down button menu_203, enter menu_202, no function attached
   {menu_203, 8, 11, 13, 12, Sub203},        // text 12; main menu, 8 options, up button menu_202, down button menu_204, enter menu_203, no function attached                                             
   {menu_204, 8, 12, 14, 13, Sub204},        // text 13; main menu, 8 options, up button menu_203, down button menu_205, enter menu_204, no function attached           
   {menu_205, 8, 13, 15, 14, Sub205},        // text 14; main menu, 8 options, up button menu_204, down button menu_206, enter menu_205, no function attached
   {menu_206, 8, 14, 16, 15, Sub206},        // text 15; main menu, 8 options, up button menu_205, down button menu_207, enter menu_206, no function attached
   {menu_207, 8, 15, 16, 2, 0}               // text 16; main menu, 8 options, up button menu_206, down button menu_207, enter menu_002, no function attached                                             
};                                         
// ********************* END MENU  DEFINITIONS ********************************                                                                 
                                                                                       
// ****************************************************************************
// functions
// ******************* COPY STRING FROM ROM TO RAM ****************************                   
void strromcpy(char *dest, rom char *source)
{
   while (*source != '\0')                             
       *(dest++) = *(source++);                                   
   *dest='\0';
}                                                                   

// ****************** WRITE A STRING ON LCD FROM ROM **************************
void LcdWriteStringRom (int8 x, int8 y, rom int8 *LCD_Data, int8 clear_line){
   int i = 0;
   lcd_gotoxy(x,y);
   strromcpy(ROM_String_Copy, LCD_Data);                 // copy string from ROM to RAM
   printf(lcd_putc, "%s", ROM_String_Copy);              // display on LCD (assumes 20 characters)
   if(clear_line){                                                                                   
      ROM_String_Length = strlen(ROM_String_Copy) + x;   // get the length of the string, add the offset at the beginning. Otherwise you go over the length of a row
      ROM_String_Length = ROW_LENGTH - ROM_String_Length;// calculate how many are missing till the end of the row of 20 characters
      while(i <=  ROM_String_Length){                    // if clear line is 1, fill the line with blanks
         lcd_putc(" ");
         i++;                                         
      }                                                 
   }                                                                     
}                                                             
// ******************** SHOW THE MENU ON THE LCD ******************************
/*  This function formats the menu. It uses fixed third row for the selected option.
Because of this we split the formatting in three parts:
-top of the menu - selected item can be also on first or second row TOP + 2 spaces
-middle of the menu - selected item is in the third row
-bottom of the menu - selected item can sink to the fourth row
*/                                                   
void show_menu (void){
   if(selected > 13){
    delay_cycles(1);   
   }
   int8 line_cnt = 1;                              // first line on LCD is 1, different than tutorial made with hi-tech that uses 0,0
   int8 from = 0;                                                         
   int8 till = 0;                                                                                   
   int8 temp = 0;                                       
   int8 Intermediate = 0;
// here we use num_menupoints defined in our struct to calculate how many options
// are available in the current menu and with that where in the menu we are with
// the selected option: top, middle or bottom
   while(till <= selected){
      till = till + Menu[till].num_menupoints;
//      till += Menu[till].num_menupoints;           // calculate end position of the current menu
   }                                 
   from = till - Menu[selected].num_menupoints;
   till--;                                            // subtract 1 because numbering starts with 0
   till_g = till;
   temp = from;
   temp_g = temp;
   from_g = from;
   FloatingHeaderAddress = from;                      // this is the address of the first entry of the current menu, i.e. header
                                                      // that can be used for a "non disappearing header" style of the menu

                                                                                                                 
                                                                                                                 
// we are in the middle of menu
  if ((selected >= (from + 2)) && (selected <=  ( till - 1))){          // my selection >= top+2 and < till - 1               
      from = selected - 2 ;                                                                           
      till = from + 3;
      till_g = till;
      from = from + FLOATING_HEADER;
      from_g = from;
      Position = 2;
// floating menu style
      if(FLOATING_HEADER){
         LcdWriteStringRom (2, line_cnt, Menu[FloatingHeaderAddress].text, 1);     // write first row, header
         line_cnt++;   
         line_cnt_g = line_cnt;     
      }
     
      for(from; from <= till; from ++){
         LcdWriteStringRom (2, line_cnt, Menu[from].text, 1);     // first column is just for indicator!!!
         line_cnt++;   
         line_cnt_g = line_cnt;
      }                   
                                                         
      ClearMinus ();       
      lcd_gotoxy(1,3);                                           // mark selected menu item
      lcd_putc ("-");       
                                                                                                             
  }    // if brace
                                                           
// we are within top + 2 spaces                   
   else                                                                 
   {                                       
     if(selected < (from + 2)){
         Position = 1;

        till = from + 3;
        till_g = till;
        from = from + FLOATING_HEADER;
         from_g = from;
// floating menu style       
         if(FLOATING_HEADER){
            LcdWriteStringRom (2, line_cnt, Menu[FloatingHeaderAddress].text, 1);     // write first row, header
            line_cnt++;   
            line_cnt_g = line_cnt;     
         }       
       
       
       
         for(from; from <= till; from ++){
            LcdWriteStringRom (2, line_cnt, Menu[from].text, 1); // first column is just for indicator!!!
            line_cnt++;
         }
         ClearMinus();           
         Intermediate = (selected - temp + 1);                 // not really needed, can be direct in lcd function
         Intermediate_g = Intermediate;
         
         lcd_gotoxy(1, Intermediate);                          // mark selected menu item
         lcd_putc ("-");                           
                                           
      }   // if                     
// bottom of the menu                   
      if(selected == till){                                    // if the last item on the menu is selected it will be on the fourth row
         from = till - 3 + FLOATING_HEADER;                    // and on LCD we need last string and another three above it
         from_g = from;
         Position = 3;                                                                                                               
// floating menu style         
         if(FLOATING_HEADER){
            LcdWriteStringRom (2, line_cnt, Menu[FloatingHeaderAddress].text, 1);     // write first row, header
            line_cnt++;   
            line_cnt_g = line_cnt;     
         }         
         
         for(from; from <= till; from ++)
         {   LcdWriteStringRom (2, line_cnt, Menu[from].text, 1); // first column stays empty for indicator
            line_cnt++;
         }
         ClearMinus ();
         lcd_gotoxy(1,4);             
         lcd_putc("-");         
     
      }  // if
                           
   }     // else
     
}        // function
// ****************************************************************************

void browse_menu (void){
   do{
      Exit = 0;
      show_menu();                                                 
     
// de-bounce is done in interrupt routine
      if(UP_switch_is_down == 1){
         UP_switch_is_down = 0;
         selected = Menu[selected].up;
      }
      if(DOWN_switch_is_down == 1){
         DOWN_switch_is_down = 0;       
         selected = Menu[selected].down;
      }
      if(ENTER_switch_is_down == 1){
         ENTER_switch_is_down = 0;
         if(Menu[selected].fp != 0){
//            Menu[selected].fp();              // original, not working
            *Menu[selected].fp();               // added by PCM Programmer
         }
         if(!Exit){
            selected = Menu[selected].enter;
         }
         else{
            selected = 1;                       // make sure the entry point to the menu is always 1
         }
      }                                       
   }                                                           
   while(!Exit);                                // you need some kind of stop condition to exit menus
                                                         
}  // function

// ****************************************************************************
                                                                     
void start(void){                   
}
// ****************************************************************************
void dummy(void)                                             
{                               
   lcd_putc('\f');
//   printf("Hello World\n\r");
   lcd_putc("Working, press ENTER");               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');
}                                                         
// ****************************************************************************             
void ClearMinus (void){             
   int8 i = 1;
   for (i = 1; i < 5; i++){
    lcd_gotoxy(1,i);                            // mark selected menu item
    lcd_putc (" ");   
   }                                         
}                                         
// ****************************************************************************
void Blank (void){
   return;
}
// ****************************************************************************
void ExitMenu(void){
   lcd_putc('\f');
   Exit = 1;                                    // signal to exit menu
}

// functions for menus and sub-menus. Note they all start with 1 and the function for last one is always return up, so it is not needed
void Main1(void){
   lcd_putc('\f');                 
   printf("Working, press ENTER");               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');
}
void Main2(void){
   lcd_putc('\f');
   lcd_putc("Working, press ENTER");               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');
}
void Main3(void){
   lcd_putc('\f');
   lcd_putc("Working, press ENTER");               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');
}
//
void Sub101(void){
   lcd_putc('\f');
   lcd_putc("Working, press ENTER");               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');         
}
void Sub102(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
//
void Sub201(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
void Sub202(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
void Sub203(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
void Sub204(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
void Sub205(void){
   DisplaySelectedOption();                     // display which menu option was selected
}
void Sub206(void){
   DisplaySelectedOption();                     // display which menu option was selected
}

void DisplaySelectedOption(void){
   lcd_putc('\f');
   printf(lcd_putc, "Selected menu = %u", selected);               
   while(!ENTER_switch_is_down);                // stay here until enter key is hit
   ENTER_switch_is_down = 0;
   lcd_putc('\f');
}                                   


menu_structure.h
Code:


typedef void(*_fptr)(void);      // added by PCM Programmer
                                                 
typedef rom struct MenuStructure{
   rom char  *text;
   unsigned char num_menupoints;
   unsigned char up;
   unsigned char down;
   unsigned char enter;
//   void (*fp)(void);           // original, not working
   _fptr fp;                      // added by PCM Programmer
   }             
   MenuEntry;                                                             


and a sample project:

menu.c
Code:

#include    <menu.h>
#include    <string.h>
#include    <i2c_Flex_LCD_driver.c>       //LCD driver 


#define  UP_switch   PIN_A3
#define  DOWN_switch   PIN_A2
#define  ENTER_switch   PIN_A1
int8 UP_switch_is_down = FALSE;           // button flags                   
int8 DOWN_switch_is_down = FALSE;                     
int8 ENTER_switch_is_down = FALSE;

#include    <menu_structure.h>
#include    <menu_structure.c>                                           
                             
unsigned int8 COUNTER = 0;

int8 GO = 0;                                 // GO flag, indicates start of main loop
int8 Tmp = 0;
int8 Heartbeat = 0; 

#define FOSC getenv("CLOCK")  // Get PIC oscillator frequency
// check if it is below or equal to 20MHz
#if(FOSC < 21000000)
  #define TIMER0_PRELOAD (256 - (FOSC/4/256/100))
#else
  #error Oscillator frequency is too high:  FOSC
#endif                                           

                                   
#define  LED1      PIN_D3           
#define  LED2      PIN_D4
#define  LED3      PIN_D5

                               
int8 Cntr = 0;                             

// ****************************************************************************
// function declarations
// ****************************************************************************
void timer0_init(void);

                                               
                           

// ****************************************************************************
#INT_TIMER0
void timer0_isr(void){     
   Cntr++;
   if(Cntr == 5){                                          // toggle led every 100ms
      output_toggle(LED1);
      Cntr = 0;   
   }
// debounce   
   int8 active_state_UP, previuos_state_UP,                       
        active_state_DOWN, previuos_state_DOWN,
        active_state_ENTER, previuos_state_ENTER;
   
   set_rtcc(TIMER0_PRELOAD);                                // Reload Timer0 for 10ms rate
   
   active_state_UP = input(UP_switch);                      // Read the button
   active_state_DOWN = input(DOWN_switch);                  // Read the button
   active_state_ENTER= input(ENTER_switch);                 // Read the button
   
   if((previuos_state_UP == 1) && (active_state_UP == 0)){
      UP_switch_is_down = TRUE;                             // raise "BUTTON PRESED" flag. Must be cleared in software.
//      output_toggle(LED1);
   }

   if((previuos_state_DOWN == 1) && (active_state_DOWN == 0)){                       
      DOWN_switch_is_down = TRUE;                           // raise "BUTTON PRESED" flag                         
//      output_toggle(LED2);
   }                                                 

   if((previuos_state_ENTER == 1) && (active_state_ENTER == 0)){                       
      ENTER_switch_is_down = TRUE;                          // raise "BUTTON PRESED" flag
//      output_toggle(LED3);             
   }                                             
                                 
   previuos_state_UP = active_state_UP;                     // Save current value for next time
   previuos_state_DOWN = active_state_DOWN;                 // Save current value for next time
   previuos_state_ENTER = active_state_ENTER;               // Save current value for next time
}                                               
                     
// ****************************************************************************                                                                                                                                                     
#INT_TIMER1                                  // cca. 131ms overflow
void  TIMER1_isr(void) {                             
   COUNTER++;
   if(COUNTER == 16){                 
      COUNTER = 0;                     
      GO = 1;
      Heartbeat++;                           // display a changing symbol on the  LCD to see it is working
   }                                     
}
// ****************************************************************************
                                                                                   
                                   
// ****************************************************************************
                                                                                                                 
void main() {
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_2);      //131 ms overflow
   enable_interrupts(INT_TIMER1);
   timer0_init();
   enable_interrupts(GLOBAL);
   port_b_pullups(true);
   lcd_init();                                  // init LCD   
   Delay_ms(100);               
   lcd_putc('\f');                     

   while(TRUE){
                                   
      lcd_gotoxy(1,1);
      lcd_putc("Press ENTER for menu");
     
      if(ENTER_switch_is_down){
         ENTER_switch_is_down = 0;
         browse_menu ();             
      }                                       
      delay_ms(100);                       
   }        // while true
                                                 
}           // main 

// ****************************************************************************
// FUNCTIONS
// ****************************************************************************

void timer0_init(void)
{
   setup_timer_0(T0_INTERNAL | T0_DIV_256 | T0_8_BIT);
   set_timer0(TIMER0_PRELOAD);
   clear_interrupt(INT_TIMER0);
   enable_interrupts(INT_TIMER0);
}


and
menu.h
Code:

#include <18F4550.h>
#device ADC=10
//#device PASS_STRINGS = IN_RAM    //copy all the strings to RAM to allow access with pointer
#FUSES NOWDT                    //No Watch Dog Timer
#FUSES DEBUG
 
#device ICD=TRUE
#use delay(internal, clock=8000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8,stream=RS232,errors)
#use i2c(Master,Slow,sda=PIN_B0,scl=PIN_B1, force_hw)
                                                                                                                                                                 


Again, many thanks to the author of the tutorial.


Last edited by PrinceNai on Sun Jun 30, 2019 9:50 am; edited 3 times in total
PrinceNai



Joined: 31 Oct 2016
Posts: 480
Location: Montenegro

View user's profile Send private message

PostPosted: Sat Jun 29, 2019 11:16 am     Reply with quote

There was a mistake in the code, per Mr. Alan's suggestion edited in the original post.

Last edited by PrinceNai on Sat Jun 29, 2019 12:45 pm; edited 1 time in total
alan



Joined: 12 Nov 2012
Posts: 357
Location: South Africa

View user's profile Send private message

PostPosted: Sat Jun 29, 2019 11:21 am     Reply with quote

You know that you can edit your own post and do the correction there.
PrinceNai



Joined: 31 Oct 2016
Posts: 480
Location: Montenegro

View user's profile Send private message

PostPosted: Sat Jun 29, 2019 12:45 pm     Reply with quote

I do now :-). Thanks.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> Code Library All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group