Best way to interrupt using a changing counter target

A bit awkward to explain perhaps but say I’ve got a NONLINEAR array of numbers like 1,2,3,5,7,10,15,31, etc (I just made those up) and they go up to like 66000 or thereabouts. I’m already using timer1 for something so I have to use timer 2 to basically check each element of the array in order and interrupt the program when there’s a match. So IF count = 31, interrupt. Then IF count = 59, interrupt. Then IF count = 2016, interrupt.

Obviously this is going to lead to overflows very quickly so I could do something like counting overflows and adding 256 for each of them and then the remainder is counted on the final cycle. However I don’t have a lot of time to do math like that in the ISR because first I have to clear TCNT2 so while I’m doing math to update the count, the timer meanwhile is back to counting again (this is at clock speed btw so very fast). So while at the end of the array I may have some time, at the beginning of the array I have very little time. In fact the lowest number in my array is 85 so 85 ticks is all the time I have to execute a code block that handles all the different possible scenarios for keeping track of this changing number. I’m afraid I’ll miss an interrupt event this way or the accuracy of triggering such an event won’t be centered on its target perfectly.

Ideally I would like to simply have TCNT2 do all the work for me all by itself because it’s got its own compare and overflow interrupts but with 8 bits it just won’t go very far. My numbers range from 85 to I think 66000’ish.

Just wondering if there’s an optimized or defacto way to do this sort of thing to avoid glitches and such. I would post code except there is none. Nothing in the main loop anyway. I just have my timer1 and timer2 setup code, both running with no prescaler and that’s it. Beyond that I just need to do my work in the ISRs.

Thanks.

For counting past 65535 you are going to need your counter to be more than 2 bytes long. I would use 16 bits for the overflow counter so you have 24 bits altogether.

Here is a little code to get you started:

  noInterrupts ();  // protected code
  // Reset Timer 2 to WGM 0, no PWM, and no clock
  TCCR2A = 0;
  TCCR2B = 0;

  TCNT2 = 0;  // Reset the counter
  TIMSK2 = 0; // Turn off all Timer2 interrupts

  // Set WGM 3
  TCCR2A = _BV(WGM21) | _BV(WGM20);

  TCCR2B = _BV(CS20); // start Timer2, no prescale

  TIMSK2 = _BV(TOIE2); // Timer 2 Overflow Interrupt Enable

  // Clear the Timer1 Overflow Flag (yes, by writing 1 to it)
  // so we don't get an immediate interrupt when we enable it.
  TIFR2 = _BV(TOV2);

  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER2_OVF_vect)
{
  Overflows++;
}

That’s a good point about creating a 24 bit number to store the current count but the way you have this set up, isn’t it only going to count in increments of 256? So if I wanted to count to 300 it would only trigger at 512 and I’d be late by (512 - 300) ticks?

The “Overflows” value contains the top 16 bits. The bottom 8 bits are in TCNT2.

You can enable Output Compare interrupts for OCR2A or OCR2B to interrupt anywhere along the way.

Can you explain what the application is.
How many entries is the array expected to have ?
At what frequency are your ticks. You’ve referred to clock speed so is that, say, 16MHz ?
What processing are you going to do when you have a match between the tick count and an array entry and what timing precision is required ?
When you get to the end of the array, are you going to start again ?

John, oh I see the strategy now. Use the upper bits to track the overflows which are in blocks of 256 and use TCNT2 to track the remainder. That’s not bad.

6v6gt: I’m trying to use timer2 to modulate a frequency on timer1. So timer1 is say 20kHz and I want to modulate that at 60Hz using timer2. The array is 256 time-stamps corresponding to when the modulation duty cycle needs to change. I could do things in the “duty domain” or the “time domain” but since duty can only be whole numbers the finer resolution is on the time side so I’m doing this in the time domain which means I need to constantly change the duty but only when a duration stored in my array has elapsed. The array will undergo a pogo oscillation rather than re-starting it. Frequency is indeed 16MHz. Once I match the current count to the array entry I need to change the duty on Timer1 to the complementary array value for duty cycles that I’ve stored in advance and that’s pretty much it. Time precision doesn’t need to be 1 count or anything laser precise but should not be +/- 5% either. It should be “close”.

