AVR Studio 4, Assembler und Timer/Watchdog

#7429162
Lesenswert?

Hallo in die geschätzte Expertenrunde,

vielleicht kann mir jemand auf die Sprünge helfen. Derzeit simuliere ich einen ATTiny13A (default-Fuses) im AVR Studio 4.16.628 unter Linux mit Wine. Dabei ist mir eine Anomalie aufgefallen, für die ich keine Erklärung habe.

Anbei voneinander nur leicht abweichende Assembler-Codes (die vielen Leerzeilen werden hier scheinbar vom Forums-Editor eingefügt, weiß nicht wo die herkommen. Im Original sind die nicht vorhanden).

Der eine nutzt drei Interrupts: einmal beim Match von OC0B im Timer0, dann beim Überlauf des Timers und als dritten den Watchdog in der Interrupt-only-Variante. Das funktioniert erst mal wie erwartet. Der OC0B-Match-Interrupt wird ausgelöst, danach der erste Timer-Überlauf. Der passende Interrupt wird auch hier ausgelöst und abgearbeitet - dann bleibt der Timer0 einfach auf Null stehen und das Programm verbleibt in der Endlosschleife, der Watchdog springt niemals an.

Darunter eine leicht abgewandelte Programmversion, die auf dem Vergleich OC0B verzichtet, alles andere ist gleich. Hier ein ähnliches Verhalten: Der erste Interrupt beim Timerüberlauf wird ausgelöst und abgearbeitet - der Timer bleibt auch jetzt bei Null stehen und läuft nicht mehr. Allerdings funktioniert im Gegensatz zur ersten Variante jetzt der Watchdog, der wie erwartet etwa alle 32 simulierten Millisekunden auslöst.

Hat zufällig jemand eine Erklärung?

Variante A: Drei Interrupts

1
.nolist
2
.include "tn13adef.inc" ; Define device ATtiny13A
3
.list
4
.def sreg_bak = R15 
5
.def rmp = R16 
6
.dseg
7
.org SRAM_START
8
.cseg
9
.org 0x0000
10
rjmp Main ; Reset vector
11
reti ; INT0
12
reti ; PCI0
13
rcall isr_timer_overflow ; OVF0
14
reti ; ERDY
15
reti ; ACI
16
reti; OC0A
17
rcall isr_oc0b; OC0B
18
rcall isr_watchdog ; WDT
19
reti ; ADCC
20

21
init_watchdog:
22
 wdr
23
 in rmp, MCUSR
24
 andi rmp, (0xff - (1<<WDRF))
25
 out MCUSR, rmp
26
 in rmp, WDTCR
27
 ori rmp, (1<<WDTIE)|(1<<WDP0) ; Interrupt-only und prescaler 32ms
28
 out WDTCR, rmp
29
 ret
30

31
init_timer0:
32
 push rmp
33
 ldi rmp, 220
34
 out OCR0B, rmp
35
 ldi rmp,(1<<WGM01)
36
 out TCCR0A, rmp
37
 ldi rmp, (1<<CS01)
38
 out TCCR0B, rmp
39
 ldi rmp, (1<<TOIE0)|(1<<OCIE0B)
40
 out TIMSK0, rmp
41
 pop rmp
42
 ret
43

44

45
Main:
46
 ldi rmp,Low(RAMEND)
47
 out SPL,rmp 
48
 rcall init_watchdog
49
 rcall init_timer0
50
 sbi DDRB,1
51
 sei 
52

53
Loop:
54
 nop
55
 rjmp Loop
56

57

58
isr_timer_overflow:
59
 in sreg_bak, SREG
60
 cbi PORTB,1
61
 out SREG, sreg_bak
62
 reti
63

64
isr_oc0b:
65
 reti

Variante B: Zwei Interrupts

1
.nolist
2
.include "tn13adef.inc"
3
.list
4
.def sreg_bak = R15
5
.def rmp = R16
6
.dseg
7
.org SRAM_START
8
.cseg
9
.org 0x0000
10

11
rjmp Main 
12
reti ; INT0
13
reti ; PCI0
14
rcall isr_timer_overflow ; OVF0
15
reti ; ERDY
16
reti ; ACI
17
reti; isr_oc0a; OC0A
18
reti; rcall isr_oc0b; OC0B
19
rcall isr_watchdog ; WDT
20
reti ; ADCC
21

22
init_watchdog:
23
 wdr
24
 in rmp, MCUSR
25
 andi rmp, (0xff - (1<<WDRF))
26
 out MCUSR, rmp
27
 in rmp, WDTCR
28
 ori rmp, (1<<WDTIE)|(1<<WDP0) ; prescaler 32ms
29
 out WDTCR, rmp
30
 ret
31

32
init_timer0:
33
 push rmp
34
 ldi rmp, 220
35
 out OCR0B, rmp
36
 ldi rmp,(1<<WGM01)
37
 out TCCR0A, rmp
38
 ldi rmp, (1<<CS01)
39
 out TCCR0B, rmp
40
 ldi rmp, (1<<TOIE0)
41
 out TIMSK0, rmp
42
 pop rmp
43
 ret
44

45

46
Main:
47
 ldi rmp,Low(RAMEND)
48
 out SPL,rmp
49
 rcall init_watchdog
50
 rcall init_timer0
51
 sbi DDRB,1
52
 sei 
53

54
Loop:
55
 nop
56
 rjmp Loop
57

58

59
isr_timer_overflow:
60
 in sreg_bak, SREG
61
 cbi PORTB,1
62
 out SREG, sreg_bak
63
 reti
64

65
isr_watchdog:
66
 in sreg_bak, SREG
67
 sbi PORTB,1
68
 out SREG, sreg_bak
69
 reti
#7429173
Lesenswert?

S. L. schrieb:

rcall isr_watchdog ; WDT reti ; ADCC mutet seltsam an, soll das nicht 'rjmp' heißen?

Habe ich mal getestet, scheint beides zu funktionieren. Aber nachdem auch im Tutorial überall rjmp verwendet wird, werde ich das künftig auch tun. Danke für den Hinweis. Ansonsten hat sich leider nichts geändert - der Timer0 bleibt nach dem ersten Überlauf einfach stehen.

#7429183
Lesenswert?

Wieder auf die Schnelle:

scheint beides zu funktionieren

Kann ich mir nicht vorstellen - in der 1. Version zum Beispiel wird ja grundsätzlich nach isr_oc0b auch noch isr_watchdog abgearbeitet.

Ansonsten: Simulator & Interrupts - ob das immer funktioniert? Da gab es mal Beiträge ... Und warum nicht der ATtiny85 - den könnte ich hier gegebenenfalls realiter laufen lassen.

#7429184
Lesenswert?

Stephan E. schrieb:

Habe ich mal getestet, scheint beides zu funktionieren.

Nicht wirklich. Bei jedem Interrupt werden zwei Byte auf dem Stack "verbraucht". Ist nur eine Frage der Zahl der Interrupts, bis der Stack überlauft (bzw. eigentlich: unterläuft). Das sinnlose (und am Ende tödliche) Wachsen des Stack sollte sich im Simulator wirklich sehr gut verfolgen lassen.

Ansonsten hat sich leider nichts geändert - der Timer0 bleibt nach dem ersten Überlauf einfach stehen.

Das kann nicht sein. Dem Timer ist der Stack egal, dem Timer ist überhaupt egal, was die MCU tut. Einmal gestartet, läuft der einfach. Es sei denn, die MCU hält ihn an. Dafür gibt es aber keinen Code.

Und übrigens: in deiner Variante A gibt es darüber hinaus auch keinen Code für isr_watchdog. Das dürfte also nichtmal assemblierbar sein...

#7429195
Lesenswert?

S. L. schrieb:

Ansonsten: Simulator & Interrupts - ob das immer funktioniert?

Jedenfalls bezüglich der MCU habe ich beim SimulatorV2 noch nie Gegenteiliges feststellen können.

Bugs gibt es aber reichlich in der Simulation der Peripherie und auch Peripherie, die praktisch garnicht simuliert wird. Bei letzterem kann man natürlich ewig auf das Auftreten der entsprechende Interrupts warten. Es werden keine erfolgen...

Wie auch immer, das Entscheidende scheint mir zu sein, dass angeblich der Timer stoppt. Das tut er natürlich nicht. Weder in der Simulation noch in der Realität. Ich tippe mal auf einen Bedienfehler. Möglicherweise ausgelöst durch einen Anzeigefehler, der durch Wine verursacht wird. Ich kann mich erinnern, dass ich auch mal versucht habe, das Studio4 unter Wine zu betreiben, das dann aber wegen extremer Anzeigefehler schon im Editor schnell wieder aufgegeben habe. Bis zum Einsatz des Simulators bin ich also unter Wine niemals gekommen.

#7429218
Lesenswert?

S. L. schrieb:

Nein, das meinte ich nicht, sondern: es gibt hier keinen Stack-Unterlauf

  • wo sollte der auch auftreten?

Es passiert folgendes:

IRQ:

  • SREG_I wird gelöscht
  • die Rücksprungadresse wird auf den Stack gelegt
  • der Vektor wird angesprungen

Vektor:

  • rcall legt legt erneut eine Rücksprungadresse auf den Stack
  • springt das angegebene Ziel an

Ziel (ISR): -wird abgearbeitet -endet mit reti (setzt SREG_I, holt 1x Rücksprungadresse vom Stack) -das ist die vom (falschen) rcall gesetzte Rücksprungadresse -führt effektiv dazu, das der nächste Vektor in der Reihenfolge der Vektortabelle durchlaufen wird.

Und jetzt wird es spannend. Beim Overflow steht auf dem nächsten Vektor ein reti. Das holt die zweite Rücksprungadresse vom Stack (setzt das in diesem Fall bereits gesetzte SREG_I) und springt zur Rücksprungadresse. Alles gut hier, die Bilanz stimmt. Schwachsinn ist es trotzdem.

Aber, nun spiele mal selber durch, was bei OC0B passiert...

#7429220
Lesenswert?

Aber, nun spiele mal selber durch, was bei OC0B passiert...

Das, was ich bereits geschrieben hatte:

in der 1. Version zum Beispiel wird ja grundsätzlich nach isr_oc0b auch noch isr_watchdog abgearbeitet.

Und anschließend wird bei 'reti ; ADCC' ins Hauptprogramm zurückgekehrt - also ich sehe nach wie vor keinen Stack-Unterlauf.

#7429259
Lesenswert?

S. L. schrieb:

Aber, nun spiele mal selber durch, was bei OC0B passiert...

Das, was ich bereits geschrieben hatte:

in der 1. Version zum Beispiel wird ja grundsätzlich nach isr_oc0b auch noch isr_watchdog abgearbeitet.

Wie kann das sein? isr_watchdog existiert in der "ersten Version" (vom TO als Version A bezeichnet) doch garnicht.

Was also steht auf diesem Vektor wirklich?

#7429282
Lesenswert?

an Stephan E.:

Zwei Anmerkungen (falls da Unkenntnis bestehen sollte):

  • 'reti' wird zwar mit "Return from interrupt" beschrieben, ist aber lediglich ein 'ret', welches zusätzlich das 'Global Interrupt Flag' in SREG setzt.
  • größere Programmblöcke können so auskommentiert werden:
1
/*
2
dies wird vorlaeufig stillgelegt:
3
disable_overflow:
4
 push rmp
5
 in rmp, TIMSK0
6
 andi rmp, (0<<TOIE0)
7
 out TIMSK0, rmp
8
 pop rmp
9
 ret
10
*/
#7429365
Lesenswert?

Falk B. schrieb:

Mit V1 gehts auch nicht. Und das Interrupt-Flag wird verspätet gesetzt, zumindest in der Anzeige der IO Register, da läuft die ISR schon ;-) Aber auch bei Prescaler 1 bleibt der Timer stehen. Ein Bug im AVR-Studio.

Der V1-Simulator ist ziemlicher Rotz. Der bildet nichtmal die MCU vollständig korrekt ab. Der V2 hingegen tut wenigstens dies, hat aber auch immer noch ziemliche Mankos bezüglich der Peripherie. Für einen Tiny13 sollte es aber eigentlich reichen, der hat ja nun nicht sehr viel an Peripherie und im konkreten Fall ist nur Timer0 und Watchdog involviert.

Aber klar: Ein Bug im Simulator wäre definitiv möglich. Kann man am einfachsten dadurch umgehen, dass man halt echte Hardware verwendet...

#7429369
Lesenswert?

Zunächst mal vielen Dank für die Antworten. Was ich daraus mitgenommen habe:

  • ISRs werden ab sofort nur noch per rjmp aufgerufen, nicht mehr per rcall.
  • Mehrzeilige Kommentare wie in z.B. c++ oder JavaScript möglich (wußte ich bei assembler tatsächlich noch nicht)
  • Der Timer-stop könnte tatsächlich ein Bug sein - ein extra Dank an die Tester. Ggf. kann ich das ja mit einem extra Timer-init im Interrupt im Quellcode "umgehen", mal schau'n.

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