Forum: Mikrocontroller und Digitale Elektronik C-Programm: Idee für Menü und Hauptschleife (LED-Lampe)


von Michael N. (garril)


Lesenswert?

Hallo,

ich möchte eine kleine LED-Lampe bauen, die mehrere "Programme" 
ausführen kann (sprich: LEDs leuchten in verschiedenen Reihenfolgen).
Nun habe ich mir das wie folgt vorgestellt:
Sobald die Schaltung Strom bekommt, wird Programm 1 ausgeführt. In der 
Schaltung gibt es einen einzigen Taster. Sobald man ihn dann einmal 
drückt, leuchtet die zweite LED auf um "Programm 2" anzuzeigen. Der 
Taster kann mehrmals betätigt werden um dann die nächsten Programme auch 
auswählen zu können. Drückt man den Taster für 5 Sekunden nicht mehr, 
soll das eben ausgewählte Programm dargestellt werden.

Wie setze ich das am besten im Code um??
Zum Einsatz soll ein Attiny 45 kommen!

Hier mal meine Idee: (nur grob programmiert)
1
programm(int programmnr) {
2
  //Hier wird der Ablauf jedes Programm hinterlegt
3
}
4
main-loop {
5
  while(1) {
6
    if selected_programm==1 {
7
      programm(1);
8
    }
9
    //if andere programme...
10
  }
11
}

Jetzt müsste ich eine Zustandsänderung am Eingang an dem der Taster 
hängt per Interrupt gemeldet bekommen und dann die main-loop 
unterbrechen und stattdessen das "Menü" mithilfe der LEDs anzeigen...
Hat da jemand schon ein fertiges Grundgerüst? Komme zwar ganz gut mit 
der Programmierung klar, aber wäre doch da sehr an einem Muster 
interessiert.

Oder ist mein Ansatz sowieso nicht ganz passen?
Danke schonmal:)

von Karl H. (kbuchegg)


Lesenswert?

Michael N. schrieb:

> Jetzt müsste ich eine Zustandsänderung am Eingang an dem der Taster
> hängt per Interrupt gemeldet bekommen

Taster wertet man nicht per Interrupt aus.

Im Artikel Entprellung findest du sauber aufgebaute Funktionalität, 
mit der man Tasten auswerten kann. Ganz unten.

Und für deine 'Programme' kannst du dann den Timer gleich mitverwenden.

von Johannes G. (gutenberg)


Lesenswert?

1
> programm(int programmnr) {
2
>   //Hier wird der Ablauf jedes Programm hinterlegt
3
> }
4
> main-loop {
5
>   while(1) {
6
>       programm(selected_programm);
7
>   }
8
> }
9
>

Bitte nicht if 1 then 1 und so weiter...

von Michael N. (garril)


Lesenswert?

@Johannes G:
In Wirklich würde hier natürlich programm(selected_programm) stehen. 
Danke trotzdem für den Hinweis. Wollte damit oben nur deutlich machen, 
dass es eben mehrere gibt und die einfach durchnummeriert sind...

@K. H. Buchegger:
Wenn in meiner mainloop aber nun folgendes passiert:
(Beispielcode)
1
LEDan(1)
2
wait (0,5s)
3
LEDaus(1)
4
LEDan(2)
5
wait (0,5s)
6
LEDaus(2)
7
LEDan(3)
8
wait (0,5s)
9
LEDaus(3)
10
LEDan(4)
11
wait (0,5s)
12
//...alle LEDs werden durchgeschaltet
Dann müssten ja immer zwischendrin eine Funktion zur Prüfung des 
Eingangs wo der Taster hängt ausführen.
Dann sähe mein Code in etwa so aus:
(nur für erste LED)
1
LEDan(1)
2
check_taster()
3
wait (0,1s)
4
check_taster()
5
wait (0,1s)
6
check_taster()
7
wait (0,1s)
8
check_taster()
9
wait (0,1s)
10
check_taster()
11
wait (0,1s)
12
LEDaus(1)
13
//LED2 usw....
Oder sehe ich das falsch?

von Achim M. (minifloat)


Lesenswert?

