Hallo Leute,
ich sitze jetzt schon seit gestern Abend an einer FastPWM, bei der ich
das Tastverhältnis bei einem Timer Compair Match umschalten will.
Anbei der Code:
Programmiert wird im Atmel Studio 7 mit einem AVR ISP mkII, das Flashen
geht ohne Probleme.
Am Oszi sehe ich auch die 0x0FFF, für OCR1B, die in PWMsetup gesetzt
werden. Tastverhältnis und Frequenz passen soweit nach der
initialisierung.
Es ist PWM Mode 15 eingestellt.
Mit
1
PORTB^=(1<<PB7)
sehe ich auch das Umschalten auf dem Oszi, wenn der µC in die ISR geht.
Was jetzt nicht funktioniert, ist
1.:
In der ISR wird immer nur der True If-Zweig abgearbeitet. Das Toggeln
von Banging funktioniert nicht.
Je nach dem, wie ich Banging bei der Definition initialisiere, springt
es bei 1 immer in den oberen Teil und bei 0 immer in den unteren Teil
der If-Abfrage.
2.:
Der Wert für OCR1B wird nicht neu gesetzt, wenn die ISR abgearbeitet
wird.
Hier habe ich auch schon versucht OCR1BH und OCR1BL getrennt von
einander zu beschreiben, bringt aber auch nichts.
Sieht jemand einen Fehler und kann mir hier weiterhelfen?
Alex
Alex Z. schrieb:> ISR(TIMER1_COMPB_vect){> if((Banging & 0x1)== 0x0){> OCR1B = 0x0F11; //^^^ war bereits 1 und wurde erneut auf 1 gesetzt - muß
umgekehrt sein
> Banging ^= (1<<0);> PORTB ^= (1<<PB7);> }else{> OCR1B = 0xF111;> Banging ^= (1<<0);> PORTB ^= (1<<PB7);> }> }
Alex Z. schrieb:> ich sitze jetzt schon seit gestern Abend an einer FastPWM, bei der ich> das Tastverhältnis bei einem Timer Compair Match umschalten will.
Das geht nicht. Die OCRnx-Werte werden in allen PWM-Modi durch die
Hardware gepuffert und erst beim Overflow übernommen. Deswegen ist es
grundsätzlich ziemlich tödlich, sie in den OCnx-ISRs zu setzen, dann
verzögert sich nämlich das Eintreten der Wirksamkeit mal um einen
Zyklus, mal um zwei, je nachdem ob die OCnx-ISR noch zum Zuge kommt,
bevor der Overflow auftritt oder erst danach, was wiederum von der
Systemlast durch konkurrierende ISRs und vom zuletzt wirksam gewordenen
Duty-Wert abhängt. Das ist SCHEISSE.
Merke also: Duty-Werte in PWM-Modi IMMER in der OVF-ISR setzen. Dann
ist (ausser bei Überlast des Systems) garantiert, dass sie im NÄCHSTEN
Zyklus wirksam werden.
Aber diese Sache scheint garnicht wirklich dein Problem zu sein, sondern
vielmehr dies:
> In der ISR wird immer nur der True If-Zweig abgearbeitet. Das Toggeln> von Banging funktioniert nicht.
Das kann nicht sein, jedenfalls nicht mit dem gezeigten Code. Es muss
also noch anderen Code geben, der an Banging rumhantiert und diesen Code
hast du uns nicht gezeigt. Das sagen die Gesetze der Logik. Sehr
wahrscheinlich gibt es also noch eine OVF-ISR, deren Fehlen S.Landolt ja
auch schon angemeckert hat.
Und sehr wahrscheinlich steht da auch wieder drin:
Banging ^= (1<<0);
Damit wird erklärlich, warum "immer" nur ein Zweig deiner OC-ISR
abgearbeitet wird. Tatsächlich kann du aber auch den anderen Zweig
abarbeiten lassen, eben wegen des am Anfang dargestellten Sachverhalts.
Du mußt nur Duty auf eine Wert sehr knapp vor dem Überlauf setzen, dann
kehrt sich nämlich zumindest kurzzeitig die Phasenlage dieses Bullshits
um, den du da programmiert hast...
Also meine Vermutung war eher, dass die Timer1-OVF-ISR tatsächlich gar
nicht existiert und das Programm immer von vorne beginnt; bin aber kein
C-Programmierer.
S. Landolt schrieb:> Also meine Vermutung war eher, dass die Timer1-OVF-ISR tatsächlich gar> nicht existiert und das Programm immer von vorne beginnt; bin aber kein> C-Programmierer.
^= in C entspricht eor in Asm (im Falle von Portpins ggf. auch einer
Ausgabe auf PIN), also: letztlich wird das Bit getoggelt.
Dementsprechend ist durch die Gesetze der Logik klar, dass das
beobachtete Verhalten nur dann eintreten kann, wenn an anderer Stelle
synchron (aber zeitversetzt) getoggelt wird. Dafür kommt ernsthaft nur
ein Interrupt desselben Timers in Frage. Sehr wahrscheinlich halt der
OVF. Es könnte aber theoretisch aber auch der andere OC-Kanal des
gleichen Timers sein...
Der Code funktioniert auch, siehe das Bild vom Scope.
Zu den einzelnen Kommentaren:
Im ersten Code gab es einfach keine Timer1_OVF ISR, nur das Enable Bit
wurde gesetzt.
Ich habe mir das Datasheet nochmal zu Rate gezogen. Bei PWM Mode 15
werden die OCRxn bei Bottom aktualisiert, so wie c-hater das geschrieben
hat. d.h. wenn die OVF ISR ausgeführt wird, kann ich schon den Wert für
den nächsten Zyklus schreiben. Bei einer Zielfrequenz von 800kHz sind
das ca. 20 Takte.
Ich hatte einfach einen Denkfehler. Das ganze beim Compair Match zu
setzen war nicht zielführend.
Das mit dem Banging funktioniert jetzt auch. Ka warum das vorher nicht
ging.
Alex
> Im ersten Code gab es einfach keine Timer1_OVF ISR,> nur das Enable Bit wurde gesetzt.
Was passiert dann beim Overflow? Also zumindest mein C-Compiler setzt an
eine solche Stelle 'jmp 0' (mit dem Kommentar "bad_interrupt"), folglich
beginnt das Programm immer wieder von vorne.
Alex Z. schrieb:> Ich habe mir das Datasheet nochmal zu Rate gezogen.
Vielleicht solltest du auch mal einen Duden oder ähnliches
zu Rate ziehen um deine Rechtschreibung zu compairen.
Hi,
also der Compiler hat zumindest keinen Fehler angezeigt angezeigt.
Warnungen oder Informationen bin ich mit grad nicht sicher. Ist das
Atmel Studio 7.
S. Landolt schrieb:>> Im ersten Code gab es einfach keine Timer1_OVF ISR,>> nur das Enable Bit wurde gesetzt.>> Was passiert dann beim Overflow? Also zumindest mein C-Compiler setzt an> eine solche Stelle 'jmp 0' (mit dem Kommentar "bad_interrupt"), folglich> beginnt das Programm immer wieder von vorne.
Eine Frage hat sich aber noch ergeben:
Der µC wird auf dem Arduino-Board mit einem 16MHz Quarz versorgt. In den
Fuse Bits ist der externe Quarz eingestellt. Laut Datenblatt berechnet
sich die Frequenz bei der Fast PWM mit f_pwm= f_clockIO/(N*(1+TOP)), das
kommt auch hin bei den 800kHz. Leider reichen die 20 Zählvorgänge nicht
um die neuen Tastverhältnise ins OCR1B zu schreiben. Gibt eine
Möglichkeit zu berechnen, wie lange eine einzelne Operation im C-Code
braucht?
Alex
P.S.: Rechtschreibfehler dürfen behalten werden.
> der Compiler hat zumindest keinen Fehler angezeigt
Das "bad_interrupt" wird auch nicht angezeigt, sondern steht im
erzeugten Assembler-listing, und zwar bei allen ISR-Adressen, die nicht
definiert wurden, in unserem Fall also auch bei 'OVF1addr = 0x0028 ;
Timer/Counter1 Overflow'.
> Gibt eine Möglichkeit zu berechnen ...
Da fällt mir jetzt wieder nur das Assembler-listing ein; aber wie schon
oben einmal erwähnt, bin ich eigentlich kein C-Programmierer.
Hallo,
S. Landolt schrieb:> PPS:> Was mir noch unklar ist: woher kommen die 800 kHz resp. 20 Takte? Ich> sehe nur etwas im ms-Bereich.
Der µC taktet mit 16MHz und die PWM Frequenz berechnet sich zu f_pwm=
f_clockIO/(N*(1+TOP)). Damit ich die 800kHz für die Ansteuerung der
WS2812 bekomme ist der Top Wert 19. Somit komme ich auf 20 Takte.
Alex
Alex Z. schrieb:> kommt auch hin bei den 800kHz. Leider reichen die 20 Zählvorgänge nicht> um die neuen Tastverhältnise ins OCR1B zu schreiben. Gibt eine> Möglichkeit zu berechnen, wie lange eine einzelne Operation im C-Code> braucht?
Nicht wirklich. Deswegen nimmt man für derart zeitkritische Sachen auch
Assembler. Da kann man das nämlich "berechnen".
20 Takte sind schon sehr knapp, denn tatsächlich stehen nur 19 davon für
den Interrupt zur Verfügung, wenn das Hauptprogramm auch noch was tun
soll (das würde dann aber effektiv mit einer "virtuellen Taktrate" von
nur noch 800kHz laufen und müsste obendrein ebenfalls bestimmte
Timing-Restriktionen erfüllen, die in C nur schwer bis garnicht
kontrollierbar sind.
Dazu kommt, dass von den 19 Takten für den Interrupt allein 8 für den
minimalen Interruptrahmen benötigt werden und in C auch noch zwingend 2
Takte für den Sprung vom Interruptvektor zur ISR. Es bleiben also nur
maximal 9 Takte für Nutzcode in der ISR. Das ist wahrlich nicht viel für
die an dieser Stelle nötige Funktionalität. Das kann nur funktionieren,
wenn man einige Register global für die ISR reserviert, was in C nur
sehr eingeschränkt möglich ist, in Assembler allerdings kinderleicht.
Fazit: was du machen willst, geht nur in Assembler wirklich. Also mach's
in Assembler.
Und WS2812 muss/soll unbedingt mit 800 kHz angesteuert werden? Nun gut,
ich kenne mich da überhaupt nicht aus.
20 Takte für eine solche Aktion - c-hater hat alles Nötige dazu
gesagt. Ich könnte allenfalls noch vorschlagen, den kleinen Bruder
ATmega1284 zu verwenden, der erlaubt laut Datenblatt 20 MHz, bei mir
werden zwei seit längerem mit 24 MHz übertaktet; ist aber in diesem Fall
weder der große Sprung nach vorn noch besonders schön.
> in C auch noch zwingend 2 Takte für den Sprung> vom Interruptvektor zur ISR
Ich sollte mir wohl doch einen neueren/anderen C-Compiler beschaffen,
meiner macht an dieser Stelle sogar ein 'jmp' mit 3 Takten.
S. Landolt schrieb:> 20 Takte für eine solche Aktion - c-hater hat alles Nötige dazu> gesagt.
Nö, leider habe ich das nicht.
> Ich könnte allenfalls noch vorschlagen, den kleinen Bruder> ATmega1284 zu verwenden
Das Wörtchen "kleiner" in deiner Formulierung hat mich nämlich stutzen
lassen.
Es ging um einen Mega2560, das war mir doch glatt durchgerutscht. Beim
dem sieht die Sachen nämlich sogar noch schlechter aus, als von mir
beschrieben, weil bei dem wegen des 22Bit-PC der minimale Interuptframe
nicht 8, sondern 10 Takte braucht. Also bleiben nur noch 7 Takte für den
Nutzcode der ISR, keine 9.
Mit 9 hätte ich es vielleicht noch realisieren können, mit 7 traue ich
es mir ganz sicher nicht mehr zu. Aus meiner Sicht bleibt für einen
Mega2560@16MHz bloß die Busy-Loop zur Lösung des Problems, also nix
Interrupt.
Die Ausgabe per PWM kann man dabei beibehalten, das entschärft die
Timing-Anforderungen für die Busy-Loop und senkt damit den Aufwand für
deren Entwicklung. Das könnte dann auch schon wieder in C funktionieren.
Aber Interruptsperre während der Ausgabe bleibt unumgänglich.
Mega1284P@20Mhz ist aber trotzdem keine schlechte Idee. Der hat nämlich
gebufferten SPI-Power in seinen UARTs. Damit kann man das anders/besser
lösen und es bleibt dann sogar genug Luft im Timing für einen (!)
konkurrierenden Interrupt und für den Code in main bleiben sogar über
40% der Rechenzeit verfügbar. Entsprechenden Code habe ich vor einiger
Zeit hier schonmal gepostet. Und zwar sowohl für die Ansteuerung der
WS28xx @800kHz als auch für eine gleichzeitige interruptbasierte
Ausgabe von DDS-Sound@78kHz.