Guten Tag,
im Moment schlage ich mich folgendem Problem 'rum:
Ich möchte eine Klasse schaffen in der die Member-Funktion(-en) in jedem
Objekt neu und unabhängig voneinander kreiert werden.
(D.h. der entsprechende Functionpointer ist für jedes(!) Objekt der
Klasse ein anderer, die entsprechenden Member-Funktionen und ihre
(statischen) Variablen sollen also im Programmspeicher koexistieren).
Normalerweise würde ich denken dass ich dies mit "inline" erzwingen
kann.
Dazu habe ich folgenden Code zum Testen geschrieben um es dann mit dem
Simulator durchzuspielen (neuste Version von AVR studio):
(tv3 dient nur zum Einfügen eines "Breakpoints)
Falls T1.Function und T2.Function separat existieren würden, wäre
natürlich das Ergebniss in tv1 = tv2 = 2. Dem ist aber nicht so,
sondern es wird: tv1=2 und tv2=5 !!
D.h. die statische Variable "g" ist für beide Objekte T1 und T2 die
Gleiche, "inline" hat also nicht wie erhoft funktioniert.
Wüsste jemand eine Möglichkeit dies zu erzingen ?
Grüsse und Danke: Hermann
Member-Variable in die Klasse einbauen. Die existiert dann für jede
Instanz getrennt.
Static in (nicht-static) Methode oder generell in einer Klasse wirkt auf
mich komisch. Dafür gäbe es in C++ die statische Member Variable.
BTW, inline bedeutet nur, daß man sich das Call spart und den
hoffentlich kurzen Code statt dessen direkt einfügt. Es bedeutet nicht,
daß man eine "Instanz der Methode" erhält. Der Compiler darf inline
sogar ignorieren. Er könnte ja nach Größe optimieren sollen und
feststellen, daß inline das vereitelt.
Danke für deine schnelle Antwort,
in der Tat, die Variablen alle auf der Klassenebene zu definieren löst
das Problem der statischen Variablen. Der Hintergrund dass ich mehrere
Funktionsinstanzen haben will ist folgender: Die Klasse soll eine Klasse
für Schrittmotoren werden. Jedes Objekt entspricht dann einer
Achse/Motor.
Für meine Anwendung ist es manchmal notwendig zwei Achen gleichzeitig zu
fahren, dies geschieht mit timer0 (ISR T0) und timer2(ISR T2)
(Atmega644).
Die Funktion um die es geht berechnet den nächsten Schritt bzw. die
Schrittdauer.
Was ich letztlich verhindern will ist dass (durch die Interruptabfolge
der beiden Timer) der erste Interrupt (sagen wir mal T0) in die Funktion
einsteigt und kurz danach T2 nachkommt -bevor T0 fertig ist- und alles
durcheinander wirbelt.
Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber
ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.
Grüsse: Hermann
Durcheinander wirbeln ergibt sich aber daraus, daß Dinge vermischt sind.
Du brauchst g, das zwischen den Interrupts erhalten bleiben soll,
richtig?
Und es gibt zwei Instanzen T1und T2, für 2 unabhängige Achsen, richtig?
Also Member-Variable g in die Klasse test. So wie das jetzt da steht,
gibt es g nur einmal und daran kann (und darf) inline nichts ändern.
hm,
es geht dabei nicht (nur) um 'g'. Die eigentliche Funktion ist natürlich
weit komplexer wie diejenige die ich eingestellt habe. Zur
Schrittberechnung sind halt noch jede Menge zusätzlicher (auf das Scope
der Funktion) beschränkte kurzlebige Variablen von Nöten. Ein zweiter
Eingriff in die(selbe) Funktion durch die ISR T2 könnte diese
kurzlebigen Parameter ja verändern und damit die Schrittberechnung von
ISR T0 (zer-)stören.
Oder versteh' ich da was falsch ?
Grüße: Hermann
Hermann E. schrieb:> (D.h. der entsprechende Functionpointer ist für jedes(!) Objekt der> Klasse ein anderer, die entsprechenden Member-Funktionen und ihre> (statischen) Variablen sollen also im Programmspeicher koexistieren).
Funktionspointer haben nichts mit Member-Funktionen zu tun. Es gibt in
deinem Beispiel genau eine test::function, und genau die wird immer
direkt (ohne Pointer) aufgerufen.
> Normalerweise würde ich denken dass ich dies mit "inline" erzwingen> kann.
inline ist bloß eine Hinweis an den Compiler, dass er den Code der
Funktion an die Stelle des aufrufs kopieren "sollte", um den
Aufruf-Aufwand zu sparen. Es verändert nicht das Verhalten des
Programms, sondern hat höchstens Einfluss auf die
Geschwindigkeit/Programmgröße.
Hermann E. schrieb:> Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber> ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.> Grüsse: Hermann
Und deswegen gibt es Controller mit Interrupt(Gruppen)Prioritäten, wie
zB. ARMv7M (zB. STM32, LPC...) - hier kannst du vollautomatisch den
Aufruf von z.B. der Timer0-ISR abschalten solange die Timer1-ISR läuft
(gleiche Priorität), aber z.B. Aufrufe der USART0-ISR immer zulassen
(höhere Priorität).
Hermann E. schrieb:> Zur> Schrittberechnung sind halt noch jede Menge zusätzlicher (auf das Scope> der Funktion) beschränkte kurzlebige Variablen von Nöten. Ein zweiter> Eingriff in die(selbe) Funktion durch die ISR T2 könnte diese> kurzlebigen Parameter ja verändern und damit die Schrittberechnung von> ISR T0 (zer-)stören.
Wie das denn? Die temporären Variablen liegen auf dem aktuellen
Stack-Frame, und wenn ein Interrupt kommt und dieselbe oder eine andere
Funktion aufruft, wird eine neues Stack-Frame aufgemacht und dort die
Variablen abgelegt. Probleme gibts erst wenn der Stack zu klein ist oder
du auf Variablen zugreifst, die zwischen den ISR's und der main geteilt
werden, wie zB globale Variablen, Member-Variablen von globalen
Objekten, Pointer jeweils auf Objekte anderer ISR's... Funktionslokale
Variablen sind jedenfalls immer lokal, solange du nicht explizit per
Pointer/Referenz Zugriff an andere Programmteile gibst.
Hermann E. schrieb:> Natürlich könnte ich mit cli() die Funktion atomar ausführen
Standardmäßig sind auf AVR's während der Abarbeitung einer ISR onehin
die Interrupts abgeschaltet solange du sie nicht explizit mit sei()
wieder einschaltest, und alles innerhalb der ISR somit "atomar". Beim
Zurückkehren der ISR (Instruktion "sei") werden sie wieder
eingeschaltet.
Falscher Lösungsansatz für eventuell richtig erkanntes Problem.
Wenn du es wirklich hinbekommst, auf deinem AVR Multithreading zu
implementieren, dann müssen die Funktionen reentrant sein. Dann kommt da
auch nichts durcheinder, wenn mehrere Threads "gleichzeitig" die selbe
Funktion nutzen. Google hilft zum Thema weiter.
Aber mal ganz dumm gefragt: Wie genau willst du das machen, mit zwei
Timern zwei Motoren gleichzeitig laufen zu lassen? Denn ich fürchte mal,
dein ganzes Programmkomzept wird so überhaupt nicht funktionieren.
Oliver
Hermann E. schrieb:> Natürlich könnte ich mit cli() die Funktion atomar ausführen, hab' aber> ein sehr ungutes Gefühl dabei, da ich dann alles außen rum blockiere.
Nun, auf einem AtMega deaktiviert die Hardware von sich aus beim
Eintritt in eine ISR das globale Interruptflag. Und solange man nicht
ganz genau weiß, was man da tut, sollte man das auch so belassen.
Eine ISR von einer anderen, die etwas ähnliches macht, unterbrechen zu
lassen, bringt gar nichts. Lass die ISRs sauber hintereinander ablaufen,
das erspart dir sehr viele unschöne Dinge, und "gleichzeitiger" gehts
sowieso nicht.
Oliver
Hermann E. schrieb:> Was ich letztlich verhindern will ist dass (durch die Interruptabfolge> der beiden Timer) der erste Interrupt (sagen wir mal T0) in die Funktion> einsteigt und kurz danach T2 nachkommt -bevor T0 fertig ist- und alles> durcheinander wirbelt.
Also werden diese Methoden der Motorenklassen aus den ISRs aufgerufen?
Und Du möchtest verhindern, dass zwei Objekte (also zwei Instanzen
dieser Motorenklassen) eine geteilte Ressource - nämlich die Timer -
koordiniert benutzen? Dann ist das Problem auch nicht bei den
Motorenklassen, sondern bei der geteilten Ressource zu lösen.
Wie andere schon sagten: mit inline hat das gar nichts zu tun. Auch zu
versuchen Funktionen zu verdoppeln ist sinnfrei. Mach sowas wie 'g' in
Deinem Beispiel zu einem normalen Member dann können sich die Instanzen
erst mal nicht gegenseitig beeinflussen.
Um das Problem mit der geteilten Ressource (Timer) zu lösen könntest Du
den in eine eingene Klasse, ein Singleton verpacken. Davon gäbe es dann
genau eine einzige Instanz, die sich alle Motorenobjekte teilen. Dieses
Timerobjekt kümmert sich dann um die Koordination der Zugroffe. Das geht
z.B. mit einer Queue, in die jedes Motorenobjekt Events wie "Motor mit
id 2 will zum Zeitpunkt X benachrichtigt werden" rein legen kann.
Jedesmal wenn ein Timerinterrupt auftritt, wird die Queue abgearbeitet,
und der entsprechende Motor gefragt, wann er wieder geweckt werden
möchte.
Es gibt auch andere Wege, z.B. Mutex Objekte, die ein beschäftigtes
Timerobjekt vor wilden Zugriffen schützen. Dann muss ein Motor, der eine
neue Weckzeit eintragen möchte warten bis der Mutex wieder freigegeben
wird.
Wow,
da kam ja 'ne ganze Lawine an Antworten !
Super, freut mich dass die Community so hilfreich ist.
Ich war gerade mit meiner Freundin draußen um vom schönen Wetter zu
profitieren und muss jetzt erst mal essen und vor allem all das was
geschrieben wurde (+Essen) verdauen.
Mehr heute Abend: H
Aaaalso,
ich antworte mal in chronologischer Folge, nicht nach Wichtigkeit
geordnet.
Offensichtlich sind da einige Dinge die ich nicht richtig verstanden
hatte (wie die Auswirkung von "inline"), doch mehr dazu im Text:
Dr. Sommer schrieb:> Funktionspointer haben nichts mit Member-Funktionen zu tun. Es gibt in> deinem Beispiel genau eine test::function, und genau die wird immer> direkt (ohne Pointer) aufgerufen.
Ich wollte mit dem Hinweis auf den Funktionspointer nur
unmissverständlich klar machen dass ich 2 komplett getrennte Instanzen
der selben (Member-) Funktionen realisieren wollte.
Dr. Sommer schrieb:> inline ist bloß eine Hinweis an den Compiler, dass er den Code der> Funktion an die Stelle des aufrufs kopieren "sollte", um den> Aufruf-Aufwand zu sparen. Es verändert nicht das Verhalten des> Programms, sondern hat höchstens Einfluss auf die> Geschwindigkeit/Programmgröße.
Da lag wohl einer meiner Denkfehler: Beim Googlen hatte ich vorher schon
die "Optionalität" (=sollte) von "inline" gesehen. Allerdings lässt es
mich mit ein wenig Verwirrung zurück: Mein Gedanke war dass ein "inline"
Programmteil sich so verhält als ob er direkt in den aufrufenden Code
eingefügt wurde. (Das würde heißen: an zwei verschiedenen Stellen im
Programm aufgerufen => als Funktion zweimal realisiert) In der Tat kann
man sich bei Memberfunktionen aber fragen was das dann in der Praxis
bedeutet (muss also nochmals selbst nachforschen)
Dr. Sommer schrieb:> Und deswegen gibt es Controller mit Interrupt(Gruppen)Prioritäten
Richtig, Das ist ja auch beim Atmega644 der Fall. Bei mir muss vor allem
der Uart und timer1 (meine Zeitbasis) noch höhere Priorität haben.
Richtig ist (hatte diesen Punkt aber vegessen) dass "nested Interrupts"
nicht der Default sind sondern erst durch "ISR_NOBLOCK" erzwungen werden
müssen.
Dr. Sommer schrieb:> Wie das denn? Die temporären Variablen liegen auf dem aktuellen> Stack-Frame, und wenn ein Interrupt kommt und dieselbe oder eine andere> Funktion aufruft, wird eine neues Stack-Frame aufgemacht und dort die> Variablen abgelegt.
Das wusste ich schlicht nicht: Danke für die Info, wieder was gelernt !
Oliver S. schrieb:> Aber mal ganz dumm gefragt: Wie genau willst du das machen, mit zwei> Timern zwei Motoren gleichzeitig laufen zu lassen? Denn ich fürchte mal,> dein ganzes Programmkomzept wird so überhaupt nicht funktionieren.
Nee, das läuft nämlich schon. Ich kann zwei Schrittmotoren einen auf T0
und den andern T2 mit einem 14Mhz Baudquartz problemlos und ohne Zuckeln
mit 2000 Schritten/s (oder mehr) parallel laufen lassen. Das
funktioniert auch wenn beide Motoren gleichzeitig beschleunigen/bremsen
(also während dem rechenintensivsten Teil der Bewegung) auch dann wenn
noch Zusatzjobs anfallen. Mir gefällt aber meine eigene Programmstruktur
nicht. Bislang habe ich nämlich eine T0 und eine getrennte T2 Klasse
(und vermeide so das ganze Interferenzproblem). Das nervt aber da ich
alle Änderungen für beide Klassen (damit also doppelt) ausführen muss.
Die ISR selbst tut nur wenig: sie setzt im wesentlichen nur den nächsten
OCR-Wert und eine Flag dass mit der Berechnung des darauffolgenden OCR
Werts in der main "while(1=1)"-Schleife begonnen werden kann.
Darüberhinaus habe ich alle OCR Werte "doppelt gebuffert", d.h. in
Wirklichkeit wird also sicherheitshalber nicht auf einen sondern auf
zwei OCR-Werte im Voraus gerechnet (falls mal ein zeitintensiver Job
dazwischen kommt).
Der springende Punkt sind aber genau die zwei Funktionen die mir die
nächsten OCR's für timer0 bzw. 2 in der main-Schleife berechnen: Diese
sind nämlich jeweils Member-funktionen der T0 bzw. T2 Klasse und die
Befürchtung war/ist ein möglicher Konflikt in der OCR-Berechnung wenn
ich die beide zu einer einzigen Klasse zusammenfüge. (Hoffe meine
Ausführung ist so verständlich).
Nachmal ein dickes Thanx und natürlich sind weitere Vorschläge/Kritiken
willkommen:
Hermann
Hermann E. schrieb:> 2 komplett getrennte Instanzen> der selben (Member-) Funktionen realisieren wollte.
Von Funktionen gibt es keine Instanzen... Jede Funktion ist genau 1x im
Programmspeicher, und wird nur mit verschiedenen Instanzen der Objekte
aufgerufen (der "this"-Pointer wird implizit übergeben). Nur eben die
lokalen Variablen werden für jeden Aufruf extra auf dem Stack angelegt,
aber da ist es völlig unerheblich ob das eine globale oder
Member-Funktion ist.
Hermann E. schrieb:> Mein Gedanke war dass ein "inline"> Programmteil sich so verhält als ob er direkt in den aufrufenden Code> eingefügt wurde.
Nicht so als ob der C/C++ Code einfach kopiert würde. Er verhält sich
von der Logik her wie eine normale Funktion, und lokale Variablen
werden dann eben im Stackframe des Aufrufers angelegt.
> (Das würde heißen: an zwei verschiedenen Stellen im> Programm aufgerufen => als Funktion zweimal realisiert)
Der Code taucht dann 2x auf, ja.
Hermann E. schrieb:> Richtig, Das ist ja auch beim Atmega644 der Fall
Ja dann ist das ja optimal, dann können sich die beiden Timer-ISR's gar
nicht in die Quere kommen.
PS: Beim AVR werden die meisten lokalen Variablen tatsächlich in
Registern sein, da er so viele davon hat, aber Stack-Zugriffe
ineffizient sind. Beim Aufruf einer Funktion oder ISR werden aber die
Register auf den Stack gesichert.
Dr. Sommer schrieb:> Von Funktionen gibt es keine Instanzen... Jede Funktion ist genau 1x im> Programmspeicher, und wird nur mit verschiedenen Instanzen der Objekte> aufgerufen (der "this"-Pointer wird implizit übergeben)
Jop, da war wohl mein Hauptdenkfehler. Mir war schon klar dass eine
Memberfunktion grundsätzlich nur einmal im Programmspeicher abgelegt
wird, ging aber fälschlicherweise davon aus dass ich mit "inline"
weitere "Instanzen" erzwingen könnte.
Gut wenn ich das hier geschriebene so für mich zusammenfasse ziehe ich
folgende Lehre:
A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro
Motor auf Klassenebene definieren
und
B) Keine statischen Variablen die eine Erinnerung an vorhergehende
Prozesse lassen könnten in die Memberfunktionen packen.
C) Solange keine "Nested Interrupts" definiert sind vermeidet die
sequenzielle Ausführung der (selben) Funktionen Interferenzen.
Mal sehen wie weit ich damit komme: Hermann
Hermann E. schrieb:> A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro> Motor auf Klassenebene definieren
Genau. Das sind die Variablen des Objekts um dessen Eigenschaften zu
halten. Da diese Variablen an die Lebensdauer des Objekts gekoppelt
sind, braucht man keine weiteren Maßnahmen zu ergreifen, damit sie
erhalten bleiben wenn z.B. eine Funktion verlassen wird. Diese Variablen
existieren weiter bis das Objekt gelöscht wird.
> B) Keine statischen Variablen die eine Erinnerung an vorhergehende> Prozesse lassen könnten in die Memberfunktionen packen.
Richtig. Sonst wären sie objektübergreifend. Falls man doch mal
Informationen halten möchte, die für alle Instzanzen einer Klasse
gelten, dann gehören die als static Variablen in die Deklaration der
Klasse, aber nicht in die Memberfunktionen.
Supa,
hab' mir gerade nochmals meinen Code angeschaut und denke dass ich das
so hinkriege. Gleichzeitig erhoffe ich mir dass er durch diese
Veränderungen auch lesbarer wird.
Danke für eure Hilfe: Hermann
Hermann E. schrieb:> A) Alle permanenten (um das Wort "statisch" zu vermeiden) Variablen pro> Motor auf Klassenebene definieren
Das ist genau das grundlegende Prinzip aller objektorienterten Ansätzen:
Jede Instanz eines Objektes hat ihren eigenen Datensatz.
Oliver