Timer Probleme mit ATtiny1606 (Portierung)

Hallo zusammen,

Ich habe auf Arduino nano ein Programm geschrieben und weiterentwickelt, welches ich nun auf einer Hardware einsetzen möchte für die es eigentlich nie gedacht war...

Speziell macht mir die Timer Konfiguration Probleme. Auf ATmega328 war der Timer folgendermaßen konfiguriert:

void startTimer()
{
  TCCR1A = 0;                       // normal mode
  TCCR1B = 0;                         // stop timer
  TCNT1 = 0;                          // count back to zero
  TCCR1B = bit(WGM12) | bit(CS11);    // CTC, scale to clock / 8
  if ( timerInterval > 65535 )
  {
    OCR1A = 65535;
  }
  else
  {
    OCR1A = timerInterval;
  }
  TIMSK1 = bit (OCIE1A);
  timerOn = true;  
}  

void stopTimer()
{
  TCCR1B = 0;                         // stop timer
  TCNT1 = 0;                          // count back to zero
  TIMSK1 = 0;                         // cancel timer interrupt
  timerOn = false;
}

ISR (TIMER1_COMPA_vect)
{

}

Der Timer wird im Verlauf des Programms mehrfach gestoppt und mit neu berechnetem timerInterval neu gestartet.
Auf dem ATtiny1606 habe ich das folgendermaßen angepasst.

void startTimer(uint32_t timerInterval)
{
  if ( timerInterval > 0 )
  {
      TCA0.SINGLE.CTRLA = 0;                                        // Stop timer
      TCA0.SINGLE.INTFLAGS = TCA_SINGLE_CMP0_bm;                    // Clear interrupt flag

      TCA0.SINGLE.CTRLB  = TCA_SINGLE_ALUPD_bm;                     // enabled Auto Lock Update
      TCA0.SINGLE.CTRLB  = TCA_SINGLE_WGMODE_FRQ_gc;                // Frequency Mode
      TCA0.SINGLE.CTRLA  = TCA_SINGLE_CLKSEL_DIV8_gc;               // Prescaler
            
      if (timerInterval > 65535) {
          TCA0.SINGLE.CMP0H = (65535 >> 8);
          TCA0.SINGLE.CMP0L = (65535 & 0xFF);
      } else {
          TCA0.SINGLE.CMP0H = (timerInterval >> 8);
          TCA0.SINGLE.CMP0L = (timerInterval & 0xFF);
      }

      TCA0.SINGLE.INTCTRL = TCA_SINGLE_CMP0_bm;
      TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;                    // enable TCA module
      timerOn = true;                                                              
       
  }

}

void stopTimer()
{
  TCA0.SINGLE.CTRLA = 0;                            // Stop timer
  TCA0.SINGLE.INTCTRL = 0;                          // Disable interrupts
  timerOn = false;
  
}

ISR (TCA0_CMP0_vect)
{
  TCA0.SINGLE.INTFLAGS = TCA_SINGLE_CMP0_bm;  // Clear interrupt flag
}

Ich habe den Eindruck, dass die ISR nicht korrekt aufgerufen wird. Auch gibt es keine Änderung im Verhalten wenn ich timerInterval anpasse.

An diesem Problem sitze ich nun schon gut 1 Woche. Bin Mittlerweile Ratlos.
Hoffe, euch fällt dazu etwas ein...

VG
Matze

Welche Motivation treibt Dich an? Möchtest Du das machen, weil es Dich interessiert oder suchst Du eher nach einer Lösung?

Ich habe die die neue Schaltung geplant und dabei den 1606 nach Datenblatt gewählt. Mein Fehler war, dass ich es zuvor nicht getestet habe. Nun sind die Platinen gefertigt und bin erst bei der in Betriebnahme auf das Problem gestossen. Klar, mein Fehler. Aber ich habe tatsächlich nicht damit gerechnet, das mir ein Timer solche Probleme machen könnte. Wenn sich das Problem nicht lösen lässt, kann die Platinen entsorgen und muss umplanen und neue herstellen lassen.

Ich wäre also tatsächlich an einer Lösung interessiert.

Gut, dann verstehe ich die Richtung.

Was willst Du mit dem Timer machen?

Hast Du schon nach Bibliotheken gesucht?

Ich frage so, weil ich in Projekten noch nie Timerregister direkt angesprochen habe, bisher ging das mit millis() oder Bibliotheken zu lösen.

Es geht dabei um die Steuerung einer digitalen Zündung. Also Verzögerungen, die schon sehr präzise sein sollten.

Verwendest Du den megaTinyCore von Spence Konde? Wenn ja, fehlt da mindestens ein
takeOverTCA0();

Bisher habe ich mich mit ATtiny85 und 4313 beschäftigt, der ATtiny1606 ist doch schon recht verschieden dazu. Da wird es andere hier im Forum geben, die sich besser damit auskennen.

Bei der Suche bin ich über ATtiny_TimerInterrupt gestolpert, kannst ja mal einen Blick drauf werfen oder Dir was abschauen :wink:

Hallo,

