« Back to home

PWM Output on the ATtiny85

The next step in the Bluebell project is a way to provide notifications. We can control the device remotely using Bluetooth and now we need to make it display the data sent to it in an easily noticeable format.

I'm going to use an RGB LED for this - it can be driven through standard digital pins to give 7 different colour values but it would be nice to expand that range. Because the ATtiny doesn't have any way to control the current or voltage on an output pin we have to simulate that using PWM.

What is PWM?

PWM (Pulse Width Modulation) is a way of simulating different voltage levels with a single digital (on/off) output pin. This is achieved by modifying the duty cycle (the portion of time that the output is high) of the signal to simulate a lower voltage (or current) over time.

PWM Generation

The graph to the left gives a brief example of how this works. In this case we want the equivalent of a 3V output from a 5V digital output pin. In the graph the blue line represents our target voltage and the green line represents the actual pin output. The output pin is pulled high for 3/5ths of each cycle and low for 2/5ths - over time this is the equivalent of a 3V output.

You can use this technique to control the brightness of an LED (or the speed of a motor) by changing the effective current passing through the device over time. If the LED is connected to the output pin with a current limiting resistor to allow 20mA of current the duty cycle will change the effective amount of current flowing through the device. Using a duty cycle of 50% (the output is high for 50% of the time and low for the other 50%) means that, over time, the effective current through the LED will be 10mA - ff the output pin was always on the LED would consume 20mAh after one hour, with a 50% duty cycle that is reduced to 10mAh.

The frequency of the PWM signal is important as well. When you are driving a LED a frequency above 30Hz will be high enough to control the brightness without any visible flicker. When controlling a motor or simulating an output voltage you will need a much higher frequency. For the purpose of this post I'm just going to concentrate on driving LEDs, motor control and other applications will be left to another post.

PWM Hardware on the ATtiny

The ATtiny has direct hardware support for generating PWM output using the two timer/counter modules on the chip. Both of them can be used to drive the output of a digital pin to generate a PWM signal through the use of a comparison register (OCR0A and OCR0B for timer 0). The counter associated with the timer will cycle from 0 to 255, reset to 0 and then repeat the sequence continuously.

When the output compare function is enabled the corresponding output pin will be set high while the current counter value is less than the comparison register and switch to low when it exceeds the value.

Each timer on the ATtiny is capable of driving two PWM output pins. One of these pins overlaps (it can be driven by either TIMER0 or TIMER1 but not by both simultaneously) which gives a total of 3 possible PWM outputs.

My template library provides control over the two PWM outputs associated with TIMER0 leaving TIMER1 available for other purposes. In this implementation the PWM runs at around 32KHz and has a full 8 bit range giving 256 discrete steps from fully off to fully on. The trace to the left shows the PWM output for a 25% and 50% duty cycle on the outputs.

Implementing PWM in Software

To use the maximum 3 channels of PWM output requires using both timer modules. It is possible, however, to simulate PWM output in software on as many pins as needed using a single timer.

Both timers can generate an overflow interrupt which is triggered when the counter transitions from 255 back to 0. Using this interrupt to generate the time base, an array of our own comparison registers and by manually changing the state of the output pins we can generate a PWM pulse ourselves (although it will be at a much lower frequency than the hardware generated pulse).

The main purpose I have in mind for the software driven PWM is to drive an RGB LED - in this case the PWM frequency needs to be above 30 Hz, enough to have some control over the brightness of the LED without any visible flicker. For this application we don't need the full 8 bit range of values either (realistically the human eye is not going to notice much of a difference when you change the output by 1 bit in either direction).

