Menus for TFT LCD and Touch Screen

En este caso voy a tratar de explicar cuál es la forma de trabajar que utilizo para crear menús ya sea en LCD TFT o LCD monocromáticos. Primero que nada se define una estructura que se utilizará para ir creando el menú, en donde se indicará que tipo de subitems es, el nombre que se puede colocar en la parte superior de la pantalla, que función invocará al ser seleccionado el ítem o a que submenú ingresar, y en el caso de lcds a color se puede utilizar un icono para cada ítem, por lo que se puede dar alguna identificación del mismo. Por ejemplo, en las pruebas que he estado realizando utilizo la siguiente estructura:

typedef struct{
    UINT8 TipoMenu;
    const UINT8 *TextoMenu;
    UINT8 *MenuSeleccion;
    void (*PtrFuncionSeleccion)(void);
    const UINT16 *PtrIcono;
}MENU;

La variable TipoMenu indicará de que se trata el ítems seleccionado (Un submenú o un comando a ejecutar (mostrar un mensaje, mostrar dialogo para cambiar una variable, ect)), y además también nos sirve para determinar si estamos parados en el menú principal, o en un submenú lo cual nos permite direccionar el menú anterior. También, como el carácter ‘\0’ de un string, saber que se ha llegado al final de un arreglo de menús. Entonces las posibilidades de esta variable son por ejemplo:

#define MENU_FIN                0
#define MENU_COMANDO            1
#define MENU_SUBMENU            2
#define SUBMENU_ANTERIOR        3
#define MENU_MAIN               4

La variable *TextoMenu contendrá el nombre del item a visualizar, o el nombre del submenú que se puede colocar en la parte superior de la pantalla.

  • MenuSeleccion, en el caso de tratarse de un items que ingresa a un submenu, se indica de cual se trata, sino se carga con NULL. PtrFuncionSeleccion)(void), esta variable se utiliza si se trata de un items que ejecuta un comando, entonces en ella direccionamos tal función. Caso contrario se carga con NULL.
  • PtrIcono, es un puntero que direcciona dentro de la memoria de programa la ubicación de la grafica para un icono. En este caso se puede modificar según sea la conveniencia, pues se puede indicar el nombre para ser cargada la imagen desde una memoria SD o la dirección dentro de una memoria externa, dependiendo de ello se deberá modificar su declaración.

Detallada cada variable, el siguiente es un ejemplo de cómo se puede crear un menú:

const MENU MenuSubMenu1[];
const MENU MenuSubMenu2[];
const MENU MenuSubMenu3[];

// Declaración del menu principal, al seleccionar el boton inferior “Menu” ingresamos al ..
// .. submenu MenuSubMenu1
const MENU MenuPrincipal[]={
    {MENU_MAIN,"Menu Principal",(UINT8 *)&MenuSubMenu1,NULL,NULL},
    {MENU_FIN,"",NULL,NULL,NULL}
};
// Declaración del subMenu1. Al retroceder volvemos al principal indicado en el primer..
// .. ítem del arreglo. Luego se declara cada ítems.
const MENU MenuSubMenu1[]={
    {SUBMENU_ANTERIOR,"Sub Menu 1",(UINT8 *)&MenuPrincipal,NULL,NULL},
    {MENU_SUBMENU,"Sub Menu 2",(UINT8 *)&MenuSubMenu2,NULL,&Icono1[0]},
    {MENU_SUBMENU,"Juegos",(UINT8 *)&MenuSubMenu3,NULL,&Icono4[0]},
    {MENU_COMANDO,"About",NULL,vAbout,&Icono3[0]},
    {MENU_FIN,"",NULL,NULL,NULL}    
};
// Declaración del subMenu2. Al retroceder volvemos al subMenu1 indicado en el primer..
// .. ítem del arreglo. Luego se declara cada ítems.
const MENU MenuSubMenu2[]={
    {SUBMENU_ANTERIOR,"Sub Menu 2",(UINT8 *)&MenuSubMenu1,NULL,NULL},
    {MENU_COMANDO,"Funcion",NULL,vModificarVariable,&Icono3[0]},
    {MENU_COMANDO,"Radio Buttons",NULL,vVisualizarRadioButtons,&Icono7[0]},
    {MENU_COMANDO,"Check Buttons",NULL,vVisualizarCheckButtons,&Icono7[0]},
    {MENU_FIN,"",NULL,NULL,NULL}    
};
// Declaración del subMenu3. Al retroceder volvemos al subMenu1 indicado en el primer..
// .. ítem del arreglo. Luego se declara cada ítems.
const MENU MenuSubMenu3[]={
    {SUBMENU_ANTERIOR,"Juegos",(UINT8 *)&MenuSubMenu1,NULL,NULL},
    {MENU_COMANDO,"Simons Says",NULL,vJuegoSimonsSays,&Icono5[0]},
    {MENU_FIN,"",NULL,NULL,NULL}    
};

