Accurate method of delaying by increments of microseconds

Hi all,

I have wrote a program that is used to implement equivalent time sampling in capturing images for a seemingly faster acquisition rate. I want an acquistion rate of 1uS for my application. So to produce the effect of a 1uS frame rate I need to delay the camera acquistion by increments of 1uS after each rising edge of of the device under test pulse. The problem that I am having is that I need an accurate uS delay that will continue to be accurate for values of greater than 16000 uS. So I do not think I can use delayMicroseconds() or Micros. I was looking into using the AVR library for delay command _delay_us(), but I am unsure on how to implement it, and if it would even work for my application. Any suggestions would be much appreciated. My section of code including delayMicroseconds is included below.

//turns on DUT and camera sync to capture first image
digitalWrite(DUTsig, HIGH);
digitalWrite(syncIn, HIGH);
delayMicroseconds(1);
digitalWrite(syncIn, LOW);
capture = 1;

//prints to lcd screen that the first image has been captured
lcd.clear();
lcd.print("Frames captured");
lcd.setCursor(0,1);
lcd.print(capture);
lcd.setCursor(6,1);
lcd.print("of");
lcd.setCursor(9,1);
lcd.print(frames);

//while loop to implement equivalent time sampling at user defined parameters
while(capture < frames)
{
  currentMillis = millis();
 
//while loop to keep currentMillis synced with millis
  while (currentMillis - previousMillis < onTime)
  {
     currentMillis = millis();
  }

//while loop to turn DUTsig off when on time is timed out
  while (currentMillis - previousMillis >= onTime)
  {
    previousMillis = currentMillis;       //resets previousMillis to currentMillis
    Serial.println(currentMillis);        //prints currentMillis to serial monitor for troubleshooting
    Serial.println("off");                //prints to serial monitor to indicate that the DUTsig was turned off
    digitalWrite(DUTsig, LOW);            //writes DUTsig low
  }
 
//while loop to keep currentMillis synced with millis
 while (currentMillis - previousMillis < offTime)
  {
    currentMillis = millis();
  }

//while loop that turns on DUTsig and triggers camera with equivalent time delay (sample)
 while (currentMillis - previousMillis >= offTime)
  {
    previousMillis = currentMillis;        //resets previousMillis to currentMillis
    Serial.println(currentMillis);         //prints currentMillis to serial monitor for troubleshooting
    Serial.println("on");                  //prints to serial monitor to indicate that the DUTsig was turned on
    digitalWrite(DUTsig, HIGH);            //writes DUTsig high
    sample += EST;                         //increments sample by the equivalent sampling time each cycle
    Serial.println(sample);                //prints sample to the serial monitor for troubleshooting
   delayMicroseconds(sample);            //delays syncIn by the sample time to create equivalent time delay
    digitalWrite(syncIn, HIGH);            //writes syncIn high
    delayMicroseconds(1);                  //holds syncIn high for 1 microsecond
    digitalWrite(syncIn, LOW);             //writes syncIn low
    capture++;                             //increments capture to show number of frames that have been taken
   
//prints to lcd screen the number of frames that have been captured
    lcd.clear();                          
    lcd.print("Frames captured");
    lcd.setCursor(0,1);
    lcd.print(capture);
    lcd.setCursor(6,1);
    lcd.print("of");
    lcd.setCursor(9,1);
    lcd.print(frames);
  }
 }

//writes the DUTsig low for the final frame
  Serial.println(currentMillis);       
  Serial.println("off"); 
  digitalWrite(DUTsig, LOW);

Most reliable timing is generated by hardware, using one of the timers. See the controller data sheet for timer modes.

I don't think this works
delayMicroseconds(1);
for under 3-4 uS.

I am using an UNO for my project. I wrote some if statements to include _delay_us() for values of 1 and 2 microseconds. I also added in delay() for values in milliseconds. Everything seemed to be working well in my simulations, but whenever I built the circuit and looked at the timing on an oscilloscope all of my uS values seemed to be off. I may just need to do some tweaking to my oscilloscope or delayMicroseconds and _delay_us() could not be accurate enough for my application. I would think they would be with the UNO having a 16MHz crystal. I need a resolution of 1 uS. I am not familiar with working with hardware timers, so I would really like to stick with coding the delays. This is my first arduino project. I thank everyone for the quick responses. Here is my new code with the if statements.

