PWM - first cycle after reset

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MCU: ATtiny2313

I use a hardware PWM pin to control the enable pin of a LED driver (constant current shift register). It works as expected, only that after reset all of the LEDs flash for a brief moment. The only explanation for this is that the enable line must be LOW for that period. Only I don't see where this happens. Initially all I/O lines are set as inputs, pull-up resistors enabled.

I've tracked down my problem to this piece of code, that is the initialization of the timer.

#include 
#include 
#include 
#include "led_driver.h"
#include "system_ticker.h"
#include "status_leds.h"

// timer1 setup
// // set prescaler to 8 for a PWM frequency of about 1kHz (F_CPU = 8000000)
// TCCR1B &= ~(_BV(CS12) | _BV(CS10));
// TCCR1B |= _BV(CS11);
// // set WGM mode 7: FAST PWM 10bit
// TCCR1B &= ~_BV(WGM13);
// TCCR1B |= _BV(WGM12);
// TCCR1A |= (_BV(WGM11) | _BV(WGM10));
// // connect OC1A (PB3) to the PWM generator (wired to the /ENABLE pin of the led driver)
// TCCR1A |= _BV(COM1A1);
// TCCR1A &= ~_BV(COM1A0);

// default value of TCCR1A: 00000000
#define TCCR1A_val ( _BV(WGM11) + _BV(WGM10) + _BV(COM1A1) )
// default value of TCCR1B: 00000000
#define TCCR1B_val ( _BV(CS11) + _BV(WGM12) )

void led_driver_setup(void)
{
    DDRB |= _BV(PB3);       // make OC1A (PB3) an output
    PORTB |= _BV(PB3);      // set PB3 HIGH
    OCR1A = OCR1A_MAX;      // compare match - 100% duty cycle - all off
    TCCR1A = TCCR1A_val;    // doing it this way saves flash space ;-)
    TCCR1B = TCCR1B_val;
}

If I don't call led_driver_setup() from main(), there is no erroneous flash.

If I read the datasheet correctly, the PWM cycle should start with OC1A as HIGH and drop to LOW once the compare match has occurred. As the code works as intended (except for the flash at startup), this assumption seems correct. I've also played with disabling interrupts while setting the TCCR1X registers, but that didn't change a thing.

Is there a special sequence to ensure that the first PWM cycle after reset doesn't glitch to LOW?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

madworm wrote:
If I read the datasheet correctly, the PWM cycle should start with OC1A as HIGH and drop to LOW once the compare match has occurred.
Why do you think so? The data sheet clearly states:
Quote:
If a system reset occur, the OC1x Register is reset to “0”.
That together with the non-inverting mode means, that the first PWM cycle is continuously low, no matter what OCR1A actually contains. To prevent that you have to use the "Force Output Compare" feature to give OC1A an initial value of 1 before the timer is configured and started.

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hmmm. Putting that piece of information into the very first section in the datasheet is a bit too obvious it seems. Anyhow.

I've added

TCCR1A = _BV(COM1A0);   // toggle OC1A on compare match
TCCR1C = _BV(FOC1A);    // force comp. match to flip OC1A from 0 (after reset) to 1

before the timer init and it is definitely an improvement, but there is still a brief and very weak flashing of the LEDs. Maybe I'm still doing something wrong here. I'll have to probe the actual signal and see what it does.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
but there is still a brief and very weak flashing of the LEDs.

    DDRB |= _BV(PB3);       // make OC1A (PB3) an output
    PORTB |= _BV(PB3);      // set PB3 HIGH 

If you haven't set PB3 in PORTB in your code before, then it is 0 here and therefore the output is 0 for a short time. Either interchange the lines, or move the DDRB-line behind your new OC1A init code (then you don't even need the PORTB-line).

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

All I/O lines are inputs with enabled pull-up resistors. This happens way before the timer business.

Putting the DDRB line at the end was the thing to do. It turns out that the remaining glitch to low happened when the forced compare match was done.

bad:

void led_driver_setup(void)
{
    DDRB |= _BV(PB3); // PORTB is 0xFF already
    OCR1A = OCR1A_MAX;
    TCCR1A = _BV(COM1A0);
    TCCR1C = _BV(FOC1A); // <-- glitch to 0, then gets 1
    // skipping the last 2 lines doesn't change a thing
    TCCR1A = TCCR1A_val;
    TCCR1B = TCCR1B_val;
}

good:

void led_driver_setup(void)
{
    OCR1A = OCR1A_MAX;
    TCCR1A = _BV(COM1A0);
    TCCR1C = _BV(FOC1A);
    TCCR1A = TCCR1A_val;
    TCCR1B = TCCR1B_val;
    DDRB |= _BV(PB3);
}

Now I can sleep peacefully ;-)