How to create train of TLL pulses of arbitrary widths (non-integer microsec)?

Hello,

My project requires the generation of trains of pulses of varying pulse widths, ranging from 500ns to 50us. I have a genuino uno. I am using a Tektronix TDS2014C to validate pulses.

My first attempt is to use a code that I found here in the forum that creates the fastWrite() function - FastWrite - Programming Questions - Arduino Forum

My code looks like this:

int w = 1;                        //in microseconds
int on;
int off;

#define fastWrite(_pin_, _state_) ( _pin_ < 8 ? (_state_ ?  PORTD |= 1 << _pin_ : PORTD &= ~(1 << _pin_ )) : (_state_ ?  PORTB |= 1 << (_pin_ -8) : PORTB &= ~(1 << (_pin_ -8)  )))
// the macro sets or clears the appropriate bit in port D if the pin is less than 8 or port B if between 8 and 13

void setup() {
 pinMode(12, OUTPUT);  
 pinMode(13, OUTPUT);          
 Serial.begin(57600);
}

void loop() {

  noInterrupts(); //disables interrupts for crucial parts of code
     
   while(1) {
    
      on = w;
      off = w;

      fastWrite(12, HIGH);       // set Pin high
      fastWrite(13, HIGH);       // set Pin high
      delayMicroseconds(on);      // waits "on" microseconds
      
      fastWrite(12, LOW);        // set pin low
      fastWrite(13, LOW);       // set Pin high
      delayMicroseconds(off);      //wait "off" microseconds
   
   }

   interrupts(); //allows interrups to happen again
 
}

This approach gives me the following 2 problems:

  1. I cannot go below 1us

  2. I cannot have pulse widths of lengths other than integers. Eg, it is not possible to get a 5.5us pulse (since delayMicroseconds only takes integers)

To solve the problems I tried to dig a bit deeper and I read a bit about assembly commands (Arduino Playground - AVR - tbh this goes beyond my knowledge).

I understand that the 'asm("nop");' command should pause the execution for 62.5 ns (1/16GHz).

So to get 500ns pulse width, in principle I would need to use 8 times the command when pin is HIGH and 8 times when the pin is LOW. However, when I do this I get different pulse widths that the timing doesn't match.

Just by trial and error, I managed to get 500ns pulse width with this code:

double w = 0.5;
int on;
int off;

int whichpin=10;

int var = 0;
int x = 0;
int pulseNumber = 1000;

#define fastWrite(_pin_, _state_) ( _pin_ < 8 ? (_state_ ?  PORTD |= 1 << _pin_ : PORTD &= ~(1 << _pin_ )) : (_state_ ?  PORTB |= 1 << (_pin_ -8) : PORTB &= ~(1 << (_pin_ -8)  )))
// the macro sets or clears the appropriate bit in port D if the pin is less than 8 or port B if between 8 and 13


void setup() {

  pinMode(10, OUTPUT);  

  Serial.begin(9600);
  Serial.println("start...");
  
}

void loop() {

   noInterrupts(); //disables interrupts for crucial parts of code

   while(1) {
    
      fastWrite(whichpin, HIGH);       // set Pin high
        asm("nop");
        asm("nop");
        asm("nop");
        asm("nop");
        asm("nop");
        asm("nop");
      
      fastWrite(whichpin, LOW);        // set pin low
        asm("nop");
        asm("nop");
        asm("nop");
        asm("nop");
        
       }
       
}

Now here I have some problems and questions again:

  1. Why do 6x nop in the HIGH give 500ns while it takes 4x nop in the LOW to give 500ns?

  2. The amount of nop needed is not linear with the actual pause. In specific, for 1us pulse, I need 14x nop in the HIGH and 12x nop in the LOW. For 2us, I need 30x nop in the HIGH and 28x nop in the LOW.

  3. If I try to make a loop that runs for specific times executing the nop, I get completely different delay times. So it seems the only way is to actually type nop; again and again, which is not practical.

  4. I wonder how reproducible will those delays be in different computers etc?

I've also read about PWM, but I wasn't able to change the frequency of my board. I calculate that if I would set the PWM frequency at 20,000 Hz and then change the duty cycle from 1% to 100%, I could, in theory get all the values I need (eg 1% is 500ns).

With all the above in mind, I would appreciate any solutions or a better direction for my project. I've also read about

Thanks a lot in advance for your help

You probably need to program one of the hardware Timers directly, See the Atmel datasheet - for the Atmega 328 if you are using an Uno. Note that Timer1 is 16 bit so may give you more flexibility.

Don't use Timer0 as it is what controls millis() and other Arduino features.

...R

  1. Why do 6x nop in the HIGH give 500ns while it takes 4x nop in the LOW to give 500ns?

You need to take into account the time needed to execute the jump back command in the while loop.

Same answer for the other questions as well as your not taking into account the time needed to execute other instructions other than the digitalWrite & delay.

EDIT:
You might also consider using an external chip to generate the signal. Something like the AD9850 or a derivative.

Sounds more like an XY problem. What device are you tying to talk to?

Mark

Robin2:
You probably need to program one of the hardware Timers directly, See the Atmel datasheet - for the Atmega 328 if you are using an Uno. Note that Timer1 is 16 bit so may give you more flexibility.

Don't use Timer0 as it is what controls millis() and other Arduino features.

...R

Hi,

Thanks for the reply. I tried to have a look at the Atmega manual, can't say I understood much :frowning:

Any chance you could provide me with some basic sample code to get me started? Eg a snippet that turns a specific pin ON.

You better explain the composition of your pulse trains first. Your example creates a symmetric square wave, that's easy to create with a timer. But pulse train suggests that the pulse and pause times can vary with every pulse?

Try this. Two 20kHz pwm signals, one is inverted. Control width low on pin9, control width high on pin10.

//Dual 20kHz PWM using Timer2 Mode14

word widthA = 200; //0-800, low width 
word widthB = 200; //0-800, high width 

void setup() {
  pinMode(9, OUTPUT); //output A
  pinMode(10, OUTPUT); //output B

  TCCR1A = 0; //clear timer registers
  TCCR1B = 0;
  TCNT1 = 0;

  TCCR1B |= _BV(CS10); //no prescaler
  ICR1 = 800; //PWM frequency = 16MHz/800 = 20kHz

  OCR1A = widthA; //Pin 9 match
  TCCR1A |= _BV(COM1A1) | _BV(COM1A0); //output A set rising/clear falling

  OCR1B = widthB; //Pin 10 match
  TCCR1A |= _BV(COM1B1); //output B clear rising/set falling

  TCCR1B |= _BV(WGM13) | _BV(WGM12);  //PWM mode with ICR1 Mode 14
  TCCR1A |= _BV(WGM11); //WGM13:WGM12:WGM11 set 1110
}

void loop() {}

andrewskg:
Thanks for the reply. I tried to have a look at the Atmega manual, can't say I understood much :frowning:

That's because you only read it 9 times.

It starts to become clear after 14 reads :slight_smile:

@dlloyd's example should answer your request.

...R