mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Wenn sich Timer0 und Timer1 streiten.


Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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
#include<avr/io.h>
#include<avr/interrupt.h>
#include "Portvariablen.h"
#define TEILER0 0x1
#define TEILER1 0x1
#define INTRP0 (255)
#define INTRP1 (65535)

volatile uint8_t flag_0 = 0;
volatile uint8_t flag_1 = 0;

ISR(TIMER0_OVF_vect) 
  {
    TCNT0 = INTRP0;
    flag_0 = 1;
  }

ISR(TIMER1_OVF_vect)                     
  {
    TCNT1 = INTRP1;
    flag_1 = 1;                        
  }


int main()
{
  /* Timer0 */
  TCCR0 |= TEILER0;
  TIMSK |= (1 << TOIE0);           // Teiler setzen
  TCNT0 = INTRP0;

  /* Timer1 */
  TCCR1B |= TEILER1;       // Teiler setzten
  TIMSK |= (1 << TOIE1);    // Timer1 Interrupt freischalten
  TCNT1 = INTRP1;        // Anfangswert laden

  OUT_CLK0_DDR = 1;
  OUT_CLK1_DDR = 1;

  sei();

  /* CLK_0 */
  while(1)
  {
  flag_0 = 1;
    if(flag_0)
    {
      OUT_CLK0 = ~(OUT_CLK0);
      flag_0 = 0;
    }
    
    if(flag_1)
    {
      OUT_CLK1 = ~(OUT_CLK1);
      flag_1 = 0;
    }
  }

  return 0;
}

Portvariablen.h
#ifndef PORTVARIABLEN_H_
  #define PORTVARIABLEN_H

  typedef struct
  {
    unsigned int bit0:1;
    unsigned int bit1:1;
    unsigned int bit2:1;
    unsigned int bit3:1;
    unsigned int bit4:1;
    unsigned int bit5:1;
    unsigned int bit6:1;
    unsigned int bit7:1;
  } _io_reg;

  #define REGISTER_BIT(rg,bt)  ((volatile _io_reg*)&rg)->bit##bt

  #define OUT_CLK0_DDR    REGISTER_BIT(DDRA,0)
  #define OUT_CLK1_DDR    REGISTER_BIT(DDRA,7)

  #define OUT_CLK0      REGISTER_BIT(PORTA,0)
  #define OUT_CLK1      REGISTER_BIT(PORTA,7)


#endif

Grüße Sebastian

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dieser Trick funktioniert auch nicht:

Unterbrechbare Interruptroutinen
#include <avr/interrupt.h>
//...
void XXX_vect(void) __attribute__((interrupt));
void XXX_vect(void) {
  //...
}

Autor: Aha (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ?

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ 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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Sebastian B. (m0nkey)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wäre es denn möglich 2 verschiedene Signal mit dem Timer1 zu erzeugen? 
Denn er hat ja auch 2 Ausgabepins.

Grüße Sebastian

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.