I did something similar for an IR learning remote control project. It worked at the carrier frequency (say 38 kHz but it could go higher). I used only one timer in the final design of the replay part.
At the begining of a cycle, it loaded, from an array, the count of the carrier pulses it had to deliver for both the mark and the space. The timer ISR triggered at the timer frequency and decremented the current count. At zero it swapped over from a mark to a space. During the space period it counted dead time. At the end of the space the cycle began again loading the data, from the array, for the next cycle. Thus generating the IR signal.

It is published here with all code etc. if you are interested: Arduino IR Learning Remote Control

Yep that sounds very much like what I’m trying to do. I think the main difference is that while you were able to increment and decriment, I can’t do that because both the time values and the duty values have a non-linear correlation to each other so I have to rely on interrupts (or constant polling but that would be abysmal) to tell me when to switch an array value.

Can you draw an example wave form that you are expecting to send. This is an IR example. Each of the narrow green vertical blocks represent about 20 carrier pulses at 38KHz.
image

something like this

Is it a fixed frequency PWM sine wave or is it just 'something like" a fixed frequency PWM sine wave. If it is different, what is different? Does the waveform change dynamically? Does the frequency change?

I have several example sketch for single phase or three phase 50/60 Hz sine waves in PWM.

I mean I want to do different types of power generation eventually but for now just 60 hz is perfectly fine. I’m only concerning myself with the first 90 degrees because the rest is symmetry. I figure lookup tables or arrays in this case is the simplest way to do it and the fastest execution time.

It occurred to me that if I use Timer1 to do my timekeeping and I put the prescaler at 8 instead of 1, then I can count the entire range and use the compare register/interrupt. The problem is that then I have to use Time2 for my carrier frequency and I haven’t been successful yet setting an arbitrary frequency AND PWM support. I think I’m using mode 3 at the moment and if I recall correctly that allows PWM but you can’t define the TOP value. It’s always 255. If you choose a mode that allows you to use OCR to define the TOP value, then you lose PWM support or at least I couldn’t figure out how to get it to work. With my current setup and the available prescalers I can either get 60kHz or 7kHz, both of which suck for the application.

EDIT: No I said that wrong. The actual problem is, if I want frequency control AND PWM support, I have to give up complimentary inverted output channels. So I can’t use this on an H-Bridge anymore. If I want the complimentary channels, then I must settle for either a loss of PWM or a loss of frequency control. If I need all 3, I’m screwed. I don’t really need frequency control but I need a frequency in and around 20kHz. With phase correct modes, I can use no prescaler and I get 30 kHz. With fast PWM modes I can use an 8 prescaler and I get about 8kHz. However in no case can I get between 8-30kHz and have PWM and complimentary outputs. For that I need Timer1 and then I’m stuck with using an 8 bit timer to count with.

I guess there are still a couple of ways out. I could use an external clock source or I could use a different arduino like the Leo built in the Nano form factor. Now that I’m talking myself through it, other than cost, I guess there’s really no reason to use a 328 if I’m having these limitations.

Maybe this example will provide some insight:

/*
  Demonstration of 50/60 Hz sine wave PWM via H-Bridge
  Written by John Wasser

  Uses Timer1 for 16-bit PWM.
  Alternates between pulses on Pin 9 and Pin 10 to get positive and negative half-cycles.

  50Hz gives you (F_CPU/50) clock cycles per cycle.  That's 320,000 on a 16 MHz UNO.
  You want as many pulses per cycle as possible but also the maximum TOP to get the
  best PWM resolution.  The maximum TOP is 65536 but that only gives you <5 PWM pulses.
  As you cut TOP in half, the number of pulses doubles. If we choose 256 pulses per cycle
  we get a TOP value of 1250 but since Phase Correct PWM counts both up and
  down the actual TOP has to be cut in half to get the right frequency.  Still, 625 levels
  of PWM is pretty good.
*/

const unsigned int LineFrequency = 50;
const unsigned int PulsesPerHalfCycle = 128;
const unsigned int TOP = (((F_CPU / 2) / LineFrequency) / (PulsesPerHalfCycle * 2)) - 1;

