Introduction to pwm [part 7]

Goodnight, seems like I still took a long time to finish this tutorial, but now I’m officially in vacations so I think I have more spare time to write the tutorials faster.

Lets start, this one will be about PWM generation using the hardware timers.
PWM stands for Pulse Width Modulation, what all this fancy words mean is that using a timer (or by software but that is not aborded in this tutorial) we control the time that our signal is HIGH and LOW, by doing this the average energy that is supplied to our target device can be controlled and this HIGH and LOW time is called duty cycle.

This means that we can control the brightness of an LED, mix colour when using RGB leds, control the speed of a dc motor, or even generate some audio. PWM signals are also used to control the so common servos that are used in so many robotics projects, and using a simple RC filter can be used as a limited analog signal, this can be used for example to generate sound or any other uses that you might come of.
In this image you can see what is the look of a PWM wave with three different duty cycle values:

In this tutorial I will write about two PWM modes that our micro-controller offers to us, one is the Fast PWM that can be generated in 8,9 or 10 bits, of course more than 8 bits resolution can only be achieved when using one 16bits timer, and I will also write about the phase correct PWM, this one is the mode that should be used when doing motor control to ensure that everything runs perfectly.

First lets see the Fast PWM. It is probably called fast because for me its the easiest mode to work with and it can work at higher frequencies than the phase correct mode, but it also as its short-comings, for example it shouldn’t be used as a PWM for motor control because it is phase incorrect, this image will enlighten you about the phase correctness problem:

The top two PWM signals are the output generated by the fast PWM mode and the two bottom ones are generated by the phase correct mode. The fast PWM HIGH part of the wave always start in the same place as you can see in the picture but the LOW part will end in an arbitrary place, that is defined by the duty cycle, this means that the center of the HIGH part of the wave will not be constant and that’s the main and key difference between the two modes and that’s why you shouldn’t use it in motor control.

The above image explains how the phase correct PWM mode works. In fast PWM the timer counts from BOTTOM to TOP and then starts over from BOTTOM again, but in phase correct mode, the timer counts up and down, and the output wave is generated comparing our desired duty cycle in both the up and down counts, instead of been compared only when the timer is counting up and then being reset at the overflow when the timer is operating in fast mode.
Overflow is the technical name that is used to said that the timer as hit TOP and as gone back to BOTTOM.

Lets start to code something to put into practice all this timers and PWM theory, in this first example I will show how to fade a led, in a smooth transition from off to on and then back to off and then repeating all that again.

#include <avr/io.h>

int main(void){

//Set led pin as output
//Configure timer

for(;;){
//Set new duty cycle value and delay a little bit
}

return 0;
}

For this example I will use the timer 0, because there is no need to use our only 16bits timer to simply fade a led, the first thing that we need to learn how to configure is the waveform generation modes, that are described for the timer in in page 109 of the Atmega328p datasheet.
The control bits described in that table can be found in two separate registers, the TCCR0A and TCCR0B and are called WGM02, WGM01 and WGM00:

To use fast pwm we have two modes to choose, mode 3 and mode 7, the main difference between this two modes is that in mode 3 TOP is fixed at 0xFF and in mode 7 TOP is defined by the TOP register, this means that if there is the need, we can change the maximum count that the timer will do until it overflows, so this means that we could control the frequency and the duty cycle, I never felt the need to do that, but if you want now you know that you can do it.
To use the mode 3, the WGM01 and WGM00 bits must be set to one, those bits can be found in the TCCR0A register.

The next thing that needs to be configured is the Compare Match Output A(B) Mode, this is what gives the pwm generation hardware the ability to control the output pins. For timer 0 the pwm pins are PD6 for the OC0A and PD5 for the OC0B.
For this particular example lets choose PD6 as the pin where we will connect our led, to have pwm behaviour in this pin we need to look to table 14-3 that can be found in page 106 to learn how to enable this particular behaviour (to simplify I will attach the table here):

Looking to that table we can see that our desired mode is number 3, so we must set both COM0A1 and COM0A0. The last thing that must be set is the prescaler that will provide the clock source to our timer, as usual this clock source should be chosen wisely and not at random, in page 103 of the datasheet is shown the formula that permits us to calculate what will be the pwm frequency when using the fast pwm.

Fclk=16Mhz    //Arduino clock/crystal speed

The formula:
Fpwm = Fclk/(Prescaler*256)

Prescaler    Fout
1        62.5Khz
8        7812.5Hz
64       976Hz
256      244.141hz
1024     61.03Hz

When using the pwm modes the frequencies are pretty low, but for what they are generally used they are more than enough, for this example I will choose the 32 prescaler, so lets get all this info together in the source-code:

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/delay.h>

int main(void){

unsigned char i=0;

DDRD = (1<<PD6);    //Set our pwm pin as an output
//PD6 is Arduino digital pin 6

//Timer configuration
TCCR0A = ((1<<COM0A1)|(1<<WGM01)|(1<<WGM00));    //Enable pwm mode in pin PD6 and set the WGM bits to Fast pwm mode
TCCR0B = ((1<<CS01)|(1<<CS00));                  //Set prescaler to 32

for(;;){

//Fade up
for(i=0; i<255;i++){
OCR0A = i;        //Set new duty cycle value
_delay_ms(50);    //delay a litle bit
}

//Fade down
for(i=255; i>0;i--){
OCR0A = i;        //Set new duty cycle value
_delay_ms(50);    //delay a litle bit
}
}

return 0;
}

video fade

Now, lets do something fancier and more complex, because all this tutorials have been very long and the visible results have been well small, so I will teach you how to do a mood light using an RGB led or if you don’t have an RGB led you can also use three leds, one red, one green and one blue, and lets also add two pots, one to control the intensity of the light and one to control the time that takes between each step. To make this even more challenging instead of delays I will use my custom milis so our code is non-blocking(this means that our micro-controller will not sit there and just do nothing while delaying the needed time).
The first thing is that we need to use two timers, because using only one timer only gives us two pwm pins/outputs, so I will also use timer 2 to get the third pwm pin, and timer 1 to put my milis function up and running, the configuration of timer 2 is precisely equal to what is done to timer 0, its just a matter of changing the name of the registers from 0 to 2.
I will now put all this in pseudo-code:

#include <avr/io.h>

int main(void){

//Set led pins as output
//Configure timer0 to generate two pwm signals
//Configure timer2 to generate one pwm signal
//Configure timer1 to be used as our milis time-base
//Configure ADC

for(;;){
//Read both the time and intensity pots
if(lastTime-milis() > timePotValue){
//Update the duty-cycles to output the next color
}
}

return 0;
}

In words it seemed complex but even in pseudo-code we can see that this will be a simple task.
Now a crash course about colour spaces. Our leds are RGB this means that we have isolated red, green and blue, but to mix this colours to create the maximum possible number of distinct colours its very complex and when we try to add intensity control to that it gets even worse, so I will use HSV (Hue, Saturation, Value), this is an also common colour space and has the great advantage that it is represented in cylindrical coordinates and it is also easy to convert an HSV color to RGB. If you really want to know more about HSV and color spaces, reading this page will teach a lot, and anything that I can write will sound strange and fuzy, because I’m not really a master in the area of color spaces:

http://en.wikipedia.org/wiki/HSL_and_HSV

Our color will be obtained varying the Hue component and the intensity will act in the Value range.
Lets start to fill our pseudo-code with real code:

#include <avr/io.h>
#include <math.h>

#define INTENSITY    0    //The pot that controll the intensity is attached to adc pin 0, aka PC0
#define TIME        1     //The delay time is controlled by the pot attached to adc pin 1, aka PC1

//Functions prototypes
void timer0_init(void);
void timer1_init(void);
void timer2_init(void);
void adc_init(void);
void set_pwm(uint8_t red, uint8_t green, uint8_t blue);
void HSV_RGB();
uint16_t read_adc(uint8_t channel);
unsigned long milis(void);

int main(void){

//Local variables declaration
uint16_t delayTime = 0;        //Variable to store the value read from one of the pots
uint16_t intensity = 0;        //Variable to store the value read from the other pot

DDRD = ((1<<PD5)|(1<<PD6));    //Set pwm pins from timer 0 as outputs
DDRD |= (1<<PD3);              //Set pwm pin OC2B from timer 2 as output

timer0_init();            //Setup timer 0
timer1_init();            //Setup timer 1
timer2_init();            //Setup timer 2
adc_init();               //Setup the ADC

sei();        //Enable global interrupts so our milis function can work

for(;;){
//Read both the time and intensity pots
delayTime = read_adc(TIME);
intensity = read_adc(INTENSITY);
if(lastTime-milis() > delayTime){
//Update the duty-cycles to output the next color
}
}

return 0;
}

void adc_init(void){
ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));    //16Mhz/128 = 125Khz the ADC reference clock
ADMUX |= (1<<REFS0);                             //Voltage reference from Avcc (5v)
ADCSRA |= (1<<ADEN);                             //Turn on ADC
ADCSRA |= (1<<ADSC);                             //Do an initial conversion because this one is the slowest and to ensure that everything is up and running
}

uint16_t read_adc(uint8_t channel){
ADMUX &= 0xF0;                   //Clear the older channel that was read
ADMUX |= channel;                //Defines the new ADC channel to be read
ADCSRA |= (1<<ADSC);             //Starts a new conversion
while(ADCSRA & (1<<ADSC));       //Wait until the conversion is done
return ADCW;                     //Returns the ADC value of the chosen channel
}

void timer0_init(void){
TCCR0A = ((1<<COM0A1)|(1<<WGM01)|(1<<WGM00));    //Enable pwm mode in pin PD6 and set the WGM bits to Fast pwm mode
TCCR0A |= (1<<COM0B1);                           //Enable pwm mode in pin PD5
TCCR0B = ((1<<CS01)|(1<<CS00));                  //Set prescaler to 32
}

The ADC code is just copied from the ADC tutorial, so you already know it, and the timer0_init function is just the code that I wrote above inside a little function, with the extra COM registers to enable pwm in the OCR0B(the PD5 pin).
Now there is the need for some math, because in the timer tutorial I used timer 0 to implement the milis functionality and now I will use timer 1 that is an 16 bits timer, and I will also add the configuration for the timer 2 to also output one pwm signal and finally I will introduce the function that does the conversion from HSV to RGB.

The timer 2 as also two pwm capable pins, those are the PD3 that is the OC2B pin and PB3 that is the OC2A pin. For this tutorial and with no special reason I choose the OC2B pin, the configuration is equal as the one done to configure timer 0, because both of them are 8bits timers. This is easy to understand, so I only show it only in the sourcecode that will follow this text.

Now, lets see how we can get an 1ms interrupt from timer 1 so we can have our milis function. Timer 1 is special, because instead of 8bits it is a 16 bits timer, this means that the max count is 65535 instead of 255, and it has much more features than its smaller brothers, like clock input from an external pin, so you can measure frequency with it, it also as some extra hardware that can do de-bouncing of buttons, but it can only be used in one pin, it can also generate pwm up to 10 bits, and has more modes of operation.


Target Timer Count = (((Input Frequency / Prescaler) / Target Frequency) - 1)
Input Frequency = 16Mhz
Target frequency = 1000Hz

Prescaler value | Target timer count
1           |    15999
8           |    1999
64          |    249
256         |    61.5
1024        |    14.625

As we are using an 16bits we can use the first value, because it is lower than the maximum count of 65535.
Lets get all this together in the code.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>

#define VAL         0       //The pot that controll the intensity is attached to adc pin 0, aka PC0
#define TIME        1       //The delay time is controlled by the pot attached to adc pin 1, aka PC1

//Functions prototypes
void timer0_init(void);
void timer1_init(void);
void timer2_init(void);
void adc_init(void);
void set_pwm(uint8_t red, uint8_t green, uint8_t blue);
void Hsv2Rgb( double *r, double *g, double *b, double h, double s, double v );
unsigned long milis(void);
uint16_t read_adc(uint8_t channel);

//Global Variables
volatile unsigned long milis_count = 0;    //This var is shared between function and an ISR, so it must be declared volatile

int main(void){

//Local variables declaration
unsigned int delayTime = 10;       //Variable to store the value read from one of the pots
uint16_t intensity = 0;            //Variable to store the value read from the other pot
unsigned long lastTime = 0;        //Variable to count how much time as passed
double red = 0.0;                  //Actual red value
double green = 0.0;                //Actual green value
double blue = 0.0;                 //Actual blue value
double hue = 0.0;                  //Actual hue value, this says what color is it
double saturation = 100;           //Set saturation value to maximum, this value can be tweaked
double value = 1.0;                //This is where we set the light intensity using the pot

timer0_init();            //Setup timer 0
timer1_init();            //Setup timer 1
timer2_init();            //Setup timer 2
adc_init();               //Setup the ADC

sei();        //Enable global interrupts so our milis function can work
DDRB = (1<<PB5);

for(;;){
if( (milis() - lastTime) >= delayTime){

PORTB ^= (1<<PB5);
lastTime = milis();

//Read both the time and intensity pots
delayTime = read_adc(TIME) * 2;    //I use times two here to set the max delay to 2 seconds, but you can tweak the code
//to increase or decrease this

if(delayTime <= 10){
delayTime = 10;                //Just to make sure that we always wait at least 10ms to change color;
}

intensity = read_adc(VAL);
value = ((double)(intensity/1024.0)*255.0);    //This is like a map, so the intensity value is between 0 and 255

if(value >= 255.0){
value = 255.0;    //Just to make sure that we don't go beyond 255 or it can cause strange results
}

if(value <= 0.1){
value = 0.0;
}

//Update the duty-cycles to output the next color

Hsv2Rgb(&red, &green, &blue, hue, saturation, value);

red = red * 255.0;            //This is needed be cause the conversion formula returns values between 0 and 1
green = green * 255.0;        //And we need values between 0 and 255 for our pwm generators
blue = blue * 255.0;

hue = hue + 1.0;        //You can also tweak this value to have bigger or smaller steps between colors

if(hue > 360){
hue = 0;
}

OCR0A = (uint8_t)red;    //Red led is connected to pin PD6/digital 6
OCR0B = (uint8_t)green;  //Green led is connected to pin PD5/digital 5
OCR2B = (uint8_t)blue;   //Blue led is connected to pin PD3/digital pin 3

//If you are using an RGB led this will work out of the box for common anode ones
}
}

return 0;
}

void adc_init(void){
ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));    //16Mhz/128 = 125Khz the ADC reference clock
ADMUX |= (1<<REFS0);                             //Voltage reference from Avcc (5v)
ADCSRA |= (1<<ADEN);                             //Turn on ADC
ADCSRA |= (1<<ADSC);                             //Do an initial conversion because this one is the slowest and to ensure that everything is up and running
}

uint16_t read_adc(uint8_t channel){
ADMUX &= 0xF0;                   //Clear the older channel that was read
ADMUX |= channel;                //Defines the new ADC channel to be read
ADCSRA |= (1<<ADSC);             //Starts a new convertion
while(ADCSRA & (1<<ADSC));       //Wait until the convertion is done
return ADCW;                     //Returns the ADC value of the choosen channel
}

void timer0_init(void){

DDRD = ((1<<PD5)|(1<<PD6));                      //Set pwm pins from timer 0 as outputs
TCCR0A = ((1<<COM0A1)|(1<<WGM01)|(1<<WGM00));    //Enable pwm mode in pin PD6 and set the WGM bits to Fast pwm mode
TCCR0A |= (1<<COM0B1);                           //Enable pwm mode in pin PD5
TCCR0B = ((1<<CS01)|(1<<CS00));                  //Set prescaler to 32
}

void timer2_init(void){
DDRD |= (1<<PD3);                                //Set pwm pin OC2B from timer 2 as output
TCCR2A = ((1<<COM2B1)|(1<<WGM21)|(1<<WGM20));    //Enable pwm mode in PD3 and set the WGM bits for fast pwm mode
TCCR2B = ((1<<CS21)|(1<<CS20));                  //Set prescaler to 32
//As you can see, the code for timer 0 and 2 is very similiar, and even almost registers are identical, this is equal also for timer 1
}

void timer1_init(void){

TCCR1B = ((1<<WGM12)|(1<<CS10));    //Timer in CTC mode, prescaler set to 1
OCR1A = 15999;                      //Our target count to have an 1ms interrupt
TIMSK1 = (1<<OCIE1A);               //Enable interrupt when OCR1A value is reached
}

unsigned long milis(void){

cli();   //Disable all interrupts so we can read our long variable atomically
//This is to ensure that no interrupt is fired while reading the long variable
//And possibly trash the readed value
unsigned long milis_value = milis_count;    //Copy the value and return it
sei();        //Enable all interrupt again
return milis_value;

}

ISR(TIMER1_COMPA_vect){
milis_count++;    //Just add 1 to our milis value
}

void Hsv2Rgb( double *r, double *g, double *b, double h, double s, double v ) {
int i;
double f, p, q, t;

s/=100.0;
v/=255.0;

if( s == 0 ) {
// achromatic (grey)
*r = *g = *b = v;
return;
}

h /= 60;            // sector 0 to 5
i = (int)floor( h );
f = h - i;            // factorial part of h
p = v * ( 1.0 - s );
q = v * ( 1.0 - s * f );
t = v * ( 1.0 - s * ( 1 - f ) );

switch( i ) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
default:        // case 5:
*r = v;
*g = p;
*b = q;
break;
}
}

