Timer1 misunderstanding of timing periods

I am trying to use 328's Timer1 to create a custom wave form. I started an earlier thread and was suggested to use TimerOne library which I tried but wasn't fast enough to generate the waveform, which is not a PWM type output but more complex.

After reading TimerOne code and some reading of 328's data sheet, I started setting registers to see if I can first generate a 50% duty cycle waves for my understanding. Here is my code that I thought should generate a 4.096ms LOW and then 4.096ms HIGH wave with 8.192ms period:

#define ABout 8
void setup() {
  digitalWrite(ABout,LOW);
  pinMode(ABout,OUTPUT);
  TIMSK1 = _BV(TOIE1);// Enable interrupt
  TCCR1B = _BV(WGM13);
  TCCR1A = 0;
  ICR1=65535U;
  TCCR1B=B00010001;
}

ISR(TIMER1_OVF_vect)
{
  PORTB=PINB^B1;
}

So in my theory if I set 65535 as TOP and with no prescalar, it counts 65536 CPU cycles, at 16MHz, so 65536/16=4096us between each logic level flip. But my logic analyzer captured consistent 8192us time between each logic level flip. I tried different TOP count values and prescalar value and always ended up twice as long as I thought. I looked at TimerOne.h and found a factor of 2 was included. I wonder why. If you count say 65536 CPU cycles at 16MHz, and then flip output, then between flips timing is 4096us, why twice?

You currently have the timer in Phase and Frequency Correct PWM mode. That means it counts up and then down. So yes, it will be double the period.

I think what you want may be Fast PWM Mode:

Or CTC Mode if you're wanting to change the frequency as well.

1 Like

Hi @liuzengqiang

It's because timer 1 is set to phase and frequency correct mode.

In this mode a single timer cycle involves counting up to the TOP value then counting back down again to the BOTTOM. This in effect doubles the timer's period when compared with normal PWM modes, which usually count up to the TOP then return to immediately back to zero.

I consider this a nice tool for helping setting the timers to what I need.

I selected fast PWM mode and a period of 6ms.

You can drag the OCR1A value up and down to get the frequency you want.

1 Like

Wow! Nice resource! I'll bookmark that.

Thanks for the help folks! I also appreciate the link to that timer calculator. So here is what I have found out after setting FPWM mode with ICR1 register storing TOP value:

Expected time between flip vs actual HIGH and LOW durations

  • 1 should give 0.0625us but 2.45 2.43
  • 4 should give 0.24us but 2.45 2.43
  • 8 should give 0.5us but 2.45 2.43
  • 16 should give 1us but 2.43 2.45
  • 32 should give 2us but 2.44 2.44
  • 64 should give 4us but 4.07 4.06 occasionally with 8.62, suspecting Timer2 update on time tick, to Timer0's interrupt. Disabling Timer0 interrupts removes this artifact.
    *128 should give 8us but 8.07 8.07

So after thinking this through, I concluded that interrupt handling causes about 2.5us of processing time, during with the new TIMER1 overflow interrupt is generated by the timer but not processed while the previous such interrupt is getting processed. So basically I suspect that any timing shorter than 2.5us will come up as 2.5us. Any longer timing will be correct. The timer always runs continuously and when it raises an interrupt, it gets responded so it restarts right away.

If I change prescaling factor, this will also likely create artifacts in the waveform. I'll see if I can just use prescalar=1 for all my delays.

If you are just interested in toggling a pin, then leave the interrupt out of it and use the WGM.

No, my original goal is to generate varying-width (up to 40us wide) pulses and varying delays (in 1-4ms) between these pulses. My experimentation with the registers was just to understand how TIMER1 works. Assume I want to generate a pulse train:
A-pulse is 10us+-0.25us wide, followed by a B-pulse that is 5us+-0.25us wide with a delay between the pulses being 1ms.

My original idea was to load A-pulse width to ICP1, then load the delay between A and B pulses, then load the width of B-pulse, etc. I think with the limitation, I should be able to generate the pulses.

I'll post an update later today after some quick tests.

Yes, that's what I mean. Use the WGM for that. Timer1 can output to pins 9 and 10 directly (UNO/328P) so there's no need to use an interrupt to toggle the pin.

