Hallo, folgendes Problem tritt bei mir auf, den Code werde ich bei bedarf auch noch posten, nur brauch ich etwas um die relevanten Teile rauszusuchen. Jedenfals, bei unserem Programm nutzen wir den Timer0 um eine CLK für einen D/A-Wandler zu generieren. Mit folgenden Einstellungen für Timer0: Teiler steht auf 0x01, Vorzähler steht auf (256-120). Der Timer1 wird für die Tasterabfrage verwednet und verwendet folgende Einstellungen: Teiler ist auf 0x01 und Vorzähler ist (65536 - 3125). Die CLK soll also ca. 64kHz haben und die Tastaturabfrage alle 50ms den Tastenstatus abfragen. Leider stimmt das Timing der CLK nicht mehr sobald Timer1 eingeschaltet wird, dass heißt auch schon ohne den ISR Befehl. Woran liegt das und wie kann ich das Problem umgehen/beheben? Grüße Sebastian
Sebastian Baier wrote: > Hallo, > folgendes Problem tritt bei mir auf, den Code werde ich bei bedarf auch > noch posten, nur brauch ich etwas um die relevanten Teile rauszusuchen. Wie wäre es denn erstmal mit Informationen über den verwendeten Controller und die Programmiersprache? Ohne die kann man nämlich überhaupt nichts Konkretes sagen! > Jedenfals, bei unserem Programm nutzen wir den Timer0 um eine CLK für > einen D/A-Wandler zu generieren. Mit folgenden Einstellungen für Timer0: > Teiler steht auf 0x01, Vorzähler steht auf (256-120). Wer ist der Vorzähler? Meinst Du vielleich den Reload-Wert? > Leider stimmt das Timing der CLK nicht mehr sobald Timer1 eingeschaltet > wird, dass heißt auch schon ohne den ISR Befehl. ISR-Befehl? Also ist es vielleicht ein AVR und C?
Tut mir leid, es ist ein ATmega16 und programmiert wird in C mit dem AVR Studio. Außerdem sollte ich vll noch sagen das der uC mit 16MHz betrieben wird.
Ich hab jetzt einfach mal ein kleines Programm geschrieben um das Problem zu verdeutlichen. Hier werden einfach 2 Clock Signale erzeugt, Timer0 erzeigt ein Signal auf PINA0 und Timer1 auf PINA7. Ich habe mittleerweile ein wenig damit gespielt und festgestellt, sobald ich einen Startwert für den Timer1 bestimme (in diesem Code INTRP1), ist das Signal vom Timer0 nicht mehr zu gebrauchen und zwar verschiebt sich die Phase ständig. Wenn ich die Timer allerdings nur einen Takt zählen lasse, so wie in meinem Beispielcode, dann ist das Signal sauber. Es wäre nett wenn mir einer den Zusammenhang bei diesem Problem erklären könnte und vll eine Lösung hat. P.S.: Manche Werte für den Startwert funktinieren gut und manche nicht, (65536 - 32768) für INTPR1 funktiniert super, (65536 - 2075) ist richtig schlecht. P.P.S: Wenn ich INTPR1 und INTPR0, also die beiden Startwerte undefiniert lasse und alle zeilen in denen die Variablen vorkommen auskommentiere, so das beide Timer voll durchzählen. Dann ist das Signal des Timer0 auch richtig unbrauchbar, Timer1 allerdings ist in Ordnung. DA-CLK.c
1 | #include<avr/io.h> |
2 | #include<avr/interrupt.h> |
3 | #include "Portvariablen.h" |
4 | #define TEILER0 0x1
|
5 | #define TEILER1 0x1
|
6 | #define INTRP0 (255)
|
7 | #define INTRP1 (65535)
|
8 | |
9 | volatile uint8_t flag_0 = 0; |
10 | volatile uint8_t flag_1 = 0; |
11 | |
12 | ISR(TIMER0_OVF_vect) |
13 | {
|
14 | TCNT0 = INTRP0; |
15 | flag_0 = 1; |
16 | }
|
17 | |
18 | ISR(TIMER1_OVF_vect) |
19 | {
|
20 | TCNT1 = INTRP1; |
21 | flag_1 = 1; |
22 | }
|
23 | |
24 | |
25 | int main() |
26 | {
|
27 | /* Timer0 */
|
28 | TCCR0 |= TEILER0; |
29 | TIMSK |= (1 << TOIE0); // Teiler setzen |
30 | TCNT0 = INTRP0; |
31 | |
32 | /* Timer1 */
|
33 | TCCR1B |= TEILER1; // Teiler setzten |
34 | TIMSK |= (1 << TOIE1); // Timer1 Interrupt freischalten |
35 | TCNT1 = INTRP1; // Anfangswert laden |
36 | |
37 | OUT_CLK0_DDR = 1; |
38 | OUT_CLK1_DDR = 1; |
39 | |
40 | sei(); |
41 | |
42 | /* CLK_0 */
|
43 | while(1) |
44 | {
|
45 | flag_0 = 1; |
46 | if(flag_0) |
47 | {
|
48 | OUT_CLK0 = ~(OUT_CLK0); |
49 | flag_0 = 0; |
50 | }
|
51 | |
52 | if(flag_1) |
53 | {
|
54 | OUT_CLK1 = ~(OUT_CLK1); |
55 | flag_1 = 0; |
56 | }
|
57 | }
|
58 | |
59 | return 0; |
60 | }
|
Portvariablen.h
1 | #ifndef PORTVARIABLEN_H_
|
2 | #define PORTVARIABLEN_H
|
3 | |
4 | typedef struct |
5 | {
|
6 | unsigned int bit0:1; |
7 | unsigned int bit1:1; |
8 | unsigned int bit2:1; |
9 | unsigned int bit3:1; |
10 | unsigned int bit4:1; |
11 | unsigned int bit5:1; |
12 | unsigned int bit6:1; |
13 | unsigned int bit7:1; |
14 | } _io_reg; |
15 | |
16 | #define REGISTER_BIT(rg,bt) ((volatile _io_reg*)&rg)->bit##bt
|
17 | |
18 | #define OUT_CLK0_DDR REGISTER_BIT(DDRA,0)
|
19 | #define OUT_CLK1_DDR REGISTER_BIT(DDRA,7)
|
20 | |
21 | #define OUT_CLK0 REGISTER_BIT(PORTA,0)
|
22 | #define OUT_CLK1 REGISTER_BIT(PORTA,7)
|
23 | |
24 | |
25 | #endif
|
Grüße Sebastian
Dieser Trick funktioniert auch nicht: Unterbrechbare Interruptroutinen
1 | #include <avr/interrupt.h> |
2 | //...
|
3 | void XXX_vect(void) __attribute__((interrupt)); |
4 | void XXX_vect(void) { |
5 | //...
|
6 | }
|
Weshalb nicht den langsamen Timer begraben un den schnellen mit einem counter versehen ? Dann alle 1000 Durchgaenge mal ein flag setzen, dass dem Main erlaubt, die Taststur abzufragen ?
Beim ATMega16 haben alle Timer eine Compare-Einheit und können im CTC-Modus betrieben werden. Das ungenaue und Ressourcen-raubende Timer-Nachladen ist da überflüssig! Abgesehen davon solltest Du mal überlegen, wie lange die Bearbeitung eines Interrupts dauert und wieviel Zeit der Controller zwischen zwei Interrupts hat, wenn Du die Timer beide mit ungeteiltem CPU-Takt versorgst und schon nach jeweils einem einzigen Takt einen Interrupt haben willst. Das geht so überhaupt nicht! Wenn Du Taktsignale in dem Frequenzbereich erzeugen willst, dann geht das nur per Hardware. Über die Compare-Ausgänge der Timer kannst Du maximal F_CPU/2 ausgeben, und das komplett ohne Interrupt.
@ Aha Am Ende wird uns nichts anderes übrig bleiben, aber der Gedanke dahinter warum wir das so gemacht haben war, warum den Prozessor mit einer Zählvariablen belasten wenn ich doch Timer habe die das erledigen. Allerdings interessiert mich trotzdem warum das so ist. @ Johannes M. Aber es funktioniert ja wenn ich schon nur nach einem Takt einen Interrupt haben will. Es sind nur bestimmt Werte die kein sauberes Ergebnis liefern. Über den Comparebetrieb habe ich noch garnicht nachgedacht, um ein CLK auszugeben ist das wohl die beste Lösung.
Sebastian Baier wrote: > Am Ende wird uns nichts anderes übrig bleiben, aber der Gedanke dahinter > warum wir das so gemacht haben war, warum den Prozessor mit einer > Zählvariablen belasten wenn ich doch Timer habe die das erledigen. Genau. Aber Du nutzt die zur Verfügung stehende Timer-Hardware gar nicht richtig. Die kann nämlich nach einmaliger Konfiguration völlig selbständig Signale an bestimmten Pins ausgeben, ohne dass das Programm überhaupt noch eingreifen muss. Wie oben schon gesagt: Timer-Reload ist Murks. > Allerdings interessiert mich trotzdem warum das so ist. Siehe oben. Und mit den Startwerten im Beispielcode geht es eben gar nicht, weil der µC theoretisch jeden einzelnen CPU-Takt zwei Interrupts bearbeiten müsste.
Sebastian Baier wrote: > @ Johannes M. > Aber es funktioniert ja wenn ich schon nur nach einem Takt einen > Interrupt haben will. Das ist eine glatte Lüge! Ein Interrupt-Handler hat bereits einen Overhead von mindestens 10 CPU-Takten (absolutes Minimum, durch die Hardware gegeben), wo noch mal durch Register-Sichern einige Takte hinzukommen. Ein Interrupt-Handler kann realistisch höchstens ungefähr alle 30-40 CPU-Takte aufgerufen werden, ohne dass Ereignisse verloren gehen. Wenn ein Interrupt-Ereignis auftritt, wird zunächst mal der gerade laufende Befehl zuende abgearbeitet, was schon mal 1-3 Takte Verzögerung bedeuten kann. Anschließend benötigt der Sprung in den Interrupt-Vektor 4 CPU-Takte. Dort steht dann i.d.R. ein Sprungbefehl zum eigentlichen Handler, der nochmal 2 oder 3 Zyklen (je nachdem, ob es ein rjmp oder ein jmp ist, beim Mega16 i.d.R. ein jmp). Dann werden vom Compiler Register gesichert, was im Minimalfall (nur SREG sichern) allein 3 Zyklen (ein in und ein push ) benötigt. Erst danach, also nach minimal 10 Zyklen beim Mega16, wird das gemacht, was in dem Handler steht. In der Realität ist es eher mehr. Hinzu kommt noch das Nachladen des Timers, was speziell beim 16-Bit-Timer 1 einige Zyklen benötigt. Dann wird ein Flag gesetzt. Dann werden die Register wieder zurückgeschrieben. Dann wird ins Hauptprogramm zurückgesprungen. Dann muss die Flag-Abfrage-Schleife erstmal zu der richtigen Stelle kommen. Erst dann wird überhaupt auf das Ereignis reagiert, indem ein Portpin getoggelt wird. Hochrechnungen ergeben vom Auftreten des Interrupt-Ereignisses bis der Portpin geändert ist minimal ungefähr 50-60 CPU-Takte...
Natürlich sind die Takte die der Prozessor selber für die Interrupt braucht nicht mitgerechnet. Aber Fakt ist, wenn ich den Code so wie er oben steht verwende, hab ich 2 gute Rechntecksignal. Trotzdem hast du recht das der Comparebetrieb besser wäre, allerdings bin ich noch nicht auf den Trick gekommen wie ich 64kHz mit dem Timer0 und einem Systemtakt von 16MHz erreiche.
Sebastian Baier wrote: > Natürlich sind die Takte die der Prozessor selber für die Interrupt > braucht nicht mitgerechnet. Aber Fakt ist, wenn ich den Code so wie er > oben steht verwende, hab ich 2 gute Rechntecksignal. Klar. Aber die Frequenz Deiner Rechtecksignale ist nicht die Frequenz, mit der der Timer überlaufen sollte, sondern die Periodendauer repräsentiert die Zeit, die der Controller für den ganzen oben beschriebenen Rotz benötigt. Und es kann passieren, dass es bei bestimmten Werten zufällig ein stabiles Rechtecksignal gibt.
Gut, schließen wir das ab, es ist also nicht auf meinem Weg klug ein Rechtecksignal zu konztruieren. Ich beschäftige mich jetzt gerade mit dem Compare betrieb, lese ich das richtig, dass der Asugabepin festgelegt ist auf PB3 beim ATmega16?
> Trotzdem hast du > recht das der Comparebetrieb besser wäre, allerdings bin ich noch nicht > auf den Trick gekommen wie ich 64kHz mit dem Timer0 und einem Systemtakt > von 16MHz erreiche. Wieso Trick? Du willst ein Rechtecksignal mit 64 kHz? Dann musst Du nur ein bisschen Rechnen. Erstmal CTC-Modus für den Timer wählen. Dann den Compare-Output-Pin so konfigurieren, dass er bei jedem Compare-Ereignis getoggelt wird. Dann überlegen, wie man von 16 MHz auf 64 kHz kommt. 16000000/64000 = 250. Irgendwie müssen also 250 CPU-Takte für eine Periode des Signals vergehen. Da eine Periode eines Rechtecksignals aber aus zwei mal Umschalten des Pins besteht, sind es 125 Takte zwischen zwei mal Toggeln. Mit einem Prescaler größer als 1 ist da sinnvoll nichts zu machen, also Prescaler auf 1 (ungeteilter Systemtakt) stellen. Und jetzt noch bedenken, dass das Umschalten des Portpins erst einen Takt nach dem Auftreten der Übereinstimmung von Zählregister und Compare-Wert geschieht. Daraus ergibt sich für den Compare-Wert 124. Das gibt ein sauberes 64 kHz-Signal an dem betreffenden Pin. Da 124 kleiner als 255 ist, kann man das mit jedem der drei Timer machen, auch mit den 8-Bit-Timern.
Sebastian Baier wrote: > Gut, schließen wir das ab, es ist also nicht auf meinem Weg klug ein > Rechtecksignal zu konztruieren. Ich beschäftige mich jetzt gerade mit > dem Compare betrieb, lese ich das richtig, dass der Asugabepin > festgelegt ist auf PB3 beim ATmega16? Es gibt mehrere Ausgänge. Die Timer 0 und 2 haben je einen, Timer 1 hat zwei davon. Und ja, die sind fest verdrahtet. PB3 (OC0) wäre der Ausgang von Timer 0.
Wäre es denn möglich 2 verschiedene Signal mit dem Timer1 zu erzeugen? Denn er hat ja auch 2 Ausgabepins. Grüße Sebastian
Sebastian Baier wrote: > Wäre es denn möglich 2 verschiedene Signal mit dem Timer1 zu erzeugen? 2 Unterschiedliche Signale: Ja. Aber mit derselben Frequenz! > Denn er hat ja auch 2 Ausgabepins. Ja, für zwei PWM-Signale mit gleicher Frequenz, aber unterscheidlichem Tastverhältnis.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.