De esta manera la estructura del menú sería la siguiente: /media/uploads/Suky/menu.jpg

Ahora vamos a lo entretenido, como crear cada pantalla y como atender a los eventos. Para esto necesitamos dos funciones o rutinas, la primera se llama una sola vez para dibujar en la pantalla el menú o comando, y la segunda que se llama indefinidamente (mientras esté visualizado el menú actual) para capturar los eventos y actuar según la selección. Una forma tradicional de hacer esto es con una interminable estructura de control switch que selecciona cada pantalla, y otra para tratar los eventos. Pero en este caso se trabaja con punteros a funciones, así que en el bucle principal solo llamaremos a la función que esta apuntada actualmente. Entonces cuando se quiere acceder a un ítem llamamos a la función que dibuja sobre la pantalla, y por lo general ésta va a tener varios parámetros dependiendo de qué tipo de pantalla se trate. Por ejemplo, una opción es visualizar un mensaje (como about, o para realizar una acción), entonces entre los parámetros posibles es visualizar el mensaje a mostrar. Otra opción es qué tipo de botón inferior mostrar ([“ Atras”,” ”]; [“ Atras”,”Ok ”];[“No”,”Si”]), ect. Pero además debemos capturar el evento que selecciona el usuario e indicarlo para tomar tal acción, entonces dentro de los parámetros de la función podemos ingresar un puntero a una función a ejecutar cuando se haya tomado una elección. De esta manera la función podría ser:

void vMensajePantalla(const UINT8 *Mensaje,UINT16 YInicial,UINT8 TipoMensaje,void (*FuncionEjecutar)(UINT8);

Guardamos en variables globales las opciones del mensaje actual para accionar cuando ocurra un evento, y dibujamos en la pantalla:

void vMensajePantalla(const UINT8 *Mensaje,UINT16 YInicial,UINT8 TipoMensaje,void (*FuncionEjecutar)(UINT8){
    MensajePantalla. FuncionEjecutar=FuncionEjecutar;
    MensajePantalla.TipoMensaje=TipoMensaje;
   
   // Dibuja fondo, en este caso es una imagen->
    vDibujarImagenFondo(IMAGEN_FONDO_MENU);
   // Dibuja barra superior con titulo del submenú o función.
    vDibujaTitulodeMenu(MenuActual.PtrMenu[MenuActual.ElementoSeleccionActual].TextoMenu);
    // Escribimos mensaje en pantalla:
    vLCDTFTEscribirMensaje(YInicial,Mensaje,&ARIAL[0],JUST_CENTRADO,COLOR_MSJ);
   // Dibujamos barra inferior según elección->
    switch(TipoMensaje){
        case MENSAJE_ATRAS_OK:
            vDibujarMenuInferior("Atras", "Ok",BOTON_NORMAL);
        break;        
        case MENSAJE_NO_SI:
            vDibujarMenuInferior("No", "Si",BOTON_NORMAL);
        break;        
        case MENSAJE_ATRAS:
            vDibujarMenuInferior("Atras", " ",BOTON_NORMAL);
        break;
    }
   // Cargamos puntero para ejecutar dentro del bucle función que captura eventos sobre pantalla:
    vSeteamosFuncionEjecutar(vEventosPantallaMensaje);
}

De esta manera al ejecutar por ejemplo:

vMensajePantalla("Libreria para creacion de Menues\nen TFT 320x240 Touch Screen\npor Suky\n\n\n\n\n(C) Copyright 2011\n\nwww.micros-designs.com.ar",100,MENSAJE_ATRAS,vEventosAbout);

el resultado es:

/media/uploads/Suky/mensajes.jpg

vEventosPantallaMensaje es la función que captura los eventos (pueden ser touch o pulsadores) y actúa según la selección. Un ejemplo para el caso de un touchscreen sería:

void vEventosPantallaMensaje(void){    
    UINT8 AccionPulsIzq,AccionPulsDer;
    const UINT8 *TextDer,*TextIzq;

   // Hay una pulsación ->
   if(kbhit_irq==1){
        kbhit_irq=0;

   // Se carga acción y texto según selección->
    switch(MensajePantalla.TipoMensaje){
        case MENSAJE_ATRAS_OK:
            AccionPulsIzq = MENSAJE_ACCION_ATRAS;
            AccionPulsDer = MENSAJE_ACCION_OK;
            TextDer="Atras";
            TextIzq="Ok";
        break;

        case MENSAJE_NO_SI:
            AccionPulsIzq = MENSAJE_ACCION_NO;
            AccionPulsDer = MENSAJE_ACCION_SI;
            TextDer="No";
            TextIzq="Si";
        break;
        
        default:
            AccionPulsIzq = MENSAJE_ACCION_ATRAS;
            AccionPulsDer = MENSAJE_ACCION_ATRAS;
            TextDer="Atras";
            TextIzq=" ";
    }
        
        if(AREA_BTN_INF_DER()){
            // cambiamos de color el texto del botón para dar efecto de pulsación->
            vDibujarMenuInferior(TextIzq,TextDer,BOTON_PRES_DER);
            wait_ms(300);
            // llamamos a la función y le indicamos cual ha sido la elección:
            (*MensajePantalla. FuncionEjecutar)(AccionPulsIzq);
        }
        if(AREA_BTN_INF_IZQ()){
            vDibujarMenuInferior(TextIzq,TextDer,BOTON_PRES_IZQ);
            wait_ms(300);
            (*MensajePantalla. FuncionEjecutar)(AccionPulsDer);
        }
    }
}

Ahora desde el archivo fuente principal o el archivo fuente que trabaje con las funciones del menú se trabajaría de la siguiente forma:

const MENU MenuSubMenu1[]={
    …
    {MENU_COMANDO,"About",NULL,vAbout,&Icono3[0]},
    …    
};

void vAbout(void){
    vMensajePantalla("Libreria para creacion de Menues\nen TFT 320x240 Touch Screen\npor Suky\n\n\n\n\nCopyright 2011\n\nwww.micros-designs.com.ar",100,MENSAJE_ATRAS,vEventosAbout);
}

void vEventosAbout(unsigned char Evento){
    // No importa la elección, solo se vuelve al menú anterior
    vSeleccionarMenuAnterior();
}

Y en el main() :

int main(){
      
    vInitLCDTFTAndTouch();

    vSeleccionMenu(MenuPrincipal);
    while(1){
        vEjecutaEventosMenu();
    }
}

Donde la función EjecutaEventosMenu() se encarga de ejecutar la función que está apuntada actualmente:

/* ** Ejecuta funcion que se encuentra apuntada ** */
void vEjecutaEventosMenu(void){
    (*MenuActual.FuncionActualEjecucion)();
}

Una forma de verlo puede ser la siguiente:

/media/uploads/Suky/flujo.jpg

Un par de funciones importantes es las que trabajan con la selección de submenús y ejecutan los ítems. La función que dibuja solo recibe como parámetro el menú a dibujar (todos sus items)

void vSeleccionMenu(const MENU *Menu){
UINT8 NElemento;

    /* ** Guardamos el Menu actual que se visualiza y trabaja ** */
    MenuActual.PtrMenu=Menu;
    MenuActual.ElementoSeleccionActual=ElementoMenuSeleccion;

    /* ** Borramos pantalla y dibujamos elementos del menu** */
    vDibujarImagenFondo(IMAGEN_FONDO_MENU);
    
    // Dibuja barra superior con titulo del submenú o función.
    vDibujaTitulodeMenu(MenuActual.PtrMenu[MenuActual.ElementoSeleccionActual].TextoMenu);

    /* ** Dibujamos Items ** */
    NElemento=1;
    while(MenuActual.PtrMenu[NElemento].TipoMenu!=MENU_FIN){    
        vDibujarElementoMenu(NElemento-1,ITEM_NORMAL); 
        NElemento++;
    }
    // Guardamos cantidad de ítems del menu actual 
    MenuActual.CantElementosMenu=NElemento-1;
    // Dibujamos barra inferior según el submenu
    if(MenuActual.PtrMenu[0].TipoMenu==MENU_MAIN){
        vDibujarMenuInferior(" ","Menu",BOTON_NORMAL);
    }else{  // SUBMENU_ANTERIOR
        vDibujarMenuInferior("Atras"," ",BOTON_NORMAL);
    }
    /* ** Cargamos en puntero funcion que atiende eventos del Submenu (Función en while del main)** */
    vSeteamosFuncionEjecutar(vEventosSubMenu);
}

Ahora la función que trata los eventos, según de cómo sean organizados los ítems puede llegar a ser algo compleja, pero a grandes rasgos se testea si corresponde al área de un ítems, se determina si es un submenú o un comando y se trabaja con el puntero correspondiente (*MenuSeleccion o void (*PtrFuncionSeleccion)(void)). Un ejemplo para el siguiente estilo de visualización:

/media/uploads/Suky/items.jpg

void vEventosSubMenu(void){
    const MENU *SubMenu;
    void (*PtrComando)(void);
    
    // hay una pulsación ->
    if(kbhit_irq==1){
        kbhit_irq=0;
        // Menu principal ?  ->             
        if(MenuActual.PtrMenu[0].TipoMenu==MENU_MAIN){
            if(AREA_BTN_INF_DER()){
                // efecto de pulsación, cambio de color de letra.-
                vDibujarMenuInferior(" ","Menu",BOTON_PRES_DER);
                wait_ms(300);
                // Cargamos menu seleccionado y mostramos->
                SubMenu=(MENU *)MenuActual.PtrMenu[0].MenuSeleccion;
                vSeleccionMenu(SubMenu);
            }
        }else{  // Un submenú cualquiera ->
            for(char k=0;k<MenuActual.CantElementosMenu;k++){
                // Testea si se ha pulsado dentro del area del botón de cada ítems->
                if((Coordenadas.x<235 && Coordenadas.x>5) && (Coordenadas.y<(60+(k*30)) && Coordenadas.y>(35+(k*30)))){
                    // Si se selecciona un items se genera efecto pulsación cambiando color letra->
                    vDibujarElementoMenu(k,ITEM_PRES);
                    wait_ms(300);
                    // Se indica que items ha sido seleccionado para trabajar cons sus propiedades ->
                    MenuActual.ElementoSeleccionActual=k+1;
                    /* ** Abrimos otro submenu o ejecutamos comando ** */
                    switch(MenuActual.PtrMenu[k+1].TipoMenu){
                        case MENU_COMANDO:
                            PtrComando=(void(*)(void))MenuActual.PtrMenu[k+1].PtrFuncionSeleccion;
                            if(PtrComando!=NULL) (*PtrComando)();
                        break;
                        case MENU_SUBMENU:
                            SubMenu=(MENU *)MenuActual.PtrMenu[k+1].MenuSeleccion;
                            vSeleccionMenu(SubMenu);
                        break;
                    }
                }
            }            
            
            if(AREA_BTN_INF_IZQ()){
                vDibujarMenuInferior("Atras"," ",BOTON_PRES_IZQ);
                wait_ms(300);
                SubMenu=(MENU *)MenuActual.PtrMenu[0].MenuSeleccion;
                vSeleccionMenu(SubMenu);
            }
        }
    }    
}

Bueno, espero se entienda algo. Les dejo algunos imágenes de los items de menú realizados:

/media/uploads/Suky/01.jpg /media/uploads/Suky/02.jpg /media/uploads/Suky/03.jpg /media/uploads/Suky/04.jpg /media/uploads/Suky/06.jpg /media/uploads/Suky/05.jpg

Ejemplo realizado en mbed:

Import programExample_Menu_LCD_TFT

Example Menus LCD TFT

Algunas de las librerías utilizadas:


9 comments on Menus for TFT LCD and Touch Screen:

10 Mar 2011

lots of work, congrats, but i think most people would need this in english, too.

10 Mar 2011

thanks! ;)

11 Mar 2011

Hi, Good work. Can you share a complete project? Thank you.

15 Mar 2011

I updated the article, in the end is linked entire project.

16 Mar 2011

Thank you very much. :)

30 Mar 2011

How did you design your buttons in hex?

01 Apr 2011

Chris Mur wrote:

How did you design your buttons in hex?

I realized the following application, Visual C++ 2008

http://www.micros-designs.com.ar/downloads/Conversi%f3n%20bmpHex.exe

22 Dec 2013

Ale,

I could not get the raw code to compile:

Warning: Members and base-classes will be initialized in declaration order, not in member initialisation list order in "TouchADS7843/Touch.h", Line: 114, Col: 29 Error: Identifier "Led1" is undefined in "MenusLCD/Menus.h", Line: 149, Col: 5 Error: Identifier "Led2" is undefined in "MenusLCD/Menus.h", Line: 150, Col: 5

Did you update the code or library?

Very nice work! I have a display on order for in oven SMT re-flow project (fancy toaster that I have).

Your display and Temp update would be a great addition.

Thanks!

Jeff

23 Apr 2016

Hola. Cual es la referencia de la pantalla

Please log in to post comments.