Forum: Mikrocontroller und Digitale Elektronik generelle Frage zur Programmstruktur


von Michael (Gast)


Lesenswert?

Hi,

ich hab ein kleines Problem damit, meine µC-Applikation zu schreiben. 
Also, es scheitert nicht an der Programmierung an sich, sondern an der 
Art der Lösung. Ich habe die Einzelteile, also z.B. Treiber für Display, 
Tastatur usw. schon geschrieben, sie funktionieren auch, soweit ich das 
Austesten konnte.

Das Problem besteht nun darin, dass ich es nicht schaffe, die Teile so 
zusammen zu pfriemeln, dass sich eine gescheite Applikation ergibt. 
Konkret scheitert es eigentlich schon am Ansatz :(

Vom Gefühl her würde ich sagen, dass es am besten ist, wenn ich das 
Programm so schreibe, dass an keiner Stelle auf irgendwas gewartet, 
sondern nur geprüft wird, ob etwas an der entsprechenden Stelle gemacht 
werden muss -> State-Machine?

Diesen Ansatz (ohne State-Machine) habe ich nun versucht zu 
implementieren, d.h. in der Main-Schleife laufen zwei Sachen. Erstens 
eine Auswertung von empfangenen DCF77-Daten (die Daten selbst lese ich 
in einem Interrupt aus -> funktioniert). Das zweite ist eine 
Menü-Ausgabe.

Die Menü-Ausgabe ist etwas spezieller, weil dort momentan irgendwie 
alles abläuft, deswegen erklär ich das mal genauer:

Die Menü-Funktion prüft, ob eine der vier Navigationstasten gedrückt 
wurde. In Abhängigkeit davon wird ein Pointer auf ein Menü-Element 
gesetzt.
Das Menü-Element hat folgenden Aufbau:
1
typedef struct m_item{
2
  struct m_item *Up;
3
  struct m_item *Down;
4
  struct m_item *Left;
5
  struct m_item *Right;
6
  void (code *fpPtr)(void);
7
} _stMenu;

Die ersten vier Einträge zeigen jeweils auf den 
oberen/unteren/linken/rechten Knoten des Menüs, bzw. auf NULL(nicht 0), 
wenn's keinen direkten Nachbarn mehr gibt. Die Menü-Struktur wird über 
ein entsprechendes Array der Menü-Elemente realisiert.
Der fünfte Eintrag ist ein Funktionspointer. Die entsprechende Funktion 
wird bei jedem Aufruf der Menü-Funktion aufgerufen, unabhängig davon, ob 
eine der Richtungstasten gedrückt wird.
Zusätzlich kann die Abfrage der Navigations-Tasten in der Menü-Funktion 
durch ein globales Flag gesperrt werden, um die Navigationstasten für 
die aufgerufenen Funktion freizumachen.
Die aufgerufenen Funktionen übernehmen alles, von der Display-Anzeige 
über die Tastatur-Eingaben, bis hin zu IO-Aufgaben.
So, und ich glaube mit dem Ansatz hab ich mir n Ei gelegt. Erstens 
gibt's irgendwie Kuddelmuddel, zweitens gefällt es mir vom Gefühl her 
nicht, und dritten tut's eh nicht :(

Die Frage wär jetzt, wie realisiert man sowas richtig? Und zwar nicht 
auf ein spezielles Problem bezogen, sondern allgemein.

Hierzu mal meine Überlegungen. Ich denke, ich brauch definitiv ne 
State-Machine. Punkt.
Wie bau ich die denn nun auf? Eine Variable für alle Zustände, oder für 
jeden Part (Tastatur, LCD, usw.) eine eigene Variable?
Wenn ich nur eine Variable verwende, gibts ja irgendwie wieder ein 
Durcheinander, weil dann m.E. die Eingabe nicht sauber von der Ausgabe 
getrennt ist.
Also eher der Ansatz von voneinander unabhängigen State-Variablen. Und 
wie realisiert man dann sowas? Wie sieht das Grundgerüst aus? Hat mir 
vielleicht jemand Pseudocode?
Oder ist der Ansatz auch murks?

Ich hoffe, ich konnte alle relevanten Infos liefern :)

