Probleme mit Jitter bei Arduino UNO Boards

Hallo,
beschäftige mich gerade wegen meiner Bachelorarbeit mit dem Arduino UNO,
momentan benötige ich nur ein Rechtecksignalburst von 6-8 Impulsen das sich immer wider wiederholt.
diese werden verstärkt und auf einen Ultraschalwandler mit 40kHz gegeben.
Problem ist das das Rechtecksignal jittert, auf dem Oszi sieht man wie das Rechtecksignal manchmal richtig ist und ab und zu aber auch verschoben und falsch ausgegeben wird. habe 2 verschiedene Arduino Uno Bords versucht.

Habe das Jittern als Video mal hochgeladen

An was liegt das ? und kann ich das irgendwie beheben ?
Unten sieht man den einfachen Quellcode !

int outPin = 13;                 // digitaler Pin 13

void setup()
{
  pinMode(outPin, OUTPUT);      // setzt den digitalen Pin als Ausgang
}
void loop()
{
  for(int i=0;i<7;i++){
    digitalWrite(outPin, HIGH);   // schaltet den Pin an
    delayMicroseconds(7);        // hält für 7 Mikrosekunden an      
    digitalWrite(outPin, LOW);    // schaltet den Pin aus
    delayMicroseconds(10);        // hält für 10 Mikrosekunden an
  }
  delay(20); // ms
}

Gruß

Microseconds kriegst du so nur in 4er Schritten. Und digitalWrite() selbst dauert auch schon ein bisschen.

Mit Timern sollte man 40 kHz genauer hinkriegen, und irgendwie auch 8 Pulse davon alle 20 ms.
Ich würde Richtung "IR - Fernbedienungscode senden" suchen, die 38 kHz in 40 ändern :wink: und dann rauskriegen wie das mit der Codierung zu vereinfachen ist. Da ist deine Anforderung ja deutlich einfacher.

Wie gesagt Timer1 oder Timer2 verwenden. Der 8 Bit Timer2 reicht bei diesen kurzen Taktzeiten.

Wenn symmetrische Impulse reichen, kann man den CTC Modus verwenden. Dann kann man das Schalten des Pins per Hand im Compare Match Interrupt machen und gleichzeitig dort die Anzahl der Impulse zählen. Und nach x Impulsen den Timer wieder deaktivieren.

Für nicht-symmetrische Impulse gibt es Fast PWM.

Siehe hier:
http://maxembedded.com/2011/06/22/introduction-to-avr-timers/
http://maxembedded.com/2011/06/28/avr-timers-timer1/
http://maxembedded.com/2011/06/29/avr-timers-timer2/
http://maxembedded.com/2011/07/14/avr-timers-ctc-mode/
http://maxembedded.com/2011/08/07/avr-timers-pwm-mode-part-i/
http://maxembedded.com/2012/01/07/avr-timers-pwm-mode-part-ii/

Achtung: es gibt beim Atmega328 ein, zwei kleinere Unterschiede zu dem da. Vor allem hat jeder Timer ein eigenes Interrupt Masken Register TIMSKn, anstatt ein gemeinsames Register für alle Timer. Im Zweifelsfall ins Datenblatt schauen.
Aber der Rest ist glaube ich identisch.

Das schalten von Pin 13 selbst geht so in zwei Taktzyklen, statt den 3-4µs die digitalWrite() braucht:
PINB |= _BV(5);
Das toggelt bei jedem Aufruf den Pin.

Oder jeweils eine 0 oder 1 auf das Ausgangsregister PORTB schreiben:
PORTB |= _BV(5); //Pin auf 1 setzen
PORTB &= ~_BV(5); //Pin auf 0 setzen

HDR_Michel:
Problem ist das das Rechtecksignal jittert, auf dem Oszi sieht man wie das Rechtecksignal manchmal richtig ist und ab und zu aber auch verschoben und falsch ausgegeben wird. habe 2 verschiedene Arduino Uno Bords versucht.

Habe das Jittern als Video mal hochgeladen
jitter - YouTube

An was liegt das ? und kann ich das irgendwie beheben ?

Das liegt im wesentlichen an ausgeführten "Interrupts" im System, die den laufenden Code an jeder Stelle zwecks Behandlungs des Interrupts unterbrechen können. Wenn Du in Deinem Code (oder verwendeten Libraries) keine Interrupts aktivierst, kämen beispielsweise auch der Timer-Interrupts in Frage, mit dem die Arduino-Software standardmäßig die Zeit zählt, oder bei aktivierter serieller Schnittstelle die Interrupts zum Senden und Empfangen von seriellen Zeichen. Die Laufzeit Deines Codes (auch delay-Zeiten) verlängert sich dann jeweils um die Dauer der Interrupt-Serviceroutine, die zwischendurch läuft.

Ein exakteres Timing von zeitkritischem Code erreichst Du zum Beispiel, indem Du die Interrupts während der Zeit blockierst, die so genau bestimmt werden soll. Beispiel:

void loop()
{
  noInterrupts();
  // zeitkritischer Code hier
  interrupts();
  // sonstiger Code hier
}

Ansonsten hast Du hier ja schon Hinweise zur Programmierung von Timern bekommen.

Hallo,
ich nutze gerne diesen Code um einen Rechteck zu erzeugen

// Benutzt wird in diesem Beispiel der Timer/Counter 2 (TCNT2)
// byte outpin = 11; // das ist Pin 17 am ATMega328P (PB3, MOSI/OC2A/PCINT3)
byte outpin = 10; // das ist Pin 23 am ATMega2560 (PB4, OC2A/PCINT4)

void setup () {

  TCCR2B = B00000001;            
  // Bits 2 1 0 = 0 0 1 : No prescaling, Teiler = 1 
  // andere Werte für Teiler: 8, 32, 64, 128, 256, 1024

  TCCR2A = B01000010;  
  // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
  // | 0 | 1 | 0 | 0 | - | - | - | - | : Toggle OC2A on Compare Match. siehe (*)

  // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
  // | - | - | - | - | 0 | 0 | 1 | 0 | : Clear Timer on Compare Match
  
  OCR2A  = 79;  // 79 entspricht 100kHz
  // jedesmal, wenn der Timer-2 der Wert x erreicht, wird der Ausgang invertiert
  // Ausgangsfrequenz = 16000000 / 2 / (OCR2A + 1)
  // Wert 255 ergibt 31250 Hz, kleinere Werte ergeben größere Frequenzen
 
  pinMode(outpin, OUTPUT);       // Pin auf Ausgang setzen   
}

void loop () {
}

Gruß
Reinhard

Das wird lesbarer wenn du dir angewöhnst das Bit Value Makro und die Namen der Bits zu verwenden :slight_smile:

TCCR2A = _BV(COM2A0) | _BV(WGM21); 
TCCR2B = _BV(CS20);

Die Bit Makros enthalten die Bit-Nummern der Bits und das BV Makro macht (1 << Nummer), also schiebt das entsprechende Bit n mal nach links. Durch das Oder werden dann die entsprechenden Bits gesetzt.

40kHz sollte OCR2A = 199 sein

Das COM2A0 Bit hat den Vorteil, dass Pin 11 (auf dem UNO) automatisch getoggelt wird. Man könnte das mit dem Compare Match Interrupt verbinden um die Anzahl der Impulse zu zählen. So wird der Pin in Hardware geschaltet und nicht von anderen Interrupts verzögert, aber man bekommt das Schalten trotzdem mit und kann es zählen. Wenn man das Schalten per Hand in der ISR macht, könnte das eventuell immer noch nicht passen, da sich Interrupts nicht gegenseitig unterbrechen. Aber beim Zählen macht es nichts wenn das später geschieht.

So ähnlich vielleicht (nicht getestet):

const int NUMBER_OF_PULSES = 8;
volatile boolean timerFinished;

void setup()
{
   TCCR2A = _BV(COM2A0) | _BV(WGM21);     //Automatisches Toggeln von Pin 11 bei Compare Match. CTC Modus.
   TIMSK2 = _BV(OCIE2A);                  //Output Compare Match Interrupt Enable, Kanal A
   OCR2A = 199;                           //Output Compare Register A
   pinMode(11, OUTPUT);
}

void loop()
{
     static boolean timerRunning;

     if(timerRunning == false)
     {
          TCNT2 = 0;              //Timer/Counter Register sicherheitshalber auf 0 setzen
          TCCR2B = _BV(CS20);     //Timer starten. CS20 = Clock Select, Prescaler 1
          timerRunning = true;
     }
     else
     {
           if(timerFinished == true)
           {
                  timerFinished = false;
                  timerRunning = false;

                  delay(20);
           }
     }
}

ISR(TIMER2_COMPA_vect)
{
      static byte counter;

      counter++;
      if(counter == NUMBER_OF_PULSES * 2)
      {
            TCCR2B = 0;              //Timer anhalten
            counter = 0;
            timerFinished = true;    //Signal an loop()
      }
}

Auf timerRunning könnte man genaugenommen verzichten, da es nichts macht wenn CS20 mehrfach gesetzt wird.

Leute ihr seit alle Super ! :slight_smile: :slight_smile: :slight_smile:

@Serenifly
Danke für den Code, echt super !
Funktioniert wunderbar !

Habe den Code ein bisschen abgeändert um ein PWM Signal zu bekommen, da meine nachfolgende Transistorschaltung nicht so sauber schaltet wie der Ausgang des Arduino. Deshalb schreibe ich abwechselnd 2 verschiedene Comparewerte in das Compareregister :slight_smile:

const int NUMBER_OF_PULSES = 8;
volatile boolean timerFinished;
volatile boolean compareSwitch;


void setup()
{
   TCCR2A = _BV(COM2A0) | _BV(WGM21);     //Automatisches Toggeln von Pin 11 bei Compare Match. CTC Modus.
   TIMSK2 = _BV(OCIE2A);                  //Output Compare Match Interrupt Enable, Kanal A
   OCR2A = 229;                           //Output Compare Register A
   pinMode(11, OUTPUT);
   compareSwitch=true;
}

void loop()
{
     static boolean timerRunning;

     if(timerRunning == false)
     {
          TCNT2 = 0;              //Timer/Counter Register sicherheitshalber auf 0 setzen
          TCCR2B = _BV(CS20);     //Timer starten. CS20 = Clock Select, Prescaler 1
          timerRunning = true;
     }
     else
     {
           if(timerFinished == true)
           {
                  timerFinished = false;
                  timerRunning = false;

                  delay(20);
           }
     }
}

ISR(TIMER2_COMPA_vect)
{
      if(compareSwitch==true)
      {
        OCR2A = 170;
        compareSwitch=false;
      }
      else
      {
        OCR2A = 229;
        compareSwitch=true;
      }
  
      static byte counter;
      counter++;
      if(counter == NUMBER_OF_PULSES * 2)
      {
            TCCR2B = 0;              //Timer anhalten
            counter = 0;
            timerFinished = true;    //Signal an loop()
      }
}

Wie gesagt, wenn du PWM willst gibt es auch richtige PWM Modi, die diese Umschaltung automatisch für dich machen. Dafür gibt es bei MaxEmbedded auch Code in Part II (für Timer0, aber das kann man anpassen). Mit PWM per Hand kenne ich mich aber nicht aus.

Aber wenn es so geht, kannst du es auch lassen :slight_smile:

Wobei compareSwitch nicht global sein muss, da das nur in der ISR verwenden wird. Das kannst du genau wie counter lokal und static machen