Forum: Mikrocontroller und Digitale Elektronik effiziente programmstruktur


von Klaus Bröntgen (Gast)


Lesenswert?

hallo an alle!

heute mal folgendes:
Wie gestatltet man eine effiziente Struktur für ein Programm, daß
folgende Eckdaten hat:

-auswerten von Tasten-Eingaben (geschieht über peter dannegers routine
im ISR)
-Aufrufen verschiedener Betriebsmodi enstsprechend der betätigten
Tasten
-betreiben einer Echtzeituhr (wird zZ. bei neuer Sekunde aus der ISR
aufgerufen)
-Ausgabe der Uhrzeit,des aktuellen Betriebsmodus und der aktuellen
Tastenbelegung auf LCD
-je nach Betriebsmodus haben die Tasten unterschiedliche Funktionen und
es werden verschiedene Aktionen durchgeführt (einfachstes Beispiel:
stellen der Uhr, Ändern von Aktionsparametern, Ausgabe von Daten und
Bedienen von Relais etc. über Ports....)

Mein Problem dabei ist, daß ich nicht organisiert bekomme, welche Jobs
in der Mainloop zu erledigen sind bzw. wie ich sie in entsprechend
Unterprogramme verfrachte, ohne dabei den "normalen" Ablauf der
kontinuierlich ablaufenden Arbeiten (zB. LCD-refresh, Uhr weiterlaufen
lassen) zu stören. Daher habe ich bereits den Uhr-job über die ISR
gesichert, da ja die Uhr auch weiterlaufen muß, wenn das Programm
gerade was anderes tut.

Wie strukturiert man sowas geschickt? Ist die Mainloop dann nur dazu
da, nichts zu tun und auf einen Unterprogrammaufruf zu warten?
Wie bekomme ich den zu schreibenden Inhalt des LCD "zusammengebaut"
und geschrieben? In jedem UP einzeln?
Oder sollte man direkt eine StateMaschine bauen, die sich in jedem
Betriebszustand um ihre aktuell Aufgabe kümmert (und wie organisiert
man dann die ständig anliegenden Jobs)?

Ist es mir gelungen, mich verständlich auszudrücken?

von Hannes L. (hannes)


Lesenswert?

Also ich programmiere noch lange nicht effizient, aber...

Wenn ich mehrere ISRs habe, dann halte ich sie möglichst kurz und setze
(selbstdefinierte) Flags für die Jobs, die die Mainloop dann ausführt
(und das Flag wieder löscht).