Vielen Dank schonmal

Michael

von Εrnst B. (ernst)


Lesenswert?

Menus implementier ich meist ähnlich, und hab dort fast alles innerhalb 
meiner menu-structs definiert:
1
#define CODE_LEFT   0
2
#define CODE_RIGHT  1
3
#define CODE_PRESS  2
4
#define CODE_IDLE   3
5
#define FAST_IDLE   4
6
#define CODE_ENTER  5
7
#define CODE_LEAVE  6
8
#define CODE_UPDATE 7
9
10
#define EVENT_ENTER  1
11
#define EVENT_LEAVE  2
12
#define EVENT_UPDATE 3
13
14
#define SHORT_IDLE_SECONDS 5
15
#define LONG_IDLE_SECONDS 30
16
17
typedef void (*display_func)(uint8_t);
18
19
struct menu_entry {
20
  const prog_char * text;
21
  display_func display;
22
  const void * left;
23
  const void * right;
24
  const void * press;
25
  const void * idle;
26
27
  uint8_t flags;
28
};
Je nach gesetztem "CODE_XXX" Flag sind die Void-Pointer Verweise auf den 
nächsten eintrag, oder Funktionspointer, die aufgerufen werden.
die display_func wird jde nach CODE_ENTER,CODE_LEAVE,CODE_UPDATE 
aufgerufen (Mit EVENT_XXX) als Parameter, und kann z.B. im "Enter" einen 
Wert aus dem EEPROM auslesen, im update darstellen, und beim Leave 
wieder ins eeprom zurückspeichern. Die left/right-Funktionen dienen dann 
zum einstellen des Wertes.

Beispiel um eine Uhrzeit einzustellen:
1
extern struct menu_entry PROGMEM menu_time;
2
extern struct menu_entry PROGMEM menu_time_hour;
3
extern struct menu_entry PROGMEM menu_time_minute;
4
extern struct menu_entry PROGMEM menu_time_second;
5
extern struct menu_entry PROGMEM menu_time_weekday;
6
7
static const prog_char text_time[]         = "Uhr stellen     ";
8
static const prog_char text_empty[]        = "                ";
9
10
static void display_time_minute(uint8_t flags);
11
static void rotate_time_minute(int8_t amount);
12
static void display_time_second(uint8_t flags);
13
static void rotate_time_second(int8_t amount);
14
static void display_time_weekday(uint8_t flags);
15
static void rotate_time_weekday(int8_t amount);
16
[...]
17
18
struct menu_entry PROGMEM menu_time={
19
  text_time,
20
  NULL,
21
  &menu_switchpoints,
22
  &menu_clocktune,
23
  &menu_time_hour,
24
  &menu_idle,      // Idle
25
  0
26
};
27
struct menu_entry PROGMEM menu_time_hour={
28
  text_empty,
29
  display_time_hour,
30
  rotate_time_hour,
31
  rotate_time_hour,
32
  &menu_time_minute,
33
  &menu_idle,      // Idle
34
  _BV(CODE_ENTER)|_BV(CODE_LEAVE)|_BV(CODE_UPDATE)|_BV(CODE_LEFT)|_BV(CODE_RIGHT)
35
};
36
struct menu_entry PROGMEM menu_time_minute={
37
  text_empty,
38
  display_time_minute,
39
  rotate_time_minute,
40
  rotate_time_minute,
41
  &menu_time_second,
42
  &menu_idle,      // Idle
43
  _BV(CODE_ENTER)|_BV(CODE_LEAVE)|_BV(CODE_UPDATE)|_BV(CODE_LEFT)|_BV(CODE_RIGHT)
44
};
45
[usw]
46
void display_time_hour(uint8_t flags) {
47
  display_time_edit(flags);
48
  if (flags == EVENT_UPDATE) {
49
    // Move Cursor
50
    lcd_command(LCD_CMD_DDRAM|0);
51
  }
52
}
53
54
void display_time_minute(uint8_t flags) {
55
  display_time_edit(flags);
56
  if (flags == EVENT_UPDATE) {
57
    // Move Cursor
58
    lcd_command(LCD_CMD_DDRAM|3);
59
  }
60
}
61
void rotate_time_minute(int8_t amount) {
62
idle_counter=0;
63
 cli();
64
 uint16_t minutes=clock.minutes;
65
 uint8_t mins=minutes%60;
66
 if (amount < 0) {
67
   if (mins!=0) {
68
     minutes-=1;
69
   } else {
70
     minutes+=59;
71
   }
72
 }
73
 if (amount > 0) {
74
   if (mins!=59) {
75
     minutes+=1;
76
   } else {
77
     minutes-=59;
78
   }
79
 }
80
 clock.minutes=minutes;
81
 sei();
82
}
Angetrieben wird die State-Maschine dann in der Main-Schleife über 
Keypresses und timer-flags:
1
     key=kbd_get_keys(_BV(KEY_PRESS));
