Software PWM for 6 Servos on ATmega168

Man, you guys are in for a treat!  Although this has been done before, people usually don’t attempt to do this while doing other things.  In this case, I datalog.  So I can have up to 6 servo’s going with different angles while I’m taking measurements every 200 ms.  Pretty awesome!

I attached the code below, but I’m sure you will see this code again as I’m going to use this ATmega168 to control a Traxxas Slash RC car 🙂
//Timing values for Timer 1
#define ONEMS           2000
#define ZEROMS          10
#define TWOMS           4000
#define THREEMS         6000
#define SAFTEYLIMIT     5800

//Port setup
#define SERVO_PORT      PORTD
#define SERVOS_OFF      0b00000011      //Set all servos to low, ignore everything else
#define SER1            PD2
#define SER2            PD3
#define SER3            PD4
#define SER4            PD5
#define SER5            PD6
#define SER6            PD7

//Local variables
volatile unsigned char pwmoff;          //toggles between on and off

volatile struct pwmTimer
{
unsigned char Enable;            //If PWM is currently outputting this channel
unsigned int OnTime;           //Store time to stay on for each channel
unsigned int OffTime;         //Store time to stay off for each channel
volatile struct pwmTimer *Next;            //Point to next timer, 6 in total
};

typedef volatile struct pwmTimer pwmType;
typedef pwmType * pwmPtr;

//setup pwm order so that pwm will progress in this order
pwmType pwmOrder[6]=
{
{0,ZEROMS,THREEMS,&pwmOrder[1]},      //First PWM pin
{0,ZEROMS,THREEMS,&pwmOrder[2]},      //Second PWM pin
{0,ZEROMS,THREEMS,&pwmOrder[3]},      //Third PWM pin
{0,ZEROMS,THREEMS,&pwmOrder[4]},      //Fourth PWM pin
{0,ZEROMS,THREEMS,&pwmOrder[5]},      //Fifth PWM pin
{0,ZEROMS,THREEMS,&pwmOrder[0]},      //Sixth PWM pin
};

//Initialize pointer to point at first location
pwmPtr pt = &pwmOrder[0];

//*****************************************************************************
//Initialize timer 1a to be a timer. It dynamically adjust so it interrupts
// the next time it needs to, not at a fixed rate.
//*****************************************************************************
void
timer_1a_init(void){
//TCCR1A Not needed since default is no output,
//Want to setup as timer, CTC mode, add to OCR1A
TCCR1B |= (1 << WGM12) | (1 << CS11);

OCR1A = TCNT1 + 39999;  //interrupt in 20 ms

TIMSK1 = (1 << OCIE1A);  //enable CTC interrupt

//initialize pwmoff so the first time it goes into the routine it will get the
//correct offtime
pwmoff = 1;
}

//Interrupt Service Routine
ISR(TIMER1_COMPA_vect){
//do not use loops so that array references are resolved at compile time
//Need this as fast as possible, timer set at 0.5 micro second, so very little time
if(pwmoff) {
//save on and off time, currently in an on phase
OCR1A = TCNT1 + pt->OnTime;
pt->OffTime = THREEMS – pt->OnTime;

pwmoff = 0; //pwmoff is 0, so pwm’s are on

//Enable current servo, set current servo to output
pt->Enable = 1;
//Efficiently set just the active pwm, with all other ones set to 0
SERVO_PORT |= ((pwmOrder[0].Enable) << SER1) |
((pwmOrder[1].Enable) << SER2) |
((pwmOrder[2].Enable) << SER3) |
((pwmOrder[3].Enable) << SER4) |
((pwmOrder[4].Enable) << SER5) |
((pwmOrder[5].Enable) << SER6);
}
else{
pwmoff = 1; //pwmoff is 1, so pwm’s are off

//set time for off phase
OCR1A = pt->OffTime + TCNT1;

//Disable flag for current servo
pt->Enable = 0;

//off phase, drive low all servos
SERVO_PORT &= SERVOS_OFF;

//point to next servo
pt = pt->Next;
}
}

//*****************************************************************************
//   Set the duty cycle for the servo to the given value
//    <inputs> – unsigned int id – identifies servo desired 1-6
//               unsigned int interval – Duty cycle desired, 0-6000, for 0 to 3ms
//*****************************************************************************
void
Servo_set(unsigned int id, unsigned int interval){
pwmOrder[id].OnTime = interval;
}

//*****************************************************************************
//   Gets the duty cycle for the servo
//    <inputs> – unsigned int id – identifies servo desired 1-6
//    <output> – unsigned int – Duty cycle desired, 0-6000, for 0 to 3ms
//*****************************************************************************
unsigned int
Servo_get(unsigned int id){
return pwmOrder[id].OnTime;
}

Note: I have this successfully ported to an ATmega328 (yeah, pretty much same controller) running with the internal OSC at 8 MHz, if anyone is interested…

Advertisements

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