Forum: Compiler & IDEs Anfängerfrage zum Programmaufbau bei mehreren Devices


von Thomas Maierhofer (Gast)


Lesenswert?

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)?

von Sascha Weitkunat (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Joerg Wunsch (Gast)


Lesenswert?

@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.

von Sebastian Fahrner (Gast)


Lesenswert?

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

von Joerg Wunsch (Gast)


Lesenswert?

Ach so: die Benutzung eines der vorhandenen Mini-Betriebssysteme
wäre ja vielleicht auch eine sinnvolle Variante.

von Thomas Maierhofer (Gast)


Lesenswert?

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!

von Joerg Wunsch (Gast)


Lesenswert?

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.

von Thomas Maierhofer (Gast)


Lesenswert?

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

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.