Forum: Mikrocontroller und Digitale Elektronik Probleme mit Timer bei Phasenanschnitt


von Christian L. (christianlienen)


Lesenswert?

Guten Morgen,
ich habe mir einen Dimmer mit einem Atmega48 aufgebaut. Der µC bekommt 
über einen Optokoppler jeden Nulldurchgang der Netzspannung auf den 
externen Interrupteingang 0. Diese erkennt er schonmal. Wenn ich jetzt 
ein Programm bastel, was einfach über _delay_ms nach dem Nulldurchgang 
ein bisschen warten und dann den Triac für 200us auf High legt, 
funktionert das auch wunderbar.
Da ich dem µC die Zeit, die er warten soll, via Rs232 mitteilen möchte, 
würde ich die ganze Geschichte über einen Timer realisieren.
Ich weiß auch, dass dieses Thema schon x-mal durchdiskutiert wurde, aber 
irgendwie werde ich aus den anderen Beiträgen auch nicht schlau.:(
1
#define F_CPU 4000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
6
 
7
int zeit;
8
9
int main(void)
10
{
11
DDRC = 0xff;
12
13
EICRA |= (0<<ISC01 | 0<<ISC00);
14
EIMSK |= (1<<INT0);
15
16
sei();
17
zeit = 150;
18
  while(1)
19
  {
20
21
  }
22
}
23
24
ISR( INT0_vect ) {
25
  TCCR0A = (1<<WGM01); // CTC Modus
26
    TCCR0B |= (1<<CS00); // Prescaler 8
27
    // ((1000000/8)/1000) = 125
28
    OCR0A = zeit;
29
 
30
  //Compare Interrupt erlauben
31
    TIMSK0 |= (1<<OCIE0A);
32
33
34
}
35
36
ISR( TIMER0_COMPA_vect ) {
37
TCNT0 = 0;
38
PORTC |= (1 << PC5);
39
_delay_us(100);
40
PORTC &= ~(1 << PC5);
41
}

Problem ist jetzt nur, das nicht erst gewartet wird, sondern der Impuls 
direkt am Nulldurchgang kommt (nach meinen Beobachtungen am Oszi).
Der interne Takt beträgt 4Mhz.
Ich hoffe ihr könnt und wollt mir helfen.

Gruß Christianlienen

von Philipp K. (philippk) Benutzerseite


Lesenswert?

Warum machst du das in der ISR?
1
TCCR0A = (1<<WGM01); // CTC Modus
2
TCCR0B |= (1<<CS00); // Prescaler 8
3
// ((1000000/8)/1000) = 125
4
OCR0A = zeit;
nimm das mal ins main, du musst den Timer nicht immer neu 
initialisieren. Beim CTC wird das TCNT0 Register selbständig 
zurückgesetzt, du musst das nicht selber machen.

Der effektive Fehler liegt aber wahrscheinlich hier:
1
TCCR0B |= (1<<CS01); // Prescaler 8
2
// ((1000000/8)/1000) = 125
wenn du einen Prescaler von 8 willst (wie es im Kommentar steht), dann 
solltest du CS01 auf 1 setzen, nicht CS00. Du hast momentan keinen 
Prescaler.

von Christian L. (christianlienen)


Lesenswert?

Philipp Kälin schrieb:
> Warum machst du das in der ISR?TCCR0A = (1<<WGM01); // CTC Modus
> TCCR0B |= (1<<CS00); // Prescaler 8
> // ((1000000/8)/1000) = 125
> OCR0A = zeit;
> nimm das mal ins main, du musst den Timer nicht immer neu
> initialisieren. Beim CTC wird das TCNT0 Register selbständig
> zurückgesetzt, du musst das nicht selber machen.

woher weiß der Timer denn dann, dass er in dem Interrupt starten soll?

Philipp Kälin schrieb:
> Der effektive Fehler liegt aber wahrscheinlich hier:TCCR0B |= (1<<CS01); // 
Prescaler 8
> // ((1000000/8)/1000) = 125
> wenn du einen Prescaler von 8 willst (wie es im Kommentar steht), dann
> solltest du CS01 auf 1 setzen, nicht CS00. Du hast momentan keinen
> Prescaler.

Entschuldigung, der Kommentar stand da noch von anderen Versuchen.

Gruß
Christian

von David M. (md2k7)


Lesenswert?

Christian L. schrieb:
> woher weiß der Timer denn dann, dass er in dem Interrupt starten soll?

Dort musst du TCNT0 auf 0 setzen (in INT0, nicht im Timer-Compare!).

Mit einem Prescaler von 1 kommst du übrigens nie im Leben auf 10 ms. Da 
brauchst du bei einem 8-bit Timer und 4 MHz schon mindestens einen 
Prescaler von 256.

Gruß
David

von Christian L. (christianlienen)


Lesenswert?

1
#define F_CPU 4000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
6
 
7
int zeit;
8
9
int main(void)
10
{
11
DDRC = 0xff;
12
zeit = 79;
13
EICRA |= (0<<ISC01 | 0<<ISC00);
14
EIMSK |= (1<<INT0);
15
TCCR0A = (1<<WGM01); // CTC Modus
16
TCCR0B |= (1<<CS02); // Prescaler 256
17
18
TIMSK0 |= (1<<OCIE0A);
19
OCR0A = zeit;
20
21
sei();
22
23
  while(1)
24
  {
25
26
  }
27
}
28
29
30
ISR( INT0_vect ) {
31
TCNT0 = 0;
32
}
33
34
ISR( TIMER0_COMPA_vect ) {
35
PORTC |= (1 << PC5);
36
_delay_us(100);
37
PORTC &= ~(1 << PC5);
38
}

Soweit ich alles richtig verstanden habe, sollte das Programm ja nun so 
aussehen. Ich habe Prescaler auf 256 geändert. So müsste der Timer nun, 
um die Phase ca. in der Mitte abzuschneiden, bis 79 zählen.
Leider kommt nun gar kein Zündimpuls mehr.
Gruß
Christian

von Sascha W. (sascha_w)


Lesenswert?

mit
1
EICRA |= (0<<ISC01 | 0<<ISC00);
hast du einen lowlevel int, du musst nur mit einer Flanke triggern!
Ausserdem bekommst du nur bei den positiven oder negativen Halbwelle 
einen  brauchbaren Impuls von deinem Optokoppler, deshalb sollte der 
Timer so laufen, das er die andere Halbwelle überbrücken kann.
Das Optimum währe ein Überlauf bei 255 alle 10ms, dann kannst du den 
Timer gleich als FastPWM laufen lassen. Da währe aber ein Quarz mit 
6,5563MHz notwendig.

Sascha

von Christian L. (christianlienen)


Lesenswert?

> hast du einen lowlevel int, du musst nur mit einer Flanke triggern!
> Ausserdem bekommst du nur bei den positiven oder negativen Halbwelle
> einen  brauchbaren Impuls von deinem Optokoppler, deshalb sollte der
> Timer so laufen, das er die andere Halbwelle überbrücken kann.

Nein das stimmt so nicht. Ich bekomme in jeder Halbwelle einen Impuls, 
da ich das Signal mit einem Brückengleichrichter gleichgerichtet habe, 
bevor es in Richtung Optokoppler geht.

Das mit dem Lowlevel int könnte jedoch sein, da werde ich morgen direkt 
mal ins datenblatt schauen.

Vielen Dank für eure Unterstützung bis jetzt!

Gruß Christian

von Christian L. (christianlienen)


Lesenswert?

Guten Abend,
nachdem ich die anderen Probleme alle hoffentlich aus dem Weg geräumt 
habe, steht bereits das nächste an. Mein Programm sieht immernoch so 
aus, wie das, was oben zu sehen ist. Ich habe es jedoch so geändert, 
dass das externe Interrupt nun bei einer steigenden Flanke starten 
SOLLTE. Leider tut es das nicht, sodass nun der Timer jetzt immer vor 
sich herläuft, und den Triac in falschen Zeitabständen zündet.
Kann es sein, das der Impuls für den µC zu kurz ist, um den Interrupt zu 
starten?
Mein Atmega48 läuft im Moment mit 4Mhz.
Mein Eingangssignal sieht wie das folgende aus:
Beitrag "Phasenanschnitt Dimmer mit AVR will nicht :- ("

Könnte es evtl. helfen, wenn ich ein Quarz mit einer hohen Frequenz 
benutzen würde, z.B. 16Mhz?

Gruß
Christian

von Sascha W. (sascha_w)


Lesenswert?

ist das Sync in deiner Schaltung auch so beschaltet wie in dem 
verlinkten Thread?
evl. ist dein OK zu langsam - du könntest mal den Pullup verkleinern, 
bzw. auf jeden Fall wie im Beispiel einen externen benutzen.

Sascha

von Christian L. (christianlienen)


Lesenswert?

Den Pullup habe ich von 4k7 auf 1k geändert. Leider startet der µC den 
Interrupt immernoch nicht. Er erkennt einfach keinen High Level, obwohl 
das Signal dort einwandfrei anliegt.
Grüße
Christian

von Sascha W. (sascha_w)


Lesenswert?

wie lang ist denn die H- bzw. L-Zeit deines Syncsignals?

die Initialisierung sieht jetzt so aus ?!
1
EICRA |= (1<<ISC01) | (1<<ISC00);

Sascha

von Christian L. (christianlienen)


Lesenswert?

Vielen Dank für eure Unterstützung:)
Mittlerweile läuft der Timer mit den Interrupts auch so, wie ich es 
gerne hätte.
Nun habe ich aber leider ein anderes Problem:
Ich möchte jetzt die Zeit, die gewartet werden soll, über den UART 
empfangen. Leider bekomme ich das noch nicht ganz so hin:
1
#define F_CPU 16000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
6
// Baudrate, das L am Ende ist wichtig, NICHT UL verwenden!
7
#define BAUD 9600L          
8
 
