Generate slow PWM signal using a Timer Interrupt

I am making a device that the main function is to output a PWM signal that is variable in range of 20-120Hz and duty cycle variable between 0-100%. I have a lot of other things going on in the program and cannot put it in my main loop function because it will be interrupted by other events. So my idea is to have the flashing in a timer interrupt. I can get it working when I just toggle the output, but when I try to incorporate the duty cycle it gets weird and out of sync. Does anyone have any ideas on how I could fix this?

Here is what I am doing right now, but I very well may be doing this completely wrong.

#define output 13

int frequency = 30;
double duty = 0.9;

volatile boolean state = LOW;
double onFreq, offFreq;

void setup()
{
  pinMode(output, OUTPUT);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  
  calcFreqs();
  TCNT1 = timer(onFreq);    // preload timer 65536-16MHz/256/Frequency
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_OVF_vect)
{
  if(state == HIGH)
    TCNT1 = timer(offFreq);
  else
    TCNT1 = timer(onFreq);
   
  digitalWrite(output, digitalRead(output)^1);
  state = !state;
}

long int timer(double frequency)
{
    return 65536-(16000000L/(256*frequency));
}

void calcFreqs()
{
  onFreq = frequency*duty;
  offFreq = frequency-onFreq;
}

void loop()
{
  calcFreqs();
  
  if(frequency == 120)
      frequency = 20;
  else
      frequency++;
  delay(500);
}

Why do you need to change the PWM frequency? - that seems an unusual requirement.

Why not program Timer1 to produce the PWM output directly?

...R

That’s a good idea. I didn’t want to do that originally because it only allows for PWM on pins 9 and 10, and I already had my circuit made. This way seems much better than what I was doing, so I will modify my circuit to make this work.

The Timer1 (https://playground.arduino.cc/Code/Timer1) allows for the frequency and duty cycle to be changed at any time, so that should work great. Thanks!

karlschmaltz:
I am making a device that the main function is to output a PWM signal that is variable in range of 20-120Hz and duty cycle variable between 0-100%.

I see you already chose a solution, but keep this idea for reference:

volatile uint8_t value;
volatile uint8_t count;

#define PWM_OUT PORTB
#define PWM_DDR DDRB
#define PWM_BIT _BV(7)

PWM_DDR |= PWM_BIT; // set pwm pin as output

// ISR on timer 1
ISR (TIMER1_COMPA_vect)
{
    (count-- > value) ? PWM_OUT |= PWM_BIT : PWM_OUT &= ~PWM_BIT;
}

The "PWM_OUT", DDR and BIT are defined by you depending on what pin you want to use. Then, setup any timer you wish to any frequency you wish (times 256) and then simply put a value from 0 to 255 into "value".

Since "count" is 8 bit, it just spins around FF, FE, FD,....2, 1, 0, FF... etc... and the pin is turned on or off depending on the comparison of "count" to "value".

Since one PWM cycle is 256 interrupts long, you need to take your desired PWM rate and multiply by 256. For example, if you want a 100 Hz. PWM frequency, you set the timer rate to 25600 and then the output changes 100 times per second with a duty cycle depending on "value".

Stick this in your notebook! :slight_smile: