Topics

► Games

► Sound & Music

► Clocks

► GPS

► Tools

► Tutorials

By processor

► ATtiny85

► ATtiny84

► ATtiny841

► ATtiny2313

► ATtiny861

► ATmega328

► ATmega1284

About Me

About Me

Feeds

RSS feed

Four PWM Outputs from the ATtiny85

7th July 2014

The ATtiny processors allow you to generate an analogue output using Pulse Width Modulation (PWM). One or more counters in the chip allow you to generate a constant-frequency square wave and specify the proportion of the time it's off and on. Feed this directly to an LED to change the brightness of the LED, or feed it to a capacitor to generate a variable DC voltage.

This article looks at the options for getting PWM outputs from an ATtiny85. The ATtiny85 provides five programmable I/O lines (six if you reprogram the Reset pin), so at first sight it should easily be able to provide the three outputs needed to control an RGB LED. However, things aren't as straightforward as they seem.

Getting two PWM outputs

The standard Arduino core, provided in the Arduino IDE, includes support for the ATtiny85 provided you install appropriate pin definitions [1]. This allows you to use the analogWrite() function to get analogue output on pins PB0 and PB1 using PWM (pulse-width modulation) in Timer/Counter0.

Here's a demonstration that controls the red and green lights to give a colour-changing light that goes red - yellow - green - yellow - red:

/* Two PWM Outputs using AnalogWrite */

// Constants
const int Red = 0;
const int Green = 1;

void setup() {
  pinMode(Red, OUTPUT);
  pinMode(Green, OUTPUT);
}

// Sets colour Red=0 Green=1 to specified intensity 0 (off) to 255 (max)
void SetColour (int colour, int intensity) {
  analogWrite(colour, intensity);
} 

void loop() {
  for (int i=-255; i <= 254; i++) {
    SetColour(Red, abs(i));
    SetColour(Green, 255-abs(i));
    delay(10);
  }
}

However there are two good reasons not to use the analogWrite() function:

First of all, it only provides two PWM channels, whereas we need three to control the RGB light. Although the ATtiny allows you to use PB4 as a PWM output, the standard Arduino core doesn't support this with analogWrite().

Secondly, analogWrite() introduces a glitch into the PWM output. The PWM in Timer/Counter0 varies the pulse width from 1/256 for an input of 0 to 256/256 for an input of 255. However, the code for analogWrite() sets the output LOW when the input value is 0, so there's a discontinuity in the output for values of 0 and 1.

We can solve both these problems by programming the ATtiny85 registers directly as described in the next section.

Getting three PWM outputs

Looking at the possible PWM output pin options the ATtiny85 supports:

  • Output PB0: OC0A or OC1A.
  • Output PB1: OC0B or OC1A.
  • Output PB2: none.
  • Output PB3: OC1B.
  • Output PB4: OC1B.

We've already got OC0A on PB0 and OC0B on PB1. We can get a third PWM output using OC1B on PB4 [2].

First configure Timer/Counter0 for PWM on pins PB0 and PB1:

TCCR0A = 3<<COM0A0 | 3<<COM0B0 | 3<<WGM00;

Setting the WGM01 and WGM00 bits to 3 selects Fast PWM mode, the same mode as used by analogWrite() on those pins.

Setting COM0A0 and COM0B0 to 3 selects an inverted output on PB0 and PB1, so 255 sets the LED off and 0 turns it full on. I used inverted mode because with normal mode a compare value of 0 generates a pulse 1/256 wide, so the LED is never off.

Timer/counter0 divides down the clock frequency using a prescaler which is configured using register TCCO0B. We will leave this set to the value used by the Arduino core, 64, to avoid upsetting the standard functions delay(), micros(), and millis() which all use Timer/Counter0. With the default ATtiny85 1MHz clock this gives a PWM frequency of about 61Hz, which is fast enough to avoid flicker:

TCCR0B = 0<<WGM02 | 3<<CS00; // Optional; already set

Now we need to configure Timer/Counter1 to generate the third PWM output on PB4, using the compare register OC1B. We do this using the register GTCCR; setting PWM1B to 1 enables Fast PWM on the output OC1B, and setting COM1B0 to 3 specifies an inverted PWM waveform:

GTCCR = 1<<PWM1B | 3<<COM1B0;

For consistency I've also set Timer/Counter1 to inverted mode, so I don't need an if statement in the SetColour routine.

Finally we need to set the prescaler for Timer/Counter1 using register TCCR1. Here I've chosen a prescaler setting of 7 to give the same divisor of 64, for consistency with Timer/Counter0. All the other bits in this register are set to 0 as they are not relevant to this mode:

TCCR1 = 3<<COM1A0 | 7<<CS10;

There's a bug in current versions of the ATtiny85, and to use inverted mode on OC1B you also have to set COM1A0 to a non-zero value [3].

Here's a demonstration controlling three LEDs, or an RGB LED. It cycles smoothly between every pair of colours:

/* Three PWM Outputs */

// ATtiny85 outputs
const int Red = 0;
const int Green = 1;
const int Blue = 2;

volatile uint8_t* Port[] = {&OCR0A, &OCR0B, &OCR1B};
int Pin[] = {0, 1, 4};

void setup() {
  pinMode(Pin[Red], OUTPUT);
  pinMode(Pin[Green], OUTPUT);
  pinMode(Pin[Blue], OUTPUT);
  // Configure counter/timer0 for fast PWM on PB0 and PB1
  TCCR0A = 3<<COM0A0 | 3<<COM0B0 | 3<<WGM00;
  TCCR0B = 0<<WGM02 | 3<<CS00; // Optional; already set
  // Configure counter/timer1 for fast PWM on PB4
  GTCCR = 1<<PWM1B | 3<<COM1B0;
  TCCR1 = 3<<COM1A0 | 7<<CS10;
}

// Sets colour Red=0 Green=1 Blue=2 to specified intensity 0 (off) to 255 (max)
void SetColour (int colour, int intensity) {
  *Port[colour] = 255 - intensity;
}

void loop() {
  for (int colour=0; colour<3; colour++) {
    SetColour((colour+2) % 3, 0);
    for (int i=0; i <= 255; i++) {
      SetColour((colour+1) % 3, i);
      SetColour(colour, 255-i);
      delay(25);
    }
  }
}

Getting four PWM outputs

The ATtiny chip provides two timer/counters, Timer/Counter0 and Timer/Counter1, and each of these provides two PWM comparators, OCR0A, OCR0B, OCR1A, and OCR1B. So in theory we should be able to get four PWM outputs.

Why would we want four analogue outputs? One example is to drive the four wheels of a robot. Alternatively, there are very nice high-power RGBW LEDs, which contain an additional white LED. These allow you to create a bright white light with an added proportion of red, green, and/or blue to alter the colour temperature depending on the time of day.

But looking at the above table of pin assignments it's clear that we can't get both OC0B and OC1A at the same time, even though there are two pins available: PB2 and PB3. It seems a pity that Atmel didn't include a configuration option for this.

Fortunately the solution is fairly simple; we define an interrupt to occur when register OCR1A matches Timer/Counter1, and another interrupt to occur when the counter overflows; ie reaches 00 again. We do this using the TIMSK register:

  TIMSK = TIMSK | 1<<OCIE1A | 1<<TOIE1;

We OR the values with the existing value of the register to avoid changing interrupts by the Arduino core functions.

There's only one other change; We set PWM1A to 1 in register TCCR1 to enable the OCR1A PWM in Timer/Counter1:

  TCCR1 = 1<<CTC1 | 1<<PWM1A | 3<<COM1A0 | 7<<CS10;

The two interrupt service routines are really simple; just a line each. They simply set or clear the output we're using, PB3. First we set the output on an overflow:

ISR(TIMER1_OVF_vect) {
  bitClear(PORTB, White);
}

Then we clear the output on a compare match:

ISR(TIMER1_COMPA_vect) {
  if (!bitRead(TIFR,TOV1)) bitSet(PORTB, White);
}

The higher the value in OCR1A, the longer the PWM output will stay high before being cleared.

Here's the full routine:

/* Four PWM Outputs */

// ATtiny85 outputs
const int Red = 0;
const int Green = 1;
const int Blue = 4;
const int White = 3;
volatile uint8_t* Port[] = {&OCR0A, &OCR0B, &OCR1A, &OCR1B};

void setup() {
  pinMode(Red, OUTPUT);
  pinMode(Green, OUTPUT);
  pinMode(Blue, OUTPUT);
  pinMode(White, OUTPUT);
  // Configure counter/timer0 for fast PWM on PB0 and PB1
  TCCR0A = 3<<COM0A0 | 3<<COM0B0 | 3<<WGM00;
  TCCR0B = 0<<WGM02 | 3<<CS00; // Optional; already set
  // Configure counter/timer1 for fast PWM on PB4
  TCCR1 = 1<<CTC1 | 1<<PWM1A | 3<<COM1A0 | 7<<CS10;
  GTCCR = 1<<PWM1B | 3<<COM1B0;
  // Interrupts on OC1A match and overflow
  TIMSK = TIMSK | 1<<OCIE1A | 1<<TOIE1;
}

ISR(TIMER1_COMPA_vect) {
  if (!bitRead(TIFR,TOV1)) bitSet(PORTB, White);
}

ISR(TIMER1_OVF_vect) {
  bitClear(PORTB, White);
}

// Sets colour Red=0 Green=1 Blue=2 White=3
// to specified intensity 0 (off) to 255 (max)
void SetColour (int colour, int intensity) {
  *Port[colour] = 255 - intensity;
}  

void loop() {
  for (int i=-255; i <= 254; i++) {
    OCR0A = abs(i);
    OCR0B = 255-abs(i);
    OCR1A = abs(i);
    OCR1B = 255-abs(i);
    delay(10);
  }
}

Here's a circuit I built to test this with a 10W Cree MC-E RGBW LED [4]. I found one on eBay for about £10:

RGBW1.jpg

This test circuit is only driving each LED at about 20mA, well within the capabilities of the ATtiny85 outputs, although with suitable output drivers and a heatsink this LED can be driven at up to 700mA.

RGBW2.jpg

Addendum

29th March 2015: Corrected a reference to PB2 which should have been PB3.


  1. ^ ATtiny microcontroller support for the Arduino IDE on GitHub.
  2. ^ ATtiny PWM (updated) on Matt's I Am Nomad blog.
  3. ^ Why does COM1A0 need to be set before PWM B will work? on StackExchange.
  4. ^ Cree MC-E RGBW LED at LED-Tech.de.

blog comments powered by Disqus