Servo-Testprogramm Variante 3 mit Timer2-Interrupt

Sowohl die Servo-Library, als auch die gezeigten Testprogramme Variante 1a, 1b und 2 nutzen den Timer1. Da der Timer1 aber auch in anderen Libraries Verwendung findet, kommt es hier bei gleichzeitiger Nutzung dieser Libraries zu "Timer-Konflikten".


Wie schon in Variante gezeigt, werden die drei Timer des Arduino Uno von verschiedenen Funktionen und/oder Libraries genutzt:

  • Timer0:   8 Bit; Verwendung z.B. in den Funktionen millis(), micros(), delay()
  • Timer1: 16 Bit; Verwendung z.B. in der Servo-, VirtualWire- und TimerOne-Library
  • Timer2:   8 Bit; Verwendung z.B. in der Funktion tone()


Wie man sieht, wird der Timer0 von Arduino-Funktionen genutzt, auf die man, zumindest was die Funktionen millis() und micros() betrifft, nicht verzichten kann. Timer1 wird hingegen von diversen Libraries benutzt.

Daher habe ich das Testprogramm in der Variante 3 geschrieben, dass für die Servo-Steuerung den 8 Bit-Timer2 nutzt.

(Timer2 wird meines Wissens für die tone()-Funktion benötigt, weitere Nutzungen für Arduino-Funktionen sind mir derzeit nicht bekannt. Für diesbezügliche Hinweise wäre ich dankbar!)


Wie durch entsprechendes Setzen der Register am Beispiel Timer1 ein Timer-Interrupt ausgelöst werden kann, findet ihr hier: Timer-Interrupt

Allerdings gibt es, abgesehen von der Bit-Breite 8 oder 16 Bit, auch noch andere Unterschiede zwischen den Timern1-3, die dem Datenblatt des Atmega328P entnommen werden können.


Mit dem 8 Bit-Timer2 ergibt sich allerdings folgendes Problem:

Mit der bereits unter Timer-Interrupt gezeigten Formel

kommt man mit einer CPU-Frequenz von 16 MHz, dem größtmöglichen Vorteiler von 1024 und einer gewünschten Interruptfrequenz von 50 Hz (= 20 ms Periodendauer) auf folgenden Vergleichswert:


Vergleichswert = (16.000.000 / (1024 * 50)) - 1 = 311,5


Dieser Vergleichswert ist allerdings zu groß, als dass er im ebenfalls 8 Bit-Register OCR2A gespeichert werden kann. Da aber ein Analog-Servo eine PWM-Frequenz von 50 Hz (= 20 ms Periodendauer) benötigt, mache ich nun folgendes:

Ich ermittle einen Vergleichswert für eine Interruptfrequenz von 100 Hz (= 10 ms Periodendauer):


Vergleichswert = (16.000.000 / (1024 * 100)) - 1 = 155,25


Dieser Vergleichswert kann nun im 8 Bit-Register OCR2A gespeichert werden. Da mit diesem Vergleichswert nun der Interrupt alle 10 ms ausgelöst wird und in die Interrupt-Serviceroutine verzweigt, gebe ich den PWM-Impuls nur bei jedem 2. Aufruf der Serviceroutine aus, womit der PWM-Impuls wiederum eine Periodendauer von 20 ms hat.

Dies geschieht mit der in der Serviceroutine als "static" deklarierte Variable "zaehler". Die Variable wird bei jedem Interrupt um 1 erhöht, hat sie den Wert 2 erreicht, wird der PWM-Impuls ausgegeben und die Variable "zaehler" wieder auf Null gesetzt.


Oszillogramm des PWM-Signals mit einer Impulsbreite von 2 ms:

Horizontal: 5 ms/Div. => Periodendauer = 20 ms => PWM-Frequenz = 50 Hz

Vertikal    : 2 V/Div.


Abbildung 2-3-1: Oszillogramm eines PWM-Signals mit einer Periodendauer von 20 ms


Programmbeispiel:

//Servo Testprogramm
//Test_Servo_IRQ_Timer2.ino
//Code fuer Arduino
//Author Retian
//Version 1.0


#define servoPin 2 //Servo auf Pin 2
#define potiPin 2 // Potentiometer auf Pin A2

int pwmTime = 0;
int potiWert = 0;
int pwmTimeMax = 2460; //maximale Impulszeit in Mikrosekunden
int pwmTimeMin = 560; //minimale Impulszeit in Mikrosekunden


void setup() {
  Serial.begin(115200);
  pinMode(servoPin, OUTPUT);
  digitalWrite(servoPin, 0);
  cli(); // Loesche globales Interruptflag
  TCNT2 = 0; //Timer Counter 2
  TCCR2A = 0; //Loesche Timer Counter Controll Register A
  TCCR2B = 0; //Loesche Timer Counter Controll Register B
  OCR2A = 155; //Setze Output Compare Register A
  // Setze CS20, CS21 und CS22 - Clock Select Bit 10,11,12 (Prescaler 1024)
  TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
  //CTC-Mode ein
  TCCR2A |= (1 << WGM21); // CTC-Mode (Clear Timer and Compare)
  //Timer/Counter Interrupt Mask Register
  TIMSK2 |= (1 << OCIE2A); //Output Compare A Match Interrupt Enable
  sei(); //Setze globales Interruptflag
}


void loop() {
  potiWert = analogRead(potiPin);
  pwmTime = map(potiWert, 0, 1023, pwmTimeMin, pwmTimeMax);
  Serial.print("Potiwert: ");
  Serial.print(potiWert);
  Serial.print(" ");
  Serial.print("Pulslaenge: ");
  Serial.print(pwmTime);
  Serial.println(" us");
}


ISR(TIMER2_COMPA_vect) //Interrupt-Serviceroutine
{
  static byte zaehler = 0;
  zaehler++;
  //Nur bei jedem 2. Durchlauf der Serviceroutine wird der Servo-Impuls ausgegeben!
  if (zaehler == 2)
  {
    digitalWrite(servoPin, 1);
    delayMicroseconds(pwmTime);
    digitalWrite(servoPin, 0);
    zaehler = 0;
  }
}