Pages

Thursday, April 19, 2012

ATtiny PWM (updated)

This is a re-post of my original ATtiny85 entry. My original was sarcastic, irritated, and (although it worked) lacked helpful comments. So now I've spent some time reading over the datasheet until I understood every single bit function. This program cycles red, green and blue on an RGB LED using the three PWM pins available to an ATtiny85, which are driven by both of the ATtiny85's Timer/Counters (the comments are wide, you may want to copy-paste this code into another editor to read them better).

/*
all my ATtiny85 chips have their 8MHz fuse set
by default they run at 1MHz, so adjust accordingly
this constant is used by delay.h, so make sure it stays above the include
*/
#define F_CPU 8000000

/*
io.h provides lots of handy constants
delay.h provides _delay_ms and _delay_us functions
*/
#include <avr/io.h>
#include <util/delay.h>

/*
program entry-point
*/
void main()
{
    /*
    Starting values for red, green and blue
    */
    uint8_t r=0, g=85, b=170;
   
    /*
    Port B Data Direction Register (controls the mode of all pins within port B)
    DDRB is 8 bits: [unused:unused:DDB5:DDB4:DDB3:DDB2:DDB1:DDB0]
    1<<DDB4: sets bit DDB4 (data-direction, port B, pin 4), which puts PB4 (port B, pin 4) in output mode
    1<<DDB1: sets bit DDB1 (data-direction, port B, pin 1), which puts PB1 (port B, pin 1) in output mode
    1<<DDB0: sets bit DDB0 (data-direction, port B, pin 0), which puts PB0 (port B, pin 0) in output mode
    */
    DDRB = 1<<DDB4 | 1<<DDB1 | 1<<DDB0;

    /*
    Control Register A for Timer/Counter-0 (Timer/Counter-0 is configured using two registers: A and B)
    TCCR0A is 8 bits: [COM0A1:COM0A0:COM0B1:COM0B0:unused:unused:WGM01:WGM00]
    2<<COM0A0: sets bits COM0A0 and COM0A1, which (in Fast PWM mode) clears OC0A on compare-match, and sets OC0A at BOTTOM
    2<<COM0B0: sets bits COM0B0 and COM0B1, which (in Fast PWM mode) clears OC0B on compare-match, and sets OC0B at BOTTOM
    3<<WGM00: sets bits WGM00 and WGM01, which (when combined with WGM02 from TCCR0B below) enables Fast PWM mode
    */
    TCCR0A = 2<<COM0A0 | 2<<COM0B0 | 3<<WGM00;
   
    /*
    Control Register B for Timer/Counter-0 (Timer/Counter-0 is configured using two registers: A and B)
    TCCR0B is 8 bits: [FOC0A:FOC0B:unused:unused:WGM02:CS02:CS01:CS00]
    0<<WGM02: bit WGM02 remains clear, which (when combined with WGM00 and WGM01 from TCCR0A above) enables Fast PWM mode
    1<<CS00: sets bits CS01 (leaving CS01 and CS02 clear), which tells Timer/Counter-0 to not use a prescalar
    */
    TCCR0B = 0<<WGM02 | 1<<CS00;
   
    /*
    Control Register for Timer/Counter-1 (Timer/Counter-1 is configured with just one register: this one)
    TCCR1 is 8 bits: [CTC1:PWM1A:COM1A1:COM1A0:CS13:CS12:CS11:CS10]
    0<<PWM1A: bit PWM1A remains clear, which prevents Timer/Counter-1 from using pin OC1A (which is shared with OC0B)
    0<<COM1A0: bits COM1A0 and COM1A1 remain clear, which also prevents Timer/Counter-1 from using pin OC1A (see PWM1A above)
    1<<CS10: sets bit CS11 which tells Timer/Counter-1  to not use a prescalar
    */
    TCCR1 = 0<<PWM1A | 0<<COM1A0 | 1<<CS10;
   
    /*
    General Control Register for Timer/Counter-1 (this is for Timer/Counter-1 and is a poorly named register)
    GTCCR is 8 bits: [TSM:PWM1B:COM1B1:COM1B0:FOC1B:FOC1A:PSR1:PSR0]
    1<<PWM1B: sets bit PWM1B which enables the use of OC1B (since we disabled using OC1A in TCCR1)
    2<<COM1B0: sets bit COM1B1 and leaves COM1B0 clear, which (when in PWM mode) clears OC1B on compare-match, and sets at BOTTOM
    */
    GTCCR = 1<<PWM1B | 2<<COM1B0;
   
    /*
    loop forever
    */
    for (;;)
    {
        /*
        increment and boundary-check each color
        */
        if (++r>255) r=0;
        if (++g>255) g=0;
        if (++b>255) b=0;
       
        /*
        update compare registers with red, green and blue values
        */
        OCR0A = r;
        OCR0B = g;
        OCR1B = b;
       
        /*
        brief pause so we can perceive what is happening
        */
        _delay_ms(10);
    }
}