die Frage ist woran siehst du das dein ISR nicht angesprungen wird? Die ISR ist leer.
Programmierst du Bare Metall? Vielleicht nur vergessen die globalen Interrupts zu aktivieren? Weil der korrigierte Code funktioniert bei mir.

Noch paar Tipps.

Das Bit ALUPD musst du nicht setzen. Wird bei dir auch nicht gesetzt, schau dir deine Zuweisungen an.

Getrennte Byte Zuweisungen

.CMP0H = (65535 >> 8);
.CMP0L = (65535 & 0xFF);

sind auch nicht notwendig. Schreib den Wert ins .CMP0 oder .CMP0BUF Register.

Buffer-Register würde ich immer vorziehen, wenn es keine Gründe dagegen gibt. Lies einmal bitte im Manual was der Unterschied zwischen gepuffert und nicht gepufferten Register beim Timer ist.

Man muss auch nicht jedesmal den Timer für einen Restart komplett konfigurieren. Der Timer hat ein Bit zum ein- und ausschalten und wie gesagt gepufferte Register. Wenn dein timerInterval unsigned 16 Bit wäre, kann es weder kleiner 0 noch größer 65535 sein.

Ich habe hier mal einen Beispielcode zusammegestuppelt. Das Übliche. Es wird eine LED im Sekundentakt aus-/eingeschaltet. Ich habe leider nur einen Attiny1614. Sollte aber bei einem Attiny1606 (auf jeden Fall aber bei einem 1616) genau so funktionieren.

#include <Arduino.h>

#if defined(MILLIS_USE_TIMERA0) || defined(__AVR_ATtinyxy2__)
  #error "This sketch takes over TCA0, don't use for millis here.  Pin mappings on 8-pin parts are different"
#endif

constexpr uint8_t OutputPin {PIN_PB1};
volatile unsigned int counter {0};

ISR(TCA0_OVF_vect) {
  ++counter;
  if (100 == counter) {     // 100 counts = 1 Second 
    counter = 0;
    PORTB.OUTTGL = PIN1_bm;
  }
  TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;   // clear interrupt flag
}

// F_CPU is 10 Mhz
// Prescaler = 8
// -> 1.250.000 Mhz
// Counter 0 -> 12499 = 12500 ticks = 0,01 seconds.
//
void setup() {
  PORTB.DIRSET = PIN1_bm;                    // PB1 = output
  PORTB.OUTSET = PIN1_bm;                    // PB1 = HIGH
  takeOverTCA0();                            // This replaces disabling and resettng the timer, required previously.
  TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;   // Enable overflow interrupt
  TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm);   // Count ticks not events
  TCA0.SINGLE.PER = 0x30D3;                       // Upper threshold ticks (12499)
  TCA0.SINGLE.CTRLA =
      TCA_SINGLE_CLKSEL_DIV8_gc | TCA_SINGLE_ENABLE_bm;   // Prescaler (clock frequency and start counter)
}

void loop() {}

Dies hier war mein Code zum testen. LED geht an, blinken tut sie nicht.

#define COIL_PIN A1  // Beispiel: Pin A1 für die Spule
#define timerInterval 2000
bool timerOn = false;

void setup() {
  pinMode(COIL_PIN, OUTPUT);  // Setzen des Pins als Ausgang
  
  takeOverTCA0();                                               // This replaces disabling and resettng the timer, required previously.
  TCA0.SINGLE.CTRLA = 0;                                        // disable TCA0 and set divider to 1
  TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc|0x03;           // set CMD to RESET to do a hard reset of TCA0.
  
  TCA0.SINGLE.CTRLB  = TCA_SINGLE_WGMODE_FRQ_gc;                // Frequency Mode
  TCA0.SINGLE.CTRLA  = TCA_SINGLE_CLKSEL_DIV8_gc;               // Prescaler

  TCA0.SINGLE.INTCTRL = TCA_SINGLE_CMP0_bm;
  
  sei();
  startTimer();
}

void loop() {
  if (!timerOn) {
    startTimer();
  }
}

void startTimer() {
  if ( timerInterval > 0 )
  {
      
      if (timerInterval > 65535) {
          TCA0.SINGLE.CMP0BUF = 65535;
      } else {
          TCA0.SINGLE.CMP0BUF = timerInterval;
      }
     
      TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;                    // enable TCA module
      TCA0.SINGLE.INTFLAGS = TCA_SINGLE_CMP0_bm;                    // Clear interrupt flag
      timerOn = true;                                                              
       
  }
}

void stopTimer() {
  TCA0.SINGLE.CTRLA = 0;  // Stop timer
  TCA0.SINGLE.INTCTRL = 0;  // Disable interrupts
  timerOn = false;
  
}

// Interrupt Service Routine für den Timer
ISR(TCA0_CMP0_vect) {
  digitalWriteFast(COIL_PIN, !digitalReadFast(COIL_PIN));
   
  stopTimer();
    
}

Der Code den ich gepostet habe ist getestet und funktioniert. Vielleicht versuchst Du es erst mal damit ....

Ich habe es jetzt auch mit einem 1604 probiert. Geht auch. Da die 0er Tinys aber einen Timer weniger haben, muss hier

