Hallo Leute,
nachdem ich jetzt schon eine Weile mit den AVR rumspiele und die
Projekte immer grösser werden hab ich mir mal ein paar Gedanken bzgl.
eine "Event" Systems gemacht. Dabei werden die Events an sich mittels
eines enums (in der events.h) definiert. Hier werden auch die maximale
Anzahl der benötigten Events festgelegt (MAXEVENTS). Danach kann man
dann eine Funktion bei dem Event - System registrieren und wenn dann so
ein Event (das man natürlich noch auslösen muss) eintritt, dann wird das
von der Funktion HandleEvents() abgearbeitet. Hier mal der entsprechende
Quellcode. Mich würde sehr Eure Meinung dazu interessieren:
events.h
Und was möchte uns der Herr damit sagen?
Code, der veröffentlich werden soll, gehört in die Codesammlung. Da kann
man dann auch wunderbar darüber diskuttieren...
Meinst du nicht, das sich das uint8_t als Rückgabewert mit dem -1 von
return etwas beißt?
Interessant bzgl der notwendigen Rechenleistung dürfte auch das werden:
@STK500-Besitzer: Ich möchte eigentlich nix sagen. Eher was dazu gesagt
bekommen. In die Codesammlung hab' ich noch nicht geschoben, da ich
dachte, dass da nur Zeugs reinkommt, was schon "approved" ist ... beim
nächsten mal, versprochen ;-)
@Gast: Genau das hat mich bzgl. der Übersichtlichkeit immer ein wenig
gestört ...
Grüße,
Michael
@Gast: Das ist schon richtig. Meist aber werden die Events ja von
beispielsweise Interrupts ausgelöst, in denen ein Flag gesetzt wird und
in der Main wird dieses abgefragt und abgearbeitet. Dabei muss dann in
der ausführenden Funktion dieses Flag auch wieder gelöscht werden. Bei
größeren Projekten wurde mit das Ganze dann einfach zu unübersichtlich.
Angenommen man hat 12 Flags die an unterschiedlichen Stellen gesetzt
werden, dann hättest Du einen if - else if - else if ... - else Baum mit
12 Einträgen. So steht bei mir das HandleEvents () in der main -- auf
der anderen Seite muss ich aber auch alle Events registrieren, eben 12
mal. Ist wohl Geschmackssache ... mir gefällt so einfach besser ...
Grüße,
Michael
OK jedem das seine :-)
ABER die Ressourcen in einem AVR sind begrenzt, besonders SRAM. Im
Beispiel mit den 12 Flags wird Platz für 12 indirekte Call Adressen im
SRAM benötigt. 12 mal if(..) benötigen 0,0 millibit.
Gute Nacht
> if (EventFlagBuffer & (1 << i))>Braucht das shiften so viel Zeit ?
Ja. Da dieses Shiften zur Laufzeit gemacht werden muss(es ist ja niht
bekannt, wieviel Bits zu schiften sind), entsteht dadurch eine Schleife.
Das hingegen:
(1<<PC4) ist eine Konstante und wird zur Compilezeit ermittelt.
(Sieh dir doch einfach mal den ASM-COde dazu an)
Ich würde Dir raten, das eher über eine Maske (im Flash) zu machen:
Das sollte einiges schneller als die Schleife sein
Statt Euch an programmtechnischen Kleinigkeiten aufzuhängen, sollte doch
eher eine Diskussion über die Notwendigkeit eines Eventsystems auf einem
AVR in Gang kommen. Manche Menschen fangen halt rechtzeitig an, zu
abstrahieren und behalten später den Überblick...;-)
Aber mal im Ernst: Immer dann, wenn von einem Programmm viele Dinge
gleichzeitig zu erledigen sind, sollte man über ein Eventsystem
nachdenken. Wie rudimentär das sein muss, hängt dann nur noch von der
Komplexität der Aufgabe ab.
Lohnend war es für mich beispielsweise bei der Umsetzung eine
Bedienoberfläche, die Messwerte, Uhrzeit usw. anzeigt, die sich
asynchron vom Vordergrundprogramm ändern. In so einer Umgebung sendet
der Timer ein Event, das der Bildschirm neu gezeichnet werden möge, weil
sich die Uhrzeit verändert hat. Auf diese Weise können viele unabhängige
Prozesse das Neuzeichnen des Bildschirms veranlassen. Gleichzeitig wird
aber auch auf Tastendrücke per Event reagiert.
@Lippy ist das nicht etwas umständlich?
Man könnte doch einfach vor der Schleife eine Variable mit 1
initialisieren und sie dann in der Schleife mit 2 multiplizieren. Mehr
sind diese shifts ja auch nicht, wobei die Shifts wahnsinnigen Overhead
erzeugen.
@Matthias Lipinsky: Noch ne kurze Frage: Statt uint8_t sollte es wohl
uint16_t sein, oder?
@Eddy Current: Genau dafür mache ich das Ganze. Das Projekt ist eine
Ansammlung von Tools für meinen Modellbaukram. Also Ladekurven plotten
mittels der Daten von den Ladegeräten (mit Log auf SD - Karte). Dann ein
Frequenzscanner mit externem Scan - Empfänger, Servotest, Empfängertest,
EWD - Messung, Drehzahlmessung, ... etc. Bei den Dingen die ich vorhabe
ist Geschwindigkeit nicht so wirklich wichtig, da meist das GLCD bedient
werden muss. Also Controller kommt ein Mega128 zum Einsatz. Aber wenn
man von vornherein optimieren kann dann bin ich immer sehr dankbar für
Tipps!
Grüße,
Michael
>Noch ne kurze Frage: Statt uint8_t sollte es wohl uint16_t sein, oder?
Ja. Das war nur, um zu prüfen ob du aufpasst ;-)
>einfach vor der Schleife eine Variable mit 1 initialisieren und sie dann in >der
Schleife mit 2 multiplizieren
Ja, wäre auch möglich. Nur statt multiplizieren, dann einfach schieben.
>In so einer Umgebung sendet der Timer ein Event, das der Bildschirm neu >gezeichnet werden möge, weil sich die Uhrzeit verändert hat.
Meine Philosophie ist eher andersrum: Die Bedienung/Anzeige ist
unabhängig von der Steuerung (also der eigentlichen Aufgabe). Somit muss
die Bedienung/Anzeige selbständig entscheiden, ob irgendwas neu
aufgebaut werden muss..
@Matthias Lipinsky: Ist zwar schon spät, aber noch komme ich mit ;-)
Zu Deiner Philisophie: Dann müsste die Display Funktion ja eigentlich
immer schauen, ob sich an den Daten die dargestellt werden sollen was
geändert hat ... ist das nicht aufwändiger ?
Grüße,
Michael
>Dann müsste die Display Funktion..
Ja. zB bei der Uhrzeit könnte das so aussehen:
1
if(SekundeLast!=Sekunde)
2
{
3
SekundeLast=Sekunde;
4
5
// Uhrzeit aktualisieren
6
}
Das kommt aus meinem Beruf. Da programmiere ich SPS. DOrt vertrete ich
dasselbe. Somit kann der Ablauf programmiert werden, mit all seinen
Variablen.
Unabhängig davon kann sich eine Visualisierung um die Anzeige von
(einigen) Varaiblen kümmern. Evtl. auch Eingabemöglichkeiten bieten.
Wann nun das Display aktualisiert werden muss, ist der Steuerung, also
dem Ablauf doch sch*egal...
@ Michael K. (mmike)
So ein Eventsystem ist akademisch aufgeblaheter Nonsense. Speziell wenn
das ganze noch dynamisch eingeklinkt wird. Viel pragmatischer ist es in
der Interruptprocedur eine Boolean zu setzen, und die dann im Main
abzufragen. Dann kann man noch eine Statusmaschine mitlaufen lassen und
gut ist.
Das was du vor hast bezeichnet man in der Softwarearchitektur als
Observer-Pattern. Dort können sich interessierte Klassen für ein
bestimmtes Ereignis registrieren, sobald dieses Ereignis dann eintritt,
werden die registrierten Objekte (Observer) informiert.
Hast du aber mal daran gedacht, dass die Ereignisse dann alle im
Interrupt Kontext deiner ISR aufgerufen werden?
Beispielsweise registrierst du 20 Module darauf, dass der RTC seine 1
Millisekunde Periode erreicht hat.
In der ISR der RTC werden nun 20 registrierte Event-Funktionen
aufgerufen. Sagen wir mal jede braucht im Schnitt 100 Mikrosekunden zur
Abarbeitung. Das klingt nicht viel, ABER 20 * 100 Mikrosekunden = 2
Millisekunden.. und schon hättest du dank deines geschickten Designs
dein System total gesprengt ...
Vielleicht habe ich dein Design, aber gerade auch nur falsch verstanden
:-)
Du kannst mich gerne aufklären!
Gute Nacht!
Eine "-1" zurückzugeben, bei einem uint8_t als Typ sorgt meiner Meinung
nach für Verwirrung.
Besser finde ich:
1
#define RE_ERROR ((uint8_t) -1)
Da ist die Absicht direkt raus abzulesen.
@Lippy: Meinst du, dass das dereferenzieren mit LPM aus dem Flash
schneller ist als eine kleine Shift-Schleife? Wenn überhaupt dann nur
ein/zwei Takte, würde ich sagen.
Deine HandleEvents funktion ist übrigens nicht interrupt-sicher. Normal
sollte die Reihenfolge so aussehen.
1.) Interrupts sperren
2.) Flag checken -> weiter zu drei, oder raus
3.) Flag clearen
4.) Interrupts freigeben
5.) Funktion aufrufen.
Am Anfang von HandleEvents würde ich erstmal schauen ob EventBuffer
überhaupt einen Wert hat (EventBuffer != 0). Sonst machst du da bis zu
32 checks für nichts unter wieder nichts. ;)
1)
Ich würde mich von dem Gedanken verabschieden, die Events zu
nummerieren. Benutze als Identifikation gleich die Maske, das spart das
ineffiziente Geschiebe. Du kannst ja ein paar Makros definieren, dann
können die Events auch gleich aussagekräftige Namen bekommen:
1
#define EVENT_TIMER (1<<0)
2
#define EVENT_KEY (1<<1)
3
#define EVENT_DISPLAY (1<<2)
4
...
5
RaiseEvent(EVENT_DISPLAY);
Und auch das Raisen mehrerer Events ist dann effizienter:
1
RaiseEvent(EVENT_KEY+EVENT_DISPLAY);
2)
µC-Programme sind praktisch immer starre Konstrukte, keine dynamischen.
Schon beim Schreiben des Programms steht fest, welche Funktion zu
welchem Event gehört. Die Funktion RegisterEvent finde ich daher
reichlich überflüssig. Initialisiere FuncArray doch gleich mit den
richtigen Werten. Dann kann das Array auch im Flash liegen, was
wertvolles RAM spart.
3)
Du musst dir unbedingt mehr Gedanken zur Interruptsicherheit machen.
Das gilt insbesondere für ClearEvent. Aber auch für RaiseEvent, wenn es
auch außerhalb von Interrupts aufgerufen wird. Bei RaiseEvent ist es
dann aber mit einem cli/sei-Pärchen nicht getan. Benutze am besten die
Makros der AVR-Libc.
Das ist zwar nett gedacht, aber der restliche Code funktioniert bei
MAXFUNCS>16 nicht. Da bedarf es schon noch mehr, wenn das alles
"automatisch" per Präprozessor gehen soll.
(wobei eine Beschränkung auf max 16 Events das einfachste wäre, und 16
sollten eigentlich auch immer reichen)
@All: Erst mal vielen vielen Dank für Eure Kommentar! Hab viel gelernt !
@Sebastian B.: Richtig. Nur eine Funktion kann sich für ein Event
registrieren.
@Simon K.: Alles klar. Ist umgebaut. Bzgl. dem shiften hab ich jetzt so
gemacht wie STK500-Besitzer es vorgeschlagen hat. Ich initialisiere ein
lokale Variable mit 1 und shifte diese dann pro Durchlauf immer um eine
Stelle nach links.
@Nico: Die Handle - Funktionen erledigen normalerweise "low-priority"
Dinge wie einen Uart String parsen bzw. die Displayausgabe. Die Sollen
eigentlich unterbrechbar sein, damit der Rest ungestört laufen kann. Der
Tip mit dem Check "(EventBuffer != 0)" ist klasse und schon eingebaut!
@Stefan Ernst: Das ist natürlich ein Punkt mit den #defines. Auch das
mehrere Event gleichzeitig "geraised" werden können wäre eine gute
Sache. Ich hatte am Anfang auch überlegt es so zu machen, aber mit den
enums muss ich selbst einfach nicht aufpassen, ob die Events auch
chronologisch definiert sind. Effektiver ist es mit Deinem Vorschlag ...
keine Frage!
Bzgl. der Interruptsicherheit: Ich verstehe den Punkt, aber leider nicht
warum es mit einem cli/sei am Anfang und Ende der Raise- bzw. ClearEvent
Funktionen nicht getan ist? Welche Makros meinst Du genau?
Bzgl. der Präprozessorgeschichte: Habs mittlerweile fest für max. 16
Events gemacht. MAXEVENTS kann dann Werte von 1 - 16 annehmen.
Grüße,
Michael
Michael K. wrote:
> @Stefan Ernst: Das ist natürlich ein Punkt mit den #defines. Auch das> mehrere Event gleichzeitig "geraised" werden können wäre eine gute> Sache. Ich hatte am Anfang auch überlegt es so zu machen, aber mit den> enums muss ich selbst einfach nicht aufpassen, ob die Events auch> chronologisch definiert sind.
Der eigentliche Punkt dabei ist, aus RaiseEvent und ClearEvent die
Konstrukte à la (1<<event) rauszubekommen, denn die sind wirklich sehr
ineffizient.
> Bzgl. der Interruptsicherheit: Ich verstehe den Punkt, aber leider nicht> warum es mit einem cli/sei am Anfang und Ende der Raise- bzw. ClearEvent> Funktionen nicht getan ist?
Weil RaiseEvent sowohl außerhalb, wie auch innerhalb von Interrupts
aufgerufen werden kann, und innerhalb eines Interrupts macht sich ein
sei nicht so gut, um es milde auszudrücken. Die Interrupts dürfen
daher nur dann wieder freigegeben werden, wenn sie auch vorher schon
freigegeben waren (was auch ganz grundsätzlich ein gutes Vorgehen bei
atomic Blöcken ist). Und genau um sowas kümmern sich die vorgefertigten
Makros.
> Welche Makros meinst Du genau?http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
@Stefan Ernst: Oooookay. Ich denke ich habs verstanden. Das mit dem sei
in einer ISR ist natürlich "unschön".
> Der eigentliche Punkt dabei ist, aus RaiseEvent und ClearEvent die> Konstrukte à la (1<<event) rauszubekommen, denn die sind wirklich sehr> ineffizient.
Das stimmt. Werde mal drauf rumdenken ...
Bzgl. der atomic.h:
Vielen Dank für den Link! Hab auch schon ne Rund google gefragt, aber
das hab ich leider nicht gefunen. Werde mit das Ganze mal zu Gemüte
führen !
Grüße,
Michael
Achja, den check auf fp!=0 würde ich in RegisterEvent nicht machen. Du
checkst das sowieso in HandleEvents verlierst dadurch aber die
Möglichkeit ein Event zu löschen, weil du es z.B. grad nicht behandeln
willst.
> @Nico: Die Handle - Funktionen erledigen normalerweise "low-priority"> Dinge wie einen Uart String parsen bzw. die Displayausgabe. Die Sollen> eigentlich unterbrechbar sein, damit der Rest ungestört laufen kann.
Ja, ABER, das problem ist halt, wenn während deine Handle-Funktion läuft
und dann der selbe Event nochmal raised wird, dann löscht ClearEvent das
neue Event wieder. Deine handle funktion startet dadurch beim nächsten
durchlauf nicht. Das ist in der Form eine sehr labile Konstruktion.
Nico
@Nico Erfurth: Stimmt. An was man da alles denken muss ... uff. Aber die
Int auszuschalten in der Zeit wenn beispielsweise das Display neu
gezeichnet wird ... das kann schon ne "Weile" dauern ...
Was könnte man dagegen unternehmen ?
Grüße,
Michael
Für zeitliche Events benutze ich das hier.
Beitrag "Wartezeiten effektiv (Scheduler)"
Wenn ich Flags in einem Interrupt setze, "verschwende" ich meistens ein
ganzes Byte, das ergibt weniger Code.
In der Regel sind Interrupt-Events zu speziell, um sie zusammen zu
fassen.
Z.B. ob die UART-FIFO ein Byte empfangen hat, hat überhaupt kein Flag,
die Funktion kbhit() überprüft, ob beide FIFO-Indexe gleich sind.
Der Timerinterrupt wiederum incrementiert das Flag-Byte. Falls das Main
mal länger Busy ist, geht somit kein Interrupt verloren.
Peter
@Stefan Ernst: Cool. Das passt. Wird geändert ...
@Peter Dannegger: Wow. Das ist mächtig. Wird wohl ne Weile dauern, bis
ich das gerallt hab, aber ich acker mich mal durch!
Grüße,
Michael