Wenn du deine Wait-Funktionen mit einem Timer machst, dann kannst du 
auch gleichzeitig den Taster in einem Timerinterrupt abfragen.

Wenn jedes Unter-Programm wie eine Statemachine aufgebaut ist, brauchst 
du auch keine langen Durchlaufzeiten, bis ein Programmwechsel erfolgen 
kann.
Du müsstest, wenn gerade z.B. Programm 1 läuft und ein Wechsel auf 
Programm 2
erfolgen soll, nicht bis zum Ende von Programm 1 warten.
mfg mf

von GhettoCoder (Gast)


Lesenswert?

Früher hat man noch selber programmiert, heute lässt man jede Zeile und 
jeden Ansatz vom Forum überprüfen... o tempora, o mores.

von bobtheworker (Gast)


Lesenswert?

Wenn ich sowas bauen müsste würde ich sofort an einen Zustandsautomaten 
denken. Das ist meiner Meinung der sauberste Weg.
In C kannst du hier für die switch case funktion nutzen, vermutlich als 
Moore-Automat.

http://www.peacesoftware.de/ckurs7.html

von Michael N. (garril)


Lesenswert?

Würde ich täglich Mikrocontroller programmieren, würde ich mir 
wahrscheinlich einfacher tun...
Allerdings habe ich eigentlich nichts damit am Hut, aber bisher (bis auf 
den Taster) schon alles zum laufen gebracht.

Genau diese Problematik, dass erst ein Unterprogramm fertiglaufen muss, 
habe ich mit einer anderen Schaltung auch (dort ist es ein Schalter 
statt eines Tasters, wodurch das nicht soooo schlimm ist, aber trotzdem 
stört).

Hast du vielleicht einen Link zu einem kleinen Beispielprogramm wo das 
so gemacht wird. Mir ist noch nicht ganz klar wie ich das umsetzen 
soll...

@bobtheworker:
Geht es hierbei nur um das abfragen des aktuellen Programmes und dann 
die Ausführung eben dieses?? Weil das ist ja mit 
"programm(selected_programm);" schon getan. selected_programm müsste 
halt nur bei jedem Tastendruck hochgezählt werden.
Und am liebsten würde ich nach dem Tastendruck über die LEDs (z.B. nur 
Leuchten der dritten LED --> drittes Programm gewählt) das aktuell 
ausgewählte Programm kurz anzeigen (bzw. weiterschalten dann mit 
erneutem Tastendruck)

von Karl H. (kbuchegg)


Lesenswert?

Michael N. schrieb:
> @Johannes G:
> In Wirklich würde hier natürlich programm(selected_programm) stehen.
> Danke trotzdem für den Hinweis. Wollte damit oben nur deutlich machen,
> dass es eben mehrere gibt und die einfach durchnummeriert sind...
>
> @K. H. Buchegger:
> Wenn in meiner mainloop aber nun folgendes passiert:
> (Beispielcode)
>
1
> LEDan(1)
2
> wait (0,5s)
3
> LEDaus(1)
4
> LEDan(2)
5
> wait (0,5s)
6
> LEDaus(2)
7
> LEDan(3)
8
> wait (0,5s)
9
> LEDaus(3)
10
> LEDan(4)
11
> wait (0,5s)
12
> //...alle LEDs werden durchgeschaltet
13
>

Dachte mir schon, das sowas kommen wird.

Das, genau das, ist der völlig falsche Weg.
Lern doch bitte erst mal ein paar Dinge aus der Programmiersprache, die 
du benutzt. Als erstes könntest du mal mit Arrays anfangen.

Und wenn du dann erst mal soweit bist, wie du diese Codemonster mittels 
Arrays auf 3 Zeilen eindampfen kannst, dann gehts weiter, wie man da mit 
einem Timer die Warterei erledigt.
Und dann ergibt sich plötzlich auch völlig klar und einfach, wie man da 
eine Tastenbedienung einbaut.

von Michael N. (garril)


Lesenswert?

...Okay^^ Sowas dachte ich mir schon, dass gleich kommt...
Ich habe schon relative große Projekte in PHP programmiert und auch in 
Java und Pyhton einiges zu tun gehabt.

Für das oben hätte ich mal gesagt:
1
for (int i=1; i<=8; i++) {
2
LEDan(i);
3
_delay_ms(500);
4
LEDaus(i);
5
}
Besser so um zu zeigen, dass ich das kann?^^

Um die Wartezeit aufzuteilen und immer wieder zu prüfen ob die Taste 
gedrückt wurde, hätte ich das halt dann wie folgt gemacht:
1
//funktion zum warten und Tasterabrufen
2
void wait (int timer) {
3
for (int i=0; i<=timer; i=i+10) {
4
//Es sind dann nur immer volle 10ms, aber der Programmablauf muss nicht so genau sein
5
_delay_ms(10);
6
check_taster();
7
}
8
}
9
//main:
10
for (int i=1; i<=8; i++) {
11
LEDan(i);
12
wait(500);
13
LEDaus(i);
14
}

Ich weiß zwar noch nicht genau für was ich da jetzt ein Array nutzen 
soll, aber je nach dem in welcher Reihenfolge die leuchten sollen, 
könnte ich natürlich ein Array z.B. mit (1, 3, 5, 7, 8, 6, 4, 2) 
befüllen und die dann in der Reihenfolge aufleuchten lassen...
(foreach bzw. gibts das glaube ich in C ja nicht!? Aber array[i] würde 
natürlich den gewünschten Wert tragen)

Eigentlich geht es mir nur um den Teil, wie ich an den Tastendruck komme 
und dann halt zB für 3 Sekunden den normalen Programmablauf "stoppe" 
(eben zB das hintereinander anschalten der LEDs) und solange das 
ausgewählte Programm mit den LEDs anzeige.

Ich denke, das Codemonster ist gebändigt! Und wie hab ich mir das mit 
dem Timer nun vorzustellen?
(Habe ich bis jetzt nur ein paarmal genutzt, aber grundlegend ist mir 
schon klar wie das Ding arbeitet. Ich weiß nur noch nicht wie ich das 
dann passend einbauen soll...)

von bobtheworker (Gast)


Lesenswert?

Michael N. schrieb:
> Würde ich täglich Mikrocontroller programmieren, würde ich mir
> wahrscheinlich einfacher tun...
> Allerdings habe ich eigentlich nichts damit am Hut, aber bisher (bis auf
> den Taster) schon alles zum laufen gebracht.
>
> Genau diese Problematik, dass erst ein Unterprogramm fertiglaufen muss,
> habe ich mit einer anderen Schaltung auch (dort ist es ein Schalter
> statt eines Tasters, wodurch das nicht soooo schlimm ist, aber trotzdem
> stört).
>
> Hast du vielleicht einen Link zu einem kleinen Beispielprogramm wo das
> so gemacht wird. Mir ist noch nicht ganz klar wie ich das umsetzen
> soll...
>
> @bobtheworker:
> Geht es hierbei nur um das abfragen des aktuellen Programmes und dann
> die Ausführung eben dieses?? Weil das ist ja mit
> "programm(selected_programm);" schon getan. selected_programm müsste
> halt nur bei jedem Tastendruck hochgezählt werden.
> Und am liebsten würde ich nach dem Tastendruck über die LEDs (z.B. nur
> Leuchten der dritten LED --> drittes Programm gewählt) das aktuell
> ausgewählte Programm kurz anzeigen (bzw. weiterschalten dann mit
> erneutem Tastendruck)

im prinzip das innenleben deiner funktion programm() ^^ Wenn du dich gut 
anstellst, brauchst du dich bei nem Zustandsautomaten um nichts mehr 
kümmern ( nichts hochzählen), Tastendruck bedeutet einfach nur noch 
wechsle auf den nächsten State. Setzt halt vorher gute Planung vorraus.
Zusatzt Features kannst du natürlich immer einbinden.
Taster -> wechsle State -> zeige State an -> Starte Programm.

Allerdings wenn du mit der Tasterabfrage schon den Microcontroller voll 
auslasten willst, frage ich mich wie du die Programme laufen lassen 
willst ^^

