Hallo,
ein ATmega168A soll aus dem Power-Down mit einem externen Interrupt
geweckt werden.
Die Fuses sind wie folgt eingestellt:
int. RC Osc. 8MHz
Start-Up Time PWRDWN/RESET: 6CK/14CK + 65ms
Per Software wird die Taktfrequenz dann auf 1 MHz reduziert.
Der Controller wird in Power-Down geschickt und dann von einem Sensor
per Flankeninterrupt geweckt.
Das funktioniert auch alles.
In der Interrupt-Routine wir als erste Instruktion ein Output-Pin
gesetzt, so dass ich damit die Zeit vom Sensor-Signal bis in die
Interrupt-Routine messen kann. Und da messe ich dann gut 60us, das wären
bei einem 1MHz-Takt 60 Clock-Zyklen.
Laut Datenblatt DS40002061B für den ATmega168A (Abschnitt 9.6 Calibrated
Internal RC Oscillator) beträgt die Start-Up Zeit aus dem Power-Down
6CK. Die in bei den Fuses angegebenen 65ms kommen hier scheinbar nicht
zum Tragen, der Messwert liegt jedenfalls weit davon entfernt.
Der Pin wird direkt gesetzt und nicht etwa über digitalWrite():
1
ISR(INT0_vect)
2
{
3
PORTB|=0x02;
4
return;
5
}
Jetzt würde mich interessieren, wie sich die Start-Up Zeit nach dem
Power-Down zusammensetzt bzw. wie sich die ca. 60us ergeben.
Danke und Grüße
Markus
Danke für die Antworten.
Der Abschnitt über BOD Disable würde das gut erklären. Allerdings habe
ich BOD per Fuse abgeschaltet. Wenn ich den Abschnitt richtig verstehe,
spielt das dann beim Aufachen keine Rolle.
Ich habe in der Interrupt-Routine auch einmal digitalWrite() verwendet,
damit dauert es dann etwas über 130us. Aber das ist ja bekannt, dass
dadurch viel Overhead erzeugt wird.
Also habe ich mir einmal das List-File angesehen, in dem das
Port-Register direkt geschrieben wird.
Da steht die Interrupt-Routine (die File-Angaben habe ich weg gelassen):
1
00000100<isrExtInt()>:
2
PORTB|=0x02;
3
100:299asbi0x05,1;5
4
return;
5
104:0895ret
Und dann gibt es aber noch Code, auf den direkt aus der Tabelle mit den
Interrupt-Vektoren verwiesen wird.
Hier die Vektoren, für INT0 ist der zweite Eintrag vector_1
relevant:
Leider verstehe ich kein Assembler. Aber immerhin sehe ich, dass aus dem
Interrupt-Vektor heraus viele Instruktionen ausgeführt werden, bis die
Routine zurück kehrt. Irgendwo darin wird wohl auch meine selbst
geschriebene Interrupt-Routine aufgerufen und das Bit gesetzt. Es müsste
also irgendwo ein jump nach Adresse 100 oder ähnlich stehen, oder?
Bei Adresse 36e wird ein Register mit 0x800101 geladen, ist das die
Vorbereitung zumSprung zu meiner Interrupt-Routine? Wenn ich von
__vector_1 bis zu icall die Clock-Zyklen laut Instruction Set Summery
abzähle, komme ich auf 37. Plus die 6 zum Aufwachen + 3 für den Jump aus
der Interrupt-Tabelle, plus 2 um den Pin zu setzen. Das ergibt 48
Clock-Zyklen, das kommt dem Messergebnis schon recht nahe.
Es sieht also ganz danach aus, als ob die Arduino-Umgebung hier viel
Code einfügt, der zu dem Delay führt...
> Es sieht also ganz danach aus, als ob die Arduino-Umgebung hier> viel Code einfügt, der zu dem Delay führt...
So ist es. Zum Vergleich siehe den Anhang.
Mark U. schrieb:> Es sieht also ganz danach aus, als ob die Arduino-Umgebung hier viel> Code einfügt
Das muß man immer dazu sagen.
Arduino erlaubt, Interrupts erst zur Laufzeit zu definieren.
Dazu muß eine Sprungtabelle im SRAM angelegt werden. Auch fehlt die
Information, welche Register diese benötigen wird, d.h. alle
zerstörbaren Register werden gesichert.
In Plain AVR-GCC wird dagegen nur der übliche Prolog/Epilog eingefügt:
Also, das ist dann wohl die Erklärung. Danke für alle Hinweise!
Wieder einmal macht mir die Arduino-Umgebung mehr Probleme als dass sie
hilft. Ich überlege, ob ich nicht doch zur reinen C-Programmierung in
Atmel Studio zurück kehre...
Ergänzung:
Jetzt habe ich den Sketch so umgeschrieben, dass der Interrupt über
Register-Zugriffe konfiguriert wird und dann in die vordefinierte
ISR(INT0_vect) springt.
Damit verkürzt sich die Zeit vom Sensor-Signal, welches das Interrupt
auslöst bis zur Änderung des Ausgangssignals an PB1 auf gut 30us. Immer
noch mehr als erwartet aber deutlich besser.
Und noch eine Anmerkung: Wenn andere Interrupts mit attachInterrupt()
konfiguriert werden, kompiliert der Code nicht mehr, weil Vektoren
doppelt definiert werden. Also entweder alles mit attachInterrupt() oder
alles mit Register-Zugriffen.
Mark U. schrieb:> Wieder einmal macht mir die Arduino-Umgebung mehr Probleme als dass sie> hilft. Ich überlege, ob ich nicht doch zur reinen C-Programmierung in> Atmel Studio zurück kehre...
Das wäre eine sehr kluge Entscheidung, denn der generierte Code aus dem
Arduino-Käse wird einen immer wieder verfolgen und hier und dort einen
Strich durch die Rechnung machen – ich kenne unter anderem auch
AVR-Assembler (habe den AVR-Einstieg mit Assembler vollführt) und weiß
demnach sehr gut, was da im Hintergrund immer vor sich geht – vor
einigen Jahren habe ich z.B. den Käse mit digitalWrite etc. aus der
Arduino-IDE analysiert und festgestellt, dass mit so einem Befehl der
ganze Bildschirm mit Assemblerbefehlen gefüllt wird (es sind oft zig
Assemblerbefehle), man das aber eigentlich über sbi/cbi mit einem
einzigen Befehl erledigen kann – der Compiler in Atmel Studio kann das
und compiliert das auch als einen einzigen Assemblerbefehl, wenn man es
in C richtig schreibt (die Einstellung der Optimierungsstufe nicht
vergessen). Arduino ist für den Anfang oder als Einstieg oder ein
einmaliges Projekt für den Schulunterricht sehr gut geeignet, wenn man
überhaupt keine Ahnung von der Materie hat, aber trotzdem etwas mit
einem µC machen und vorführen möchte. Wenn man es allerdings dauerhaft
vernünftig und solide machen möchte, sollte man sich nach der
Einstiegsphase so schnell wie möglich von der Arduino-IDE lösen und z.B.
auf Atmel Studio und C übergehen. Programmiergeräte werden dort auch
unterstützt und man hat auch direkt Zugang zu den Fuses, die dort nicht
nur als Zahlen dargestellt werden. Wie es auch mit dem 'PG164100 MPLAB
Snap' geht, habe ich bereits ausführlich beschrieben, ist aber auch auf
meiner Homepage nachlesbar – neuere µC wie z.B. AVR128DB28 kann man dann
auch über UPDI live debuggen, programmieren kann man aber damit fast
alle AVRs, so etwas wie der ATMEGA328P gehört auf jeden Fall dazu.
Wer aber an seiner Arduino-IDE unbedingt festhalten will, der darf das
gerne bis zum Ende seiner Tage tun – verboten ist es nicht. Man sollte
sich dann aber nicht wundern und auch nicht beschweren, wenn es
merkwürdige Probleme, insbesondere beim Timing, gibt.
Georg G. schrieb:> Such mal nach "Arduino interrupt naked". Damit geht es schneller.
Ja, damit geht es tatsächlich schneller, aber in den ABGRUND, wo alle
Programmierer, die schlechte Arbeit abgeliefert haben, hineinfallen.
Auch wenn so eine Beschneidung zum Testen noch durchaus sinnvoll sein
kann, so bedeutet sie im Normalbetrieb den sicheren Tod, wenn man (a)
Assembler nicht kann und (b) überhaupt nicht weiß, was da im Hintergrund
auf Prozessorebene vor sich geht.
___S. L. schrieb:> 60 us? Erstaunlich, ganz erstaunlich - in Assembler programmiert sehe> ich bei einem ATmega328P 19 us.
Die Angabe im Datenblatt mit 6 Clocks ist bestimmt eine Netto-Angabe und
Netto ist bekanntermaßen Teil vom Brutto. Vor den 6 Clocks muss der
RC-Oszillator anschwingen, was an sich relativ zügig geht, denn bei
einem Quarz bräuchte man einige Millisekunden dafür, bei
Low-Frequenz-Quarzen dauert es noch länger; und nach diesen 6 Clocks
steht der Programmzähler vermutlich erstmal im Interruptvektor, wo erst
der Sprungbefehl noch eingelesen und ausgeführt werden muss. Danach
beginnt die eigentliche Interruptbehandlung, wo zuerst aber noch der
Prolog ausgeführt werden muss – wenn man das selbst in Assembler
schreibt, kann man einiges wegoptimieren, ansonsten macht der Compiler
hier in der Regel eine Push-Orgie, erst danach wird der eigentliche
Befehl wie z.B. sbi/cbi ausgeführt, allerdings auch mit einer
Verzögerung, da alles synchron ausgeführt wird und man es am Port ein
bis zwei Clocks später sieht. Am Ende kommt natürlich das Pendant zum
Prolog – der Epilog, also die Pop-Orgie in umgekehrter Reihenfolge.
Bei meiner auf ATMEGA328P geschriebenen Fernbedienung dauert das
Aufwachen beispielsweise ca. 3,5 bis 5 Millisekunden, weil ich einen
4MHz-Quarz benutze (die Frequenz wird aber noch intern durch 4 geteilt,
es sind also de facto 1 MHz als Systemtakt), der Pierce-Oszillator darf
aber wegen der möglichen geringen Spannungsversorgung – weil ich auch
unter 2,7V gehe – nicht als Full-Swing, sondern nur im Low-Power-Modus
betrieben werden – entsprechend länger dauert es, bis so ein Quarz
anschwingt. Danach folgen dann vermutlich die '1K-CK', die ich in den
Fuses gewählt habe, also ein Tausend zusätzliche Clocks, nachdem der
Quarz zu schwingen begonnen hat. Wenn ich hier 16K-CK wähle, dauert der
Weckprozess 16ms länger, also so insgesamt ca. 18-20 Millisekunden. Die
Messungen habe ich mit einem Zweikanaloszilloskop durchgeführt –
getriggert wird beim Tastendruck, der den Aufweckvorgang auslöst und wo
der Messvorgang dann beginnt. Das Ende des Messvorgangs ist ein
Portausgang, der im Pin-Change-Interrupt auf High gesetz wird – da ich
im Millisekundenbereich und mit 1000 Clocks agiere, spielen ±100 Clocks
bei mir keine große Rolle.
Gregor J. schrieb:> Vor den 6 Clocks muss der RC-Oszillator anschwingen
Die 6 CK beinhalten IMHO die Anschwingzeit des internen RC-Oszillators.
Wäre es anders, dann müsste irgendwo noch die maximale Anschwingzeit
angegeben werden – wird sie aber nicht.
Rolf schrieb:> Die 6 CK beinhalten IMHO die Anschwingzeit des internen RC-Oszillators.> Wäre es anders, dann müsste irgendwo noch die maximale Anschwingzeit> angegeben werden – wird sie aber nicht.
Zu dem RC-Oszillator werde ich meine Tests (für mich) noch machen, aber
ich gehe jetzt erstmal von einer Analogie zu den Tests mit einem Quarz
aus. Die Weckzeit variiert hier immer zwischen 3,5 bis 5 ms und ist
jedesmal anders innerhalb dieser Zeit, was mit der unterschiedlichen
Anschwingszeit zusammenhängt (Zufallsprinzip) und die Gesamtzeit ist
dann die Summe dieser Zeiten, obwohl im Datenblatt nur von 1 Tausend
Clocks die Rede ist (1K CK), was bei 1 MHz Takt eigentlich eine
Millisekunde sein müsste. Diese Variation wandert auch mit und ist auf
dem Oszilloskop zu sehen, wenn man auf 16 Kiloclocks einstellt. Das
genaue Zählen der Clocks (egal, ob 6 oder 1 Tausend) geht auch nur, wenn
ein Oszillator (egal welcher jetzt) angeschwungen ist, also muss das
vorher stattgefunden haben und diese Zeit addiert sich dann unweigerlich
dazu. Anders ausgedrückt – wenn der Oszillator nicht schwingt, kann auch
nichts gezählt werden, denn das Zählen wird mit einem Binärzähler
durchgeführt, der getaktet werden muss.
Im Anhang ein paar Screenshots aus den Tests mit dem RC-Oszillator bei
VCC=4,8V mit 1 MHz und mit 8 MHz – mal mit der Push-Orgie, mal komplett
ohne. Getriggert wird beim Drücken des Tasters und der Endstrich für die
Zeitmessung kommt dann von dem sbi-Befehl. Das würde erstmal meine
Annahme von vorhin bekräftigen.
Edit: der BOD wird vor dem Power-Down abgeschaltet, da das kurz vorher
per Software so eingestellt wird und mir das der Stromverbrauch von
0,1µA, den ich messen kann, auch bestätigt, ich werde das aber noch
explizit mit dem abgeschalteten BOD über die Fuses testen
Hier noch die Screenshots mit expliziter BOD-Abschaltung über Fuses und
auf den weiteren 4 Bildern mit eingeschaltetem BOD mit 1 und 8 MHz. Das
Ergebnis mit 22 Clocks bei 1 MHz kommt dem Ergebnis hier im Thread mit
19 Clocks in Assembler geschrieben sehr nah, allerdings kennen wir die
VCC nicht, bei der derjenige das gemacht hat, von den 6 Clocks aus dem
Datenblatt sind wir aber – wie ich schon erläutert habe – weit entfernt,
selbst wenn wir den Sprung aus der Vektortabelle, den sbi-Befehl und die
daraus resultierende kleine Verzögerung aus dem Synchronverhalten davon
abziehen würden. Die mindestens 60µs als Sicherheit gibt es immer dann,
wenn BOD abgeschaltet wird, wie in dem zitierten Ausschnitt aus dem
Datenblatt erklärt wird.
> Wie es auch mit dem 'PG164100 MPLAB> Snap' geht, habe ich bereits ausführlich beschrieben, ist aber auch auf> meiner Homepage nachlesbar –
URL der Homepage bitte