Baby-O RC and Servo Control

Hello

I have been using Baby-O boards for dual motor control on small robots for a while. I recently purchased a new 560Hz 760us servo to use on a flipper. This servo requires a 760us pulse every 1.8ms for the mid position. Full range is approximately 500us to 1000us. Using the pulse in function to read in the normal RC servo commands 1.5ms every 20ms to drive the motors as a standard motor controller and set the motor speeds. I am having problems controlling this servo using delays or while loops in the main control loop (last few lines of code below_. With the code below the servo oscillates about the control point and gets warm and drains the batteries. I know the normal servo library can’t be used with the rest of the code, so is there another way to make a reliable 500-1000us pulse every 1.8ms while still controlling the two motors?

Temp is normal pulse 1.5ms / 2 then /0.4us for the get ticks command to give ticks for the new servo. This should only update when a new 20ms pulse arrives from the RC servo.

Thanks in advance.

#include <pololu/orangutan.h>

const int minPulseTime     = 500;//156;  // 0.5 ms
const int minOpenTime	   = 1300;//406;  // 1.3 ms
const int neutralPulseTime = 1500;//469;  // 1.5 ms
const int maxOpenTime	   = 1700;//532;  // 1.7 ms
const int maxPulseTime     = 2500;//782;  // 2.5 ms

const unsigned char pulseInPins[] = { IO_C2, IO_C1, IO_C0};

struct ChannelStruct
{
	unsigned int pulse;
	unsigned char error;
};

struct ChannelStruct ch[3];

int main()
{
	pulse_in_start(pulseInPins, 3);		// start measuring pulses
	
	set_digital_input(IO_C1, PULL_UP_ENABLED);
	set_digital_input(IO_C2, PULL_UP_ENABLED);
	set_digital_input(IO_C0, PULL_UP_ENABLED);

	while (1)  // main loop
	{
		for( int i = 0; i<3; i++)
		{	
			struct PulseInputStruct pi;
			get_pulse_info(i, &pi);
			if (pi.newPulse == HIGH_PULSE)
			{
				int pulse_length = pulse_to_microseconds(pi.lastHighPulse);
				if (pulse_length <minPulseTime || pulse_length > maxPulseTime)
				{
					ch[i].error = 5; // out of range
					ch[i].pulse = neutralPulseTime; // set pulse length
				}
				else
				{
					if (ch[i].error >0) //now in range reduce error on channel
					{
						ch[i].error--;
						ch[i].pulse = neutralPulseTime; // set pulse length
					}
					else
					{
						ch[i].pulse = pulse_to_microseconds(pi.lastHighPulse); // set pulse length
					}
				}
			}
		}

		if (ch[0].error || ch[1].error || ch[2].error)
		{
			red_led(1);
			set_motors(0, 0);
			
			unsigned long prev_ticks = get_ticks();
			while(ticks_to_microseconds(get_ticks()-prev_ticks)<=760)
			{
				set_digital_output(IO_B4, HIGH);
			}
			set_digital_output(IO_B4, LOW);
			delay_us(940);
		}
		else
		{
			red_led(0);
			long m1 = (neutralPulseTime - (int)ch[0].pulse);// + ((int)ch[1].pulse - neutralPulseTime);
			long m2 = (neutralPulseTime - (int)ch[1].pulse);// - ((int)ch[1].pulse - neutralPulseTime);
			m1 = m1 * 255 / 350;//minPulseTime;
			m2 = m2 * 255 / 350;//minPulseTime;
			set_motors(m1, m2);
			
			int temp = ((int)ch[2].pulse) *1.25;

			unsigned long prev_ticks = get_ticks();

			while((get_ticks()-prev_ticks)<=temp)
			{
				set_digital_output(IO_B4, HIGH);
			}
			
			//set_digital_output(IO_B4, HIGH);
			//delay_us(temp);
			set_digital_output(IO_B4, LOW);
			delay_us(1700 - temp);
		}
	}
}

Hello.

You might consider using the hardware PWM on the ATmega328 to produce the servo pulses. Timer0 and Timer2 are used by the motor driver on the Baby Orangutan, so you will want to use Timer1 for producing the PWM signal.

- Jeremy

Thanks for the advice. I have read up on hardware PWM and this is the test code I have created which produces the 760us pulse every 1.8ms.

#include <pololu/orangutan.h>

int main()
{
DDRB |= (1<<PB1);

TCCR1A |= (1<<COM1A1);  //non inverting PWM

TCCR1A |= (1<<WGM11);
TCCR1B |= (1<<WGM12) | (1<<WGM13); //fast PWM

TCCR1B |= (1<<CS10); // no prescaler

ICR1 = 36000; //1.8ms reset

TIMSK1 = (1 << TOIE1); //enable interrupt

while(1)
{
         OCR1A = 760*20;
}
}

However when I include this code with the rest of my RC code the motors drive to full speed and the red LED blinks several times a second. If I use OCR1A = 760*20; in the code below I do get a pwm signal from PB1, if I try with the ch[2].pulse /2 *20 then I do not get the output. It appears that the hardware pwm is causing issues with the pulse in command. Are you aware of any conflicts that could be happening between these?

#include <pololu/orangutan.h>

const int minPulseTime     = 500;//156;  // 0.5 ms
const int minOpenTime	   = 1300;//406;  // 1.3 ms
const int neutralPulseTime = 1500;//469;  // 1.5 ms
const int maxOpenTime	   = 1700;//532;  // 1.7 ms
const int maxPulseTime     = 2500;//782;  // 2.5 ms

const unsigned char pulseInPins[] = { IO_C2, IO_C1, IO_C0};

struct ChannelStruct
{
	unsigned int pulse;
	unsigned char error;
};

struct ChannelStruct ch[3];

int main()
{
	DDRB |= (1<<PB1);

	TCCR1A |= (1<<COM1A1);  //non inverting PWM

	TCCR1A |= (1<<WGM11);
	TCCR1B |= (1<<WGM12) | (1<<WGM13); //fast PWM

	TCCR1B |= (1<<CS10); // no prescaler

	ICR1 = 36000; //1.8ms reset

	TIMSK1 = (1 << TOIE1); //enable interrupt


	pulse_in_start(pulseInPins, 3);		// start measuring pulses
	
	set_digital_input(IO_C1, PULL_UP_ENABLED);
	set_digital_input(IO_C2, PULL_UP_ENABLED);
	set_digital_input(IO_C0, PULL_UP_ENABLED);

	while (1)  // main loop
	{
	
		for( int i = 0; i<3; i++)
		{	
			struct PulseInputStruct pi;
			get_pulse_info(i, &pi);
			if (pi.newPulse == HIGH_PULSE)
			{
				int pulse_length = pulse_to_microseconds(pi.lastHighPulse);
				if (pulse_length <minPulseTime || pulse_length > maxPulseTime)
				{
					ch[i].error = 5; // out of range
					ch[i].pulse = neutralPulseTime; // set pulse length
				}
				else
				{
					if (ch[i].error >0) //now in range reduce error on channel
					{
						ch[i].error--;
						ch[i].pulse = neutralPulseTime; // set pulse length
					}
					else
					{
						ch[i].pulse = pulse_to_microseconds(pi.lastHighPulse); // set pulse length
					}
				}
			}
		}

		if (ch[0].error || ch[1].error || ch[2].error)
		{
			red_led(1);
			set_motors(0, 0);
			
			OCR1A = 760 * 20;
		}
		else
		{
			red_led(0);
			long m1 = (neutralPulseTime - (int)ch[0].pulse);
			long m2 = (neutralPulseTime - (int)ch[1].pulse); 
			m1 = m1 * 255 / 350;//minPulseTime;
			m2 = m2 * 255 / 350;//minPulseTime;
			set_motors(0,0);//(m1, m2);
			
			OCR1A = ch[2].pulse * 10;
		}
	}
}

The pulse-measuring part of the library uses the AVR’s pin-change interrupts, so there should not be any conflicts with Timer1. I looked at your code and nothing particularly stood out. You might try simplifying your code to just a few lines that are problematic to find the particular functions that are conflicting with one another.

- Jeremy

I have added an LED on off time delay sequence at the start of the main routine and found that the uC is resetting itself and the LED blinks at the 1s frequency.

int main()
{
	red_led(1);
	delay_ms(1000);
	red_led(0);
	delay_ms(1000);

I have tried the code on another baby-o and forced values into the pulses, this has the same effect. This eliminates the hardware being a problem. I am not too familiar with the set up of the AVR chip but is there a possibility that an overflow is causing the reset or a fuse bit being set incorrectly?

Thanks again for your help.

I took another look at your code, and I suspect the problem is happening because you enabled a Timer 1 interrupt but did not define an ISR for it. If disabling it does not help, then I still recommend simplifying your code to a few lines of code to find exactly which ones are causing the problem. Could you also post pictures of your setup?

- Jeremy

Removing the Timer 1 interrupt resolved the problems with the controller resetting. The PWM on the oscilloscope looks perfect regardless of what the motors are doing.

Thank you for your support I am very happy this works.

Here are some photos of my test set up, receiver is a rx31 from deltang 0.21grams powered from the 5V on the baby-o (red and black loops), servo is HV9780 not connected in the pictures. The two wires on the crocodile clips are on the oscilloscope (gnd and PB1), battery is 2cell lipo 260mah:

Thanks for letting us know. I am glad you got it working. This looks like a cool project. I look forward to seeing pictures when it is finished!

- Jeremy