Hallo,
ich möchte mit einem ATtiny13 eine Frequenzmessung über den Pin INT0 für
externe Interrupts durchführen. Über diesen Pin zähle ich in der
zugehörigen ISR durch Inkrementieren einer Variable jede positive
Taktflanke, was auch gut funktioniert. Um gleichzeitig die eigentliche
Frequenz aus diesen Zählwerten zu bestimmen, nutze ich noch zusätzlich
Timer0 für Overflow-Interrupts um nach jeder Sekunde die Frequenz zu
überprüfen, da diese sich in Abhängigkeit eines Sensors ändert.
Nun ist es dummerweise so, dass sich beide Interrupts gegenseitig
blockieren. Die ISR für den Overflow-Interrupt wird nämlich nicht
aufgerufen, wenn gleichzeitig im GIMSK-Register der externe Interrupt
freigeschaltet wurde. Wenn ich das Bit INT0 in diesem Register wieder
lösche, klappt es mit dem Overflow-Interrupt aber und die zugehörige ISR
wird aufgerufen.
Weiß vielleicht jemand, wie ich beide ISRs störungsfrei nutzen kann?
Ich habe zwar gesehen, dass in der Interruptvektortabelle, der
Interruptvektor für externe Interrupts eine höhere Priorität hat, als
jener für Overflow-Interrupts, aber ich weiß nicht, ob das vielleicht
die Ursache meines Problems darstellt!
Wahrscheinlich hast Du den externen Interrupt als LowLevel, statt als
Change/Rising/Falling konfiguriert, der löst dann solange aus, wie
Low-Pegel anliegt, alles andere wird blockiert.
Holger schrieb:> Weiß vielleicht jemand, wie ich beide ISRs störungsfrei nutzen kann?
In meinem Kaffeesatz kann ich sehen, dass der INT0 nicht auf Flanken-
sondern auf Low-Level-Trigger steht. Wenn mein Kaffee sich also nicht
irrt, sollte das mit einer Umstellung im MCUCR-Register funktionieren.
mfg.
die übliche Vorgehensweise bei der Fehlersuche ist die:
man reduziert das Programm so lange bis entweder der Fehler verschwindet
oder bis nur noch ein Rumpfprogramm übrig bleibt dass den Fehler noch
hat.
Dann schaut man erneut ins Datenblatt ob man alles richtig gemacht hat
und dann endlich öffnet man einen Thread bei dem man den Fehler genau
beschreibt UND das Rumpfprogramm im ORIGINAL beifügt.
Holger schrieb:> Leider ist eure Vermutung falsch. Ich habe sowohl ISC00 als auch ISC01> in MCUCR gesetzt, also Triggerung auf positive Taktflanken.
Wir mussten nur deshalb vermuten, weil Du Code in Prosa beschreibst,
anstatt Deinen Teil zu tun und den fehlerhaften Code hier einzustellen.
Das Einzige, was aus Deiner Beschreibung schlüssig hervorgeht ist, dass
Dein Code fehlerhaft ist.
Besser wäre schon gewesen, Dein Programm zu Gesicht zu bekommen!
Die Interrupts kann man eigentlich ganz entspannt nebeneinander
betreiben, ohne daß etwas blockiert wird. Als Beispiel habe ich ein
Programm für den ATtiny45 in der 'Schublade', bei dem allerdings PCINT0
anstatt INT0 verwendet wird:
http://www.mino-elektronik.de/7-Segment-Variationen/LCD.htm#lcd2
Beachte bitte, daß bei ISR (TIMER0_OVF_vect), die nur fürs Ansteuern des
LCD verwendet wird, und ISR (TIMER1_OVF_vect) das sei() möglichst am
Anfang der ISR steht. Dadurch wird der globale Interrupt wieder
zugelassen.
Ich habe es mir abgewöhnt, mit festen Torzeiten zu arbeiten, sondern
nutze nur noch Programme mit reziproker Messung.
besser als innerhalb einer Sekunde alle Flanken zu zählen ist es doch
einfach die Impulsdauer zu messen und dann die Frequenz zu errechnen.
Timer mit steigender Flanke starten und bei fallender Timerwert
abspeichern und Frequenz berechnen.
Ja, es wäre wahrscheinlich besser gewesen, sofort das Programm
mitzuschicken.
Anbei nun das auf's Wesentliche reduzierte Programm!
Ich habe meinen eigenen Fehler durch Zufall gerade behoben. Und zwar
hatte ich die Variable incr vorher als 64-Bit Variable definiert, weil
ich bis zu sehr großen Werten zählen wollte. Damit ging es
komischerweise nicht, aber nach einer Änderung auf 32 Bit ging es dann
plötzlich, sprich die ISR für den Overflow-Interrupt wurde aufgerufen.
Kann mir vielleicht jemand erklären, warum das mit einer 64
Bit-Variablen nicht funktioniert?
Dann noch eine allgemeine Frage an m.n. :Was macht es eigentlich für
einen Sinn in einer ISR das sei() zu setzen. Man muss doch eigentlich
immer in der main-Funktion das sei() setzen, damit man überhaupt in
einer ISR eintreten kann, oder?
Thomas O. schrieb:> besser als innerhalb einer Sekunde alle Flanken zu zählen ist es doch> einfach die Impulsdauer zu messen und dann die Frequenz zu errechnen.
Kann man so nicht pauschal sagen, für niedrige Frequenzen ist die
Messung der Periode genauer, für hohe ist der Weg über Torzeit/Zähler
sinnvoll.
Holger schrieb:> Kann mir vielleicht jemand erklären, warum das mit einer 64> Bit-Variablen nicht funktioniert?
evtl. weil die 64-Bit Operationen zu viel Zeit brauchen
> Dann noch eine allgemeine Frage an m.n. :Was macht es eigentlich für> einen Sinn in einer ISR das sei() zu setzen. Man muss doch eigentlich> immer in der main-Funktion das sei() setzen, damit man überhaupt in> einer ISR eintreten kann, oder?
damit wird ein neuer IRQ sofort erlaubt, ansonsten erst nach der
Rückkehr aus der Interruptroutine
Holger schrieb:> Dann noch eine allgemeine Frage an m.n. :Was macht es eigentlich für> einen Sinn in einer ISR das sei() zu setzen.
Mit Aufruf einer ISR werden automatisch alle Interrupts gesperrt, als ob
man den Befehl cli() in den Code schreiben würde.
Das sei() gibt nun alle Interrupts wieder frei, sodaß diese nicht
blockiert werden.
In meinem Beispiel wird ISR (TIMER0_OVF_vect) mit 122 Hz (ca. alle 8 ms)
aufgerufen und braucht dann einige Zeit, bis sie fertig ist. Nehmen wir
an, diese ISR würde 0,5 ms brauchen.
Ohne sei() könnten in dieser Zeit keine weiteren Eingangsimpulse gezählt
werden; als max. Eingangsfrequenz wäre nur < 2 kHz möglich: für einen
Frequenzzähler völlig daneben!
Mit sei() wird die Impulszählung zugelassen, und die ISR
(TIMER0_OVF_vect) dafür kurz unterbrochen.
Vor dieser Form der Reduzierung der INT-Priorität haben viele Leute
panische Angst, da ein neuer Aufruf der selben ISR stattfinden könnte,
obwohl der alte noch nicht abgeschlossen ist. Betrachtet man aber die
Zeiten - alle 8 ms werden 0,5 ms benötigt -, so sieht man, daß das nicht
passieren kann. Wenn ein neuer TIMER0_OVF-Interrupt kommt, ist der alte
zuverläsig abgeschlossen.
Wenn das Timing enger wird, könnte der neue Aufruf allerdings doch zu
früh kommen. In diesem Fall schaltet man in der ISR zunächst das IE-Bit
ab, damit dieser spezielle Interrupt momentan nicht stattfinden kann und
setzt dann sei(). Ganz am Ende wird dann das IE-Bit zur ISR wieder
gesetzt.
Holger schrieb:> Code.txt (779 Bytes, 10 Downloads)> Ja, es wäre wahrscheinlich besser gewesen, sofort das Programm> mitzuschicken.
Einen Compiler für die Sprache TXT, bzw. einen, der diese Extension
verwendet, kennen hier die wenigsten.
Warum verheimlichst du die von dir verwendete Programmiersprache?
Walter schrieb:> evtl. weil die 64-Bit Operationen zu viel Zeit brauchen
Nein, die ISR benötigt mit unsigned 64Bit insgesamt 148 Takte, der
Abstand zwischen den Aufrufen der T0-OVF beträgt 2048 Takte.
Ein Fehler unter Verwendung eines uint64_t ist im übrigen nicht
feststellbar.
Moin!
Ich bin mir jetzt nicht sicher, ob ich alles richtig verstanden habe,
aber warum verwendest du eine ISR um zu zählen? Das kann der ATtiny13A
(oder ist es wirklich der alte ATtiny13?) doch ganz alleine: Pin T0.
Per ISR könnte man dann sinnvollerweise die Zählerüberläufe zählen. Das
heißt, die ISR wird nur noch 1/256 mal so oft aufgerufen, was dann kein
Problem mehr darstellen sollte.
MWS schrieb:> Nein, die ISR benötigt mit unsigned 64Bit insgesamt 148 Takte, der
Ist ja Horror. In Assembler kriegt man das locker mit ungefähr 20 Takten
hin...
Markus Weber schrieb:> Ist ja Horror. In Assembler kriegt man das locker mit ungefähr 20 Takten> hin...
Zu spät aufgestanden? Träum' weiter. LOL
Ein LDS/STS kostet 4 Takte pro Byte, mal 8 sind bereits 32 Takte, da ist
noch nichts inkrementiert, Ein-/Aussprung in die ISR fehlt und Register
sind auch keine gesichert.
Markus Weber schrieb:> Ich bin mir jetzt nicht sicher, ob ich alles richtig verstanden habe,> aber warum verwendest du eine ISR um zu zählen? Das kann der ATtiny13A> (oder ist es wirklich der alte ATtiny13?) doch ganz alleine: Pin T0.
Du weißt, wieviele Zähler ein ATtiny13 hat?
MWS schrieb:> Ein LDS/STS kostet 4 Takte pro Byte, mal 8 sind bereits 32 Takte, da ist> noch nichts inkrementiert, Ein-/Aussprung in die ISR fehlt und Register> sind auch keine gesichert.
In Assembler kann man rücksichtslos acht Register (R8 - R15) als 64-Bit
Variable reservieren. Da muß man dann nichts sichern oder speichern und
ein Inkrement geht ganz schnell.
64 Bit scheinen mir allerdings angesichts des bald bevorstehenden
Weltunterganges doch etwas viel zu sein ;-)
MWS schrieb:> Markus Weber schrieb:>> Ist ja Horror. In Assembler kriegt man das locker mit ungefähr 20 Takten>> hin...>> Zu spät aufgestanden? Träum' weiter. LOL> Ein LDS/STS kostet 4 Takte pro Byte, mal 8 sind bereits 32 Takte, da ist> noch nichts inkrementiert, Ein-/Aussprung in die ISR fehlt und Register> sind auch keine gesichert.
Hast du es mal probiert? Die AVR haben wunderbar viele Register, vor
allem die nicht so universell nutzbaren Register R0 bis R15 kann man
recht schön für solche Zwecke verwenden – ohne nennenswerte Nachteile
für die übrige Funktionalität.
Mal überschlagsmäßig gerechnet:
- Einsprung in die ISR: ca. 5 Takte (ohne RJMP, wenn man die ISR direkt
an die ISR-Vektor-Position legt),
- SR sichern: 1 Takt,
- Inkrementieren des ersten Bytes per ADD: 1 Takt,
- Addieren für die folgenden 7 Bytes per ADC: 7 Takte,
- Wiederherstellen des SR: 1 Takt,
- Rücksprung per RETI: 4 Takte.
Sind sogar nur 19 Takte, wenn ich mich jetzt nicht verzählt habe.
Falls du die ISR nicht an die Vektor-Position legen willst oder kannst,
sind es auch nur 21 Takte.
Wolfgang schrieb:> Warum verheimlichst du die von dir verwendete Programmiersprache?
Ich vermute, dass es Wischuell Bräsig ist. Ich hoffe, dir damit geholfen
zu haben.
mfg.
Markus Weber schrieb:> MWS schrieb:>> Markus Weber schrieb:> Mal überschlagsmäßig gerechnet:> - Einsprung in die ISR: ca. 5 Takte (ohne RJMP, wenn man die ISR direkt> an die ISR-Vektor-Position legt),> ...> sind es auch nur 21 Takte.
Damit vergleichst Du Äpfel mit Birnen, da diese Aktionen die weitere
Programmgestaltung einschränkten, welche so nicht entstehen, wenn der
Zähler im SRam gehalten und die IVT nicht mit Programmcode zugeschrieben
wird. Bei einem kleinen AVR ist das jedoch machbar und auch
überschaubar.
Das lenkt aber davon ab, dass es den Fehler, so wie ihn der TE
festgestellt zu haben glaubt, nicht gibt.
Denn ob nun 150 oder 20 Takte verbraten werden, ist völlig egal, wenn
2048 Takte als Pause zur Verfügung stehen.
Genauso irrelevant ist's, ob's mit Assembler schneller geht, der TE wird
einen Grund haben, warum er C nutzt.
Der Antwort, die also von Dir urprünglich kam, entspricht der auf die
Frage eines Autobesitzers, "was an seinem Wagen defekt ist", die Du mit:
"Nimm 'nen Roller, damit bist Du schneller" beantwortest.
Du erkennst die Unsinnigkeit dessen?
MWS schrieb:> Genauso irrelevant ist's, ob's mit Assembler schneller geht, der TE wird> einen Grund haben, warum er C nutzt.
Du hast Recht. Wenn es so rüberkam, als wollte ich damit sagen, dass
Assembler für alle Zwecke besser ist als C, bitte ich um Entschuldigung,
das wäre natürlich Blödsinn.
Ich bin nur erschrocken, was die große Anzahl an Takten dieser ISR
betrifft. Die Optimierungen, die ich für eine Alternative mit Assembler
angedeutet habe, lassen sich zu größten Teil auch in C durch
entsprechende Compiler-Direktiven realisieren. Möglicherweise wäre das
hier angebracht...
> Der Antwort, die also von Dir urprünglich kam, entspricht der auf die> Frage eines Autobesitzers, "was an seinem Wagen defekt ist", die Du mit:> "Nimm 'nen Roller, damit bist Du schneller" beantwortest.> Du erkennst die Unsinnigkeit dessen?
Dein Vergleich ist gut. :-) Ich greif ihn gleich mal auf und schlage
vor, statt dem ATtiny13 einen ATtny85 zu verwenden. Der ist weitgehend
pinkompatibel und hat einen Timer/Zähler mehr.
Markus Weber schrieb:> Dein Vergleich ist gut. :-) Ich greif ihn gleich mal auf und schlage> vor, statt dem ATtiny13 einen ATtny85 zu verwenden. Der ist weitgehend> pinkompatibel und hat einen Timer/Zähler mehr.
Den hatte ich oben schon im Angebot ;-)
Eine weitere Steigerungsmöglichkeit wäre dann ATtiny44/84 oder
ATtiny4313 oder ATmega48 oder, oder ...
MWS schrieb:> Markus Weber schrieb:>> Ist ja Horror. In Assembler kriegt man das locker mit ungefähr 20 Takten>> hin...>> Zu spät aufgestanden? Träum' weiter. LOL
DAs solltest du dir selber erzählen...
> Ein LDS/STS kostet 4 Takte pro Byte, mal 8 sind bereits 32 Takte
Ja, das stimmt: Nur in Assembler brauche ich "frequently" genutzte
Variablen nicht im Speicher zu halten, sondern kann Register dafür
reservieren, natürlich unter Inkaufnahme der Tatsache, daß der Rest des
Codes dadurch möglicherweise ineffizienter wird, weil ich sie dann
dort nicht mehr (universell) verwenden kann.
Ist eine reine Frage des Ergebnisses der Laufzeitanalyse, ob es Vorteile
bringt und ich habe in Assembler die freie Wahl, meinen Code
entsprechend dem Analyseergebnis zu organisieren.
Wenn ich also mit einer Aufruffrequenz von, sagen wir mal, 100kHz
rechnen muß, dann lohnt es nahezu garantiert, dem Zähler acht Register
zur exclusiven Nutzung zu spendieren, denn das spart
100.000*32=3,2Millionen Takte in jeder Sekunde oder anders ausgedrückt:
das Äquivalent eines um 3,2MHz höheren Systemtaktes.
Naja, solange es (ohne signifikante Nachteile) möglich ist, den
Systemtakt um eben diese 3,2MHz zu erhöhen, muß ich diese Optimierung
nicht machen. Aber was, wenn doch...
In Asm habe ich die Wahl, in Dummlall-C mußt du tendenziell in Richtung
GHz-Monster gehen, selbst um so eine triviale Aufgabe zu lösen. Mit
allen Nachteilen bezüglich Stromverbrauch usw.
Das ist der Hauptvorteil von Assembler: optimal dem Problem angepaßte
Nutzung der Resourcen der Hardware.
Holger schrieb:> Und zwar> hatte ich die Variable incr vorher als 64-Bit Variable definiert, weil> ich bis zu sehr großen Werten zählen wollte. Damit ging es> komischerweise nicht
Welche Compilerversion?
Ältere benötigen für die 64Bit Lib 264Byte SRAM und ~5KB Flash.
Das ist zuviel für den ATtiny13.
Holger schrieb:> Was macht es eigentlich für> einen Sinn in einer ISR das sei() zu setzen.
In der Regel keinen, aber es macht oft Probleme.
Nur in sehr seltenen Fällen kann es einen Interrupt minimal
beschleunigen.
c-hater schrieb:> Ist eine reine Frage des Ergebnisses der Laufzeitanalyse, ob es Vorteile> bringt und ich habe in Assembler die freie Wahl, meinen Code> entsprechend dem Analyseergebnis zu organisieren.
Ich machs kurz:
https://gcc.gnu.org/onlinedocs/gcc/Global-Reg-Vars.html#Global-Reg-Vars
Keine Programmiersprache ersetzt die nötigen Kenntnisse dieser.
(sonst gehts wie im Thread "irgendwas mit C++" -_- )
Bastler schrieb:> Wenn man mit einer Interruptrate von 100khz rechnen muß, dann sollte man> sein Konzept überdenken.
Ach nee.
Allein ein Inkrementalgeber kann mit einigen kHz auskommen, in Spitzen
aber auch einige 100 kHz benötigen. Damit man keine Schritte verliert,
muß die Routine entsprechend schnell sein.
Mit passendem Konzept geht das auch mit einem AVR.
Bastler schrieb:> Wenn man mit einer Interruptrate von 100khz rechnen muß
Über die Anforderungen schweigt er sich ja komplett aus.
Mit dem ATtiny85 könnte man problemlos bis 10MHz messen.
c-hater schrieb:> DAs solltest du dir selber erzählen...>>> Ein LDS/STS kostet 4 Takte pro Byte, mal 8 sind bereits 32 Takte> ...> Das ist der Hauptvorteil von Assembler: optimal dem Problem angepaßte> Nutzung der Resourcen der Hardware.
Du hast jetzt zwar viel geschrieben und es war wie üblich Dein
Lieblingsthema, also auf den Compiler Deiner Wahl einzuhacken, aber
tatsächlich war's komplett am Thema vorbei, welches da war, warum eine
ISR die andere blockieren kann. Es ist in diesem Zusammenhang dann
unwichtig, ob eine ISR in 20 Takten gelöst werden kann, solange man
deutlich unter 2048 bleibt, was der Fall war.
Und ob Du dann denkst, da wieder missionarisch tätig sein zu müssen, ist
auch unwichtig. Denkst Du denn, Du hättest einen Sendungsauftrag, bzw.
der Gott des ASM hat gerade Dich auserkoren, seine Lehren unter's
gemeine Volk zu bringen, egal, ob's gerade passt oder nicht?
Und wenn wir schon OT sind: Es gibt genügend Aufgaben, wo Du mit ASM
scheitern wirst, nämlich dann wenn's um Strukturen und Werkzeuge geht,
welche Dir ein Compiler bereitstellt. Dafür hat der Compiler dann eben
bestimmte Trade-Offs, welche man mit ASM umgehenden kann, das dann eben
die direktere Hardwarekontrolle erlaubt.
Was ich allerdings finde, ist dass Deine ständigen Missionierversuche
niemandem (außer vielleicht Dir eine gewisse Befriedigung) etwas
bringen. Schau' doch mal über Deinen Topfdeckel hinaus, damit Du
erkennst, dass alles nur Werkzeuge sind. Diese Werkzeuge haben je nach
zu bearbeitendem (Soft)Werkstück ihre Vorzüge und Nachteile. Indem Du
Dich vor dieser Erkenntnis verschließt, tust Du Dir selbst keinen
Gefallen und bleibst unter Deinen eigenen Möglichkeiten zurück.
>> Bastler schrieb:>> Wenn man mit einer Interruptrate von 100khz rechnen muß>>Über die Anforderungen schweigt er sich ja komplett aus.>>Mit dem ATtiny85 könnte man problemlos bis 10MHz messen.
Ich bezog mich auf den Hater, der Interrupts mit 100khz erwartet. Die
10MHz willst Du sicher nicht in einem Interrupt hochzähler, oder? Und
mit mit Konzept überdenken meinte ich z.b. in einem solchen Fall die
Hardware machen zu lassen, was der Hardware ist.
Bastler schrieb:> Die> 10MHz willst Du sicher nicht in einem Interrupt hochzähler
Nein, dazu hat der ATtiny25 ja den Timer0 Eingang.
Damit ergeben sich minimal 512 Zyklen zwischen 2 Überlaufinterrupts. Das
ist selbst in C reichlich Zeit für die Behandlung.
Und wenns unbedingt der ATtiny13 sein soll, dann kann man ja den ADC als
2. Timer für die Zeitbasis nehmen.
m.n. schrieb:> FelixW schrieb:>> Ich machs kurz:>> Und welche Register sind bei AVR verfügbar?
Google, dein Helfer (mit dem 'Freund' bin ich vorsichtig):
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_regbind
Auszug:
R2-R7 sind gute Kandidaten.
Im Zweifel müssen alle aufgerufenen (auch avr-libc!) Routinen neu
übersetzt werden!
FelixW schrieb:> Auszug:> R2-R7 sind gute Kandidaten.
Danke für Deine Suche, ich hatte erst heute Deine Antwort gesehen.
Wer ihn hat, ist mit einem IAR-Compiler wohl glücklicher. Dort können
R4-R15 reserviert werden, wobei die Bibliotheken dies schon
berücksichtigen.
Bastler schrieb:>> ... die Hardware machen zu lassen, was der Hardware ist.> Das meinte ich mit dem besseren Konzept ;-)
Bei ca. 1 Hz Eingangsfrequenz und 1 s Torzeit ergeben sich die
einstelligen Ergebnisse "0", "1" oder "2". Ob das dann das bessere
Konzept ist?
Dann doch eher einen internen 8-Bit Timer als Vorteiler arbeiten lassen
und für die reziproke Messung bei Bedarf dazuschalten.
Mit festen Torzeiten zu messen ist entbehrlich.
m.n. schrieb:> Bei ca. 1 Hz Eingangsfrequenz und 1 s Torzeit ergeben sich die> einstelligen Ergebnisse "0", "1" oder "2". Ob das dann das bessere> Konzept ist?
Du weißt aber schon, daß man einen MC auch rechnen lassen darf:
f_in = f_cpu * count_in / count_cpu
Damit man für die gewünschte Auflösung auch genügend hohe Counts
erreicht, läßt man die CPU z.B. 500ms zählen und dann bis zur nächsten
Flanke des Eingangssignals.
FelixW schrieb:> Ich machs kurz:> https://gcc.gnu.org/onlinedocs/gcc/Global-Reg-Vars.html#Global-Reg-Vars
Hmm... DAS kannte ich noch nicht. Ich wußte, das IAR sowas kann, aber
ich wußte nicht, daß das auch der gcc kann.
> Keine Programmiersprache ersetzt die nötigen Kenntnisse dieser.
Nunja... Das kann man ja wohl nicht unter "C-Kenntnisse" verbuchen. Das
sind hochspezialisierte Kenntnisse von konkreten
C-Compiler-Implementierungen. Das, was entweder garnicht oder erst auf
Seite 1142 der Doku unter "Sonstiges" dokumentiert ist.
Dazu kommt:
1) gegenüber pure Assembler in den Möglichkeiten massiv eingeschränkt
2) nutzt man die gebotene Möglichkeit, landet man trotzdem oft bei der
Notwendigkeit, die ISR in Asm schreiben zu müssen
3) die hochgelobte "Portabilität" ist damit mindestens genauso am Arsch,
als wenn man den Kram gleich in Asm schreibt
Angesichts dessen: Warum sollte ich mir da C antun, wenn ich nicht muß?