20 comments:

  1. Beautiful!! I have just started programming AVR's with GCC and this is EXACTLY what I have been looking for to get me started. Thank you :-)

    ReplyDelete
  2. Glad I could help :) For what it's worth, I now rely completely on datasheets and don't know how I lived without them. The trick is to keep like 3 instances of the datasheet open at the same time so you can cross-reference pins, registers and higher-level functionality at the same time.

    ReplyDelete
  3. This looks awesome. What is the license for this code. Is it opensource as I might want to use it in one of my projects?

    ReplyDelete
  4. I didn't apply any specific license, but as the owner of this original work I hereby release it freely for anyone to use :)

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. excuse me,
    may i ask how to compile and upload this code to attiny85.
    i'm an arduino IDE user,
    so i'm not familiar with this kind of code.

    ReplyDelete
  7. I use AVRStudio (which is free from www.atmel.com) to build all of my ATtiny projects. I upload the binary file using an Olimex AVR-ISP500. It's worked really well for me, too.

    ReplyDelete
  8. 熱血阿重

    For Arduino IDE, take the variables out of 'main()'; call main 'setup()' until you get to the 'loop forever' part, then call that 'loop().

    then it will work fine in Arduino. hereis what I used:



    #define F_CPU 8000000

    #include
    #include
    uint8_t r=0, g=85, b=170;

    void setup()
    {

    uint8_t r=0, g=85, b=170;
    DDRB = 1<255) r=0;
    if (++g>255) g=0;
    if (++b>255) b=0;
    OCR0A = r;
    OCR0B = g;
    OCR1B = b;
    _delay_ms(10);
    }


    I just converted it to Arduino style, and took out the comments for breviity.


    ReplyDelete
  9. In my above comment, the includes didn't come across in the blogger comments but the includes are

    avr/io.h
    util/delay.h

    ReplyDelete
  10. Your article contains precise comments that helped me understand attiny's registries. Now I can read the datasheet and see what all this is about. Thank you!!

    ReplyDelete
  11. So what frequency is the pwm signal...? I need lower than 500hz for a led driver i have. I have 490Hz on a Arduino uno, but want to use a ATtiny...

    ReplyDelete
    Replies
    1. (sorry about the delay, didn't get a notification that people left comments)
      The ATtiny 25/45/85 default to 1MHz but I set the 8MHz fuse. Since I don't touch the PLLCSR register, and set no prescalars, all my PWM outputs run at internal-oscillator speed (8MHz). Hope that helps; better late than never :)

      Delete
  12. May I ask you, why do you have this code in there ?
    if (++r>255) r=0;
    if (++g>255) g=0;
    if (++b>255) b=0;
    these variables are unsigned char[0-255] and you are not able to go over that number, so when your r equals to 255 next value after incrementation is going to be 0, not 256, therefore you will never reach your conditions.

    ReplyDelete
    Replies
    1. (sorry about the delay, didn't get a notification that people left comments)
      That's a very good question :) I bet the compiler optimizes those statements out completely. I should really look at the intermediate assembly and see if it did.

      Delete
    2. Confirmed, those lines are completely removed by the compiler (I'm still on AVR Studio 5).

      Delete
  13. I have an Attiny45 with a vcc=5 V, I uploaded the code but it is just working with a pwm frequency of 4kHz.
    Do you know what is wrong with my system?
    I have a led with a 1 kohm resistor at ocr1b.
    I want the pwm at the maximum frequency. Around 31 kHz.
    Thanks for your help.

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. It's few years later and this again helped - this time to me.
    Thanks a lot!

    ReplyDelete
  16. Much later yet and this is still quite helpful to start understanding that there are a lot of things that are not available when programming MCUs at a higher level.
    A big thanks to the author (and to the link that brought me here (http://www.technoblogy.com/show?LE0).
    It took me weeks to find out how to get 3 pwm from an attiny85 so that I don't have to use a Nano or Pro mini to control a simple rgb led.
    There are a lot of comments on arduino.cc stating that the attiny85 only have 2 pwm.
    I can't imagine what is the reason for analogWrite() only to work with pins 5 and 6 (physical chip pins).

    ReplyDelete