2

On the Atmel ATmega328P (datasheet), there are three timers available for PWM generation (timer0, timer1, and timer2).

I already have what I need using the 8-bit timer2, I am just concerned with using different timer instad of timer2, because timer2 is used in various libraries, and I'd like to have more granularity. Thus I'd like to use the 16-bit timer1.

Here is what I am using to generate a 25 kHz, variable duty cycle using timer2. For this example, lets consider a 35% duty cycle:

void setup() 
{

    /*
    16*10^6/ [prescalar] / ([OCR2A]) / 2 = [desired frequency]
    16*10^6/ 8 / [OCR2A] / 2 = 25*10^3

    Prescalar table for Timer2 (from datasheet section 17-9):
    CS22    CS21    CS20
      0     0       0       = No clock source (Timer/couter stopped)
      0     0       1       = clkT2S/(No prescaling)
      0     1       0       = clkT2S/8 (From prescaler)
      0     1       1       = clkT2S/32 (From prescaler)
      1     0       0       = clkT2S/64 (From prescaler)
      1     0       1       = clkT2S/128 (From prescaler)
      1     1       0       = clkT2S/256 (From prescaler)
      1     1       1       = clkT2S/1024 (From prescaler)
    */

    pinMode(3, OUTPUT);
    TCCR2B = _BV(WGM22) | _BV(CS21);
    TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0) | _BV(WGM20);
    OCR2A = 40; 
    OCR2B = 16; //40*0.35=16
}

void loop()
{ 
}

To get the same result using timer1 must be incredibly simple, however I'm not familiar with these registers. I have been looking around for explanations outside the datasheet. I have found this post: Secrets of Arduino PWM, however it only covers the use of timer2.

I have tried the following as per Stephan's suggestion, however it just leads to both outputs (D9 and D10) being held HIGH:

void setup() 
{

    pinMode(9, OUTPUT); //D9
    pinMode(10, OUTPUT); //D10

    // Set GPIO for timer1 output for OC1A and OC1B
    //DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x0009;

    // 75% duty cycle
    //OCR1B = 0xBFFF;

    //20.14.1, pg170
    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    //Table 20-6, pg171
    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12) | (1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

}

void loop()
{
}

I tried changing everything (ICR1, OCR1A, TCCR1A), but the only combination that did anything different was the following, which gives a 25kHz frequency on D10, and D9 held HIGH, but the HIGH duration is stuck at 4μs regardless of the registers. (I just guessed and checked with OCR1A to get 25kHz. I'm not sure why that works.)

void setup() 
{

    pinMode(9, OUTPUT);
    pinMode(10, OUTPUT);

    // Set GPIO for timer1 output for OC1A and OC1B
    //DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x0009;

    // 75% duty cycle 
    //This line causes both outputs to be held HIGH
    //OCR1B = 0xBFFF;

    //20.14.1, pg170
    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    //Table 20-6, pg171
    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12) | (1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

}

void loop()
{
}

I'm using an Arduino Nano breakout board for prototyping, with D9 and D10 being the timer1 output pins:

Arduino Nano pins highlighted for B1 and B2 (Image from https://bigdanzblog.wordpress.com)

I've tried changing the board but I have the same result.

Here is relevant information from the datasheet:

atmega28p table 20-3 through 20-5

atmega328p table 20-6

atmega328p table 20-7 timer prescaler

Bort
  • 493
  • 1
  • 8
  • 26

2 Answers2

3

Your problem is the order of initialization of the registers. Your Timer 2 code

TCCR2B = _BV(WGM22) | _BV(CS21);
TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0) | _BV(WGM20);
OCR2A = 40; 
OCR2B = 16; //40*0.35=16

works because OCR2A and OCR2B are initialized after TCCR2A and TCCR2A are configured. Your Timer 1 code

ICR1 = 0xFFFF;
OCR1A = 0x0009;
OCR1B = 0xBFFF;
TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM12) | (1 << WGM13);
TCCR1B |= (1 << CS10);

does not work because ICR1, OCR1A and OCR1B are initialized before TCCR1A and TCCR1B are configured. There are two different issues at play here, see the comments in the code below for explanation.

#include <inttypes.h>

#include "Arduino.h"
#include <avr/io.h>

// http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf
// https://content.arduino.cc/assets/Pinout-UNOrev3_latest.pdf

void setup()
{
    uint8_t sreg = SREG;
    cli();

    // Stop timer before configuring
    TCCR1B = 0;

    // 16.11.1 TCCR1A – Timer/Counter1 Control Register A
    TCCR1A =
        0
        | (1 << COM1A1) | (0 << COM1A0)  // Clear OC1A on Compare Match, set OC1A at BOTTOM (non-inverting mode)
        | (1 << COM1B1) | (0 << COM1B0)  // Clear OC1B on Compare Match, set OC1B at BOTTOM (non-inverting mode)
        | (1 << WGM11) | (0 << WGM10)    // Fast PWM mode 14 (TOP = ICR1), part 1/2
        ;

    // 16.11.2 TCCR1B – Timer/Counter1 Control Register B
    TCCR1B =
        0
        | (1 << WGM13) | (1 << WGM12)    // Fast PWM mode 14 (TOP = ICR1), part 2/2
        ;

    // IMPORTANT NOTE ABOUT ORDER OF INITIALIZATION:
    //   "The ICR1 Register can only be written when using a Waveform
    //   Generation mode that utilizes the ICR1 Register for defining
    //   the counter’s TOP value. In these cases the Waveform
    //   Generation mode (WGM13:0) bits must be set before the TOP
    //   value can be written to the ICR1 Register."
    // Thus initializing OCR1 before TCCR1A and TCCR1B has been
    // configured with Fast PWM mode 14 is wrong.

    // Set TOP value
    ICR1 = 0xFFFF;

    // IMPORTANT NOTE ABOUT ORDER OF INITIALIZATION:
    //   "The OCR1x Register is double buffered when using any of the
    //   twelve Pulse Width Modulation (PWM) modes. For the Normal
    //   and Clear Timer on Compare (CTC) modes of operation, the
    //   double buffering is disabled."
    // If initializing OCR1A before configuring TCCR1A and TCCR1B to
    // a PWM mode the value is written to the non-buffered OCR1A
    // register and the buffered OCR1A register contains some "random",
    // unused garbage value. When later changing to PWM the buffered
    // register will be enabled, and its existing garbage value will
    // be used.
    // Thus initializing OCR1A/OCR1B before TCCR1A and TCCR1B has
    // been configured with Fast PWM is wrong.

    // 25% duty cycle - yellow scope signal
    OCR1A = 0x3FFF;

    // 75% duty cycle - blue scope signal
    OCR1B = 0xBFFF;

    // 14.4.3 DDRB – The Port B Data Direction Register
    DDRB =
        0
        | (1 << DDB1) // PB1 (aka OC1A) as output - pin 9 on Arduino Uno
        | (1 << DDB2) // PB2 (aka OC1B) as output - pin 10 on Arduino Uno
        ;

    // Start the timer with no prescaler
    TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);

    SREG = sreg;
}

void loop()
{
}

With the code above I get the following signals

working screenshot

If I move the ICR1, OCR1A and OCR1B initializations to between TCCR1B = 0 and TCCR1A = ... I get the following

non-working screenshot

which I assume corresponds to that 4μs spike you observed.

hlovdal
  • 26,565
  • 10
  • 94
  • 165
1

Timer1 has 2 outputs, OC1A and OC1B. Both run off the same hardware timer and are therefore synchronized. The timer is capable to running in three different modes: Fast PWM Mode, Phase Corrected PWM Mode and Phase and Frequency Corrected Mode. You will need to chose the correct mode for you as well as the correct timer prescaler that fits your application. Below is an example.

// Timer1 Resolution 16-bit
// Timer1 A output at 25% Duty Cycle, Fast PWM Mode
// Timer1 B output at 75% Duty Cycle, Fast PWM Mode 

#include <avr/io.h>

int main(void)
{
    // Set GPIO for timer1 output for OC1A and OC1B
    DDRB |= (1 << DDB1) | (1 << DDB2);

    ICR1 = 0xFFFF;

    // 25% duty cycle
    OCR1A = 0x3FFF;

    // 75% duty cycle
    OCR1B = 0xBFFF;

    // set none-inverting mode
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));

    // Fast PWM mode
    TCCR1A |= (1 << WGM11);
    TCCR1B |= (1 << WGM12)|(1 << WGM13);

    // START the timer with no prescaler
    TCCR1B |= (1 << CS10);

    while (1);
}
Stefan Bossbaly
  • 6,682
  • 9
  • 53
  • 82
  • Your example results in both outputs being held HIGH. I don't understand *why* though. In fact pretty much any change to the code has the same result. See me edits. I'm in over my head here, so I'm sorry but I am not yet able to take what you've provided and get something working. I'm reading the datasheet over and over but I don't understand why your example doesn't work. – Bort Aug 31 '18 at 13:48
  • In your code you set `OCR1A` to `0x0009`. That is an extremely low value and means your duty cycle is less than 1%. For a 35% duty cycle you should set `OCR1A` to `0x5999`. Are you measuring the output with a oscilloscope? – Stefan Bossbaly Aug 31 '18 at 14:55
  • Yes, I'm using a scope. With OCR1A set to 0x0009, I get a freq of 25.00kHz and a HIGH duration of 4.00μs. With it set to 0x00FF, I get a freq of 976.6Hz and it's HIGH for 4.00μs. Can you deduce why that would be? – Bort Aug 31 '18 at 15:55
  • `OCR1A` is the compare register. Changing it should only effect your duty cycle and not your frequency since the "Fast PWM" mode 14 (`WGM11`, `WGM12`, `WGM13`) is selected. To change the frequency you would have to set the "Clock Selection Bit" in the `TCCR1B` register. The timer should count up to the value in `OCR1A` then invert the value then count up to the value in `ICR1` and then invert again. You can try selecting another PWM Mode with less resolution if you want. I can update my answer to include another mode if you want. – Stefan Bossbaly Aug 31 '18 at 16:01
  • If I set just CS11 with `TCCR1B |= (1 << CS11);`, I still get the 25kHz 4μs signal (no change). If I set just CS12, then both D9 and D10 are held LOW. (Keep in mind that the oscillating signal I see is on D10, and D9 is typically HIGH.) I will try anything you throw at me. I didn't find changing the mode to help either, but I may have used it wrong. Once I get something actually working as it should, I should be able to work out the specifics for my project. But right now, it seems like the registers are doing something they shouldn't be. Odd that timer2 works as expected and timer1 does not. – Bort Aug 31 '18 at 16:22