atmega328p pwm servo(sg90) issues

Go To Last Post
7 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Hello first post here as I struggle with a PWM issue driving a servo. I confirm that the port is outputting signal on that port as it OCR0B does cause servo to move when AVR is powered. Copied code directly along with comments for what each line is expected to be doing.

Any advice is appreciated. Iv read probably 20 articles on PWM/timers/servos for AVR. The expected behavior is to have it move back and forward. Currently it moves to one side and stays there, seems like its trying to move beyond where its meant to.

If any additional information is desired please let me know.

Specs:
    Atmega328p
        running on internal clock at 8Mhz
        driving servo with timer0 off OCR0B pin
    sg90 servo
Toolchain:
    avr-g++ and avrdude

 

class SERVO
{
	private:
		char dir=0;
	public:
	
		void init()
		{
			DDRD |= (1 << DDD5);    // OCR0B(PD5) is now an output
		
			// Clear OC0B on compare match
			// fast PWM mode, TOP = OCR0A
			
			TCCR0A = 1<<COM0B1; //COM0B1=1 others 0 means NonInverted
			TCCR0A = 0<<COM0A0 | 0<<COM0A1; //leaving both 0 = OC0A disabled
			TCCR0A = 1<<WGM01 | 1<<WGM00;
			
			TCCR0B = 1<<CS01 | 1<<CS00; // 1:64(since 8Mhz clock) //NOT 100% SURE HERE??
			//TCCR0B = 1<< WGM00 | 1<< WGM01 | 1<<WGM02;//fast pwm OCR0A top
			TCCR0B = 1<< WGM00 | 0<< WGM01 | 1<<WGM02;//phase correct pwm OCR0A top
			
			OCR0A = 1;
			OCR0B = 0;

		}

		void shiftdc()  //just cycles back and foward one byte (believe thats the size for Timer0 times)
		{
			if(dir==0)
				{
				OCR0B=OCR0B+0x01;
					if(OCR0B=0xFF)
						dir=1;
				}
			if(dir==1)
				{
				OCR0B=OCR0B-0x01;
					if(OCR0B==0x00)
						dir=0;
				}
		}
		void run()
		{
			while(1)
			{
				shiftdc();
				_delay_ms(2000);
			}
		}
};