The Hsv2Rgb function is a bit complex/more advance than the usual code shown in this tutorials, probably the most complex part of it is the use of pointers, I hope that for now you can just understand it, but if there are at least one request, in the future I will also make a tutorial about pointers, but for now this might enlighten you:

http://en.wikipedia.org/wiki/Pointer_%28computing%29

I know that I’m linking to wikipedia twice in this tutorial, but it is a great resource of information and if I explained everything here this tutorial would be even longer.
Also one note of advice, when using math.h you should tell the IDE/compiler to link with lib math that was written just for AVR’s, and is much more improved than the stndart one, here is how you do it. First open the Configuration Option menu that is available under the Project tab:

Then go to the Libraries tab/section select libm.a, press Add Library and it should appear in the Link with these Objects listing, this image might help:

To keep things clear here is an schematic on how you should connect everything:

Now just build the circuit, its really easy just one/three leds, three resistor, they can 330 as I used or 270Ohm or a bit more than 330Ohm, but that will decreased the maximum brightness of a typical led.
Here is two videos, one showing the first example and the other showing this example working.

Both videos will be uploaded tomorrow.

And also there is a new thing! I will start to also include the AvrStudio projects ready to press compile and just upload the code, the two projects created in this tutorial are inside the following .zip archive:

http://code.google.com/p/avr-tutorials/downloads/detail?name=pwm_tutorial.zip

Happy codding!!!

PS.: I had some problems with the hsv function so the final code is a little different from the initial stages, but nothing to drastic, and everything is commented. Also it seems like wordpress doesn’t allow zip files, so I will set up a google code page for the tutorials.

Code is now hosted in a google code page!

About these ads

12 responses to “Introduction to pwm [part 7]

  1. Thanks for yet another excellent tutorial, these have been a real kick-start for a total AVR newbie like me.

  2. Hello.
    I learned a lot with your blog. Thanks to all.

    Now.. I’m translating your articles to Spanish.
    My idea is to end of year to start writing a full tutorial about “Programming of microcontrollers AVR in C from arduino” in Spanish.
    Probably make use of these translations to write a good part of tutorial. You have a problem to it?

    For now you can find the translations here: http://arduino.cc/forum/index.php/topic,76191.0.html

    Has plans to continue writing??

    Well…
    Regards.
    Matias.

  3. Your tutorials are awesome!! Keep goin’ ;)

  4. Ups, for some reason I never saw this comments, yes I plan to keep posting here tutorials, and to keep responding to coments. You are welcome to translate then to Spanish, I also have all this tutorials written in Portuguese in the the Luso-Robótica forum:
    http://lusorobotica.com/

  5. Hi! GREAT work. Thanks to this tutorial I’ve just entered in real world of AVR. But did you know others tutorials about this real programmation of AVR MCU? Because I find a lot of documentation about PIC, and really more few about any Atmega.

  6. Hi! Thanks for the tutorial on PWM its clearing things up for me. I am attempting to generate a 10kHz clock that divides down to 5 Hz in the hope of having synchronous waves. I have not coded anything as of yet but have searched the internet for what I could find. Thanks again take care.

  7. These are super awesome tutorials!! I’ve looked a lot of places for something like these and found very few short sometimes unclear tutorials, but these are by far the best I’ve found. WOW, THANK YOU!!!! When’s the next tutorial coming out? How about Reading/Writing to SD Cards? Using EEPROM? Using Interrupts? Using the PicoPower Features? Thanks AGAIN!!

  8. Thank you for getting me through all this. Trying to advance beyond easy Arduino environment but I have one obstacle: I have an IP-controlled tank(Tamiya kit) in which I use iPhone and my www page through a web host. One Arduino has Ethernet Shield as little server then sends character command via Xbee to the XbeeArduinoMotorShield on my tank. It’s a little slow on response but was all instructive for a non-programmer as myself.
    Problem is: Ethernet library needed in C. Way above me.

  9. …anyway, thanks again for your (unpaid) time. You’re the man! (? Lady?)

  10. Hi there!
    Thank you very much for your tutorial! It really helped me out getting my mind around those strange registers and bitwise operators! YOU’RE THE MAN!

  11. thank u 4 ur post.hopin 2 see new post on pic uC programing in C language.thank u

  12. Sir, I just wanna say thank you so much. I am in the process of doing my stage after just finishing college, and I just wanna say your tutorials are helping me so much. One of the things I had the most trouble with was configuring timers, and this tutorial has made it a walk in the park, thank you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s