# Setting a simple PWM on atmega328 at certain frequency

I'm having a hard time to set a 16 bit PWM on atmega328 that works with frequency of 50Hz and have a duty cycle that can be varied. I also have my doubts that if I can have an ADC working together with this 16 bit PWM since both use timer1. There are the auto trigger source selection ADTSX on the register ADCSRB that I supposed should be able to select the clock source for my analog conversion but I've setted and nothing changed, I couldn't have timer1 ISR and analog conversion at same time and I didn't tested for PWM and ADC together because I don't know how to set the PWM.

This is the code just for setting PWM that I've right now:

#include <avr/io.h>

int flag = 0;

ISR(TIMER1_COMPA_vect) {
if (flag == 0)
OCR1A = 11;
else
OCR1A = 160;
}

int main(void) {
DDRB |= (1 << DDB1);
// PB1 as output
OCR1A = 160;
// set non-inverting mode
TCCR1A |= (1 << COM1A1);
// set 10bit phase corrected PWM Mode
TCCR1B |= (1 << WGM12);
// set no prescaler
TCCR1B |= (1 << CS10);
while (1)
{
}
}


PS: I'll be varying the duty cycle between 1ms to 2ms activating an ESC controlling a brushless motor.

• What is the system clock rate? Jul 6, 2016 at 8:15
• Hi Ignacio, thank you for your quick reply. The system clock rate is 16Mhz. Jul 6, 2016 at 8:22
• Have you considered using a servo library instead of hand-rolling your own code? Jul 6, 2016 at 8:55
• No I have not, because I may want to change the frequency later, or use a different duty cycle for another project, and the servo library would not suffice. Jul 6, 2016 at 9:08
• "I couldn't have timer1 ISR and analog conversion at same time"? Why not? Shouldn't be a problem at all. Jul 6, 2016 at 12:38

## 1 Answer

For full 16 bit resolution, you'll want Waveform Generation Mode 8, i.e. WGM13:0 = 1000b.

In this mode, you define the resolution (=max counter value) through the value of the Input Capture Register, so you need ICR1 = 0xffff.

Depending on the polarity you want you set the Compare Output Mode COM1A1:0 to 10b or 11b.

TCCR1B = 0; // (If timer may already be running:) Make sure the timer is stopped during (re-)configuration

TCNT1 = 0x0000; // (If timer may have already been used since last reset:) Set counter value to begin of PWM cycle to prevent a potential glitch in the first cycle

ICR1 = 0xffff; // "TOP" = 0xffff -> full range of 16 bit counter

OCR1A = <initial duty cycle>; // 0x0000...0xffff

TCCR1A =
// Compare output mode (10b):
(1 << COM1A1) | (0 << COM1A0) |

// lower 2 WGM bits (00b):
(0 << WGM11) | (0 << WGM10);

TCCR1B =
// upper 2 WGM bits (10b):
(1 << WGM13) | (0 << WGM12) |

// Set prescaler/start timer (001b):
(0 << CS12) | (0 << CS11 ) | (1 << CS10);


(Haven't tried this.)

Now the timer is running up and down toggling the OC1A pin every time it passes OCR1A. All you have to do is set a new OCR1A value when you want to change the duty cycle; the rest is done by the hardware.

• Thank you for your response Jimmy. I was able to set the frequency right because I can see 50Hz calculated by the eq: f_{pfc} = f_{clk} / (2 * prescaler * ICR) and setting 8 for prescaler 0x4E20 for ICR. I don't know I'm can't vary the duty cycle altering OCR1A with double slope operation I don't know if I need to change ICR to another value to compensate the time when the pwm is on the duty cycle (high level). May 11, 2017 at 22:56
• OCR1A needs to be setted on every loop iteration. Eg. to set duty cycle of 2ms OCR1A must be equal to 0x07D0. With the configuration mentioned previously we'll be able to vary 20000 times the duty cycle on the 20ms period. Thank you very much everyone. May 11, 2017 at 22:57
• Notice that ICR1 should only be set after TCCR1A and TCCR1B has been configured to use ICR1 as TOP. And OCR1A and OCR1B use double buffering for PWM and should also only be set after TCCR1A and TCCR1B has been configured to use PWM. See this answer for more details. Nov 16, 2020 at 20:13
• @hlovdal All setup should be done before the timer is actually started (via TCCR1B) or you risk a race condition by reconfiguring a timer that's already running. Dec 22, 2020 at 9:41
• Yes, I have split up configuring TCCR1B and starting TCCR1B in two operations (with modifying ICR1, OCR1A and OCR1B in between) in my answer. Dec 23, 2020 at 18:24