I settled on a 60Hz PWM frequency with 6 bits (64 steps) of resolution. There are a number of advantages to this:

  1. The overflow interrupt will be triggered at 3840 Hz (60 x 64) which is once every 2083 clock cycles when using an 8MHz clock. Using an average of 2 clock cycles per instruction means that the interrupt is triggered once every 1000 instructions or so. The interrupt handler is less than 100 instructions in length so we are using less than 10% of the processors capacity to provide the PWM.
  2. The 60Hz frequency is a reasonable base for longer time measurements. Using the same interrupt I maintain a tick counter which measures the number of ticks that have elapsed - at this frequency there are 60 ticks per second.

    Setting up the timer for this is fairly straight forward:

 Most of the timer settings are left at power up defaults. I use the system clock (8MHz) as the timer clock source and use the prescaler to divide by 8 which will increment the counter at 1MHz. The overflow interrupt will be triggered every 256th count (when the counter wraps around) or 3906 times a second - about as close as to the 3840Hz target frequency as we can get.

 Every time the interrupt is triggered we update a *ticklet* count. To map the 6 bit range to a full 8 bit range we increment it by 4 each time prior to using it - this means that 0 is still 'off' and 255 is 'on' just as they are for the hardware PWM even though there are only 64 steps available. Finally we compare the current counter value against our target to determine if we should turn the output on or off. The code looks like this:
 // Update the 'ticklet' count     g_ticklet += (256 / TICKLETS);     // Update PWM outputs     if((g_pwmout[0]>0)&&(g_ticklet<=g_pwmout[0]))       pinHigh(SPWM_PIN0);     else       pinLow(SPWM_PIN0);

The output is a nice stable PWM signal at close to 60Hz as shown in the trace to the right. The library uses #define statements in hardware.h to specify the number of PWM channels to enable and to assign each one to an output pin.

Although you could theoretically use all 6 IO pins as PWM outputs using this method I've imposed an upper limit of 4 pins in the implementation. This can easily be extended if needed.

A typical set up (in this case for 3 PWM outputs on B0, B1 and B2) looks like this:

    #define SOFTPWM_ENABLED

     /** Use 3 PWM channels */
    #define SPWM_COUNT 3

     /** Pin associated with SPWM0 */
    #define SPWM_PIN0 PINB0

     /** Pin associated with SPWM1 */
    #define SPWM_PIN1 PINB1

     /** Pin associated with SPWM2 */
    #define SPWM_PIN2 PINB2

 You can use both the hardware PWM and software PWM side by side. The software implementation is useful for controlling LED's while the hardware PWM can be used to control motors or servos.


# Controlling an RGB LED

 Using the software PWM output to control an RGB LED is straight forward. You will need three channels as shown above (one each for the red, green and blue elements of the LED). I run my ATtiny from a 3.3V source which is not enough to drive a blue LED (they typically have a forward voltage drop of over 3V) so I drive the LED's from a separate 5V supply through a 2N7000 FET. This also has the advantage of minimising the current being pulled through the output pins of the ATtiny (20mA per LED which means 60mA if they are all on simultaneously).

 The sample code below transitions the LED from red to green to blue and back again:
 static uint8_t g_red = 0;     static uint8_t g_grn = 0;     static uint8_t g_blu = 0;

 /** Set the target values for the LED's      *      * @param red target for red LED      * @param grn target for green LED      * @param blu target for blue LED      */     static void ledSet(uint8_t red, uint8_t grn, uint8_t blu) {       g_red = red;       g_grn = grn;       g_blu = blu;       }

 /** Update the current PWM values to bring them closer to the target      */     static void ledUpdate() {       // Update RED value       uint8_t val = spwmValue(SPWM0);       if(val<g_red)         val++;       else if(val>g_red)         val--;       spwmOut(SPWM0, val);       // Update GREEN value       val = spwmValue(SPWM1);       if(val<g_grn)         val++;       else if(val>g_grn)         val--;       spwmOut(SPWM1, val);       // Update BLUE value       val = spwmValue(SPWM2);       if(val<g_blu)         val++;       else if(val>g_blu)         val--;       spwmOut(SPWM2, val);       }

 static bool ledMatch() {       return ((spwmValue(SPWM0)==g_red)&&(spwmValue(SPWM1)==g_grn)&&(spwmValue(SPWM2)==g_blu));       }

 //---------------------------------------------------------------------------     // Main program     //---------------------------------------------------------------------------

 /** Program entry point      */     void main() {       spwmInit();       sei();       // Set up output values       uint32_t output = 0x00FF0000L;       ledSet((output>>16)&0xFF, (output>>8)&0xFF, output&0xFF);       while(true) {         if(ledMatch()) {           output = output >> 8;           if(output==0)             output = 0x00FF0000L;           ledSet((output>>16)&0xFF, (output>>8)&0xFF, output&0xFF);           }         ledUpdate();         wait(10);         }       }

Rather than simply force the requested colour output the code performs a smooth transition by incrementing (or decrementing) the actual PWM output over time until the target is reached.