int main(void)
{
	SERVO servo0;
	servo0.init();
	servr0.run();
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If the comments are correct and this is really a PWM mode in which OCR0A sets TOP then why are you setting it to just 1?

 

But also why pick an 8bit timer? Surely the 20ms that a servo requires is more easily achievable using a 16 bit timer? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was trying to use that one due to the pin it was associated with. I wanted to use 1 timer to control 2 servos and some of the other pins were tied up with SPI and a few other things. Was just trying to get 1 moving first.

 

Regarding OCR0A being at 1. I had trying incrementing that earlier instead of OCR0B to get the servo moving(attached to OCR0B) but didn't seem to get any results.

 

Would i be correct in guessing that 8bit timer pwm cannot accurately control a servo over its whole range at 20ms ? i.e. it would be very jerky? I had thought that 8bits would  be enough.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The real issue with 8 bit timers when controlling servos is that you want the complete 256 step range of the timer to represent the full 20ms frame. But the servo is only really active between 1ms (5%) and 2ms (10%) of the range. So even if you configure things so TOP=255 (the full counting range of the timer) then 10% of 256 is 25.6 and 5% is 12.8. Call that 13 to 26. So you have 26-13 = 13 distinct positions for the servo. Think of an aileron or elevator servo, I suppose only being able to set its position to one of 13 positions between the limits of its movement range *might* be enough but consider something like the servo on the tail rotor of a helicopter - you need MUCH finer control over something like that! 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A few things here:

  • How are you setting your clock to 8MHz?  Did you change the fuse bits?  The ATmega328p comes with a clock prescaler on by default that results in a system clock of 1MHz.  You have to either manually disable the prescaler somewhere in your code or disable the prescaler via the fuse bits.
  • Are you sure that your output pin is connected to the PWM output.  It looks to me like you are setting COM0B1 back to 0 after setting it to 1.  You have 3 lines that assign the value of the TCCR0A register, so the last time you assign it will be its final value.  The last assignment sets it to 0x03, which configures the output pin for normal port operation.  Try changing your register assignments to this:
TCCR0A |= 1<<COM0B1; //COM0B1=1 others 0 means NonInverted
TCCR0A &= ~(1<<COM0A0) & ~(1<<COM0A1); //leaving both 0 = OC0A disabled
TCCR0A |= 1<<WGM01 | 1<<WGM00;
  • You have the same issue with your assignments of TCCR0B as you do with TCCR0A.
  • Why are you using phase correct PWM as opposed to fast PWM?  From the rest of your setup, it appears to me that you want to have the counter count up to 20ms, set the output pin at the bottom of the count and clear it on compare match, which is consistent with the fast PWM configuration.
Last Edited: Fri. May 19, 2017 - 08:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are correct I intended to setup fast PWM. I have made progress with this issue. I believe it is now set to fast PWM due to  WGM01 and WGM00 being 1.

I have two separate code tests that move the respective servo.  Double checked my fuses provide 8Mhz internal on a fuse calculator.   Based on what I had read I didnt think 256 was going to be the correct prescaler but it worked when i tested it. Perhaps somone knows why that is. Below is the updated code for 2 seperate timers i tested.

//OCR0B test
int main(void) {
  DDRD |= _BV(PD5); //COM0B output
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
  TCCR0B = _BV(WGM02) ;//| _BV(CS20);
  
  
  //TCCR0B |= _BV(CS00) | _BV(CS01); // Prescaler 64 DOES NOT WORK HERE, SERVO SCREECH
  TCCR0B |= _BV(CS02) ; // Prescaler 256 working
  //TCCR0B |= _BV(CS02) | _BV(CS00); // Prescaler 1024 DOES NOTHING HERE
  
  uint8_t testA = 240;
  OCR0A = testA;
  OCR0B = 50;

  while (1)
  {
    _delay_ms(333);
    if ( OCR0B < testA )
      OCR0B += 10;
    else
      OCR0B = 50;
  }
}



//OCR2B test Rough but working
int main444 (void) {
  DDRD |= _BV(PD3);
  DDRD |= _BV(PB3);
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(WGM20);
  
  //TCCR2B |= _BV(CS20); //no Prescaler EEK screech
  //TCCR2B |= _BV(CS21); // Prescaler 8 , screech
  //TCCR2B |= _BV(CS21) | _BV(CS20); // Prescaler 32 NOT WORKING
  //TCCR2B |= _BV(CS22) ; // Prescaler 64 working
  //TCCR2B |= _BV(CS20) | _BV(CS22); // Prescaler 128 working
  TCCR2B |= _BV(CS21) | _BV(CS22); // Prescaler 256 


  //TCCR2B |= _BV(CS21) | _BV(CS22) | _BV(CS20); // Prescaler 1024
  
  
  // OCR2A holds the top value of our counter, so it acts as a divisor to the
  // clock. When our counter reaches this, it resets. Counting starts from 0.
  // Thus 63 equals to 64 divs.
  uint8_t testA = 150;//63;
  OCR2A = testA;
  // This is the duty cycle. Think of it as the last value of the counter our
  // output will remain high for. Can't be greater than OCR2A of course. A
  // value of 0 means a duty cycle of 1/64 in this case.
  OCR2B = 50;

  // Just some code to change the duty cycle every 5 microseconds.
  while (1)
  {
    _delay_ms(500);
    if ( OCR2B < testA )
      OCR2B += 10;
    else
      OCR2B = 50;
  }
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm not sure what the details are about the servo you are using are , but typically servos expect a 1-2ms pulse every 20ms (50Hz), so I'm going to assume that your servo is the same.

 

If you are using the 8MHz clock, you really should be using the 16 bit counter, but I'll do the calculations with the counter you are currently using.  You're actually using the 1024 prescale value not 256, which is good because your timer can't count high enough to generate a signal that has a frequency of 50Hz.  Look at your assignment for TCCR2B:

 

TCCR2B = _BV(WGM20);
TCCR2B |= _BV(CS21) | _BV(CS22); // Prescaler 256 

The WGM20 bit is the LSB in TCCR2A, so your first line is actually setting the LSB in TCCR2B, which happens to be CS20.  That means you are setting all CS2 bits, which is a prescale value of 1024.  Like I mentioned earlier, setting all this CS2 bits is actually what you want, but you are not doing it on purpose in this case.  This means that your clock frequency is:

 

8MHz/1024 = 7.8215KHz which means that each clock tick means that 128us has elapsed.

 

You probably are trying to set up the WGM bits to use the Fast PWM that resets the timer on a compare match with OCR2A, but you are currently configured to use the Fast PWM mode that resets at 255.  Change your first TCCR2B assignment line to this and you are going to get the setup that you want:

TCCR2B = _BV(WGM22);

Now that your clock is configured correctly that means that there are 156.25 ticks in 20ms, so setting OCR2A to 156 should get you close enough to the 20ms clock frequency that you are looking for.  This also means that there are 7.8125 ticks in a 1ms and 15.625 in 2ms, so your range for OCR2B would be 8-15.  Now this is why multiple people have suggested using a 16 bit counter as opposed to the 8 bit counter you are trying to use.  Your timer only has enough resolution to move the servo to 8 distinct positions.

 

Hope that helps