Tastenabfrage mit Interrupt du wartest auf ne steigende Flanke und 
fragst im Interrupt nach den Eingang "1" ab ja-> state wechsel nein-> 
interrupt verwerfen. Zwischen Interrupt und Abfrage sollte min 1ms 
liegen. Das sollte die Prellungen abfangen und schneller drückt ein 
Durchschnittlicher Mensch eh nicht

von JojoS (Gast)


Lesenswert?

man sollte sich hierzu mal einfache Multitasking Konzepte ansehen...

Ein einfacher Ansatz:
[c]
initTimer()
{
  // starte einen Timer der alle 10 ms einen Interrupt auslöst
}

TimerISR()
{
  IntCounter++;

  if (IntCounter & 1)  // Aktion jeden 2. Takt, 20 ms
  {
     // Tasterzustand abfragen, wenn geändert ProgrammNr hochzählen
  }

  if (IntCounter % 5)  // Aktion jeden 5. Takt, 500 ms
  {
     // lade LED Muster aus aktueller Programmtabelle
  }
}

main()
{
  initTimer();

  // Interrupts freigeben

  while (1)
  {
     // wenn ProgrammNr geändert dann aktiviere neues Programm
  }
}
{/c]

das ist alles. Die 'Programme' können Tabellen (Arrays) sein mit den 
Bitmustern die nacheinander dargestellt werden sollen. Zujm Umschalten 
der Programme setzt man einen Zeiger mit Namen 'AktuellesProgramm' auf 
eine Startadresse der gespeicherten Programme. Die Tabelle kann noch 
zweidmensional gemacht werden um z.B. die Zeit je Muster variabel zu 
gestalten.
Die Zeit von 10 ms ist dann die kleinste Zeit im System mit der 
Änderungen erfasst (Taster) oder Ausgaben (LED Muster umschalten) 
gemacht werden können.

von JojoS (Gast)


Lesenswert?

Mist, ein falsches Zeichen in der C-Formatkennung. Und für 500 ms muss 
man bei 10 ms Takt natürlich % 50 rechnen, aber es ging ja nur ums 
Prinzip. Und wenn die 50 Takte erreicht sind wieder auf Null setzen 
sonst passt es beim Überlauf nicht, für verschiedene Zykluszeiten sind 
mehrere Zähler anzulegen. Und in der Int Routine nur das nötigste 
machen, siehe auch das AVR-GCC Tutorial (wenn das dein Spielsystem ist).

von Michael N. (garril)


Lesenswert?

Also ich habe mir jetzt etwas überlegt:
(Code wird noch nicht ganz für C passend sein, werde das aber dann 
natürlich noch anpassen)
Grundgerüst:
1
volatile int unterprogramm=1;
2
//Aktuelles Unterprogramm
3
volatile int show_menu=0;
4
//Wenn show_menu==1 Menü anzeigen
5
volatile int time_to_next_input=0;
6
//Erst wenn dieser Zähler wieder auf 0 steht, wird ein Tastendruck verarbeitet
7
volatile int last_input=0;
8
//Letzter Wert des Tasters. 0=nicht gedrückt, 1=gedrückt
9
10
void LEDan(int i) {
11
//Schaltet LED Nummer i an
12
}
13
14
void LEDan(int i) {
15
//Schaltet LED Nummer i aus
16
}
17
18
void alle_LEDaus() {
19
//Schaltet alle LEDs aus
20
}
21
22
void check_taster() {
23
  //prüfe ob Taster gedrückt
24
  if ((gedrückt) and (time_to_next_input<1) and (last_input==0)) {
25
    programm++;
26
    if (programm>8) {
27
      programm=0;
28
    }
29
    time_to_next_input=5;
30
    show_menu=1;
31
    last_input=1;
32
    break(); //<<Schaffe ich es, alles abzubrechen, sodass sogar in der Mainloop der Unterprogrammaufruf gestoppt wird???
33
  }
34
  elseif (time_to_next_input<=1) { //time_to_next_input noch nicht auf 0
35
    time_to_next_input--;
36
  }
37
  if (!(gedrückt)) {
38
    last_input=0;
39
  }
40
}
41
42
void wait (int timer) {
43
  for (int i=0; i<=timer; i=i+10) {
44
  //Während Wartezeit wird der Taster alle 10ms
45
  _delay_ms(10);
46
  check_taster();
47
  }
48
}
49
50
void programm(int unterprogramm) {
51
  switch(x)
52
  {
53
    case 1:
54
      //Von links nach rechts
55
      for (int i=1; i<=8; i++) {
56
        LEDan(i);
57
        wait(500);
58
        LEDaus(i);
59
      }
60
61
     case 2:
62
      //Von rechts nach links
63
      for (int i=8; i>=1; i--) {
64
        LEDan(i);
65
        wait(500);
66
        LEDaus(i);
67
      }
68
      //restliche Cases, bis 8
69
  }
70
}
71
72
int main () {
73
  while(1) {
74
    if (show_menu==1) {
75
      //Menü wird angezeigt
76
      alle_LEDaus();
77
      LEDan(unterprogramm);
78
      wait(3000); //3 Sek warten, wobei der Taster wieder abgefragt wird
79
    }
80
    else {
81
      programm(unterprogramm);
82
    }
83
    
84
  }
85
}
Voraussetzung wäre allerdings, dass das break() oben, bis in die 
Mainschleife zurückspringen kann.
Funktioniert das über mehrere aufgerufene Funktionen hinweg???

von Karl H. (kbuchegg)


Lesenswert?

Michael N. schrieb:

> Ich weiß zwar noch nicht genau für was ich da jetzt ein Array nutzen
> soll, aber je nach dem in welcher Reihenfolge die leuchten sollen,
> könnte ich natürlich ein Array z.B. mit (1, 3, 5, 7, 8, 6, 4, 2)
> befüllen und die dann in der Reihenfolge aufleuchten lassen...
> (foreach bzw. gibts das glaube ich in C ja nicht!? Aber array[i] würde
> natürlich den gewünschten Wert tragen)

Damit fängt es schon mal an.
Jetzt bist du auf Kurs.

Nur:
Anstelle dieser for-Schleife machst du jetzt etwas ganz anderes:

Du bemühst einen Timer für das Timing. Der Timer sorgt dafür, dass eine 
bestimmte Funktion, die ISR (Interrupt Service Routine) in regelmäsigen 
zeitlichen Abständen aufgerufen wird.

zb alle 10 Millisekunden

Damit baust du das dann um:

Du hast ein Array, in dem die LED-Nummern stehen der LED in der 
Reihenfolge in der sie aufleuchten sollen.
Und bei jedem Zeitschritt lässt du dann die jeweils nächste aufleuchten.

Im Prinzip so für 1 "Programm"
1
uint8_t  ledNummer[6] = { 1, 2, 3, 4, 3, 2 };
2
uint8_t  actStep;
3
4
ISR( ... )
5
{
6
  led mit der Nummer ledNummer[ actStep ]  ausschalten
7
8
  actStep++;
9
  if( actStep == 6 )
10
    actStep = 0;
11
12
  led mit der Nummer ledNummer[ actStep ] einschalten
13
}
14
15
int main()
16
{
17
  ...
18
}

dadurch, dass die ISR regelmässig, ohne dein Zutun aufgerufen wird, 
arbeitet sie sich selbst völlig selbsttätig durch die 
"Programmbeschreibung" durch.

Und da hast du jetzt deine einfache Programmwechselfunktionalität:
alles was du tun musst, ist diesem Mechanismus von aussen je nach 
'Programm' ein anderes Array unterzujubeln. Hast du ihm das 
untergejubelt, dann arbeitet die ISR ein ganz anderes "Leucht-Programm" 
ab.

Zb. könnte man anstelle des 1-D Arrays ein 2D Array nehmen, wenn alle 
Leucht-Programme gleich lang sind.
1
uint8_t  ledNummer[2][6] =
2
{
3
  { 1, 2, 3, 4, 3, 2 },
4
  { 1, 3, 2, 4, 3, 2 }
5
}
6
7
uint8_t  actStep;
8
uint8_t  actPgm;
9
uint8_t  lastLitLed;
10
11
12
ISR( ... )
13
{
14
  led mit der Nummer lastLitLed  ausschalten
15
16
  actStep++;
17
  if( actStep == 6 )
18
    actStep = 0;
19
20
  led mit der Nummer ledNummer[actPgm][ actStep ] einschalten
21
  lastLitLed = ledNummer[actPgm][ actStep ]
22
}