Habe ich nur eine ISR (auch das gibt's), dann wird meist alles in der
ISR erledigt, in der Mainloop wird dann nur noch gerechnet (falls
erforderlich) und geschlafen.

Ab wann die Programmsteuerung mittels Flags (eigene, nicht die des
SREG) und anderer Zustandsvariablen (z.B. Menüpunktnummer) als
State-Machine gilt, ist mir allerdings (noch) unklar.

LCD-Ausgaben mache ich inzwischen timersynchronisiert über einen
Ringbuffer. Dabei schreiben die betreffenden Print-Routinen (die die
ASCII-Zeichenfolgen erzeugen) und die Locate-Routine nur in den
Ringbuffer. Ein Job der Mainloop (mit geringer Priorität) schaufelt
dann die Bytes des Ringbuffers einzeln an das LCD, wenn der
Timer-Interrupt das LCD-Flag wieder gesetzt hat.
Die Prioritäten der Mainloop erreiche ich, indem ich die Jobroutinen
nicht als Unterprogramm (RCALL/RET) aufrufe, sondern mit RJMP und RJMP
mainloop. Manchmal muss es aber RCALL/RET sein, es kommt auf die
Situation an, jedes Programm ist anders.

Übrigens glaube ich nicht, dass man mit einigen wenigen Sätzen ein
allgemeingültiges Regelwerk der effizienten Programmierung erstellen
kann. Das Thema ist so komplex, dass es mehrere (viele?) Bücher füllt.
Auch meine ich, dass zum effizienten Programmieren langjährige
Erfahrung gehört, man also auf diesem Weg einen großen Teil seiner
Fehler selbst machen muss. Auch die hier vertretenen Profis haben
einmal angefangen und sich ihr Wissen in mühevoller Kleinarbeit nach
und nach angeeignet.

...

von Ich B. (ichbin)


Lesenswert?

Ha, genau das 'Problem' habe ich auch gerade!

Ein eigentlich relativ lineares Programm, die zeitkritischen Aufgaben
(Tasten,Schrittmotor, Buzzer) werden vom durchlaufenden Timer gelöst.
Zudem zählt ein TOI einen 'Softwaretimer' hoch, dessen Überlauf bei
einer Sekunde liegt. Ich habe sogar noch einen HW-Timer frei.

Wie ich das eigentliche Programm übersichtlich und effizient
strukturiere (kein blockieren, kein redundanter Code), leuchtet mir
aber noch nicht ein.

An, initialisieren:

Auf Tastendruck warten (nächster Programmschritt), oder Befehl von
serieller Schnittstelle ausführen (nächster Programmschritt, oder was
völlig anderes)
...
Auf Tastendruck warten, oder Befehl von serieller Schnittstelle
ausführen.
...

Nach der Initialisierung dann ggf. zyklische Abarbeitung des
eigentlichen Programms.

Ich könnte mir noch vorstellen, verschiedene Runlevels in eine Variable
zu schreiben, anhand derer die Tasten (wiederum eine entsprechende
Aktion auslösen...

oder eben die Aktionstasks Runlevels haben, die sie dann anhand von
'Ereignissen' hochzählen.

Also fürchte ich, daß man nicht um einen Haufen Flags herumkommt..sehe
ich das richtig? Vielleicht kann jemand mich auf ein Programm
verweisen, daß so eine 'interaktive' Struktur beispielhaft vorführt?
Ich werde wohl mal in der Codesammlung stöbern..

von Hannes L. (hannes)


Lesenswert?

@du bist:

Also so richtig verstanden habe ich dein Anliegen nicht...

Aber:
Auf Tastendrücke "wartet" man nicht (jedenfalls nicht ohne Not), die
liest man einfach nebenbei im Vorbeigehen ein und entprellt sie.
Sind neue "Ereignisse" (z.B. Tastendruck) da, dann reagiert man
darauf. Sind keine Ereignisse mehr da (alle Jobs abgearbeitet), wird
gepennt. Der nächste Int weckt dann den MC, führt die ISR aus und
schaut nach, ob neue "Ereignisse" eingetroffen sind.

Klar, ein Programm beginnt mit der Init-Routine, die (meist) nur ein
einziges mal (beim Reset) durchlaufen wird. In ihr werden die genutzten
Komponenten initialisiert, auch die entsprechenden Interrupts.

Dann kommt meist die Mainloop, in der Jobflags (auch Tastenflags der
entprellten Tasten) überprüft werden und darauf reagiert wird (in
Routine verzweigen, die das Jobflag löscht und den Job ausführt). Sind
alle Jobs erledigt, dann wird gepennt.

Dann gibt es diverse Interrupts, die auf Ereignisse reagieren und die
entsprechenden ISRs aufrufen. Hier kommt es auf die Situation an, ob in
der ISR nur ein Jobflag für die Mainloop gesetzt wird oder ob der Job
gleich in der ISR erledigt wird.

Auch die Frage der Redundanz kann von Fall zu Fall unterschiedlich
ausfallen. Manchmal ist es eben schneller, einen kurzen Programmteil
mehrmals an verschiedenen Stellen zu haben, als ihn jedesmal neu
aufzurufen. Es richtet sich immer danach, was knapper ist, Rechenzeit
oder Speicherplatz. Manchmal ist es sogar effizienter, Dinge, die man
üblicherweise in einer Schleife erledigt, mehrfach hintereinander zu
schreiben um sich den Sprung zu sparen. Das kostet etwas Speicherplatz,
kann aber in zeitkritischen Phasen Rechenzeit sparen.

...

von Ich B. (ichbin)


Lesenswert?

Hallo Hannes,

danke für Deine Antwort.

Das Strukturierungsproblem schlägt sich also auch in meiner
Formulierung nieder :-)

Das Problem besteht darin, daß mir keine eindeutig 'gute' Methode
einfällt, mit der die Jobs/State-Machines/Tasks untereinander
kommunizieren können (ohne blockierend auf etwas zu warten).

Bei periodischen Aufgaben, oder solchen die nicht-linear sind, ist der
Einsatz von Flags oder Zähl-Bytes recht überschaubar (Pieper an für x,
LED-machine blinkt n mal, Motor 100 Schritte).

Sobald es aber darum geht, z.B. eine Initialisierung (Motor manuell/per
RS232 auf Position bringen, Werte in EEProm ablegen) zu strukturieren,
wird es komplexer.

Zerpflücke ich die Aufgabe in mehrere 'Tasks', auch wenn sich die
Funktionalität etwas überschneidet (task_initlevel1->..evel2->..)? Wo
wird der (entprellte) Tastenzustand abgefragt - in einer 'Task
Taste', oder in der vom Programmablauf her wichtigeren
'task_initlevelx' ?

Es scheint mir gerade, als wäre eine einzelne task_init mit
verschiedenen runlevel doch nicht übermäßig kompliziert - mal sehen.

Ich habe allerdings noch kein exemplarisches, komplettes C Programm aus
der Codesammlung gefunden, das in etwa meiner Anwendung nahekommt.

von Hannes L. (hannes)


Lesenswert?

Mit C habe ich nix am Hut...

...

von hans dieter (Gast)


Lesenswert?

Das kommt immer auf das Problem an.
mal ein beispiel: eine steuerung für eine Dreh-Maschine. An der Sind
ein Paar Taster dran, dann gibt es da noch einen Not-Aus und einen
Motor (der natürlich erst anlaufen muss) und ein paar Lampen.
So um das Ding in gang zu bringen gibt es eine Start-Taste und zum
Anhalten eine Stop-Taste. Wenn das Ding anläuft, dann soll die
Betriebs-Lampe langsam blinken. Wenn ein Fehler vorliegt, dann soll die
Lampe schnell blinken und im Betrieb einfach nur leuchten.
Also baut man 3 State-Maschines: Eine für die Tasten, eine für die
Lampen und eine für den Motor.
Dann kommt ein Timer zum Einsatz, der alle 20ms (oder anderer Wert) die
3 FSMs der reihe nach bedient: Tasten einlesen (dabei werden die Tasten
gleich mit entprellt), die Lampen ansteuern (dann hat die Lampe halt 4
Betriebsmodi - Aus, Ein, Schnell, Langsam)
und dann noch den Motor ansteuern (Stop, Anfahren, Betrieb, Anhalten)
In den FSMs gibt es Zähler, die den Verlauf timen (d.h. zwischen den
Ein/Ausschalten der Lampe gibt es z.B. mal 50 oder 200 Durchläufe Zeit)
Wenn der Zähler auf 0 steht, dann wird halt eine Aktion ausgelöst (z.B.
wechsel von Anfahren zu Betrieb)
Und in der Hauptschleife kann dann schön geschlafen werden. Der Not-Aus
ist auch recht schnell eingebaut: In der Hauptschleife wird auf
Interrupts gewartet - also bekommt das Not-Aus eine ISR verpasst, die
die FSM entsprechend schnell (und rabiat) auf die neuen Werte setzt.

von Hannes L. (hannes)


Angehängte Dateien:

Lesenswert?

Hier werden einige Begriffe benutzt, die mir (noch) nichts sagen.

Wie würde man denn die Struktur des Programms im Anhang bezeichnen?

Es ist allerdings schon etwas älter, heute würde ich einige Dinge etwas
anders machen (z.B. Timer).

...

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.