PWM on the ATmega168

mbateman correctly noted : This tutorial deals with PWM that does not care too much about the base freqency and can use the whole range of 0-100% duty cycles. It is not suitable for servo control since it is not the correct base freqency and with the 8 bit timers, does not have sufficient granularity for the 5-10% range that servos require. For things like LED dimming and H-bridge motor control it is useful.

 

Before beginning this tutorial make sure you understand what PWM is . Google it. Do some research, then come back. Read this : http://www.societyofrobots.com/member_tutorials/node/228 .

This tutorial assumes that you are using the internal oscillator of ATmega168 at 8MHz. If you have the $50 robot make sure you have followed the instructions here : http://www.societyofrobots.com/step_by_step_atmega168_swapout.shtml . That page shows you how to upgrade to the Atmega168 and how to set the oscillator setting to 8MHz.

Open up AVRstudio to your existing project - if you followed the $50 Robot tutorial this would be Photovore_V1. Then right click Source Files and select "Create New Source File".

 

Type in "pwm.c" without the quotation marks. Press OK. If you look in the left sidebar under the folder "Source Files" , there will be a new file called pwm.c . Click that file. Now paste the following code into pwm.c .

 

// IMPORTANT : The following code was done for the Roboduino board.
//The Roboduino is ATmega168 based so the hardware is the same. Only
//difference is that I used pin numbers instead of port numbers. To see
//what pin //number correlates to what port number use this chart -
//http://www.arduino.cc/en/Hacking/PinMapping168 .

// http://www.CuriousInventor.com/kits/roboduino

 

//For example  pwmInit56 is for pins 5 and 6 which translates into PD5 and PD6

//There are two channels of PWM per timer, in the comments I wrote down which pin is which

//thanks to mbateman for his PWM on ATmega640 code which I modified

//code by airman00 - Visit narobo.com

 

void pwmInit56(void)  {    // Pin 6 is channel A and Pin 5 is channel B
   /***************************************************/
   /* Initialize timer0 for PWM  */
   /***************************************************/
   // 00   A off, toggle on compare
   // 00   B off, toggle on compare
   // 00   C channel does not exist
   // 11   Low part of WGM: PWM, Fast PWM
   TCCR0A=0b00000011;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 00   Fast PWM
   // 010   Prescale 8
   TCCR0B=0b00000010;              
   // Clear TCNT
   TCNT0=0x00;  

}

void pwmOff56(void) {
 /**********************************************************/
 /* Return timer and pins to normal use */
 /**********************************************************/
   // 00   A off, pin works as general I/O
   // 00   B off, pin works as general I/O
   // 00   C off, pin works as general I/O
   // 00   Low part of WGM: Normal
   TCCR0A=0x00;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 00   High part of WGM: Normal
   // 000   Prescale None
   TCCR0B=0x00;
   // Clear TCNT
   TCNT0=0x00;
}

void pwmInit311(void)  {  // Pin 11 is Channel A and pin 3 is channel B
   /***************************************************/
   /* Initialize timer0 for PWM  */ 
   /***************************************************/
   // 00   A off, pin works as general I/O
   // 00   B off, pin works as general I/O
   // 00   C channel does not exist
   // 11   Low part of WGM: Fast PWM
   TCCR2A=0b00000011;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 00   Fast PWM
   // 010   Prescale 8
   TCCR2B=0b00000010;              
   // Clear TCNT
   TCNT2=0x00;  
}

void pwmOff311(void) {
 /**********************************************************/
 /* Return timer and pins to normal use */
 /**********************************************************/
   // 00   A off, pin works as general I/O
   // 00   B off, pin works as general I/O
   // 00   C off,pin works as general I/O
   // 00   Low part of WGM: Normal
   TCCR2A=0x00;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 00   High part of WGM: Normal
   // 000   Prescale None
   TCCR2B=0x00;
   // Clear TCNT
   TCNT2=0x00;
}

void pwmInit910(void) {  // pin 9 is channel A and pin 10 is channel B
   /***************************************************/
   /* Initialize timer1 for PWM */
   /***************************************************/
   // 00   A off, pin works as general I/O
   // 00   B off,pin works as general I/O
   // 00   C off,pin works as general I/O
   // 00   Low part of WGM: PWM, phase & freq, ICRn
   TCCR1A=0x00;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 10   High part of WGM: PWM, phase & freq, ICRn
   // 010   Prescale 8
   TCCR1B=0b00010010;
   // Clear TCNT
   TCNT1=0b00000000;

ICR1 = 1000; // every 1000 uS or 1mS
}


void pwmOff910(void) {
   /**********************************************************/
   /* Return timer and pins to normal use */
   /**********************************************************/
   // 00   A off
   // 00   B off
   // 00   C off
   // 00   Low part of WGM: Normal
   TCCR1A=0x00;
   // 0   No input noise cancelling
   // 0   Input edge select does not matter
   // 0   Not used
   // 00   High part of WGM: Normal
   // 000   Prescale None
   TCCR1B=0x00;
   // Clear TCNT
   TCNT1=0x00;
}

void pwmOn9(void) {
   /****************************/
   /* Pin 9 on*/
   /****************************/

//9 A on - set at Bottom, clear at compare match
   sbi(TCCR1A,7);
  cbi(TCCR1A,6);

}

void pwmOff9(void) {
   /********************************/
   /* Pin 9 off */
   /********************************/
   // 00   A off , pin works as general I/O
   cbi(TCCR1A,7);
   cbi(TCCR1A,6);
}

void pwmOn10(void) {
   /****************************/
   /* Pin 10 on */
   /****************************/
   // 10   B on, set at Bottom, clear at compare match
   sbi(TCCR1A,5);
   cbi(TCCR1A,4);
}