void loop() {

//initializes time at current mS of program and prints to serial monitor for troubleshooting
currentMillis = millis();
previousMillis = currentMillis;
Serial.println(currentMillis);
Serial.println("on");
  
//turns on DUT and camera sync to capture first image
digitalWrite(DUTsig, HIGH);
digitalWrite(syncIn, HIGH);
_delay_us(1);
digitalWrite(syncIn, LOW);
capture = 1;

//prints to lcd screen that the first image has been captured
lcd.clear();
lcd.print("Frames captured");
lcd.setCursor(0,1);
lcd.print(capture);
lcd.setCursor(6,1);
lcd.print("of");
lcd.setCursor(9,1);
lcd.print(frames);

//while loop to implement equivalent time sampling at user defined parameters
while(capture < frames)
{
  currentMillis = millis();
 
//while loop to keep currentMillis synced with millis
  while (currentMillis - previousMillis < onTime)
  {
     currentMillis = millis();
  }

//while loop to turn DUTsig off when on time is timed out
  while (currentMillis - previousMillis >= onTime)
  {
    previousMillis = currentMillis;       //resets previousMillis to currentMillis
    Serial.println(millis());             //prints millis to serial monitor for troubleshooting
    Serial.println("DUT off");            //prints to serial monitor to indicate that the DUTsig was turned off
    digitalWrite(DUTsig, LOW);            //writes DUTsig low
  }
 
//while loop to keep currentMillis synced with millis
 while (currentMillis - previousMillis < offTime)
  {
    currentMillis = millis();
  }

//while loop that turns on DUTsig and triggers camera with equivalent time delay (sample)
 while (currentMillis - previousMillis >= offTime)
  {
    previousMillis = currentMillis;        //resets previousMillis to currentMillis
    Serial.println(millis());              //prints millis to serial monitor for troubleshooting
    Serial.println("DUT on");              //prints to serial monitor to indicate that the DUTsig was turned on
    digitalWrite(DUTsig, HIGH);            //writes DUTsig high
    sample += EST;                         //increments sample by the equivalent sampling time each cycle
   
//if statement to increment mSdelay, uSdelay, and accdelay each millisecond
   if(sample - uSdelay == 1000)
    {
     mSdelay++;                            //increments mSdelay every 1000 uS
     uSdelay = mSdelay*1000;               //updates uSdelay with mSdelay
     accdelay + 1000;                      //updates accurate delay every 1000 uS
    }

//if statement to delay in milliseconds
   if(sample - uSdelay == 0)
    {
     delay(mSdelay);                       //delays the mS amount for each whole millisecond
     Serial.println(sample);               //prints value of sample to serial monitor for troubleshooting
    }

//if statement for accurate 1 microsecond delays
   if(sample - uSdelay == 1)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     _delay_us(1);                         //delays syncIn by an additional 1 to create an accurate equivalent time delay
     Serial.println(sample);               //prints sample to serial monitor for troubleshooting
    }

//if statement for accurate 2 microsecond delays
   if(sample - uSdelay == 2)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     _delay_us(2);                         //delays syncIn by an additional 2 to create an accurate equivalent time delay
      Serial.println(sample);
    }
   
//if statement for delays with both milliseconds and microseconds
   if(sample >= accdelay)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     delayMicroseconds(sample-uSdelay);    //delays by residual amount of uS after millisecond delay
     Serial.println(sample);               //prints sample to serial monitor for troubleshooting
    }
   Serial.println(millis());               //prints millis to serial monitor for troubleshooting
   Serial.println("sync on");              //prints sync on to serial monitor to let user know that the above millis is for the sync turning on
    digitalWrite(syncIn, HIGH);            //writes syncIn high
    _delay_us(1);                          //holds syncIn high for 1 microsecond
    digitalWrite(syncIn, LOW);             //writes syncIn low
    capture++;                             //increments capture to show number of frames that have been taken
   
//prints to lcd screen the number of frames that have been captured
    lcd.clear();                          
    lcd.print("Frames captured");
    lcd.setCursor(0,1);
    lcd.print(capture);
    lcd.setCursor(6,1);
    lcd.print("of");
    lcd.setCursor(9,1);
    lcd.print(frames);
  }
 }

//two while loops to finish timing the final cycle's on time
  while(currentMillis - previousMillis < onTime)
  {
   currentMillis = millis();
  }
 while (currentMillis - previousMillis >= onTime)
  {
   previousMillis = currentMillis;
   Serial.println(currentMillis);        //prints currentMillis to serial monitor for troubleshooting
   Serial.println("off");                //prints to serial monitor to indicate that the DUTsig was turned off
   digitalWrite(DUTsig, LOW);            //writes DUTsig low
  }
  
