Elimination of jitter in a train of pulses

Hello everyone. This is my first practical project with an Arduino UNO, and the truth is that I have not touched anything easy :frowning: I need to convert my Arduino into a 14-bit encoder driver for this I need to generate a 14-pulse train to A fixed frequency greater than 30 Khz and to establish between each train a dead time of 50 microseconds, or until a little more.
In all the variants that I have realized I have stumbled on my oscilloscope with an annoying jitter or phase shift in the wave, which should be as clean as possible.
This was my first crude variant:

void setup() {
  pinMode(11, OUTPUT);
}
void loop() {
  for (int i=0; i<15; i++){
 digitalWrite(11, HIGH);
 delayMicroseconds(12.5);
 digitalWrite(11, LOW);
 delayMicroseconds(12.5);
}
 delayMicroseconds(50);
}

Then I tried to solve it using the timer to make the wave, and there seems to be a time offset product to stop and summarize the timer to make up the dead time. I use the TimerOne library which I download at: GitHub - PaulStoffregen/TimerOne: TimerOne Library with optimization and expanded hardware support

#include <TimerOne.h>
const byte CLOCKOUT = 11;
volatile long counter=0;

void setup() {
  Timer1.initialize(15);         //Cada 15 microsegundos cambio el estado del pin en la funcion onda dando un periodo
  Timer1.attachInterrupt(Onda);  //de 30 microsegundos
  pinMode (CLOCKOUT, OUTPUT);
  digitalWrite(CLOCKOUT,HIGH);
}
void loop() {
  if (counter>=29){               //con 29 cambios logro los pulsos que necesito
     Timer1.stop();               //Aqui creo el tiempo muerto, el cual esta debe estar en HIGH
     digitalWrite(CLOCKOUT,HIGH);
     counter=0;
     delayMicroseconds(50);
     Timer1.resume();
     }
}
void Onda(){
  digitalWrite(CLOCKOUT, digitalRead(CLOCKOUT) ^ 1);   //Cambio el estado del pin
  counter+=1;
}

I even went a little further on the next variant and implemented two timers. Timer2 generates a wave using PWM and Timer1 detects me the 14 pulses and then configures the timer2 so that it generates for a time a wave with a useful cycle of 100% (state in HIGH, it is the dead time), but it does not work:

#include <TimerOne.h>
volatile int counter=0;
volatile int counterD=0;
volatile int counterU=0;
volatile char AS=0;
volatile int counterP=0;
void setup() {
  
 pinMode(11, OUTPUT);
 pinMode(11, INPUT);
 Timer1.attachInterrupt(DetctrPlsos);
 Serial.begin(57600);
 Timer1.initialize(4);
 digitalWrite(11,HIGH);
 
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);     //Regula la frecuencia PWM en pin 11 a 31KHz
  TCCR2B = _BV(WGM22) | _BV(CS22);
  OCR2A = 2;
}

void loop() {
}

void DetctrPlsos(){
  if (AS==0){                                    //As me dice si estoy generando los 14 pulsos o si estoy en el tiempo muerto
    if ((digitalRead(11)==0) && (counterD==0)){      //Leo la misma salida en la cual genero la onda para cambiar de Low a High, counterD y counterU
  counterD +=1;                                   //los uso para no entrar mas de una vez en un mismo estado, ya que timer1 va mas rapido en este caso que timer2                                                  
  counter +=1;                                    //counter me cuenta cuantos pulsos llevo                                            
  counterU =0;                                     
    }                                            
   else if ((digitalRead(11)==1) && (counterU==0)){
    counterU +=1;
    counterD =0;
   }
    if (counter==14){                          //Si ya se generaron los 14 pulsos cambio la configuracion del timer2 para crear tiempo muerto

   counter=0;
   
   TIMSK2 &= ~(1<<TOIE2);                      //deshabilito el timer 2 para configurarlo
   
   TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
   TCCR2B = _BV(CS22);
   OCR2A = 255;                    //ciclo util de 100% es un estado HIGH, es el tiempo muerto
   OCR2B = 50;

   TIMSK2 |= (1<<TOIE2);          //habilito timer2
   
   AS=1;                          //cuando entre de nuevo al timer1 contara el tiempo muerto
     }
   
  }
 if (AS==1){         
  counterP+=1;              //ahora cuento en timer1 el tiempo que la onda estara en HIGH 
  if (counterP==12){        //cd haya transcurrido ese tiempo vuelvo a generar la onda con el timer2
     counterP=0;
     AS=0;
     
      TIMSK2 &= ~(1<<TOIE2);
      
     TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
     TCCR2B = _BV(WGM22) | _BV(CS22);
     OCR2A = 2;

     TIMSK2 |= (1<<TOIE2);
  }
}

 }

