Forum: Mikrocontroller und Digitale Elektronik ATmega: Wake-Up Zeit nach Power-Down


von Mark U. (residuum)


Angehängte Dateien:

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

von S. L. (sldt)


Lesenswert?

60 us? Erstaunlich, ganz erstaunlich - in Assembler programmiert sehe 
ich bei einem ATmega328P 19 us.

von Björn W. (bwieck)


Angehängte Dateien:

Lesenswert?

Eventuell hat es was mit dem BOD zu tun?
Siehe Anhang

von Mark U. (residuum)


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

von S. L. (sldt)


Angehängte Dateien:

Lesenswert?

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

von Georg G. (df2au)


Lesenswert?

Such mal nach "Arduino interrupt naked". Damit geht es schneller.

von Peter D. (peda)


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

von Mark U. (residuum)


Lesenswert?

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

von Georg M. (g_m)


Lesenswert?

Und was ist mit den 65ms?

von S. L. (sldt)


Lesenswert?

> Und was ist mit den 65ms?

'Additional Delay from Reset'

von Mark U. (residuum)


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.

von Gregor J. (Firma: Jasinski) (gregor_jasinski)


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.

: Bearbeitet durch User
von Gregor J. (Firma: Jasinski) (gregor_jasinski)


Angehängte Dateien:

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.

: Bearbeitet durch User
von Rolf (rolf22)


Lesenswert?

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.

von Gregor J. (Firma: Jasinski) (gregor_jasinski)


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.

: Bearbeitet durch User
von Gregor J. (Firma: Jasinski) (gregor_jasinski)



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

: Bearbeitet durch User
von Gregor J. (Firma: Jasinski) (gregor_jasinski)



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.

: Bearbeitet durch User
von Thomas R. (analogfreak)


Lesenswert?

> 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

von Gregor J. (Firma: Jasinski) (gregor_jasinski)


Lesenswert?

Thomas R. schrieb:
> URL der Homepage bitte

Wurde soeben per PM übermittelt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.