I am prototyping a simple LED dimmer on an Arduino Uno (Atmega328p) and I have issues with the PWM frequency and duty cycle I am trying to adjust.
To get a frequency of 200Hz flat I am using the CTC mode and I used the formula in the datasheet (OCR2A = (F_CPU/2N200Hz)-1) to calculate the upper output compare register (OCR2A). It's 38 in my case.
The lower output compare register (OCR2B) is being controlled by an ADC input.
The ADC is working fine. I checked it with the serial monitor. So that part of the code can be ignored.
My main gripe is the frequency. According to my cheap handheld oscilloscope the frequency is stuck at 60Hz even if I change OCR2A. I can't explain why.
Then there's a problem with the duty cycle. If OCR2B is at 6 the duty cycle is at ca. 13%. If OCR2B is at 37 the duty cycle is close to 0%. I expected the duty cycle to be much higher the lower OCR2B is. Why is this not the case?
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
void setup() {
sei();
DDRC = 0; // Input for ADC
DDRD |= (1<<PD7); // PWM Output
ADMUX = 0; // use ADC0
ADMUX |= (1 << REFS0); // use AVcc as the reference
ADMUX |= (1 << ADLAR); // Right adjust for 8 bit resolution
ADCSRA |= (1 << ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Enable the ADC, 128 prescale
TIMSK2 |= (1<<OCIE2B)|(1<<OCIE2A)|(1<<TOIE2); // enable Timer/Counter Output Compare Match B Interrupt, enable Timer/Counter Output Compare Match A Interrupt, Ebable Timer/Counter Overflow Interrupt
TCCR2A |= (1<<WGM01); //CTC Mode enabled
TCCR2B |= (1 << CS22)|(1<<CS21)|(1<<CS20); //prescale: N=1024
OCR2A = 38; // 200Hz PWM (upper threshold of timer compare register), OCR2A = (F_CPU/2*N*200Hz)-1
}
void loop() {
ADCSRA |= (1 << ADSC)|(1 << ADIF); // Start the ADC conversion -> ADSCRA = 0101 0000
while(ADCSRA & (1 << ADIF) == 0); //ADSCRA = 0101 0000 & 0001 0000 = 1 ---- 0100 0000 & 0001 0000 = 0 -> conversion finished
OCR2B = ADCH*38/255; //scaling of OC2RB, since OC2RB (lower threshold of timer compare register) must not be higher than OC2RA!
}
ISR(TIMER2_COMPA_vect) {
PORTD &= ~(1<<PD7);
}
ISR(TIMER2_COMPB_vect) {
if (OCR2B < 5){
PORTD &= ~(1<<PD7);
} else {
PORTD |= (1<<PD7);
}
}
Best Answer
You are probably also seeing is that the two interrupts can't execute simultaneously (they take a bit of time each) meaning that you either start missing some, or they start toggling the output out of sync with each other.
In practice this isn't the best way to generate a PWM signal using the timers, ideally you should use the PWM waveform generator modes.
Timer 2 has two dedicated outputs,
OC2A
andOC2B
, which correspond toPB3
(digital pin 11 in Arduino speak) andPD3
(digital pin 3). These timer outputs allow direct PWM generation with various controls over the paramters. Each of these outputs has a special compare registerOCR2A
andOCR2B
.Because you want accurate frequency control, you need to be using the special modes which use both compare registers to generate a single PWM signal. The timer is configured such that the counter resets each time it matches
OCR2A
(the same as CTC mode) to set the period of the counter, leavingOCR2B
to allow setting of the duty cycle.This means you'd need to use
OC2B
(drivesPD3
) for your output. You've mentioned in the discussion we had that this is no issue, so you're good to go with the hardware method.Based on the datasheet timer information, this means you'll need the following settings.
Firstly, you'll want to set the generator into either Fast PWM mode, or Phase Correct PWM mode. The former will allow twice the timing resolution, the latter results in a symmetric waveform which can help with harmonic suppression. In your case a higher resolution for the duty cycle would be better, so lets go for Fast PWM.
Looking up the Waveform Generator Mode setting for the timer (
WGM2[2:0]
) on Table 18-8, we see for Fast PWM withOCR2A
as the top value (to set frequency), mode 7 is required. This means all bits in theWGM2
setting need to be a 1. This gives:Next the comparison mode for the output pin - this allows you to tie PD3 directly into the timers PWM generation. You can either have non-inverting PWM or inverting PWM. Lets assume non-inverting, this gives a Compare Output Mode for
OC2B
output (registerCOM2B[1:0]
) of 2. So we need to setCOM2B1
and leaveCOM2B0
clear.Now we need to work out the prescalar value for the timer, along with the
OCR2A
value such that you get a PWM frequency of 200Hz. For some insight into where this equation comes from, the period of the PWM signal is set by the number of count values (OCR2A+1
in Fast PWM mode) before the timer overflows, divided by the frequency of the counter. The frequency of the counter is simply the CPU frequency divided by a prescalar. This gives for Fast PWM:$$f_{pwm} = \frac{f_{cpu}}{\mathrm{Prescalar} \times (\mathrm{OCR2A} + 1)}$$
For completeness for Phase Correct PWM, the period of the counter would be \$(2\times\mathrm{OCR2A})\$ instead of \$(\mathrm{OCR2A} + 1)\$ because it counts up from 0 to
OCR2A
then back down to 0.Now we have an \$f_{cpu}\$ of 16MHz, and an \$f_{pwm}\$ of 200Hz. Rearranging a bit we get:
$$\mathrm{OCR2A} = \frac{16000000}{\mathrm{Prescalar} \times 200} - 1 = \frac{80000}{\mathrm{Prescalar}} - 1$$
The smallest prescalar value which results in an
OCR2A
value of less than 256 (8-bit) is the 1024 setting, which gives:Then we want to enable the timer to count at \$f_{cpu}/1024\$ which corresponds to a Clock Select (
CS2[2:0]
) value of 7.And we are good to go. All that is left is to enable the PWM output pin itself:
Now you can change the duty cycle between 0% and 100% by setting the duty cycle register
OCR2B
to any value between 0 (0%) andOCR2A=77
(100%) inclusive, giving you 78 possible duty cycles and a fixed 200Hz period.