Forum: Projekte & Code Ein Menü für LCD Textdisplays hier Nokia3310


von Ralf W. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

vor einigen Jahren wollte ich eine Heizungsteuerung bauen, da die alte 
defekt war.Leider ist sie nie ganz fertig geworden und mittlerweile auch 
obsolet, da ich einen neuen Kessel habe.

Warum ich das hier trotzdem poste?
Weil ich hier im Forum viel gelernt habe und andere vielleicht von dem 
implementierten Menü lernen können und weils Spaß macht und weil ich 
auch einiges an Code geborgt habe.

Im Anhang der komplette Quellcode und ein Foto der Hardware auf dem ich 
ihn entwickelt habe. Das war nicht die endgültige Hardware, sondern 
wurde von einem anderen Projekt geborgt. Es ging mir ja erstmal ums Menü 
und die Schaltpunkte.

So los gehts:

Als erstes mal die Menüs deklarieren, bei mir in der main.c:
1
t_Menu SettingsMenu={
2
    .Text = "Settings", //der Name des Menüs
3
    .menu_id = 1,    //eine eindeutige ID
4
    .Previous = NULL,  //wird von der Menüroutine gebraucht und sollte NULL bleiben
5
    .Count = 2,      //die Anzahl der Menuitems
6
    .MenuItems = {
7
        {
8
        .Text = "BackLight", //der Name des Items
9
        .item_id = 15,     //eine eindeutige ID
10
        },
11
        {
12
        .Text = "Set Time",
13
        .item_id = 14,
14
        },
15
    },
16
};
17
18
t_Menu EditSwitchpoints ={
19
    .Text = "Edit Switp",
20
    .menu_id = 2,
21
    .Previous = NULL,
22
    .Count = SWITCHPOINTS_COUNT,
23
    .ItemType = 1      //ein Spezialmenü
24
};
25
26
t_Menu MainMenu ={
27
    .Text = "Hauptmenue",
28
    .menu_id = 0,
29
    .Previous = NULL,
30
    .Count = 3,
31
    .MenuItems = {
32
        {
33
        .Text = "@Settings", //hier habe ich einen Trick verwendet, um nicht alle Menüs geschachtelt aufschreiben zu müssen
34
               //zeigt das @ an das dies ein Untermenü ist. Der Code findet das Untermenü anhand vom Namen
35
               //selbst.
36
        .item_id = 16,     //wieder eine eindeutige ID
37
        },
38
        {
39
        .Text = "@Edit Switp",
40
        .item_id = 19,
41
        },
42
        {
43
        .Text = "Fertig",
44
        .item_id = 20,
45
        },
46
    },
47
};
48
49
t_Menu *Menus[] = {    //ein Array von Menüs, wie gesagt die Untermenüs werden durch das @ selbständig gefunden
50
    &MainMenu,
51
    &SettingsMenu,
52
    &EditSwitchpoints,
53
};
54
55
56
uint8_t MenuCount = 3; //wieviele Menüs sind im Array
57
58
t_ActiveApplication ActiveApplication = &MenuApplication; // dem von mir erfundenen Menü die Kontrolle übergeben

Ein Wort zu den Applikationen. Ich habe alle Menuitems als Apps 
definiert. Das heißt eine App hat die Kontrolle
über das Display und den Controller. Wenn zB. "Backlight" aufgerufen 
wird, wird die backlightapp aktiv und zeigt
hier ihre Ausgaben auf dem Display. Auf dem Foto im Anhang sieht man zB. 
die Ausgaben der idleapp.

So weiter geht es. MySelect wird gebraucht um auf die Menüitems 
reagieren zu können und somit die entsprechenden
Apps zu aktivieren.
1
void MySelect(uint8_t MenuId, uint8_t action, uint8_t ItemType){ 
2
    switch(ItemType){
3
        case MENU_ITEMTYPE_BUILDIN:{
4
            switch( MenuId){
5
                case 14:{
6
                    Clear();
7
                    SetDateTimeToChange(RealTime_GetDateTime());
8
                    ActiveApplication = &SetTimeApp;
9
                    ActiveApplication(NO_KEY);
10
                }
11
                break;
12
                case 15:{
13
                    //Beep();
14
                    Clear();
15
                    PrintStringPos(0,0,"BackLight");
16
                    Highlight(1);
17
                    PrintStringPos(1,1,"Value:");
18
                    //setBackLightPWMValue(&BackLightPWM);
19
                    ActiveApplication = &BackLightApplication;
20
                    ActiveApplication(NO_KEY);
21
                }
22
                break;
23
                case 20:{
24
                    idle = 1;
25
                }
26
                break;
27
                default:
28
                break;
29
            }
30
        }
31
        break;
32
        case 1:{//my special menu type for eeprom array
33
            if(action == MENU_ACTION_SELECT_SPECIALITEM){
34
                switch(MenuId){
35
                    case 2:{
36
                        Clear();
37
                        ActiveApplication = &EditSwitchpointApp;
38
                        ActiveApplication(NO_KEY);
39
                    }
40
                    break;
41
                    default:
42
                    break;
43
                }
44
            }
45
        }
46
        break;
47
    }
48
}