//prints to lcd the number of frames capture and allows the program to restart from main loop
  lcd.clear();
  lcd.print(frames);
  lcd.setCursor(7,0);
  lcd.print("captured");
  lcd.setCursor(0,1);
  lcd.print("* to restart");
  char key = kpd.waitForKey();

//resets the sample and capture back to zero for the next cycle of the program
  sample = 0;
  capture = 0;
}

When I look at the latest code, I see the use of millis(), how does one expect to get uS out of millis()?

You can adjust the timing against code execution time.

Say you want the thing to do every 200uS, no more or no less.

Record the start of code execution, record the end of code execution, and adjust 200uS to remove the delay caused by code execution.


In the following code, the time between the rising and falling edges won't be 1 microsecond, because digitalWrite is big and slow.

digitalWrite(syncIn, HIGH);
_delay_us(1);
digitalWrite(syncIn, LOW);

You need to use direct port access instead, something like

PORTD |= 1<<2; //Uno pin 2 high
_delay_us(1);
PORTD &= ~(1<<2); //Uno pin 2 low

But a timer can do the port pin toggling without intervention by the CPU. See https://www.gammon.com.au/timers

But a timer can do the port pin toggling without intervention by the CPU. See Gammon Forum : Electronics : Microprocessors : Timers and counters

Would using timers interfere with the millis function? By using the direct port access would I be able to obtain a resolution of 1 uS, or should I just move towards using timers instead? I am in somewhat of a time crunch. I want to take the shortest route of revision, but I need the 1 uS resolution.

Would using timers interfere with the millis function?

Only if you use Timer0.

By using the direct port access would I be able to obtain a resolution of 1 uS

The time resolution would be 62.5 nanoseconds, using a timer (e.g. Timer1) clocked at 16 MHz to toggle the port pin.

The actual resolution of _delay_us() is not entirely clear from the documentation, as it has to know the CPU clock frequency and do a divide.

If you insist on using the CPU to do the timing, delay by clock cycles using the functions _delay_loop_1() or _delay_loop_2() instead, and do the math yourself. To use that method, tou will have to turn off the interrupts, which disables millis().

That is very helpful information! Thank you so much for replying. I am going to try to work with the timers to create my delays. It seems like the most accurate solution and will probably save me some trouble in the long run! :slight_smile:

I replaced all data writes with direct port access and removed all serial prints, and I still can not get the timing to be correct. I was going to try to use timers, but it looks like it take 2.4 uS to enter or exit an ISR. I do not think the timers would be much better because I need an accurate delay of 1 uS. I can not get an accurate millisecond delay of less than 10 mS and microsecond delay of 15 uS for if statements. I guess my series of while loops and if statements are slowing the program down too much. Does anyone have any suggestions on how I can get an accurate 1 uS delay that increments each period. Here is my new code. Thank you to everyone in advance for the help.

