[Assembler] 50Hz mit Timer1 und Interrupts

Gast #1556964
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.
Gast #1556984
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
Gast #1557038
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
Gast #1557051
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!
Gast #1557078
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
Gast #1557132
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;
Gast #1557138
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.
Gast #1557146
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.
Gast #1559897
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

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