Look in the data sheet at how the compare match registers are buffered. In some modes, you could write the value for one pulse, start the timer, and immediately write the value for the second pulse and the second write would be buffered until the timer overflows. So the second run of the timer would get the second value for compare match and give a different pulse width.

Many of the timer registers are buffered this way.

OK I think I am intrigued by the compare match mode but I must clarify myself. Both A and B pulses are coming out of a single pin so from this pin, a train of pulses come out to drive an NMR device. That's why I'm using interrupts to load new count numbers.

But, if I use both compare-output pins with an OR gate, I may be ablet to get the same outcome. Just thinking out loud because I haven't read the output compare features much, was focused on timer overflow mechanism as TimerOne library is using to generate delays:

Pause the pre-scalar to stop the timer from running
Load OCR1 and OCR2 with different values
Set both OC pins to LOW
Unpause pre-scalar to start both OC mechanisms simultaneously.
Now when OCR1 is reached, will the system turn OC1 pin to HIGH with some settings?
When OCR2 is reached, will the system turn OC2 output pin to HIGH as well?

My idea, will output compare be able to do this if properly set? If yes, I can forge ahead with reading the 328 manual more.

If you can live with the limitation that the ISR overhead requires then I guess just go with that. Nothing is more harmful than optimizing that which was already good enough.