9
// Berechnungen
10
// clever runden
11
#define UBRR_VAL  ((F_CPU+BAUD*8)/(BAUD*16)-1)  
12
// Reale Baudrate
13
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))     
14
// Fehler in Promille 
15
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD-1000) 
16
17
 
18
// globale Variablen für den UART
19
 
20
// Puffergrösse in Bytes, RX und TX sind gleich gross
21
#define uart_buffer_size 3             
22
23
uint8_t zeit;
24
25
void uart_init(void)
26
{
27
    UCSR0B |= (1<<RXEN0);                         // UART RX einschalten
28
    UCSR0C |= (1<<USBS0)|(1<<UCSZ00);  // Asynchron 8N1 
29
 
30
    UBRR0H = UBRR_VAL >> 8;
31
    UBRR0L = UBRR_VAL & 0xFF;
32
}
33
34
uint8_t uart_getc(void)
35
{
36
    while (!(UCSR0A & (1<<RXC0)))   // warten bis Zeichen verfuegbar
37
        ;
38
    return UDR0;                   // Zeichen aus UDR an Aufrufer zurueckgeben
39
}
40
 
41
void uart_gets( char* Buffer, uint8_t MaxLen )
42
{
43
  uint8_t NextChar;
44
  uint8_t StringLen = 0;
45
 
46
  NextChar = uart_getc();         // Warte auf und empfange das nächste Zeichen
47
 
48
                                  // Sammle solange Zeichen, bis:
49
                                  // * entweder das String Ende Zeichen kam
50
                                  // * oder das aufnehmende Array voll ist
51
  while( NextChar != '\n' && StringLen < MaxLen - 1 ) {
52
    *Buffer++ = NextChar;
53
    StringLen++;
54
    NextChar = uart_getc();
55
  }
56
 
57
                                  // Noch ein '\0' anhängen um einen Standard
58
                                  // C-String daraus zu machen
59
}
60
61
62
int main(void)
63
{
64
volatile buffer;
65
DDRC = 0xff;
66
zeit = 120;
67
68
uart_init();
69
EIMSK |= (1<<INT0);
70
EICRA |= (1<<ISC01 | 1<<ISC00);//Externen Interrupt erlauben
71
72
TCCR0A = (1<<WGM01); // CTC Modus
73
TCCR0B |= (1<<CS02)|(1<<CS00); // Prescaler 256
74
TIMSK0 |= (1<<OCIE0A);
75
76
77
78
sei();
79
80
  while(1)
81
{
82
uart_gets(zeit,3);
83
OCR0A = zeit;
84
}
85
}
86
87
88
89
ISR(INT0_vect) {
90
91
TCNT0 = 0;
92
93
}
94
95
ISR(TIMER0_COMPA_vect) {
96
PORTC |= (1 << PC5);
97
_delay_us(200);
98
PORTC &= ~(1 << PC5);
99
}

