Forum: Mikrocontroller und Digitale Elektronik Softwaretechnik für uC


von Moritz -E- (Gast)


Lesenswert?

Hallo Leute.
Ich habe eine Frage zur Vorgehensweise bei der Entwicklung eines
komplizierteren Programms für uC. Und zwar geht es darum wie man
diverse Software Module (=Dienste) die oft gleichzeitig laufen sollen
und verschiedene gegenseitige Abhängigkeiten haben, mittels Interrupts
und main-routine am besten implementiert.

z.B. Es sei eine Applikation die verschiedener Dienste bedarf. Einige
Dienste (A) sollen in regelmäßigen Abständen aufgerufen werden. Einige
Dienste (B) werden von extern oder von software aufgerufen. Und wieder
andere Dienste (C) sind allgemeine Dienste oder Hintergrunddienste,
z.B. eine interaktive Menü-geführte Benutzersteuerung (User
Interface).

Was tut man in die main routine und was verteilt man auf interrupts?

Einige A-Dienste kann man gut mit Timer Interrupts erschlagen, für
andere weniger zeitkritische A-Dienste wäre es auch nicht schlecht sie
in einen main-loop zu bauen, da man u.U. nicht soviele T-IRQs hat
oder?

B-Dienste mit Externen IRQs, software IRQs oder durch normale
Prozeduraufrufe.

C-Dienste sind aber nun schwierig zu implementieren da sie für lange
Zeit oder ständig laufen müssen. Also kann man sie als mainroutine
vorsehen, dann hat man aber keinen loop mehr, und alle A-Dienste müssen
in Timer Interrupts versorgt werden. Außerdem kann die mainroutine auch
nur einen C-Dienst, z.b. das User-Interface enthalten. Einen weiteren
C-Dienst müsste man auch mit Timer-Interrupts stückeln. Andere
Alternativen laufen auf einen multitasking Kern hinaus oder auf
Pointer-Orgien, was aber wieder zu viel des Guten wäre.

Also wie macht es der erfahrene Entwickler? Tut er in fast allen Fällen
immer das User-Interface ins main und alles andere in Interrupts? Oder
würde er standardmäßig einen mainloop für A-Dienste verwenden und eine
eventuelle Benutzersteuerung in die Interrupts stecken, und wie genau
würde er dies machen?
Und die Abhängigkeiten würde man wohl über Globalvariablen im RAM
steuern.

Und nun zur Benutzersteuerung: Welche Verfahren würde der uC Veteran
für Benutzereingaben verwenden? Einen Ext. Interrupt wenn irgendeine
Taste gedrückt wurde, worauf hin die Taste eingelesen wird, oder einen
ständigen poll aller Tasten, was aber Rechenzeitverschwendung ist (Und
was wohl bedingt dass die Benutzersteuerung nicht in Interrupts ist).
Eine Tastenblockade muss dabei auch noch implementiert werden.

Letztlich gibt es das Problem der Interrupt-Prioritäten. Da diese fest
definiert sind, müsste man um andere Priotäten zu erziehlen die
Interrupts erst nur flags setzen lassen und dann in main (nur wenn dies
ein loop ist) oder in einer Timerinterrupt Routine neu aufrufen.
Oder was für Alternativen gibt es?

Das alles vor dem Hintergrund eines ausreichenend großen AVRs (Ich
möchte z.B. ATmega 64 verwenden)

Ich hoffe ihr könnt mir viele Tips geben wie ihr das löst, oder Quellen
aufzeigen wo derartige Probleme angegangen werden...

Danke für alle Antworten,
mfg Moritz

von Florian Hrubesch (Gast)


Lesenswert?