2
     delta=kbd_get_delta();
3
     nextaction.v=0;
4
     is_code=0;
5
     if (key & _BV(KEY_PRESS)) {
6
        nextaction.v=&current_menu->press;
7
        is_code=current_menu_flags & _BV(CODE_PRESS);
8
     }
9
     if (delta < 0) {
10
        nextaction.v=&current_menu->left;
11
        is_code=current_menu_flags & _BV(CODE_LEFT);
12
     }
13
     if (delta > 0) {
14
        nextaction.v=&current_menu->right;
15
        is_code=current_menu_flags & _BV(CODE_RIGHT);
16
    }
17
    if (idle_counter > (uint8_t)(((current_menu_flags & _BV(FAST_IDLE))?SHORT_IDLE_SECONDS:LONG_IDLE_SECONDS) / TIMER0_IVAL / 256)) {
18
        idle_counter=0;
19
        nextaction.v=&current_menu->idle;
20
        is_code=current_menu_flags & _BV(CODE_IDLE);
21
    }
22
    if (nextaction.v) {
23
       nextaction.v=(void *)pgm_read_word(nextaction.v);
24
    }
25
    if (nextaction.v) {
26
       if (is_code) {
27
         nextaction.c(delta);
28
       } else {
29
         switch_menu(nextaction.m);
30
       }
31
    }
32
    if (scroll_flag) {
33
      scroll_display();
34
    }

Hoffe die Code-Fragmente helfen dir weiter, wenn vielleicht auch nur als 
abschreckendes Beispiel...

von tuppes (Gast)


Lesenswert?

> Die aufgerufenen Funktionen übernehmen alles,
> von der Display-Anzeige über die Tastatur-Eingaben,
> bis hin zu IO-Aufgaben.

Konsequenz daraus ist: Solange eine der Menüfunktionen läuft, ist alles 
andere tot. Ist das vielleicht das Problem?

von Michael (Gast)


Lesenswert?

Hallo,

sorry für die späte Antwort.

@Ernst:

Danke, ich werd mir deinen Code mal durchlesen.

@Tuppes:

> Konsequenz daraus ist: Solange eine der Menüfunktionen läuft, ist alles
> andere tot. Ist das vielleicht das Problem?
Nein, es ist eben nicht alles andere tot, die Funktionen sind extra so 
geschrieben, dass nix stehenbleibt. Aber irgendwie wird alles dort 
gemacht, und das fühlt sich eben nicht richtig an. Ich bin der Meinung, 
dass sich das sauberer trennen lassen muss, nur fehlt mir dazu der 
Lösungsansatz.

Michael

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.