Wenn ich das Programm nun ausführe, ist die Glühbirne zunächst aus. 
Sobald ich jetzt irgendeinen Dezimalwert über das Terminal sende, 
schaltet die Lampe ein. Leider lässt sich die Lampe nicht regeln und 
somit die Helligkeit auch nicht verändern. Ausschalten lässt sich die 
Glühbirne übrigens auch nicht.

Bitte helft mir!

Gruß
Christian

von Sascha W. (sascha_w)


Lesenswert?

Uart_gets gibt doch einen String zurück wie soll da ein brauchbarer Wert 
für OCR0A draus werden? Da musst du den String schon erst mal wieder in 
einen Bytewert umrechnen.

Sascha

von Christian L. (christianlienen)


Lesenswert?

Ja das hört sich ganz logisch an. Ich weiß jedoch nicht, wie ich das 
machen soll. Hast du vielleicht einen Tipp für mich?
Gruß
Christian

von Sascha W. (sascha_w)


Lesenswert?

jetzt hört's auf - also mit C kenne ich mich nicht aus!

Schau dich mal um - da wird sich sicher was finden lassen. Oder übergib 
zu Testzwecken nur ein Byte, das dann direkt als Wert interpretiert 
wird.

Sascha

von Christian L. (christianlienen)


Lesenswert?

Trotzdem Vielen Dank Sasha!
Ich werde mich dann mal umsehen und gegebenenfalls einen neuen Thread 
posten.

Grüße
Christian

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.