| 
	
	|  |  |  
	
		| View previous topic :: View next topic |  
		| Author | Message |  
		| PrinceNai 
 
 
 Joined: 31 Oct 2016
 Posts: 554
 Location: Montenegro
 
 
			    
 
 | 
			
				| menu structure with 4x20 LCD |  
				|  Posted: Sat Jun 29, 2019 9:57 am |   |  
				| 
 |  
				| 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: 554
 Location: Montenegro
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Sat Jun 29, 2019 11:16 am |   |  
				| 
 |  
				| 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: 358
 Location: South Africa
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Sat Jun 29, 2019 11:21 am |   |  
				| 
 |  
				| You know that you can edit your own post and do the correction there. |  |  
		|  |  
		| PrinceNai 
 
 
 Joined: 31 Oct 2016
 Posts: 554
 Location: Montenegro
 
 
			    
 
 | 
			
				|  |  
				|  Posted: Sat Jun 29, 2019 12:45 pm |   |  
				| 
 |  
				| I do now :-). Thanks. |  |  
		|  |  
		|  |  
  
	| 
 
 | 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
 
 |