This is the second time I've looked at your project and thought about how the GPT timers on the R4 would be so perfect for your job. But to use the R4 you'd have to use the Minima and you'd have to add a proper crystal to the board. That's documented - kind of. Without adding the crystal (which isn't as do-able on a R4-WiFi) the timing wouldn't be good enough for your NMR experiments.

There you can set up event links behind the scenes to trigger one timer from the other. And every pin has a timer behind it. There are 4 pins with 32 bit timers.

It's not as well documented and there's a LOT of library code that doesn't work on it yet because it doesn't have an AVR processor anymore. So it can be a little bit of a minefield. But the more I think about your NMR project the more I think it might actually be the board for you if you can handle adding the crystal to it and figuring out the timers.

Here's a very long thread about the crystal thing. Frequency counter for UNO R4 wanted

I really would love to see this thing work.

1 Like

Thanks for the help! Here is what I managed to get with an interrupt-driven sequence on one pin:


You can see 4 white pulses, first one being an A-pulse, and next 3 are all B-pulses. I also showed one more white pulse on the far right to show how limited a 16-bit timer is with how long you can count (without changing prescalar and reducing further the resolution of time plus losing up to a whole tick worth of time from adjusting prescalar while it is not reset).
I also have a few flags to show delta time
First flag pair is from A start to B start, which should be 1ms, very good with 1us extra time.
Second flag pair is from first B start to second B start, which should be 2ms, now we are seeing 2us extra time.
Third flag pair is from A start to third B start, which should be 5ms, with a 4-5us extra time
Last flag pair is from A start to second B start (kind of out of order).
A pulse itself is 10.02us (vs 10) and B pulse is 5.89 (vs 5). So maybe my extra lines of code is to be blame:

ISR(TIMER1_OVF_vect)
{
  if (!ptr_ABt) PORTB=0;
  else PORTB=PINB^B1;
  ICR1=ABt[ptr_ABt]-1;
  ptr_ABt++;
  if (ptr_ABt==len_ABt)
  {
    ptr_ABt=0;
  }
}

I had to add more logic in order to do this, which is limiting how short my pulses can get, near 6us as shortest.

I could look up AVR assembly and optimize a bit more if I can.

So with this timing scenario, I can produce somewhat decent pulses with us timing. My latest NMR experiment was using a roughly 5us B-pulse so this is cutting close to be not-useful-but-only-academic project :wink:

I made a bit more progress in optimizing the ISR. My base line is 2.5us when I was just inverting pin outputs with a single line of code PORTB=PINB^1;

But since I have to do a delay after all pulses are out before repeating the pulses, I must force the pin to low at the start of the timing sequence. That caused a minimal of pulse being 5.9us with the extra code. If I directly load the array pointer to PORTB, which will drive pin 8 to the desirable level despite driving pins 9,10, and 11 as well (I don't care about these pins), I could achieve a 5.0us minimal width. I also tested if I could get 1/4us increments on pulse widths and was able to confirm that. So this project may actually be viable on 328. My thoughts are to try this and see how bad the NMR data will come out to be, at least as a test of the hardware's limit and my own limit in developing this device. I may start with the R4 or RPI PICO's programming IO in my future iteration(s).

ISR(TIMER1_OVF_vect)
{
  PORTB=ptr_ABt; // This takes the LSB of the array pointer which directly indicates the state of the output pin and don't even care if other output pins are also changed by other bits of the points, i.e. pins 9,10, and 11 for up to 16 element arrays.
  //PORTB=(ptr_ABt&1); // This takes the LSB of the array pointer which directly indicates the state of the output pin.
  /* This is too slow.
  if (!ptr_ABt) PORTB=0;
  else PORTB=PINB^B1;
  */
  ICR1=ABt[ptr_ABt]-1;
  ptr_ABt++;
  if (ptr_ABt==len_ABt)
  {
    ptr_ABt=0;
  }
}

That 32 bit timer at 48MHz is sounding better and better.

I marked your last response as solution because I think it would be best way to do, given some study time on that system. But I haven't had this much fun tinkering with ISR code since the early 90's. So my optimized code distilled into "load the array pointer into PORTB" to avoid having to do any more logic while in ISR. I also removed the -1 operation. I can get down to about 4.9us minimal width. This is sacrificing future development for 3 more output signals, sync, blanking, and an MG signal that I yet have to understand first. The only realistic way is a faster processor. Or should I look for 32-bit timer ICs with buffered inputs? :wink:

ISR(TIMER1_OVF_vect)
{
  PORTB=ptr_ABt;   
  ICR1=ABt[ptr_ABt];
  ptr_ABt++;
  if (ptr_ABt==len_ABt)
  {
    ptr_ABt=0;
  }
}

@liuzengqiang
If you did listen to @Delta_G and stopped manually toggle pin in interrupts, you could get significantly more accurate pulses.
If you need interrupts, use them to set the delay and parameters of the next pulse. But leave switching the pin to the timer itself by PWM.

1 Like

Silly question, perhaps. Would an ATMEGA328PB, with it's extra timers, be more useful but still familiar for the OP? It's also current product, as opposed to sunset product 328P, though that may not matter.
Just askin'

1 Like

@liuzengqiang

The code below generates a single pulse train of ABBB pulses, where A pulse is 4us and B pulses is 2us. The intervals between pulses are 4ms, 8ms, 8ms correspondingly.
I specifically took these values to demonstrate that using PWM we can set precise pulse lengths of less than 2.5us.
After generating of 4th pulse the program stops.

The code:

uint16_t pulses[4] = {7,3,3,3};  // ABBB pulses = 4us, 2us, 2us, 2us with prescaler = 8
uint16_t intervals[4] = {1000, 8000, 16000,16000}; // Interpulse intervals = 4ms, 8ms, 8ms
                                                 
volatile uint8_t count =0;
void setup(){
  noInterrupts();
  TCCR1B =  0;
  DDRB = 1 << DDB1;
  TCCR1A = 1 << COM1A1 | 1 << WGM11; // enable channel A output
  TIMSK1 =  1 << OCIE1A;   // enable Compare A interrupt
  // initial pulse and interval values
  OCR1A = pulses[0];  // first pulse duration
  ICR1 = intervals[0];  // dummy interval before first pulse
  TIFR1 |= bit (OCF1A);    // clear interrupt flag
  TCNT1 = ICR1 - 1;
  TCCR1B = 1 << WGM13 | 1 << WGM12 | 1 << CS11;  // Fast PWM, TOP = ICR1, pre = 8
  interrupts();
}

ISR(TIMER1_COMPA_vect) {
    count++;
    if (count >= 4)  TCCR1B = 0;  // stop the timer after 4th pulse
    else {
      ICR1 = intervals[count];
      OCR1A = pulses[count];
    }
}
void loop() {

}

The board is Arduino Nano (Atmega328P)

Thanks. I'll look into the PWM features. So what you are saying is I set the delay t1 (see my post #10) in interrupt and properly set up the output pin to "toggle" at compare equal and use the corresponding interrupt to load t2-t1, then t3-t2, then t4-t3. That would be better. But still since the interrupt processing takes about 2.5us, I don't know if I can improve past that point, correct?