I hope you find a solution. Apologies for the comments in Spanish on the variants, I did not have time to translate them. Thank you very much

Your first attempt

void setup() {
  pinMode(11, OUTPUT);
}
void loop() {
  for (int i=0; i<15; i++){
 digitalWrite(11, HIGH);
 delayMicroseconds(12.5);
 digitalWrite(11, LOW);
 delayMicroseconds(12.5);
}
 delayMicroseconds(50);
}

was not a bad idea but

delayMicroseconds(12.5);have you read anywhere this takes a floating point value as parameter?

You should not rely on the loop looping if you want to maintain a clean signal. Use a while loop inside the loop(), and use port manipulation to be way quicker than what you get with digitalWrite. The for loop will introduce jitter, either repeat manually 15 time the code to have perfect timing or take into account the cost of the loop in the second delayMicroseconds

Measure with your oscilloscope the cost of jumping back to to top of the while and fine tune the 50 in the last delayMicroseconds(50) to actually get a 50 microsecond delay.

I think the delayMicroseconds function is the cause of jitter. I worked on this version using the timer1, but in the end I should use delayMicroseconds to create dead time

#include <TimerOne.h>
const byte CLOCKOUT = 11;
volatile byte counter=0;

void setup() {
  Timer1.initialize(15);         //Every 15 microseconds change the state of the pin in the wave function giving a period of 30 microseconds 
  Timer1.attachInterrupt(Onda);  
  pinMode (CLOCKOUT, OUTPUT);
  digitalWrite(CLOCKOUT,HIGH);
}
void loop() {
  if (counter>=29){               //With 29 changes I achieve the amount of pulses I need
     Timer1.stop();               //Here I create the dead time, which must be in HIGH
     PORTB = B00001000;
     counter=0;
     delayMicroseconds(50);
     Timer1.resume();
     }
}
void Onda(){
  PORTB ^= B00001000;   //Change pin status
  counter+=1;
}

There's even jitter in this version

Well executing code takes time... 100 clock cycles is 6.25 microseconds

The loop() introduces delays
Interrupts to maintain millis() and other stuff takes time
A for loop takes time to loop
...

So your code if you want great timing should take execution time into account

What does this mean?

fixed frequency greater than 30 Khz

how much greater?

cgalcala:
I think the delayMicroseconds function is the cause of jitter. I worked on this version using the timer1, but in the end I should use delayMicroseconds to create dead time

#include <TimerOne.h>

const byte CLOCKOUT = 11;
volatile byte counter=0;

void setup() {
  Timer1.initialize(15);        //Every 15 microseconds change the state of the pin in the wave function giving a period of 30 microseconds
  Timer1.attachInterrupt(Onda); 
  pinMode (CLOCKOUT, OUTPUT);
  digitalWrite(CLOCKOUT,HIGH);
}
void loop() {
  if (counter>=29){              //With 29 changes I achieve the amount of pulses I need
    Timer1.stop();              //Here I create the dead time, which must be in HIGH
    PORTB = B00001000;
    counter=0;
    delayMicroseconds(50);
    Timer1.resume();
    }
}
void Onda(){
  PORTB ^= B00001000;  //Change pin status
  counter+=1;
}