und jetzt hast du die Hauptschleife in main frei
1
int main()
2
{
3
  ...
4
5
  while( 1 ) {
6
    if( get_key_press( 1 << Button ) ) {
7
      cli()
8
      actPgm++;
9
      if( actPgm == 2 )   // weil es nur 2 Progamme gibt
10
        actPgm = 0;
11
      sei();
12
    }
13
  }
14
}

Die ISR ist mithilfe der Hilfsvariablen lastLitLed so geschrieben, dass 
man zu jedem Zeitpunkt der Variablen actPgm einen neuen Wert verpassen 
kann und dann dieses Programm abgearbeitet wird.
Und genau das tut main(). Wird ein Tastendruck erkannt, dann wird 
einfach ein neuer Wert an actPgm zugewiesen.
Und das erkennen des Tastendrucks erfolgt mit den Hilfsfunktionen aus 
Entprellung. Die ihrerseits wieder einen Timer benötigen.

Aber: Wundersamerweise gibt es kein einziges _delay_ms im Programm. 
Nirgends wird gewartet. Und das ist schon mal ein extrem gutes Zeichen.

von JojoS (Gast)


Lesenswert?

In deiner Welt fehlt das Zauberwort 'Interrupt'.

von JojoS (Gast)


Lesenswert?

ok, ich halt mich da jetzt raus :-)

von Michael N. (garril)


Lesenswert?

Na ich dachte das soll man ohne Interrupts lösen^^ (Stand oben i-wo mal)
Aber eure beiden Lösungen sind wirklich super.
Werde das später gleich mal in passenden Code ausarbeiten.

Durch den Timerinterrupt erfolgt die eigentliche Ausgabe (+ 
Tasterabfrage)
Und sobald sich da etwas ändert, wird einfach etwas anderes zur Ausgabe 
herangezogen (eben immer die passenden Muster für das jeweilige 
Unterprogramm)

von Karl H. (kbuchegg)


Lesenswert?

Michael N. schrieb:
> Na ich dachte das soll man ohne Interrupts lösen^^ (Stand oben i-wo mal)

Gemeint war:

Tastenabfrage ohne externen Interrupt.

Interrupt ist nicht gleich Interrupt. In deinem µC gibt es viele 
verschiedene Interrupts. Einen Taster einen Interrupt auslösen zu lassen 
ist eine schlechte Idee. Einen Timer regelmässig einen Interrupt 
auslösen zu lassen, bei dem dann nachgesehen wird, ob eine Taste 
gedrückt ist, ist hingegen eine gute Idee. Genau das macht der Code, der 
in Entprellung ganz unten angeführt ist.

> Aber eure beiden Lösungen sind wirklich super.

Es ist die Standard-Lösung für µC-Dinge.
Du musst weg von der Denkweise
Zuerst macht mein Programm das
dann macht es das
dann wartet es ein wenig
und dann macht es das

Statt dessen brauchst du
ich habe einen Zeitgeber
der löst alle x Zeiteinheiten ein Ereignis aus
-> was gibt es jetzt, genau zum jetzigen Zeitpunkt zu tun

wenn du deine Problemstellung auf diese Denkweise anpasst, dann gehst du 
praktisch nie verkehrt und landest automatisch bei einem Programm, bei 
dem du zu jedem Zeitpunkt Benutzereingaben aktiv hast und die, bei 
laufendem Betrieb vom restlichen Programm, immer das richtige auslösen.

von Peter D. (peda)


Lesenswert?

Man kann es z.B. so machen:

Beitrag "Re: AVR Sleep Mode / Knight Rider"

Es handelt sich um 2 Statemaschinen:

"sample" in "main.c" wählt aus, welches Muster angezeigt werden soll und 
wird mit der Taste weiter gezählt.

"step" in "knightrider.c" zählt den Anzeigeschritt im ausgewählten 
Muster und wird per Timer weiter gezählt.


Peter

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.