void loop() {

//initializes time at current mS of program and prints to serial monitor for troubleshooting
currentMillis = millis();
previousMillis = currentMillis;

  
//turns on DUT and camera sync to capture first image
PORTB = B00000011;
_delay_us(1);
PORTB &= ~(1 << syncIn);
capture = 1;

//prints to lcd screen that the first image has been captured
lcd.clear();
lcd.print("Frames captured");
lcd.setCursor(0,1);
lcd.print(capture);
lcd.setCursor(6,1);
lcd.print("of");
lcd.setCursor(9,1);
lcd.print(frames);

//while loop to implement equivalent time sampling at user defined parameters
while(capture < frames)
{
 
 
//while loop to keep currentMillis synced with millis
  if (currentMillis - previousMillis < onTime)
  {
     currentMillis = millis();
  }

//while loop to turn DUTsig off when on time is timed out
  if (currentMillis - previousMillis >= onTime)
  {
    previousMillis = currentMillis;       //resets previousMillis to currentMillis
    PORTB &= ~(1 << DUTsig);
  }
 
//while loop to keep currentMillis synced with millis
if (currentMillis - previousMillis < offTime)
  {
    currentMillis = millis();
  }

//while loop that turns on DUTsig and triggers camera with equivalent time delay (sample)
if (currentMillis - previousMillis >= offTime)
  {
    previousMillis = currentMillis;        //resets previousMillis to currentMillis
    PORTB |= (1 << DUTsig);           //writes DUTsig high
    sample += EST;                         //increments sample by the equivalent sampling time each cycle
   
//if statement to increment mSdelay, uSdelay, and accdelay each millisecond
   if(sample - uSdelay == 1000)
    {
     mSdelay++;                            //increments mSdelay every 1000 uS
     uSdelay = mSdelay*1000;               //updates uSdelay with mSdelay
     accdelay + 1000;                      //updates accurate delay every 1000 uS
    }

//if statement to delay in milliseconds
   if(sample - uSdelay == 0)
    {
     delay(mSdelay);                       //delays the mS amount for each whole millisecond
    }

//if statement for accurate 1 microsecond delays
   if(sample - uSdelay == 1)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     _delay_us(1);                         //delays syncIn by an additional 1 to create an accurate equivalent time delay
    }

//if statement for accurate 2 microsecond delays
   if(sample - uSdelay == 2)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     _delay_us(2);                         //delays syncIn by an additional 2 to create an accurate equivalent time delay
    }
   
//if statement for delays with both milliseconds and microseconds
   if(sample >= accdelay)
    {
     delay(mSdelay);                       //delays by whole milliseconds
     delayMicroseconds(sample-uSdelay);    //delays by residual amount of uS after millisecond delay
    }
    PORTB |= (1 << syncIn);            //writes syncIn high
    _delay_us(1);                          //holds syncIn high for 1 microsecond
    PORTB &= ~(1 << syncIn);             //writes syncIn low
    capture++;                             //increments capture to show number of frames that have been taken
   
//prints to lcd screen the number of frames that have been captured
    lcd.clear();                          
    lcd.print("Frames captured");
    lcd.setCursor(0,1);
    lcd.print(capture);
    lcd.setCursor(6,1);
    lcd.print("of");
    lcd.setCursor(9,1);
    lcd.print(frames);
  }
 }

//two while loops to finish timing the final cycle's on time
  while(currentMillis - previousMillis < onTime)
  {
   currentMillis = millis();
  }
 while (currentMillis - previousMillis >= onTime)
  {
   previousMillis = currentMillis;
   PORTB &= ~(1 << DUTsig);            //writes DUTsig low
  }
  
//prints to lcd the number of frames capture and allows the program to restart from main loop
  lcd.clear();
  lcd.print(frames);
  lcd.setCursor(7,0);
  lcd.print("captured");
  lcd.setCursor(0,1);
  lcd.print("* to restart");
  char key = kpd.waitForKey();

//resets the sample and capture back to zero for the next cycle of the program
  sample = 0;
  capture = 0;
}

The ESP32 chip contains two hardware timer groups. Each group has two general-purpose hardware timers. They are all 64-bit generic timers based on 16-bit prescalers and 64-bit up / down counters which are capable of being auto-reloaded.

https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/timer.html Works down to 12.5nS.

I was going to try to use timers, but it looks like it take 2.4 uS to enter or exit an ISR

Do not use an ISR.

The timers can control output pins directly, to give extremely precise timing independent of the CPU. Study the link posted in reply #6 to learn how.

I am having a hard time finding the pin toggle independent of CPU in the Gammon post for timers. Under the header Timer Hardware Input/Output there is a table that I posted below. Is this where the toggle without CPU is described? Also if I use timers will I be limited by the number of counts of the timer. For example Timer 1 counts up to 65536. Say I put a prescalar of two on there then it will count up every 500nS. I want a pin to toggle on then off at 1 uS, then 2 uS, then 3 uS, and so on until the camera has captured the whole on time of the device under test. If I had the on time set to 1 second which is 1,000,000 uS then I could only toggle the pin up to 32768 uS, correct?

Timer 0

input T0 pin 6 (D4)

output OC0A pin 12 (D6)
output OC0B pin 11 (D5)

Timer 1

input T1 pin 11 (D5)

output OC1A pin 15 (D9)
output OC1B pin 16 (D10)

Timer 2

output OC2A pin 17 (D11)
output OC2B pin 5 (D3)

There are ways to set up the timer where the timer resets when it toggles the pin. So you could set it up to fire at 1uS intervals indefinitely. You could use another timer running in software to turn that on and off at will.

Thanks for the information Delta_G. I have figured out how to use the hardware timers to count up each time it matches a compare register. So I can get the timer to count in 1 uS intervals, but I need to be able to turn the timer on when another pin goes high and let it count the number of microseconds needed for the delay then turn on a second pin, wait another microsecond then turn that second pin back off. Could I use a software timer to start the hardware timer count at the time the first pin goes high, then turn on the second pin after the hardware time has reached a certain count? A major issue is that the count for the delay increments every loop cycle.