There's even jitter in this version

Don't bother with port manipulation crap, just use the Toggle on Compare Match function of the OC1A pin.

also, how close to 50 us do you really need? If you're fine with 45 us, put the counting stuff in the interrupt and disable the output pin function when it's time to delay.

The jitter keeps popping up, I make some modifications and it does not seem to work:

#include <TimerOne.h>
const byte CLOCKOUT = 11;
volatile byte counter=0;
volatile int counter2=0;

void setup() {
  Timer1.initialize(30);         
  Timer1.attachInterrupt(Onda);  
  pinMode (CLOCKOUT, OUTPUT);
  digitalWrite(CLOCKOUT,HIGH);
}
void loop() {
}
void Onda(){
  if (counter<30){                    //I create the pulses here                       
     PORTB ^= B00001000;   
     counter+=1;
     }
   if (counter>=30){                    // I create the dead time here                        
     PORTB = B00001000;
     counter2+=1;
     if (counter2 >=20){            //go back to create the pulses
      counter=0;
      counter2=0;
     }
     }
}

:o :o :o Jitter appears by creating dead time

The Adafruit Neopixel library also requires precise timing of pulses - they do it using assembler, using strings of NOP instructions that have a known timing.

Hallo,

your cross postings in the forum are not nice ... thats a special note :wink:

your jitter problem.
Better it, you use the CTC Mode of the Timer and add up a value every time in the ISR for new compare value.
With the ISR toogle a Pin.

cgalcala:
The jitter keeps popping up, I make some modifications and it does not seem to work:

I made a suggestion. Try following it.

I also asked a question. Try answering it.

Sorry for being late for answering your question Jiggy-Ninja. The dead time does not have to be exact, it must only be the same between each train of pulses, to avoid the jitter. Here is a variant that I made guiding by the tips of all of you. I do it in CTC mode and Output compare match. There is still jitter, although there is less. I would appreciate any improvement. Thank you all

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

volatile int counter=0;

void setup() {
 pinMode(11, OUTPUT);
 Serial.begin(57600);
 digitalWrite(11,HIGH);
 
  noInterrupts(); 
  
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2 = 0;
  
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(WGM22) | _BV(CS22);
  OCR2A = 4;
  TIMSK2 |= (1 << OCIE2A);
  
  interrupts(); // enable all interrupts
  
}
ISR (TIMER2_COMPA_vect){   //When the counter is 31 I change the comparison register to introduce the same delay in each pulse train. On the next increase I charge the register with the value that makes up the pulses
   counter+=1;
   if (counter==31){
    OCR2A = 50;
   }
   if (counter>31){
    OCR2A = 6;
    counter=0;
   }
}

void loop() {
  Serial.print(counter);
  Serial.print(" ");
}
  • using Serial probably does not help with the jitter as it's using interrupt as well

  • counter could be a byte, that will make maths faster on a 8 bit microprocessor

  • an else in the ISR between the two if would also make the ISR code faster

I corrected everything you told me J-M-L and I get the same result :confused: :confused: :confused:

can you show a picture of your oscilloscope?

everything being timer based it should be quite stable. can you add a while(true); in the loop() to avoid launching the loop over and over again?

These are two photos:
Oscilloscope in stop: What should i have

Oscilloscope running: what I have :confused:

What is observed in the scope is the second photo(The running wave, the signal is not both high and low at the same time ), the first is seen when I stop the scope

Oscilloscope in stop.jpg

Oscilloscope running.jpg

photos seem invisible :frowning:

I would refer to the datasheet and modify code from here.

It would be possible to lower the frequency, set the dead time and use complimentary or non complimentary outputs (jitter free). Would need to also count the pulses to enable/disable the PWM at count = 14.

Another (unique) possibility is to use SPI with no delay between transfers. Then the data itself could create the necessary pulse width, deadtime and frequency. The second channel could be accomplished with direct port manipulation (have about 11 cycles to spare for additional code between each byte transfer).