Electrical – Controlling Servo motor with ATmega328p microcontroller

atmega328pavrpwmservo

For a while, I've been working with Arduino, but about a month ago, I switched over to using solely the ATmega328-PU microcontroller. I use the Atmel Studio IDE. So far, I have successfully blinked an LED, used programs involving buttons/switches, and used the Fast PWM function to dim an LED. Currently, I am attempting to drive a servo motor using Fast PWM, but have had little success. I have not found any source codes that work for the specific needs of my microcontroller, and I don't really have the ability to change around all the parameters, as I am still learning.

The below is the code which I have developed. It does not work, but I was hoping that someone may be able to fix it up. The servo moves in one direction, but does not stop, and just ends up grinding the motor shaft gear, indicating this could be an issue with the wave length. An additional question: is there an equation to convert degree to PWM wavelength? Thanks!

Microcontroller specifics:

  • Model – ATmega328-PU
  • 16,000,000 hz crystal
  • 28 pins
  • Using 8 bit timer

Servo:

  • Servo model: Tower Pro SG90
  • Servo Frequency 50 hz (is this standard?)

Code:

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

int main(void)
{

    DDRD |= (1 << DDD6);
    // PD6 is now an output - corresponds to OCR0A

    TCCR0A |= (1 << COM0A1);
    // set non-inverting mode

    TCCR0A |= (1 << WGM01) | (1 << WGM00);
    // set fast PWM Mode

    ICR1 = 39999;

    OCR0A = ICR1 - 2000;
    // gives servo motor position

    TCCR0B |= (1 << CS01);
    // set prescaler to 8 and starts PWM


    while(1)
    {
    }
}

Best Answer

There are several problems with your code:-

  1. In non-inverting mode the PWM pulse starts at a count of 0 and ends on compare match, so you should load the pulse width directly into the compare register (not subtract it from the period).

  2. ICR1 is part of 16-bit Timer1, not 8-bit Timer0.

  3. An 8-bit timer will not accept 16 bit values.

To produce 16 bit PWM you must use Timer1, then the numbers 2000 (for 1ms pulse width) and 3999 (for 20ms period) will work properly. So change all Timer0 references to their Timer1 equivalents, ie. TCCR0A -> TCCR1A, COM0A1 -> COM1A1 etc. and set up PB1 to output the servo pulse.