Behandeln von Timerüberläufen und Vergleichen von Interrupts in ATMega328 (Arduino)

Ich versuche, den PMW-Modus an mehreren Pins in der Software zu simulieren, indem ich einen Timer2 steuere.

Ich verwende den maximalen Prescaler-Wert, um ungefähr 60 Impulse pro Sekunde zu erhalten, wenn der Timer auf seinen Maximalwert zählt. Der Wert von OCR2A wird verwendet, um den tatsächlichen Moment innerhalb des vollen Zeitgeberzyklus auszulösen. Der Überlauf-Interrupt wird verwendet, um die LED (PIN13) einzuschalten, und der Vergleichs-Interrupt wird verwendet, um die LED zurückzusetzen.

Wenn ich es ausführe, sehe ich, dass die LED blinkt, aber sehr schwach ist und ihre Helligkeit nicht vom CUT_OFF-Wert beeinflusst wird. Wenn ich die LED im Vergleichsinterrupt ausschalte und die LED im Überlauf einfach umschalte, sehe ich, dass sie mit einer angemessenen Geschwindigkeit blinkt, die dem an der seriellen Schnittstelle gedruckten Endbericht entspricht (Anzahl der Interrupts in 5 Sekunden). Der Abschlussbericht zeigt auch, dass beide Interrupts korrekt oft aufgerufen werden.

Hier ist der Code, den ich verwende:

#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_PORT (13)

#define CUT_OFF (100)

volatile unsigned long time1 = 0;
volatile unsigned long time2 = 0;

ISR(TIMER2_OVF_vect) {
  PORTB |= 0x20;
  time1++;
}

ISR(TIMER2_COMPA_vect) {
  PORTB &= ~0x20;
  time2+=2;
}

void startTimer() {
  cli();
  // enable timer interrupt overflow + reg a
  TIMSK2 = _BV(OCIE2A) | _BV(TOIE2);
  // counter
  TCNT2  = 0x00;
  // cut off value
  OCR2A  = CUT_OFF;
  // mode - normal + prescaler 1024
  TCCR2A = 0x00;
  TCCR2B = _BV(CS22) | _BV(CS21) | _BV(CS20);
  // async mode off
  ASSR  &= ~(_BV(AS2) | _BV(EXCLK));
  sei();
}

void stopTimer() {
  cli();
  // disable interrupts
  TIMSK2 = 0x00;
  // mode (disconnect clock source)
  TCCR2A = 0x00;
  TCCR2B = 0x00;
  // async mode off
  ASSR  &= ~((1<<AS2) | (1<<EXCLK));
  sei();
}

void blinkLed(uint8_t times) {
  for(uint8_t i=0;i<times;i++) {
    digitalWrite(LED_PORT, HIGH);
    delay(500);
    digitalWrite(LED_PORT, LOW);
    delay(500);
  }
}

void setup() {
  pinMode(LED_PORT, OUTPUT); 
  Serial.begin(9600);
}

unsigned long iterations = 0;

#define MAX_ITERATIONS (1000000)

void loop() {
  blinkLed(3);
  startTimer();
  delay(5000);
  stopTimer();
  blinkLed(2);

  Serial.println("Iterations finished!");
  Serial.print("Timer1=");
  Serial.println(time1);
  Serial.print("Timer2=");
  Serial.println(time2);

  while(1);  
}

Stimmt etwas mit meinem Verständnis nicht, wenn Interrupts aufgerufen werden:

  • COM2A - wenn wir den CUT_OFF-Wert erreichen
  • OVF, wenn der Zähler 255 erreicht und auf 0 springt

oder gibt es ein Problem mit dem Einrichten des Timers/oder dem Umgang mit Interrupts?

Update: Könnte das Problem in der Verwendung von digitalWrite() in Interrupts liegen? Update2: Die Umstellung von digitalWrite auf direkte PORTB-Bit-Manipulation erzeugt wahrscheinlich beim ersten Interrupt ein einzelnes Blinken und anschließend eine sehr schwache LED, sodass dies nicht das einzige Problem ist.

Die blinkende PS-LED ist nur ein Test, daher ist die Verwendung des integrierten PWM keine Option.

Haben Sie Atmels App-Hinweis zu AVR-Timern gelesen? atmel.com/dyn/resources/prod_documents/doc2505.pdf
@ JobyTaffey Ich habe den oben genannten Hinweis gelesen, kann aber immer noch keine Erklärung für das Verhalten finden, das ich sehe. Beide Interrupts werden aufgerufen, die Interrupt-Periode hängt nicht vom Ausgangswert des Vergleichs ab, aber das Timing zwischen den Interrupts ist falsch.

Antworten (1)

Ich bin mir nicht ganz sicher, warum , aber die Verwendung des schnellen PWM-Modus anstelle des normalen Modus scheint dieses Problem zu beheben.

Verwenden Sie denselben Code, aber ersetzen Sie ihn

TCCR2A = 0x00;

mit

TCCR2A = _BV(WGM20)|_BV(WGM21);

Ich würde auch fragen, warum Sie den asynchronen Modus sowohl beim Betreten als auch beim Verlassen des PWM-Modus zu löschen scheinen, aber es scheint nichts zu beeinflussen.

Die Beschreibung des PWM-Modus gibt ausdrücklich die Verwendung von 2 Interrupts an, um Momente zu verfolgen, wenn die nächste Periode beginnt und wenn der Ausgangswert umgedreht werden sollte. Aber es schaltet OCRxn in den doppelt gepufferten Modus und Sie können es nur am Ende des Zyklus ändern. Was ich tun wollte, ist, einige unregelmäßige Momente innerhalb einer Periode abzufangen, indem ich das Vergleichsregister neu lade und den Handler nach jedem Zyklus zurücksetze. Daher schließt es den PWM-Modus für mich vollständig aus. Sieht so aus, als würde ich zurückgreifen, um Interrupts zu vergleichen und das Neuladen von Registern zu vergleichen, aber den Überlauf-Interrupt vergessen und nur den TOP-Wert laden, sobald alle Zwischenschritte gefiltert sind.
@Brog, niemand hat die Chance genutzt, einen Repräsentanten zu machen, um zu erklären, warum das das Problem behebt, also als erster, der antwortet, hier ist ein Repräsentant.
@Brog, ich habe den asynchronen Modus bei der Eingabe von pwm gelöscht, nur um sicherzustellen, dass er auf etwas Vernünftiges eingestellt ist, da es nicht einfach ist, herauszufinden, in welchem ​​​​Zustand Arduino-Bibliotheken sie verlassen können. Am Ende sah ich mir Servo-Bibliotheken an und sie scheinen das Register mehrmals innerhalb eines Zyklus manuell neu zu laden, wobei sie den Überlauf-Interrupt ignorieren, wie ich es geplant hatte. Sieht so aus, als hätten sie es nicht geschafft, auch mit Überlauf zu arbeiten.