#if defined(MILLIS_USE_TIMERA0) || defined(__AVR_ATtinyxy2__)
  #error "This sketch takes over TCA0, don't use for millis here.  Pin mappings on 8-pin parts are different"
#endif

auskommentiert werden, damit das Programm compiliert werden kann. Wenn das beim 1606 auch funktioniert, kann man mit Deinem Code ja mal weiter schauen...

Es wäre ja besser, du würdest Dir 1616er kaufen :slight_smile:

Und wenn Du

constexpr uint16_t  timerInterval {2000};

statt

#define timerInterval 2000

verwendest, kannst Du Dir dieses Konstrukt:

      if (timerInterval > 65535) {
          TCA0.SINGLE.CMP0BUF = 65535;
      } else {
          TCA0.SINGLE.CMP0BUF = timerInterval;
      }

auch sparen. Zumal das bei meinem Codebeispiel auch

TCA0.SINGLE.PER = timerInterval;  

wäre.

Hallo,

jetzt haben wir genau den Mist mit dem Crosspoting. Eigentlich habe ich darauf keine Lust mehr. Eine Bemerkung zu #10. Deckt sich mit der Antwort im Crossposting. Kennst du den Unterschied der Verknüpfungen/Zuweisungen? =, |= und &=.

Du stoppst den Timer mit:
TCA0.SINGLE.CTRLA = 0; // Stop timer

Danach startest du ihn mit:
TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;

Welche Einstellung des Timers geht dabei verloren?
Wie setzt und löscht man einzelne Bits?

Ach .... auch auf Mikrocontroller.net? Na dann ....

Hallo,

ich sag mal so. Der TO sollte sich entscheiden in welchen Forum er Hilfe haben möchte und sich darauf konzentrieren. Diese Entscheidung dem anderen Forum mitteilen. Das wäre fair für alle. Sein Eingangscode ist nicht komplett falsch. Enthält nur kleine Denkfehler.

Sorry Leute, ich weiß selbst nicht mal mehr welchen ich zuerst geschrieben hatte...
Das dasein dämliche Idee ist , ist einzusehen. Werde mich daran halten.

Das Timer Problem ist wohl gelöst. Ich habe die Zeiten versucht mitzuschreiben und ausgeben zu lassen, das ist sicher nicht hochpräzise, aber die Werte sind Plausibel.

Bleibt noch die Frage warum der Ausgang nicht gesetzt wird.

Erstmal vielen Dank für eure Hilfe!

VG

Hallo,

okay. Mehrere Änderungen stehen an. Du möchtest hoffentlich verstehen was falsch läuft.

Dein bool timerOn muss volatile sein, weil es innerhalb einer ISR geändert wird. Also außerhalb des normalen Programmablaufes. Ein Funktionsaufruf aus der ISR heraus gehört dazu.

In der ISR fehlt das löschen des Interrupts Flags. Das war schon einmal da.

Desweiteren löschst du in stopTimer() mit
TCA0.SINGLE.CTRLA = 0;
das Register, heißt die gesamte Timer Konfig ist futsch.

Deshalb war die Frage als Hausaufgabe wie setzt und löscht man einzelne Bits ernst gemeint. Stichwort CTRLA Enable Bit.

Desweiteren löschst du das Interrupt Control Bit von INTCTRL, setzt es aber nicht wieder. ISR wird generell nicht mehr angesprungen. Das ständige löschen und setzen ist hier überflüssig.

sei() mit Arduino Framework ist nicht unbedingt notwendig. Nur bei Bare Metall Programmierung. Arduino schaltet das immer ein.

Überlege was alles in eine einmalige Änderung bzw. Konfiguration des Timers gehört und was zur Laufzeit immer geändert werden muss.

Wenn du für den Pin und timerInterval ebenfalls passende Datentypen verwendest, statt define, kannste noch die Sicherheitsabfrage von timerInterval optimieren. Welchen Datentyp bzw. Wertebereich kann CMP0 bzw. CMP0BUF annehmen? Von welchem Datentyp ist 65535 der Maximalwert? Stichwort #11.

CTRLESET musst du nicht behandeln, dafür verwendest du schon takeOverTCA0().

Die Zeilen werden schrumpfen ...

Das wäre alles bezogen auf #10.

Wobei man sich das timerOn komplett sparen kann, weil man jederzeit das enable Bit des Timerregisters TCA0 abfragen kann.

Hallo,

wenn er das am Laufen hat, muss man sowieso Nachfragen wofür die bool Variable noch genutzt wird und warum der Timer nicht selbstständig durchlaufen kann. Ich wollte aber nicht zu viel Durcheinander reinbringen. Am Ende können wir immer noch eindampfen. :joy:

Ich gehe doch richtig in der Annahme, dass die Compare Register nur benötigt werden, wenn man ein PWM Signal erzeugen will. Oder?

Darum verstehe ich nicht, warum CMP0BUF verwendet wird. Für den Timer verwendet man PER. So verstehe ich jedenfalls die ursprüngliche Intention des TO wenn ich mir seinen ATMega Beispielcode anschaue.

Oder verstehe ich da etwas falsch?