|
|
View previous topic :: View next topic |
Author |
Message |
PrinceNai
Joined: 31 Oct 2016 Posts: 478 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: 478 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: 357 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: 478 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
|