void pwmOff10(void) {
   /********************************/
   /* Pin 10 off */
   /********************************/
   // 00   B off pin works as general I/O
   cbi(TCCR1A,5);
   cbi(TCCR1A,4);
}

void pwmOn6(void) {
   /****************************/
  /* Pin 6 on */
   /****************************/
   // 6  A on, set at Bottom, clear at compare match
   sbi(TCCR0A,7);
   cbi(TCCR0A,6);
}

void pwmOff6(void) {
   /********************************/
   /* Pin 6 off */
   /********************************/
   // 00   A off pin works as general I/O
   cbi(TCCR0A,7);
   cbi(TCCR0A,6);
}

void pwmOn5(void) {
   /****************************/
   /*pin 5 on */
   /****************************/
   // 5   B on, set at Bottom, clear at compare match
   sbi(TCCR0A,5);
   cbi(TCCR0A,4);
}

void pwmOff5(void) {
   /********************************/
   /* pin 5 off */ pin works as general I/O
   /********************************/
   // 00   B off
   cbi(TCCR0A,5);
   cbi(TCCR0A,4);
}

void pwmOn11(void) {
   /****************************/
   /* Pin 11 on*/
   /****************************/
   // 11   A on, set at Bottom, clear at compare match
   sbi(TCCR2A,7);
   cbi(TCCR2A,6);
}

void pwmOff11(void) {
   /********************************/
   /* Pin 11 off */
   /********************************/
   // 00   A off ,pin works as general I/O
   cbi(TCCR2A,7);
   cbi(TCCR2A,6);
}

void pwmOn3(void) {
   /****************************/
   /*pin 3 on */
   /****************************/
   // 3   B on,set at Bottom, clear at compare match
   sbi(TCCR2A,5);
   cbi(TCCR2A,4);
}

void pwmOff3(void) {
   /********************************/
   /* pin 3 off *, pin works as general I/O
   /********************************/
   // 00   B off
   cbi(TCCR2A,5);
   cbi(TCCR2A,4);
}


//8 bit  val can be up to 255
#define pwmSet6(val)   OCR0A=val     
#define pwmSet5(val)   OCR0B=val     

//8 bit val can be up to 255
#define pwmSet11(val)   OCR2A=val     
#define pwmSet3(val)   OCR2B=val 

// 16 bit  val can be up to 65535
#define pwmSet9(ICR1 * val /255)   OCR1A=val     
#define pwmSet10(ICR1 * val /255)   OCR1B=val


 

The next step is to open up SoR_Utils.h ( one of the header files that should be included if you followed the $50 Robot Tutorial.) Scroll to the top of SoR_Utils.h and you should see a bunch of #includes . They would look similar to this ( but not necessarily exactly)

//AVR includes
#include <avr/io.h>            // include I/O definitions (port names, pin names, etc)
#include <avr/interrupt.h>    // include interrupt support
//AVRlib includes
#include "global.h"        // include global settings
#include "buffer.h"        // include buffer function library
#include "uart.h"        // include uart function library
#include "rprintf.h"    // include printf function library
//#include "timerx8.h"        // include timer function library (timing, PWM, etc)
#include "a2d.h"        // include A/D converter function library

Add in     #include "pwm.c"     right under the last #include .

Then scroll down a bit until you see something similar ( but not necessarily exact ) like this :

 

//************CONFIGURE PORTS************
//configure ports for input or output - specific to ATmega168
void configure_ports(void)
    {

DDRC = 0xFF;  //configure all C ports for input.  0xFF is  '0b11111111'

PORTC = 0x00; //make sure pull-up resistors are turned off
    DDRD = 0xFF;  //configure all D ports for output.  0xFF is  '0b11111111'
    DDRB = 0xCF;  //configure B ports 0, 1, 2,3, 6, 7 for output.  0xCF is  '0b11001111

    }


 

Any pin that you want to be PWM needs to be set to be an output. If you use the the configure_ports code I used above, then all your PWM channels will be set to be outputs ( and cannot be used for inputs).

 

To actually use the PWM in your code you need to do the following:

Initialize  timer for pins

Turn on PWM for that pin

Set PWM


 

Note that you need to initialize the PWM and turn on a PWM only once in your code and not each time you want to set a PWM.

For example to use PWM on pins 9 and 10  you would use the following code in your main code:

 

pwmInit910(); // Intialize PWM for timer which controls pins 9 and 10 ( since pins 9 and 10 are on the same timer)
    pwmOn9(); // turn on PWM for pin 9
    pwmInit56(); //Initialize timer for pins 5 and 6(since pins 5 and 6 are on the same timer)
    pwmOn5(); // turn on PWM for pin 5
        pwmSet5(255); //pin6   which is 8 bit PWM

    pwmSet9(65535); //pin9 which is 16 bit PWM

After you already initiliazed the PWM and turned on PWM for that pin you would only need to use the pwmSet function each time you wanted to change the PWM duty cycle. So lets say you initiliazed the PWM for 6 and turned on PWM for pin 6 once already, then later on you want to change the duty cycle to 50%, you would only need to do : pwmSet6(128); .

Also note that Pins 3,11,5,6 are 8 bit PWM and pins 9,10 are 16 bit PWM. 8 bit means you have 256 steps of PWM (from 0 - 255). So 255 would be 100% duty cycle (full voltage) and 128 would be 50% duty cycle (half voltage). The 16 bit PWM is basically the same except it has 65536 steps ( from 0-65535).

 

If you have any questions use the Robot Forum - societyofrobots.com/robotforum .

 

,Eric