Search code examples
carduino-unopwmatmega32

ATmega328 Timer1 Fast PWM Mode 15 Not properly set up


I am having issues with properly setting up the fast PWM mode 15 in my Arduino Uno (ATmega328).

Most of the registers seem pretty straight-forward, but I am not sure about a few of them.. For example, I struggle with understanding what type of compare output modes (COM) to choose.. What does it mean if OC1A or OC1B are disconnected. Will OC1A still serve as the TOP value for mode 15?

I am aware that OC1A will not generate a signal with mode 15, so I am measuring the PWM signal on the OC1B pin (PB2).

This is my code for setting a PWM frequency of 100Hz with a duty cycle of 0.5, but I am picking up no signal on PB2:

void setup(){
    DDRB = 0;
    DDRB |= (1 << PB2) | (1 << PB1);  // Set OC1A and OC1B to outputs

    // Set WGM 3:0 = 0b1111
    // Set prescaler to 1024 with CS2:0 = 0b101
    // Set COM to non-inverted for OC1B (UNSURE HOW THIS WORKS)
    TCCR1A |= (1 << COM1B1) | (1 << WGM11) | (1 << WGM10);
    TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS02) | (1 << CS00);
    TCNT1=0;

    /* Formula for calculating OCR1A for 100Hz with prescaler 1024 chosen:
    f = Fclk/(presc*(OCR1A + 1)) -> OCR1A = (16000000/(100*1024))-1 = 155
    */
    OCR1A = 155;
    OCR1B = 77; // For a duty cycle of 50%
    SREG |= 0b10000000;     // Enable global interrupts
}
void loop() {}

Solution

  • The error is the ORing assignment of values into TCCR1A and TCCR1B.

    This little sketch reveals the values when setup() is called:

    void setup() {
        Serial.begin(115200);
        while (!Serial);
        Serial.println(TCCR1A, HEX);
        Serial.println(TCCR1B, HEX);
    }
    
    void loop() {
    }
    

    TCCR1A holds 1, and TCCR1B holds 3. Consequently, ORing CS12 and CS10 into TCCR1B results in the selection of the pin T1 with rising edge (CS2:0 = 0b111). And there is no clock.

    Apparently the reset values of zero were changed. I assume the bootloader does this, but I did not verify it. The reason doesn't matter, the fact does.

    The solution is to assign the chosen value directly:

    void setup() {
        // Set pins for OC1A and OC1B to outputs
        DDRB = (1 << PB2) | (1 << PB1);
    
        // Set WGM 3:0 = 0b1111
        // Set prescaler to 1024 with CS2:0 = 0b101
        // Set COM to non-inverted for OC1B
        TCCR1A = (1 << COM1B1) | (1 << WGM11) | (1 << WGM10);
        TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12) | (1 << CS10);
        TCNT1=0;
    
        /* Formula for calculating OCR1A for 100Hz with prescaler 1024 chosen:
        f = Fclk/(presc*(OCR1A + 1)) -> OCR1A = (16000000/(100*1024))-1 = 155
        */
        OCR1A = 155;
        OCR1B = 77; // For a duty cycle of 50%
    }
    
    void loop() {
    }
    

    Other notes:

    • Since you assign 0 to DDRB before the ORing in the next line, you can use a simple assignment here, too.
    • It is not necessary to enable interrupts, your code does not use interrupts.
    • You used CS0x instead of CS1x, but since these are just names for identical values, it makes no difference. However, it is misleading.
    • You can let the compiler calculate the values for OCR1A and OCR1B. I leave this as an exercise.

    Embedded questions:

    What does it mean if OC1A or OC1B are disconnected?

    Presumably you refer to the entry in table 16-1. Scroll back some pages to figure 16-5 and look at the OR gate. If both COM1B1 and COM1B0 are 0, the output compare signal is not connected to the output pin.

    Will OC1A still serve as the TOP value for mode 15?

    Yes, it does.

    Set COM to non-inverted for OC1B (UNSURE HOW THIS WORKS)

    Use another value for OCR1B for a different percentage. Then watch the output signal with (1 << COM1B1). It is set with BOTTOM and reset with OC1B. Now use (1 << COM1B1) | (1 << COM1B0) and observe that the signal is inverted.