ATmega: Wake-Up Zeit nach Power-Down

#7771853
Lesenswert?

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

Angehängte Dateien:
#7772127
Lesenswert?

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:  29 9a         sbi  0x05, 1  ; 5
4
    return;
5
 104:  08 95         ret

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:

1
00000000 <__vectors>:
2
__vectors():
3
   0:  0c 94 61 00   jmp  0xc2  ; 0xc2 <__ctors_end>
4
   4:  0c 94 a6 01   jmp  0x34c  ; 0x34c <__vector_1>
5
   8:  0c 94 cd 01   jmp  0x39a  ; 0x39a <__vector_2>
6
   c:  0c 94 7e 00   jmp  0xfc  ; 0xfc <__bad_interrupt>

Und hier der Code, zu dem gesprungen wird:

1
0000034c <__vector_1>:
2
__vector_1():
3
#elif defined(__AVR_ATmega8__)  || defined(__AVR_ATmega48__)  || defined(__AVR_ATmega48P__)  \
4
|| defined(__AVR_ATmega48PB__)  || defined(__AVR_ATmega88__)  || defined(__AVR_ATmega88P__)  \
5
|| defined(__AVR_ATmega88PB__)  || defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) \
6
|| defined(__AVR_ATmega168PB__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) \
7
|| defined(__AVR_ATmega328PB__)
8
  IMPLEMENT_ISR(INT0_vect, EXTERNAL_INT_0)
9
 34c:  1f 92         push  r1
10
 34e:  0f 92         push  r0
11
 350:  0f b6         in  r0, 0x3f  ; 63
12
 352:  0f 92         push  r0
13
 354:  11 24         eor  r1, r1
14
 356:  2f 93         push  r18
15
 358:  3f 93         push  r19
16
 35a:  4f 93         push  r20
17
 35c:  5f 93         push  r21
18
 35e:  6f 93         push  r22
19
 360:  7f 93         push  r23
20
 362:  8f 93         push  r24
21
 364:  9f 93         push  r25
22
 366:  af 93         push  r26
23
 368:  bf 93         push  r27
24
 36a:  ef 93         push  r30
25
 36c:  ff 93         push  r31
26
 36e:  e0 91 01 01   lds  r30, 0x0101  ; 0x800101 <intFunc>
27
 372:  f0 91 02 01   lds  r31, 0x0102  ; 0x800102 <intFunc+0x1>
28
 376:  09 95         icall
29
 378:  ff 91         pop  r31
30
 37a:  ef 91         pop  r30
31
 37c:  bf 91         pop  r27
32
 37e:  af 91         pop  r26
33
 380:  9f 91         pop  r25
34
 382:  8f 91         pop  r24
35
 384:  7f 91         pop  r23
36
 386:  6f 91         pop  r22
37
 388:  5f 91         pop  r21
38
 38a:  4f 91         pop  r20
39
 38c:  3f 91         pop  r19
40
 38e:  2f 91         pop  r18
41
 390:  0f 90         pop  r0
42
 392:  0f be         out  0x3f, r0  ; 63
43
 394:  0f 90         pop  r0
44
 396:  1f 90         pop  r1
45
 398:  18 95         reti

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...

#7772134
Lesenswert?

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:

1
// ...
2
   4:  0c 94 40 00   jmp  0x80  ; 0x80 <__vector_1>
3
// ...
4
ISR(INT0_vect)
5
{
6
  80:  1f 92         push  r1
7
  82:  0f 92         push  r0
8
  84:  0f b6         in  r0, 0x3f  ; 63
9
  86:  0f 92         push  r0
10
  88:  11 24         eor  r1, r1
11
  PORTB |= 1<<1;
12
  8a:  29 9a         sbi  0x05, 1  ; 5
13
}
14
  8c:  0f 90         pop  r0
15
  8e:  0f be         out  0x3f, r0  ; 63
16
  90:  0f 90         pop  r0
17
  92:  1f 90         pop  r1
18
  94:  18 95         reti
#7772156
Lesenswert?

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.

(Firma: Jasinski) #7772217
Lesenswert?

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.

(Firma: Jasinski) #7774539
Lesenswert?

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.

Angehängte Dateien:
(Firma: Jasinski) #7774715
Lesenswert?

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.

(Firma: Jasinski) #7774820
Lesenswert?

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

(Firma: Jasinski) #7774904
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren