Forum: Mikrocontroller und Digitale Elektronik [Assembler] 50Hz mit Timer1 und Interrupts


von Denny (Gast)


Lesenswert?

[ATmega8]

Hallo
Ich muss mit Hilfe von Timer1 und den Interrupts einen 
50Hz-Rechteckgenerator erzeugen.
Dafür verwende ich den Timer1 mit einem Prescaler von 1024.
Der Quarz hat 6MHz.
Der Interrupt wird bei einem Überlauf ausgelöst.

Berechnungen sind oben im Code.

Code:
1
.include "m8def.inc"
2
.org 0x0000
3
4
;Quarz = 6Mhz
5
;6Mhz/1024 = 170µs pro Takt
6
;Um 50 Herz zu bekommen muss der Interrupt immer bei 10ms ausgelöst werden
7
;10ms = 10000µs -> 100000/170 = 6Takte
8
;Timer1-Wert muss dann 1024-6 sein
9
10
RJMP Main
11
RETI
12
RETI
13
RETI
14
RETI
15
RETI
16
RETI
17
RETI
18
RJMP INT_T1_OVF
19
20
Main: 
21
LDI R16, 0x04                ;Stack Pointer definieren
22
OUT SPH, R16                ;Stack Pointer definieren
23
LDI R16, 0x00                ;Stack Pointer definieren
24
OUT SPL, R16                ;Stack Pointer definieren
25
26
LDI R16, 0b11111010              ;Low-Byte des Timer1 Wertes beschreiben 1024-6 = 00000011_11111010
27
OUT TCNT1L, R16
28
LDI R16, 0b00000011
29
OUT TCNT1H, R16
30
31
SBI DDRB, 0                  ;Rechteckgenerator: Ausgang
32
33
LDI R16, 0b00000101              ;Prescaler = 1024
34
OUT TCCR1B, R16
35
36
LDI R16, 0b00000100              ;T1 Interrupt aktivieren
37
OUT TIMSK, R16  
38
39
SEI                      ;Globale Interrupts aktivieren
40
41
Immer: RJMP immer              ;Endlosschleife
42
43
INT_T1_OVF:                  ;Interrupt: Overflow von T1
44
COM R20                    ;Register 20 invertieren
45
OUT PORTB, R20                ;Und auf PORTB ausgeben (nur PORTB.0 = Output)
46
47
LDI R16, 0b11111010              ;Low-Byte des Timer1 Wertes beschreiben 1024-6 = 00000011_11111010
48
OUT TCNT1L, R16
49
LDI R16, 0b00000011
50
OUT TCNT1H, R16
51
RETI

Leider funktioniert das nicht, wie ich mir das vorstelle.
Der Port wechselt erst nach 11 SEKUNDEN.

Ich weiß, dass man 50 Hz nicht wirklich mit dem Auge sehen kann...

Was mache ich falsch??

Mit freundlichen Grüßen Denny.

von Denny (Gast)


Lesenswert?

Ich denke da stimmt irgendwas beim Laden des Timers nicht...

von spess53 (Gast)


Lesenswert?

Hi

>Leider funktioniert das nicht, wie ich mir das vorstelle.
>Der Port wechselt erst nach 11 SEKUNDEN.

Das ist genau die Zeit, die der Timer zwischen 2 Overflow-Interrupts 
braucht.

>LDI R16, 0b11111010              ;Low-Byte des Timer1 Wertes beschreiben >1024-6 
= 00000011_11111010
>OUT TCNT1L, R16
>LDI R16, 0b00000011
>OUT TCNT1H, R16

Falsch. OUT TCNT1H muss als erstes beschrieben werden.

Das ganze geht wesentlich einfacher, wenn du den CTC-Mode benutzt.

MfG Spess

von Gastofatz (Gast)


Lesenswert?

T/C1 mit Prescaler 8 im CTC-Mode. Output-Compare-Wert 7500 --> alle 10 
ms ein Tick.

von spess53 (Gast)


Lesenswert?

Hi

>T/C1 mit Prescaler 8 im CTC-Mode. Output-Compare-Wert 7500 --> alle 10
>ms ein Tick.

Falsch. Der OCR-Wert muss 7499 sein.

MfG Spess

von Denny (Gast)


Lesenswert?

Mir ist gerade eingefallen, dass T1 ein 16Bit und nicht ein 10Bit Timer 
ist.

von gtf (Gast)


Lesenswert?

Hi Denny,
habe leider nichtdie Zeit dir eine vollständige lösung zu bieten

Du mußt nur noch den Richtigen CTC Wert eintragen
1
; Register definitionen_____________
2
; Hier werden den "General Purpose Working Register" Namen vergeben 
3
4
    .def  temp    =r16      //  Bit 1-8      |  32- Bit Akku
5
    .def    temp2    =r17      //  Bit 9-16    |  32- Bit Akku
6
    .def  temp3    =r18      //  Bit 17-24    |  32- Bit Akku
7
    .def  temp4    =r19      //  Bit 25-32    |  32- Bit Akku    
8
    .def  temp5    =r20
9
    .def  temp6    =r21
10
11
12
13
14
.include "m8def.inc"
15
16
17
.device Atmega8
18
19
20
.cseg
21
22
; IRQ____Address_____Routine______________
23
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
24
 .org  $0000 
25
  rjmp   RESET       ; Reset Handler
26
  reti;jmp EXT_INT0    ; IRQ0 Handler
27
  reti;jmp EXT_INT1    ; IRQ1 Handler
28
  reti;jmp TIM2_COMP    ; Timer2 Compare Handler
29
  reti;jmp TIM2_OVF     ; Timer2 Overflow Handler
30
  reti;jmp TIM1_CAPT     ; Timer1 Capture Handler
31
;reti;
32
  rjmp   TIM1_COMPA     ; Timer1 CompareA Handler
33
  reti;jmp TIM1_COMPB   ; Timer1 CompareB Handler
34
  reti;jmp TIM1_OVF     ; Timer1 Overflow Handler
35
  reti;jmp TIM0_OVF     ; Timer0 Overflow Handler
36
  reti;jmp SPI_STC     ; SPI Transfer Complete Handler
37
  reti;jmp USART_RXC     ; USART RX Complete Handler 
38
  reti;jmp USART_UDRE   ; UDR Empty Handler
39
  reti;jmp USART_TXC     ; USART TX Complete Handler
40
  reti;jmp ADC_Complete   ; ADC Conversion Complete Handler
41
  reti;jmp EE_RDY     ; EEPROM Ready Handler
42
  reti;jmp ANA_COMP     ; Analog Comparator Handler
43
  reti;jmp TWSI       ; Two-wire Serial Interface Handler
44
  reti;jmp EXT_INT2     ; IRQ2 Handler
45
  reti;jmp TIM0_COMP     ; Timer0 Compare Handler
46
  reti;jmp SPM_RDY     ; Store Program Memory Ready Handler
47
48
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
49
50
51
RESET:
52
53
54
; Stack___pointer______________
55
    ldi    temp, high(RAMEND)
56
    out     SPH, temp
57
    ldi    temp, Low(RAMEND)
58
    out    SPL, temp
59
60
61
; I/O_inint______________
62
    sbi    DDRB, PB0    
63
64
65
66
67
68
; Timer Counter 1 Init , MODE 4 CTC , TOP = OC1A
69
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   
70
71
    ldi    temp, (0<<COM1A1)|(1<<COM1A0)|(0<<COM1B1)|(0<<COM1B0)|(0<<FOC1A)|(0<<FOC1B)|(0<<WGM11)|(0<<WGM10)
72
    out    TCCR1A, temp
73
74
    ldi    temp, (0<<ICNC1)|(0<<ICES1)|(0<<5)|(0<<WGM13)|(1<<WGM12)|(1<<CS12)|(0<<CS11)|(1<<CS10)  
75
    out    TCCR1B, temp
76
77
    in    temp, TIMSK
78
    sbr    temp, 1<<OCIE1A
79
    out    TIMSK, temp
80
81
82
83
84
; CTC- Wert 
85
    ldi    temp, Low (58)
86
    ldi    temp2, High (58)
87
    out    OCR1AH, temp2
88
    out    OCR1AL, temp
89
90
    sei
91
92
93
94
;***********************************    
95
;***********************************
96
;*       Hauptprogramm      **
97
;***********************************
98
;***********************************
99
Programm:
100
    nop
101
    rjmp  Programm
102
103
104
105
106
107
108
109
110
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
111
TIM1_COMPA:      ; Timer1 CompareA Handler (IRQ)
112
113
COM R20                    ;Register 20 invertieren
114
OUT PORTB, R20                ;Und auf PORTB ausgeben (nur PORTB.0 = Output)
115
116
  reti

von Denny (Gast)


Lesenswert?

Ich habe es nun geschafft!
1
.include "m8def.inc"
2
.org 0x0000
3
4
;Reihenfolge sehr wichtig
5
6
RJMP Main                  ;Reset Interrupt
7
RETI
8
RETI
9
RETI
10
RETI
11
RETI
12
RJMP Gleich                  ;Timer1: Komparator A Interrupt
13
14
Main:
15
LDI R16, 0x04                ;Stack Pointer definieren
16
OUT SPH, R16
17
LDI R16, 0x00
18
OUT SPL, R16 
19
20
SER R16                    ;PORTB als Ausgang
21
OUT DDRB, R16
22
23
LDI R16, 0b00011101              ;Komparator A von Timer1 High-Byte
24
OUT OCR1AH, R16
25
26
LDI R16, 0b01001100              ;Komparator A von Timer1 Low-Byte
27
OUT OCR1AL, R16
28
29
LDI R16, 0b00000010              ;Prescaler = 8
30
OUT TCCR1B, R16
31
32
LDI R16, 0b00010000              ;Komparator A Interrupt aktivieren
33
OUT TIMSK, R16  
34
35
SEI                      ;Interrupts global aktivieren
36
37
Immer: RJMP Immer
38
39
Gleich:
40
COM R20                    ;Register invertieren
41
OUT PORTB, R20                ;Auf den Port ausgeben
42
43
CLR R16                    ;Timer1 auf 0 setzen
44
OUT TCNT1H, R16                ;Muss aber nicht sein
45
OUT TCNT1L, R16                ;Damit er von vorne anfängt
46
RETI

Danke an euere Hilfe!

von spess53 (Gast)


Lesenswert?

Hi

Dein Comparewert ist nicht korrekt. Muss $8D8B nicht $8D8C sein.

Warum schreibst du eigentlich so unverständlich?

>LDI R16, 0b00011101 ->  ldi r16,High($8D8B)

>LDI R16, 0b01001100 ->  ldi r16,Low($8D8B)

>LDI R16, 0b00010000 ->  ldi r16,1<<OCIE1A

>OUT TCNT1H, R16                ;Muss aber nicht sein
>OUT TCNT1L, R16                ;Damit er von vorne anfängt

Unnötig. Wird automatisch gemacht.

MfG Spess

von Drachenbändiger (Gast)


Lesenswert?

Gastofatz hat schon einen guten Vorschlag gemacht .... warum nimmst Du 
den nicht an? Lass doch die Hardware alles machen! Dann sähe es nämlich 
so aus (den Grund für die Wahl von OCR1A lass' ich Dir als Übung):
1
.include "m8def.inc"
2
3
.def wr=r16
4
5
.org 0
6
  rjmp ini_        ; reset vector
7
ini_:
8
    
9
    sbi DDRB, 1<<PB1   ; OC1A is output
10
11
    ldi wr, high (RAMEND)
12
    out SPH, wr
13
    ldi wr, low(RAMEND)
14
    out SPH, wr
15
16
    // initialize TC1
17
    ldi wr, high(60000) ; 
18
    out OCR1AH, wr
19
    ldi wr, low(60000)
20
    out OCR1AL, wr
21
22
    ldi wr, 1<<COM1A0 ; toggle OC1A on compare match
23
    out TCCR1A, wr
24
25
    ldi wr, (1<<CS10) | (1<<WGM12)  ; FCLOCK/1, CTC mode
26
    out TCCR1B, wr ;
27
28
be_lazy:
29
    rjmp be_lazy;

von Gastofatz (Gast)


Lesenswert?

>LDI R16, 0b00011101              ;Komparator A von Timer1 High-Byte
>OUT OCR1AH, R16
>
>LDI R16, 0b01001100              ;Komparator A von Timer1 Low-Byte
>OUT OCR1AL, R16

Dasselbe, aber leichter zu lesen:

LDI R16, High(7500)
OUT OCR1AH, R16

LDI R16, Low(7500)
OUT OCR1AL, R16

Kollege spess53 hat übrigens recht: Der in das Output-Compare-Register 
zu schreibende Wert muss um 1 kleiner sein (!) als der errechnete Wert. 
So wäre es also ganz korrekt:

LDI R16, High(7500-1)
OUT OCR1AH, R16

LDI R16, Low(7500-1)
OUT OCR1AL, R16

Und das hier...

>CLR R16                    ;Timer1 auf 0 setzen
>OUT TCNT1H, R16                ;Muss aber nicht sein
>OUT TCNT1L, R16                ;Damit er von vorne anfängt

...ist purer Nonsens. Lass TCNT1 in Frieden. Der Timer zählt im CTC-Mode 
von selbst auf den Takt genau richtig (das ist ja der Sinn der Sache), 
und Du bekommst damit die 50 Hz mit der höchstmöglichen Präzision 
(16000000 MHz-Quarz --> 50.00000 Hz). Herumfummeln an TCNT1 kanns nur 
schlechter machen.

von Drachenbändiger (Gast)


Lesenswert?

Oh -- Du sollst ja Interrupts benutzen --- dann vergiss meine schöne 
Methode mit CTC und WGM12=1 mal schnell! Ist sicher auch 'ne gute Übung 
mit den Interrupts, selbst wenn damit Ressourcen verbraten werden.

von Drachenbändiger (Gast)


Lesenswert?

Nachtrag: 59999 statt 60000 für OCR1A bei 6MHz SysClk und TC1-Teiler=1, 
um alle 10ms eine Änderung an OC1A zu erhalten.

von Mike D. (hero2992)


Lesenswert?

Hallo... ich werde nicht schlau aus den berechnungen für OCR1A..

Wie muss ich vorgehen um den wert bei 3,868400Mhz zu errechnen?

MFG Hero

von Klaus (Gast)


Lesenswert?

Wenn du  f=3,868400Mhz hast und eine Prescaler von 1024 wird der Timer 
mit f/1024= 3777,734375 Hz getaktet. Bei f_out=25Hz sind für 1/50s genau 
75,5546875 Takte pro Halbwelle.

Wenn du rückwärts zählst, lädst du den Timer1 mit 75 oder 76. Wenn du 
aufwärts zählst dann lädst du den Timer1 mit 2^16 - 75 (76), weil beim 
Erreichen der 0 der IRQ ausgelöst wird. Wenn das Ausgabebit nur 
wechselt, erhältst du 25Hz. Für 50Hz selbst rechnen.

Mit Prescaler=1024 ist das relativ ungenau.

Für Prescaler=1 ist f/1= 3868400 Für 1/100s erhältst du genau 38684 
Takte.
Also diese Zahl in den Timer laden, wenn du rückwärts zählst, dan das 
Ausgabebit ändern. Beim Aufwärtszählen 2^16-38684 in den Timer laden.

Diese 50Hz sind so genau wie dein Quarz.

kl

von Mike D. (hero2992)


Lesenswert?

Vielen Dank Klaus...hast mir sehr geholfen...

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.