Jetzt noch die main Routine:
1
int main(void){
2
  SetPorts();
3
  InitTimer1_Timebase();
4
    InitKeys();
5
  lcd_init();
6
  lcd_contrast(0x40);
7
    InitBackLight();
8
  //InitUart();
9
10
  EnterMenu(&MainMenu, &MySelect); // dies muß einmal aufgerufen werden, um das Menü zu initialisieren
11
                    // irgendwie muß das Menü ja wissen was es anzeigen soll und welche
12
                    // Aktion ausgeführt werden soll, wenn ein Menüitem ausgewählt wird.
13
  sei();
14
  for(;;){
15
       if (onesecondover){
16
            onesecondover = 0;
17
            ActiveApplication(TICK);  //der aktiven ActiveApplication die Ereignisse wie Tastendrücke
18
                    //oder Timerevents mitteilen
19
        }
20
        if (get_key_short((1<<DOWN))){// get_key_short ist ein Teil von Peter Dannegger Tastenabfrage
21
                     // die ich aber nicht komplett verwendet habe.
22
            idle = 0;
23
            ActiveApplication(DOWN);  
24
        }
25
        if (get_key_short((1<<LEFT))){
26
            idle = 0;
27
            ActiveApplication(LEFT);
28
        }
29
        if (get_key_short((1<<RIGHT))){
30
            idle = 0;
31
            ActiveApplication(RIGHT);
32
        }
33
        if (get_key_short((1<<OK))){
34
            idle = 0;
35
            ActiveApplication(OK);
36
        }
37
        if (get_key_short((1<<UP))){
38
            idle = 0;
39
            ActiveApplication(UP);
40
        }
41
        if(oneminuteover){
42
            oneminuteover = 0;
43
            if(ActiveApplication != EditSwitchpointApp){ //nicht aufrufen wenn Schaltpunkte editiert werden
44
                IdleApplication(ONE_MINUTE_TICK);
45
            }
46
        }
47
        if(idle){
48
            idle = 0;
49
//            if(ActiveApplication == MenuApplication){//nur im Menü Idle
50
//                //display Schaltzustand
51
                IdleApplication(NO_KEY);
52
//            }
53
        }
54
   }

Hier jetzt der menu.h in dem die Menüs und die wichtigsten Routinen 
dafür definiert werden.
1
#define TextLenght DisplayColumns -2  //maximum lenght of entry.
2
                    //DisplayColumns ist die Anzahl der Spalten, die das Display hat
3
4
#define MENU_ITEMTYPE_BUILDIN 0
5
6
#define MENU_ACTION_CHANGE_MENU 0
7
#define MENU_ACTION_SELECT_BUILDINITEM 1
8
#define MENU_ACTION_SELECT_SPECIALITEM 2
9
10
/*                  menu_id or item_id,   action, ItemType*/
11
typedef void (*t_UserSelect)(uint8_t, uint8_t, uint8_t); //die Definition der UserSelect Routione, die aufgerufen
12
                              //wird, wenn ein Menüitem selektiert wird, welches eine App
13
                              //ist
14
15
extern char* getItemText(uint8_t ItemType, uint8_t index);//im Moment habe ich keinen Plan wozu das hier dient
16
17
uint8_t menu_getCurrentItemId(void);//im Moment habe ich keinen Plan wozu das hier dient
18
19
20
struct s_item;
21
struct s_menu;
22
23
24
typedef struct s_item
25
  {
26
    char Text[TextLenght];  //entry title
27
        uint8_t item_id;
28
  } t_MenuItem;
29
30
31
32
33
typedef struct s_menu
34
  {
35
    char Text[TextLenght];  //menu title
36
    struct s_menu *Previous;  //pointer to previous menu (backtracking)
37
        uint8_t Count;
38
        uint8_t ItemType;
39
        uint8_t menu_id;
40
    t_MenuItem MenuItems[];  //entrys
41
  } t_Menu;
42
43
extern uint8_t MenuCount;
44
extern t_Menu *Menus[];
45
46
/* called only once from user code, set's the first menu and the Select function*/
47
void EnterMenu( t_Menu *newMenu, t_UserSelect UserSelect);
48
49
/* makes the previous item the current item*/
50
void PreviousItem();
51
52
/* makes the next item the current item*/
53
void NextItem();
54
55
/* exit's the current menu, and makes the menu in Previous the current menu*/
56
void ExitMenu();
57
58
/* executes the items select function or enters a submenu*/
59
void SelectItem();
60
61
/* call this after UserSelect */
62
void ExitItem();

Hier jetzt die MenuApplication. Die ist recht aufgeräumt. Oder?
1
void MenuApplication(uint8_t Button){
2
    switch( Button){
3
     case UP:
4
        PreviousItem();
5
        break;
6
     case DOWN:
7
        NextItem();
8
        break;
9
     case LEFT:
10
        ExitMenu();
11
        break;
12
     case RIGHT:
13
     case OK:
14
        SelectItem();
15
     case TICK:
16
        break;
17
    }
18
}

Und hier mal die BacklightApplication.
1
void BackLightApplication(uint8_t Button){
2
    switch (Button){
3
     case TICK: return;
4
     case OK:
5
         ActiveApplication = &MenuApplication;
6
         ExitItem();
7
         return;
8
     case UP:
9
        rangedInc_uint8_t((t_ranged_uint8_t *)&BackLightPWM);//t_ranged_uint8_t ist ein von mir erfundener Typ, den 
10
                                //erkläre ich gleich.
11
        break;
12
     case DOWN:
13
        rangedDec_uint8_t((t_ranged_uint8_t *)&BackLightPWM);
14
        break;
15
     case RIGHT:
16
        break;
17
    }
18
    char buffer[3];
19
    utoa( BackLightPWM.value, buffer, 10);
20
    PrintStringPos(7,1,"    ");
21
    PrintStringPos(7,1,buffer);
22
}

t_ranged_uint8_t habe ich dafür erfunden, um Zahlen zu inkremenitieren 
und dekrementieren, die eine Ober und Untergrenze
haben. Um nicht bei jeder Verwendung dieser Zahl mit if Bedingungen 
darauf prüfen zu müssen, habe ich das halt ausgelagert.
Zum Beispiel wird BackLightPWM so deklariert:
1
#define BackLightPWMStart 16
2
t_ranged_uint8_t volatile BackLightPWM = {
3
    .value = BackLightPWMStart,
4
    .top = BackLightPWMStart,
5
    .bottom = 0
6
};

Das BackLightPWM.value ist 16. BackLightPWM.top ist ebenfalls 16. Beim 
incrementiern muß nach der 16 also
BackLightPWM.bottom kommen. Hier ist BackLightPWM.bottom 0.

ranged.h
1
#ifndef RANGED_H_INCLUDED
2
#define RANGED_H_INCLUDED
3
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <string.h>
7
#include <stdint.h>
8
9
typedef struct{
10
    uint8_t value;
11
    uint8_t top;
12
    uint8_t bottom;
13
}t_ranged_uint8_t;
14
15
void rangedInc_uint8_t(t_ranged_uint8_t *value);
16
void rangedDec_uint8_t(t_ranged_uint8_t *value);
17
18
#endif // RANGED_H_INCLUDED

ranged.c
1
#include "ranged.h"
2
3
void rangedInc_uint8_t(t_ranged_uint8_t *value){
4
    if(value->value == value->top){
5
        value->value = value->bottom;
6
    }
7
    else{
8
        value->value +=1;
9
    }
10
}
11
12
13
void rangedDec_uint8_t(t_ranged_uint8_t *value){
14
    if(value->value == value->bottom){
15
        value->value = value->top;
16
    }
17
    else{
18
        value->value -=1;
19
    }
20
}

So erstmal genug geschrieben. Wer Fragen hat gerne Fragen und 
diskutieren. Den mir ist klar,
das hier ist nicht der Weisheit letzter Schluß.

gruß ralf

von Ralf W. (Gast)


Angehängte Dateien:

Lesenswert?

Sorry Leute, ich habe gar nicht gesehen wie groß das Archiv mit dem 
Quellcode ist. Deshalb hier noch mal kleiner.

Ich hatte das Datenblatt vom Atmega 88 noch mit im Archiv.

von Ralf W. (Firma: HEB industrie elektronik) (ralf_werner)


Lesenswert?

Hallo,

wollte nur für später darauf hinweisen, das es jetzt einen Artikel
für das Menüsystem gibt.

https://www.mikrocontroller.net/articles/YaMen%C3%BC



Ich hoffe ihr habt Spaß.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.