Inaccurate ISR timings

Greetings, i have a program on Arduino Nano that sends 3 signals when I press a button:
1st signal is around 700ns in width and it has a 20uS delay after it.
2nd signal ~100ns in width and after that signal i start timer1 and waiting for a "TIMER1_COMPA_vect" event.
And in that event i send 3rd signal that is 100ns in width.

I set "OCR1A" each time and read "TCNT1" to check if timings was correct.
But for some reason if i set delay for example 500 clocks, timer stops at 500;
then i set clock to 501, timer stops at 500;
then delay = 502 and timer stops at 502.
Basically i cant stop timer with 1 tick precision.

Or maybe there is a better way to achieve precision up to 1 clock?
Code:

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>

//LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line displa

bool flagBtn = false; //high or low for btnStart
int OCR1AmyValue = 30; //time setting value
int currentStep = 1; //current step
uint32_t btnTimer = 0;

void setup() {
  Serial.begin(9600);
  //lcd.init();                      // initialize the lcd
  //lcd.backlight();

  pinMode(8, OUTPUT); //RESET
  pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

  //High
  digitalWrite(8, HIGH);
  digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

  cli();                      //stop interrupts for till we make the settings
  TCCR1A = 0;                 // Reset entire TCCR1A to 0
  TCCR1B = 0;                 // Reset entire TCCR1B to 0
  TIMSK1 |= (1 << OCIE1A); //Set OCIE1A to 1 so we enable compare to match OCR1A
  TCNT1 = 0; //reset clock value
  sei();                     //Enable back the interrupts

  prepareMethod(); //initialize variables and show on display
}


void loop() {
  for (int i = 1; i < 500; i++) {
    prepareMethod();
    startBtnPressed();
    showInfoSerial();
    OCR1AmyValue++;
    delay(100);
  }
  delay(1500000);
}

void showInfoSerial() { //SHOW ALL THE INFORMATION ON THE LCD
  Serial.print("My/RC:");
  Serial.print(OCR1AmyValue + 25);
  Serial.print(" ");
  Serial.println(TCNT1); //

}

void prepareMethod() {
//  lcd.clear();
//  lcd.setCursor(0, 0); //Column, Row
//  lcd.print("#");
//  lcd.print(currentStep);
//
//  lcd.print("ET:");
//  lcd.print((OCR1AmyValue + 25) * 0.0625);
//
//  //number of clocks i set
//  lcd.print("MC:");
//  lcd.print(OCR1AmyValue + 25); //20 interrupt, 4 H>L>H, 1 stop timer

  //OCR1A = OCR1AmyValue;             //Finally we set compare register A to this value
  OCR1AH = highByte(OCR1AmyValue);
  OCR1AL = lowByte(OCR1AmyValue);
  TCNT1  = 0;                  //First, set the timer back to 0 so it resets for next interrupt
}

//SEND SIGNALS
void startBtnPressed() {
  //Reset L
  PORTB = B110; //8 L 687,5ns lenght
  //9nop x 0.0625us = 0.5625uS +2 ticks for PORTB
  __asm__ __volatile__ (
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
  );
  //Reset H
  PORTB = B111; //8 L

  //delay ~20uS before start
  delayMicroseconds(20);

  //START 9 H>L 4 clocks
  PORTB = B101; //8 L; 9 L
  PORTB = B111; //8 L; 9 H

  TCCR1B = B1;  //T1 ON; running at 16 MHz //1ั‚ะฐะบั‚
}

//COMP A TIMER
ISR(TIMER1_COMPA_vect) { //~20 clock takes interrupt

  //STOP 10 H > L
  PORTB = B011; //8 L; 9 H; 10 H 2 clocks
  PORTB = B111; //8 L; 9 L; 10 L 2 clocks
  TCCR1B = 0;    //Timer-1 OFF 1 clock
}

Its an example code, but it shows in a serial port that it differs by 2 or even more(like in range when TCNT1 around 165)

Please post your code here (in code tags) to avoid the need to visit an unknown website and download it, which many members will not do

One clock tick on 16MHz is 62 ns, so you can't measure 100ns in accurate, it will either about 60ns, or something like 120 ns

Do not use interrupts, it can't be accurate in timing.
Use the hardware timer outputs instead

Thanks for reply. I do know that it's 62,5ns. I need to adjust 2nd delay by a fixed amount of clocks each time.

By hardware timer you mean some external device that is controlled by Arduino?

no, I mean the output pins, controlling by A & B compare channels of Timer

Thanks! I will look into it tomorrow

Reading through atmega328 datasheet I came to the following additional code lines:

TCCR1A |=  (1<< COM1A0); //TOGGLE D9 when compare match occurs

But how I can toggle it back(set it high again) the next 2 cpu cycles?

The only solution so far for me it to put some small capacitor (like ~20pf or maybe another value, need to test) so it won't let impulses longer than 100-150ns pass through it.

It has been awhile since I worked with the Atemega's timers, but perhaps you could use one of the PWM modes to send the pulse with an interrupt after where PWM will be terminated.

So far timer1 making A going low (with TCCR1A |= (1<< COM1A0); ) when it reaches a value of OCR1A might be the answer if it's that accurate, but I see no possible way to execute PORTB command to make it high again in the next 2cpu cycles.
Maybe some external device that can generate fixed pulse from Arduino's output.

After many thoughts and tries with other ways to make precise delay (1 clock precision) between 2 pins going low(2cpu cycles) and then high(2cpu cycles) again I think esp32 with higher cpu clock rate maybe the answer.

You can use a FastPWM mode to turn the OCxx pin LOW at compare match and HIGH at TOP. Those can be 2 cycles apart.

Yea, I had suggested something similar. Suggestion was ignored.

gfvalvo, thanks I am new to this so I may not understand everything, examples will makes things much easier.
I will also look into those suggestions tomorrow
Thanks again, you gave me hope :wink:

Thanks again for suggestion, my brain is burning right now :slight_smile:
After some hours of reading i wrote the following code in "setup section".

Since i have no oscilloscope to test it at home i have few questions, to make sure i understood everything correct.
If i need a delay, lets say 500 clocks, between fronts of "START"(D9) and "STOP"(D10) signals, i have to:
1)make "START" signal going LOW then HIGH again with PORTB, it stays LOW for only 2 clocks.
2)Start timer1, 1 clock.
3)and make before hand values of OCR1B(compare match) and OCR1A(TOP value) equal 497 (-3 clocks) and 498 respectively.
So timer hits 498, then overflows and at this clock it set D10 HIGH, so D10 will stay LOW for only 2 clocks with a delay 500 clocks.

And i believe i should add TIMSK1 |= (1 << TOIE1); and ISR(TIMER1_OVF_vect) { //stop timer1 here} to stop timer1

pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

////FAST PWM SET UP:
  cli(); //stop interrupts for till we make the settings

  TCCR1A = 0; // Reset entire TCCR1A to 0
  TCCR1B = 0; // Reset entire TCCR1B to 0
  TCNT1 = 0; //reset clock value

////Compare Output Mode, Fast PWM:
TCCR1A |= (1 << COM1B1); //SET MODE FOR OC1B(D10): Clear(set LOW) OC1B(D10) on compare match OCR1B, set(HIGH) OC1B at BOTTOM(non-inverting mode) (COM1B1 = 1; COM1B0 = 0)

////set to fast PWM mode 15 via WGM bits(count from 0 to OCR1A):
  ////TOP = OCR1A (output compare register timer1 channelA)
  TCCR1A |= (1 << WGM11);
  TCCR1A |= (1 << WGM10);
  TCCR1B |= (1 << WGM13);
  TCCR1B |= (1 << WGM12);

TIMSK1 |= (1 << TOIE1); //Set TOIE1 to 1 so we call overflow vector when overflow happens - ISR(TIMER1_OVF_vect) {//something}
 sei();                     //Enable back the interrupts

Thanks in advance.

Cant make it work.

With COM1A1 =1; COM1A0 = 1 (Set OC1A/OC1B on compare match, clear OC1A/OC1B at BOTTOM (inverting mode))
OCR1B = 1; OCR1A = 1000;
If i let it run without stopping timer with ISR(TIMER1_OVF_vect) i have D10 level HIGH with ~100ns negative pulses, works like intended.
But if i stop this after the 1st overflow i dont have a correct signal. Like atmega 328p has to set up something first steps

Also tried to use COM1A1 =1 and COM1A0 = 0 (Clear OC1A/OC1B on compare match, set OC1A/OC1B at BOTTOM (non-inverting mode)). but with that mode if i have
OCR1B = 999; //compare match
OCR1A = 1000; //top value
i will have 3uS negative signal. In order to make it ~100ns i need to make OCR1B = 1022 (why this should be higher than OCR1A to make 100ns?) and again cant get single pulse.

void setup() {
  pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

  digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

  cli(); //stop interrupts for till we make the settings

  TCCR1A = 0; // Reset entire TCCR1A to 0
  TCCR1B = 0; // Reset entire TCCR1B to 0
  TCNT1 = 0; //reset clock value

  //Set OC1B on compare match, clear OC1A/OC1B at BOTTOM (inverting mode)
  TCCR1A |= (1 << COM1B1); //
  TCCR1A |= (1 << COM1B0); //

  ////set to fast PWM mode 15 via WGM bits(count from 0 to OCR1A):
  ////TOP = OCR1A (output compare register timer1 channelA)
  TCCR1A |= (1 << WGM10) | (1 << WGM11) ;
  TCCR1B |= (1 << WGM12) | (1 << WGM13) ;

  ////Timer/Counter1 Interrupt Mask Register:
  ////bit OCIE1B - (COMPARE B)The corresponding interrupt vector is executed when the OCF1B flag, located in TIFR1 register, is set
  ////bit OCIE1A - (COMPARE A)The corresponding interrupt vector is executed when the OCF1A flag, located in TIFR1 register, is set
  ////bit TOIE1  - (OVERFLOW) The corresponding interrupt vector is executed when the TOV1  flag, located in TIFR1 register, is set;
  //TIMSK1 |= (1 << OCIE1A); //Set OCIE1A to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPA_vect) {//something}
  //TIMSK1 |= (1 << OCIE1B); //Set OCIE1B to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPB_vect) {//something}
  //TIMSK1 |= (1 << TOIE1); //Set TOIE1 to 1 so we call overflow vector when overflow happens - ISR(TIMER1_OVF_vect) {//something}
  sei();                     //Enable back the interrupts

  ////The extreme values for the OCR1x register represents special cases when generating a PWM waveform output in the fast
  ////PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the output will be a narrow spike for each TOP+1 timer clock
  //////cycle. Setting the OCR1x equal to TOP will result in a constant high or low output (depending on the polarity of the output set
  ////by the COM1x1:0 bits.)

  OCR1B = 1; //compare match
  OCR1A = 1000; //top value
  TCCR1B = B1;  //T1 ON; running at 16 MHz no prescaling; //1 clock
}
void loop() {
  delay(100000);
}
ISR(TIMER1_OVF_vect) { //OVERFLOW VECTOR
  //TCCR1B &= ~_BV(CS10); //turn off timer1 29 clocks
  TCCR1B = B0; //turn off timer1 (CS10 = 0) 18 clocks
}

I think you mean:
TCCR1B |= B1;
That will turn on CS10 (Clock Select, Timer1 Bit 0) without also turning off WGM12 and WGM13.

If you use WGM 15 you can't use OC1A for PWM output. I recommend changing to WGM 14 so TOP is in ICR1 instead of OCR1A.

Thank, I will check that mistake tomorrow. Don't have time today :frowning:
I manually set OC1A low then high. Right after I enable timer1.
Then a delay(with fast pwm 50-500) and at the end OC1B going low then high.
That is the plan

It helped but another issue appeared:
Is there any way to re write OCR1A value with WGM mode 15 if it's already was set. Since in normal way to update this double buffered value Tncn1 should count to OCR1A and only the next cycle it's updated.

To stop timer1> update OCR1A > start timer1 so it counts to new a new value

No, according to datasheet writing to OCR1A is buffered in the FastPWM mode . New value of OCR1A will not be loaded until the counter reached TOP/BOTTOM value.