Als erstes:Bin kein Profi-Entwickler;)
Hmm, generell ist blos zu sagen interrupts so kurz wie möglich halten
hatte da neulich schon bekanntschaften.Und keine funktionen in den
interrupts aufrufen dann muss der uc alle möglichen register sichern.
Wenn deine A dienste nicht absolut zeitkritisch sind würd ich die wie
das userinterface und die tastenabfrage in ne main-loop stecken und von
nem timer-interrupt aus triggern.Alles zeitkritische dann in interrupt
rein.
cu Flo
(bitte fehler von mir korrigieren will auch was lernen)

von Frank (Gast)


Lesenswert?

Hi,
Hast Du schonmal über den einsatz eines RTOS (Real Time Operating
System) nachgedacht? Das ist in etwa so wie wie eine kleines Windows
für Microcontroller. Du kannst hier einzelne eigenständige Tasks
programieren welche alle für sich eine eigene main loop haben. Um die
zuteilung der rechenzeit sowie die speicherzuteilung und
interruptzuordnung kümmert sich das Betriebssytem oder Scheudler. Genau
für die Anforderungen die du beschreibst wurden diese RTOS entwickelt
und es giebt inzwischen einige freie und ettliche komerzielle zb. embos
von segger: http://www.segger.com/embos.htm
Ich habe schon komplexere Programme mit derartigen Betriebssytemen
realisiert und ich finde es eine erhebliche erleichterung.

Gruß Frank

von Moritz -E- (Gast)


Lesenswert?

Schon zwei interessante Antworten ;).
Zu Florian: Wenn ich das Userinterface in main tue, kann das aber ohne
weiteres kein loop mehr werden, weil die Menüsteuerung ja ständig auf
neuen input wartet. Der Programmcounter muss auch dort stehen bleiben
damit der "Menüzustand" nicht verloren geht. Um dies zu erreichen
müsste ich den PC abspeichern, dann weiter den mainloop laufen lassen,
und später per EXT-Int oder so beim nächsten Tastendruck wieder
aufrufen. Entspricht das deiner Idee? Wird das oft so gehandhabt? Oder
kann man den Menüzustand anders bewahren?

Zu Frank: Vielen dank für deinen Tip! Im Prinzip ist das ja der
Multitasking-Kern. Das Problem was ich sehe ist nur das der Rechenzeit
die es verbraucht. Kannst du/jemand eine grobe Zahl sagen wieviel % der
Rechenleistung für die RTOS-Verwaltung draufgeht? Ich möchte nämlich
evtl. mehrere geschachtelte PWMs benutzen, und dann die vielen
Timer-Interrupts. Da muss ich froh sein wenn die main-routine überhaupt
noch was abbekommt, fürchte ich.

mfg Moritz

von Matthias (Gast)


Lesenswert?

Hi

wieso blockiert ein Menüsystem den main-loop? Du solltest dich mal mit
dem Prinzip der State-Machines auseinandersetzen. Da kannst du ein sehr
komplexes Menü in main() realisieren und kommst doch alle parr µs
einmal rum. Von einem fremden OS auf so kleinen µC halte ich persönlich
nichts. IMHO ist es besser sich da was eigenes selber zu programmieren.


Matthias

von Stefan Kleinwort (Gast)


Lesenswert?

Meine Erfahrungen mit RTOS-Systemen sind auch sehr gut. Die Rechenzeit
die der Multitasker braucht, ist nicht das Problem: je nach Applikation
kannst Du mit 1 - 5% der Rechenleistung rechnen. Diese Zahl muss aber
auch in Relation zum Projekt gesehen werden, auch ein auf Häppchen
verteilter Main-loop braucht Rechenzeit.
Das größte Problem auf einem kleinen mc ist meiner Ansicht nach der
RAM-Verbrauch durch die Stacks.

Was richtig schön ist beim Multitasking: Dein Projekt teilt sich in
mehrere kleine Teile auf, die Beeinflussung der Einzel-Programme ist
viel geringer als bei anderen Ansätzen.

Einen interessanten Multitasking-Ansatz (open-source) habe ich übrigens
hier gefunden:

http://home.eunet.no/~jen/newebsrv.html

Stefan

von Berti (Gast)


Lesenswert?

Ich würde auch zu RTOS raten...
Habe mit der 8051er Familie und dem Keil RTX-51 Tiny gute erfahrungen
gemacht! Vorteilhaft ist die eigenständige Entwicklung von
"Systemkomponenten" ohne langwieriges beachten vn Abhängigkeiten!

von Fritz Ganter (Gast)


Lesenswert?

Hallo Moritz!

Im Prinzip hast du dir das eh richtig überlegt. Du musst dir nur im
klaren sein, was wann gemacht werden muss.
Bei meinem (allerdings ersten µC-) Projekt läuft alles
interrupt-gesteuert, wobei in der Interrupt routine nur wenig gemacht
wird, um schnell wieder rauszukommen. Meist werden globale Flags
gesetzt, auf diese Flags reagiert dann die Mainloop (die aber eh meist
im sleep hängt).
Ich polle eine Tastaturmatrix 128x in der Sekunde, die paar Befehle
brauchen (pro Sekunde) praktisch keine Rechenzeit und kann deshalb in
der Interrupt-Routine gemacht werden. Das Hauptprogramm hat dann immer
in der Variablen "key" die aktuelle Taste (bzw. " " wenn sie
losgelassen wurde). Die Taste entscheidet dann über die Zustände, die
das Programm hat.
Im Menü häng ich zwar rum, bis es beendet ist, aber ich versäume nix,
da ich alles wichtige im Interrupt mache.
Natürlich kannst du auch durch ein Menü durchlaufen wenn eine Taste
gedrückt ist, musst nur dafür sorgen beim nächsten Durchlauf richtig
weiterzumachen. Also als eine State-Machine aufbauen.

Ich komme eigentlich aus der Linux/GTK+ Ecke, deswegen ist es für mich
nicht so schwer ereignisgesteuert zu programmieren.

von mthomas (Gast)


Lesenswert?

Bin ebenfalls kein Profi-Entwickler, aber vielleicht sind das noch
nuetzliche Informationsquellen:

RTOS: Eine Liste von RTOS fuer AVR ("freie" und kommerzielle) findet
im "Tools"-Bereich von avrfreaks.net. Welches "das Beste" vermag
ich nicht zu sagen.

Zustands-Maschine: Der C-Quellcode der AVR-Butterfly-Application
(google findet) zeigt die Implementierung einer State-Machine. Auch
dafuer mag es bessere Loesungen geben, aber fuer AVR habe zumindest ich
noch keinen aehnlich umfangreichen und frei zugaenglichen Quellcode
gefunden.

Beide Ansaetze haben Vor- und Nachteile. Bei RTOS gibt man die
Kontrolle ueber die "Main-Loop" an den Sheduler, verliert damit etwas
Rechnenzeit aber gewinnt "Komfort". Spaetestens wenn man
Systemresoucen in mehreren Tasks ansteuert (Timer etc.) sind die
Einzelkomponenten aber nicht mehr ganz so eigentstaendig und eine Art
"Sychronisation" ist erforderlich. Evtl. mit einem eigenen Task der
die Systemkomponente abstrahiert. Für die gestellte Aufgabe mit
"mehreren PWMs" mag das krtisch werden, wenn man diese in Software
implementiert. Die Entwicklung mittels Zustands-Machine ist mglw. etwas
komplexer, aber es wird keine Rechnezeit mit "task-switching"
verbraucht (was bei schnellen Controllern mglw. keine Rolle spielt) und
kein Platz fuer das RTOS benoetigt. Aber die State-Machine selbst
benoetigt ja auch "ein paar Bytes". Die mir bekannten AVRs haben
keine einstellbaren Interruptprioritaeten.

von Peter D. (peda)


Lesenswert?

Die Lösung ist folgende:

Man kennzeichnet den Zustand, in dem man gerade ist, nicht durch die
Stelle im Programm, an der man gerade steht.
Da steht man nämlich wirklich fest, wie in Stein gemeißelt und kann
keine anderen Sachen mehr nebenbei machen.

Sondern man merkt sich diesen Zustand in einer Variablen, und verzweigt
dann in der Main-Loop entsprechend dieser Variable in die einzelnen
Funktionen.


Hier ein kleines Beispiel:

http://www.specs.de/users/danni/appl/soft/c51/thclock/index.htm

Da kennzeichnet die Variable "menu", ob man nun gerade die Temperatur
anzeigt, die Weckzeit-Minuten stellt usw.
Zusätzlich wird in der Main-Loop die Tastenbetätigung geprüft und der
gerade aktiven Funktion mit übergeben.
Trotzdem laufen natürlich die Temperaturmessung und Zeit-,Datumszählung
 ungehindert weiter.

Da es in diesem Beispiel nur wenige Tasten und Menüpunkte sind, habe
ich beides in eine Variable addiert. Das spart dann die separate
Tastenauswertung in den jeweiligen Menüpunkten.

Ansonsten sind nach diesem Prinzip aber auch wesentlich größere und
komplexere Menüstrukturen realisierbar.


Man muß eigentlich nur das Denken etwas umstellen, d.h. die Programme
immer so schreiben, das sie zum Main zurückkehren, wenn gerade nichts
mehr zu tun ist und das man sich merkt, wo man beim nächsten Durchlauf
fortsetzen will.


Peter

von Moritz -E- (Gast)


Lesenswert?

Danke für die vielen hilfreichen Antworten.

Also Entweder Statemaschine oder RTOS. Die Statemaschine kann man doch
im Prinzip auch mit Zeiger der den State representiert realisieren
oder? Was ist der Vorteil einer Statemaschine gegenüber Zeiger?
Zum RTOS habe ich noch einige kurze Fragen:
- Ab wieviel Ram lohnt es sich? Ich will den ATmega64 (16MHz) mit 1kb
RAM einsetzen. davon möchte ich gerne etwa 400byte für meine App.
haben.
- Ich vermute der Rechenzeitverbrauch des RTOS hängt stark von der
Frequenz der Task-switches ab. Mein schnellster Task mit höchster
Priorität soll ein möglichst exakt getimter Aufruf mit einer Freq. von
etwa 1.6-16Khz (Je nach dem wieviel Potential noch ist) sein und
mindestens mehrere hundert (eher 1000) Zyklen verbrauchen. Somit würde
dieser einzelne Task einen Großteil der Rechenzeit beanspruchen. Ich
kann mir nur schwer vorstellen das bei einer solchen Anwendung nur 1-5%
für RTOS draufgehen. Was meint ihr, ist es möglich/sinnvoll?

mfg Moritz

von Stefan Kleinwort (Gast)


Lesenswert?

16khz würde ich weder mit main-loop noch mit Multitasking machen,
sondern im Interrupt. 1000 Zyklen sind schon ein Wort, bei 16khz
bedeutet das exakt 100% Auslastung ... das ist ein Fall zum
Software-Optimieren.
Ich würde es per Timer-IR machen, im IR den eigenen Timer-IR disablen
und alle anderen freigeben. Alles andere dann im Hauptprogramm, da
musst Du Dich eben entscheiden zwischen main-loop und multitasking.

Wieviel RAM Du beim Multitasking brauchst, kommt auf Deine Applikation
an. Pro Tasking mind. 35 Byte RAM für Register plus lokale Variablen
und Unterprogrammaufrufe, je nach Task unterschiedlich. Wenn DU wie
oben beschrieben, während einem IR andere IR freigibst, muss die
IR-Schachtelung auch beim Stack berücksichtigt werden. Über den Daumen:
600 Byte sollten für 5-8 Tasks reichen.

Stefan

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.