Ich bin neu in der AVR Programmierung und habe gerade mein STK500 und
STK501 bekommen. Ich bin allerdings berflich Softwareentwickler und
arbeite seit 15 Jahren mit C++. Mir ist aufgefallen, dass es einen in
meinen Augen sehr komischen Stil gibt, die AVR Programme zu schreiben.
Im Prinzip ist der Kern eines Programms immer eine große Endlosschleife.
Das klappt ja auch hervorragend, wenn ich ein paar Ports habe, und will
diese dann mnit ein bischen Logik verknüpft auf andere Ports ausgeben.
Aber:
Ich habe mir jetzt gerade ein LCD Modul beschafft und einen Adapter für
den STK500 gebastelt. Jetzt ist mir aufgefallen, das ich zwar die
Ansteuerung für das LCD prima programmieren kann, wegen dem Timing der
AVR aber während dieser Zeit nichts anderes mehr macht als Schleifen
absitzen usw.
Will ich gleichzeitig I/O machen wird die geschichte schon richtig
schwierig. Durch das Warten auf den LCD Controller bekomme ich im
Prinzip riesige Latenzzeiten in die Abwicklung der I/O Routinen. Mein
Durcsatz reduziert sich also auf das langsamste Gerät am Controller.
Grundsätzlich passiert das doch eigentlich immer, wenn ich 2
unterschiedliche Devices am AVR betreibe, die entsprechende Pausen für
ihr Timing benötigen.
Ich habe hierfür keine vernünftige Lösung gesehen. Insbesondere die C
Bibliotheken sind für dieses Problem anscheinend gar nicht vorbereitet.
Ich stelle mir vor, dass solch ein Programm als Zustands-Maschine
abgebildet wird. (Das kann naiv sein, gebe ich ja zu). Das formale
Beschreibungsmitte wäre dann ein Petri Netzwerk. Ich habe das in anderem
Kontext (Multitasking auf dem PC) bereits für Simulationen verwendet.
Gibt es hierfür einen Standard Ansatz (also ein Design Pattern)?
Bei LC-Displays ist die sache furchtbar einfach. Das Display muss/kann
ja garnicht bei jedem Schleifendurchlauf angesprochen werden. Wenn du
z.B. einen Wert auf dem Display anzeigen willst:
if (AlterWert != NeuerWert)
{
LCD_Anzeige_erneuern();
}
Nun ändert sich der Inhalt der Variable vielleicht jede Sekunde, die
übrigen Durchläufe übergeht er einfach das IF-Statement und rennt durch
den ürbigen Code.
Das gleiche gilt natürlich auch für andere Devices... So wird eine
angeschlossene MMC z.B. natürlich nur ausgelesen wenn bedarf besteht -
nicht bei jedem Durchlauf des Programms.
Das Prinzip der Mainloop ist eigentlich, die einzelnen Tasks schnell
genug zu machen, damit keine unzulässig hohe Verzögerung eintritt.
D.h. einfach größere Tasks in viele kleinere Abschnitte unterteilen.
Eventuell benötigt man dazu ein Byte, was sich merkt, mit welchem
Programmpunkt man beim nächsten Aufruf fortsetzen muß.
Im Falle des LCD legt man am besten einen gleich großen Puffer im RAM ab
und jede Ausgabe erfolgt in diesen Puffer.
Die Ausgaberoutine testet dann immer nur einmal das Busy-Bit und gibt
ein Byte aus dem Puffer aus. Das sollte dann schnell genug sein.
Alternativ kann das Busy-Testen und zyklische Ausgeben des Puffers zum
LCD auch im Timerinterrupt erfolgen.
Also immer, wenn im Programmablauf Wartezeiten auftreten, dann die
nächste Task an die Reihe lassen.
Peter
@Thomas:
Klar, kannste sicher gern tun, aber solch ein Automat hat
die unangenehme Eigenschaft, praktisch nicht mehr zu
pflegen zu sein.
Wenn Du zeitkritische Dinge zu erledigen hast, funktioniert
das sicher am besten, wenn man die jeweiligen Ereignisse
interruptgesteuert verwaltet. Die Mainloop fragt dann nur
noch nach, welche Interrupts gerade anliegen und führt die
entsprechenden Aktionen aus (bzw. kann sich ggf. in einen
wohlverdienten avr_sleep() zurückziehen, wenn gerade nichts
zu tun ist ;-). Die einzelnen Aktions-Routinen dürfen
natürlich auf keinen Fall mehr irgendwelche Warteschleifen
enthalten (und die ISRs erst recht nicht), sondern sie
schieben die jeweilige Aktion nur an und setzen dann einen
Trigger, der irgendwann zu einem Interrupt führt. Wird
nur schwierig für Geräte, die nicht selbst interrupten
können (je nach Anschlußvariante kann ein LCD dazu gehören),
dort muß man sich dann evtl. mit einem geeigneten Timer
aus der Patsche helfen.
Am besten baust Du für jeden "Task" eine Statusmaschine auf. Diese
Statusmaschine muß ihren aktuellen Status innerhalb einer vorher
festgelegten Zeit abarbeiten können. Dann synchronisierst Du diese
"Tasks" mit einer festen Umlaufzeit, z.B. mit einem Timer.
Beispiel:
while(1)
{
/* Timerflag wird bei Timerüberlauf auf 1 gesetzt */
while (Timerflag == 0) {;}
Timerflag = 0;
/* Jetzt hast Du eine feste Umlaufzeit */
...
LCD_Maschine();
IO_Maschine();
...
}
In der LCD-Maschine hast Du jetzt einen switch, z.B. so:
switch (LCDStatus)
{
...
case WARTEN_BIS_LCD_FERTIG:
if (Flag_LCD_fertig)
{
LCDStatus = LCD_FERTIG;
}
break;
case LCD_FERTIG:
...
}
Der Funktion LCD_Maschine() kann also seelenruhig auf das LCD warten,
verbraucht dabei aber kaum Prozessorzeit.
War's verständlich?
Grüße,
Sebastian
Ich bin jetzt bei Timer Interrupts gelandet. Ich verwende den Timer0 als
Interruptquelle und habe in C auch schon eine Service Routine
geschrieben. Hat sich eigentlich jemand mit den typischen Stack
Manipulationen für Multitasking Betrieb auseinandergesetzt? Wenn es
gelingt, die Rücksprungadesse - sprich den Kontext neu zu setzen, dann
steht preemptivem Multitasking nichts mehr im Weg. Das Bedeutet es sind
Threads möglich.
Ich habe gesen, das es Echtzeit Betriebssysteme für den AVR gibt. Das
interessiert mich aber nicht, da ich den Mini Kernel auch selber
schreiben kann. Wer hat bereits Erfahrung mit der notwendigen Stack
Manipulation und dem Verwalten der Rücksprungadessen?
Ich teste momentan auf dem AT90S8515, der beim STK500 dabei ist. den
ATMEGA128 vom STK501 hab ich noch nicht gesteckt.
Ok für alle Skeptiker:
RAM frisst das schon, das weiß ich auch!
Schau Dir exakt diese Echtzeitbetriebssysteme an, eben die,
die diese geschrieben haben (es dürfte deren kaum mehr als
eine Handvoll geben) haben sich auch mit genau Deinen
Fragen auseinandergesetzt.
Hallo zusammen, ich habe eine kleine Testroutine für einen preemtiven
Taskwechsel geschrieben (ohne Kontextsicherung usw). Das hat mit dem
ursprünglichen Problem zwar zu tun, ist aber wesetlich allgemeiner.
Da ich Interesse daran habe das breiter zu diskutieren beginne ich einen
neuen Thread