volatile uint16_t SineTable[PulsesPerHalfCycle]; // Sine values from 0 to TOP for the first PI radians

void FillSineTable(uint16_t scaleFactor)
{
  for (uint16_t i = 0; i < PulsesPerHalfCycle; i++)
  {
    float angle = (PI * i) / PulsesPerHalfCycle;
    noInterrupts();
    SineTable[i] = sin(angle) * scaleFactor;
    interrupts();
  }
}

void PWM16Begin()
{
  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TCNT1 = 0;   // Reset the counter
  TIMSK1 = (1 << TOIE1);  // Timer/Counter1 Interrupt Mask Register: Enable Overflow Int.
  TIFR1 = 0;   // Timer/Counter1 Interrupt Flag Register
  ICR1 = TOP;
  OCR1A = 0;  // Default to 0% PWM on Pin 9
  OCR1B = 0;  // Default to 0% PWM on Pin 10
  digitalWrite(9, LOW);
  pinMode(9, OUTPUT);
  digitalWrite(10, LOW);
  pinMode(10, OUTPUT);

  // Set clock prescale to 1 for maximum PWM frequency
  TCCR1B |= (1 << CS10);

  // Set to Timer/Counter1 to Waveform Generation Mode 10:
  // Phase Correct PWM with TOP set by ICR1
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM13);
}

// Overflow interrupt is triggered at BOTTOM and the Output Compare
// Registers (OCR1A and OCR1B) are buffered until TOP (the middle
// of the next pulse)
ISR(TIMER1_OVF_vect)
{
  static boolean positiveHalfCycle = true;
  static uint16_t pulseCount = 0;

  // Load both Compare registers, even though only one is active at a time.
  OCR1A = OCR1B = SineTable[pulseCount];

  pulseCount++;
  if (pulseCount >= PulsesPerHalfCycle)
  {
    // Half Cycle is Over.  Start the other half cycle
    pulseCount = 0;

    if (positiveHalfCycle)
    {
      // Positive Half Cycle just ended
      TCCR1A &= ~(1 << COM1A1);  // Turn off Pin 9 (OCR1A) PWM
      digitalWrite(9, LOW);
      TCCR1A |= (1 << COM1B1);  // Turn on Pin 10 (OCR1B) PWM
      positiveHalfCycle = false; // Starting negative half cycle
    }
    else
    {
      // Negative Half Cycle just ended
      TCCR1A &= ~(1 << COM1B1);  // Turn off Pin 10 (OCR1B) PWM
      digitalWrite(10, LOW);
      TCCR1A |= (1 << COM1A1);  // Turn on Pin 9 (OCR1A) PWM
      positiveHalfCycle = true; // Starting positive half cycle
    }
  }
}

void setup()
{
  Serial.begin(115200);
  while (!Serial && millis() < 5000) {} // Give USB up to 5 seconds to connect
  delay(200);  // Give Serial Monitor time to connect and reset the Arduino
  Serial.println();

  FillSineTable(0);  // Start with the amplitude set to 0

  PWM16Begin();  // Start the AC output on pins 9 and 10.

  Serial.print("LineFrequency = ");
  Serial.println(LineFrequency);
  Serial.print("PulsesPerHalfCycle = ");
  Serial.println(PulsesPerHalfCycle);
  Serial.print("TOP = ");
  Serial.println(TOP);
}

void loop()
{
  static uint16_t oldScaleFactor = 0;

  // Adjust PWM amplitude with an analog input.
  uint16_t analogValue = analogRead(A0);

  uint16_t scaleFactor = (TOP * analogValue) / 1024;

  if (scaleFactor != oldScaleFactor)
  {
    Serial.print("scaleFactor = ");
    Serial.println(scaleFactor);
    FillSineTable(scaleFactor);
    oldScaleFactor = scaleFactor;
    delay(100);
  }
}

Interesting. I seems you opted to do your work in the duty domain rather than the time domain, which would have advantages in terms of the size of the numbers you are working with. I may have overestimated the need for sine wave resolution. With a smoothing capacitor, the differences between 128 steps between 0 - pi and 256 steps from 0 - 1/2 pi is probably not going to make a difference in the end.

You could change both the duty cycle and the frequency by having look-up tables for both duty cycle and TOP.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.