Baby-O interrupts

I have a working mecanum driven robot using 2 baby-O controllers:

http://www.youtube.com/watch?v=hk6nfKTvEZI

I am using 3 interupts one on each port B,C&D with 3 channel mixing to give forward/back left/right and sideways motion.

I would like to include a weapon to this robot. The idea is take another rc input and if above 1.8ms send an rc signal out to another controller (2ms pulses) to fire the weapon for ~300ms. Then turn off the signal (1.5ms) until the input is in the middle. Then when the input drops below 1.3ms retract the weapon for ~300ms (1ms pulses) then sit at 1.5ms until the input returns to the middle.

video of the hardware being tested:
http://www.youtube.com/user/HIBBERDAR#p/a/u/0/sFsAQwFJSY4

I am after some help with the interupts. Can i set up interupts on port A? or how would i determine which pin is interupted on each port, or where can i find useful information to read up.

Using the 3pi rc code, is it possible to set up one of the pwm outputs that are not connected to the motor driver chip to control the weapon motor?

Many thanks in advance.

Hello.

The ATmega328 used by the Baby Orangutan doesn’t have a port A. You can use pin-change interrupts to detect changes on every digital I/O pin, but you have to use global variables to store the pin state so you can tell which one has changed when the interrupt occurs. An easier solution for you might be to use the OrangutanPulseIn functions in the Pololu AVR Library. These functions were included with the latest Library release, but they haven’t been documented in the command reference yet, so right now I guess they’re more of a secret bonus feature.

OrangutanPulseIn uses pin-change interrupts to determine the duration of pulses on inputs you specify and works well for reading multiple RC inputs (it was actually initially written just for this purpose, though it works for more general pulse-reading applications). If you’re interested in using this portion of the library to read your RC inputs, let me know and I can help you out.

- Ben

I have downloaded the libary, however my c++ coding isn’t great.

However for pseudo code:

#include OrangutanPulseIn;

OrangutanPulseIn::init( ~not sure here~ , 4 ~inputs~, 3 ~3.2ns inline with 3pirx code~);

main:
while (1)
{
OrangutanPulseIn::update();
OrangutanPulseIn::getPulseInfo(~not sure on input here~)

setmotors( , );

}

Any information you can give Ben would be much appreciated.

Andy

Andy,

Have you used the Pololu AVR library before? As a first step, I recommend you try playing around a bit with the library (the sample programs are a good place to start).

Are you writing your programs in AVR Studio? If so, I recommend using the library’s C functions, not the C++ ones. You can find the OrangutanPulseIn C functions in the header file:

libpololu-avr/pololu/pulsein.h

You can get access to those functions by including <pololu/orangutan.h> in your program. All other library functions are documented in the command reference.

I am going to try to document the OrangutanPulseIn library tomorrow (which would include writing a sample program to demonstrate how to use it), but in case I don’t find the time for this, I’ll give you a quick example program so you can get started trying it out. This example causes the Orangutan to monitor pulses on pins PC0, PC1, PC2, and PC3. Since these are RC pulses, we set the maximum pulse duration to 26ms using the argument MAX_PULSE_26MS, which gives us pulse measurements with 0.4us resolution (see the header file for possible maximum pulse durations; longer durations yield lower-resolution readings).

Note that this example is intended for the Orangutan SV, so it uses LCD functions to display the pulse data. You should replace the LCD functions with some other feedback mechanism if you want to run it on your Baby Orangutan. For example, if you have a Pololu programer, use it as a USB-to-serial adapter to send data serially from the Baby Orangutan to your computer.

#include <pololu/orangutan.h>

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

int main()
{
   clear();  // clear LCD
   pulse_in_init(pins, 4, MAX_PULSE_26MS);  // can only measure pulses 
up to 26 ms long, but gives 0.4 us resolution

   while (1)  // main loop
   {
     pulse_in_update();  // you should call this function in your main 
loop at least once per MAX_PULSE_XXMS ms

     struct PulseInputStruct pi = get_pulse_info(0);  // get info for 
pulses on pin IO_C0
     if (pi.curPulseWidth == 0xFFFF)
     {
       // IO line has been in the same state for longer than we can time 
(e.g. it is held high or low)
       lcd_goto_xy(0, 0);
       if (pi.inputState)
         print("line=5V ");
       else
         print("line=0V ");
     }
     else if (pi.newPulse == HIGH_PULSE)
     {
       lcd_goto_xy(0, 0);
       print_unsigned_long(pi.lastHighPulse);  // print the length of 
the most recently received high pulse in units of 0.4 us
       print("    ");
     }
   }
}

The comments in the sample program should help you follow what is going on, but if anything is still unclear, please ask.

- Ben

Thanks Ben,

I have only done very basic modifications to the 3pi-rc software in AVr studio for my robot projects. However this example code looks fairly clear at a first glance, i will have a play this evening (GMT) and let you know how i get on.

Andy

OrangutanPulseIn is a pretty simple library. In the background it is monitoring pin-change interrupts that are set up based on the pins specified to the init function. I neglected to mention that you can use numbers 0 - 7 for pins PD0 - PD7, respectively, 8 - 13 for pins PB0 - PB5, respectively, and 14 - 19 for pins PC0 - PC5, respectively. Alternatively, you can use the pin defines from OrangutanDigital like the example program does: IO_D0 for PD0, IO_B3 for PB3, IO_C5 for PC5, etc.

The pin-change interrupts update an array of pulse-information structs as the pulses come in, and at any time you can request the struct associated with one of your pulse input pins to get the most recent information about the pulses on the pin, including whether the pin is currently high or low, the length of the most recent high pulse, the length of the most recent low pulse, and whether a new pulse has occured since you last checked. The unit of the pulse length is in timer ticks; you can convert this to an actual time based on the maximum pulse time specified as an argument to the init function (e.g. multiply by 0.4 microseconds when using MAX_PULSE_26MS).

It is important that you call the pulse_in_update() function at least once every timer period (e.g. at least once every 26 ms when using MAX_PULSE_26MS) if you want to be able to tell if a pulse ever exceeds your maximum pulse length. A curPulseWidth of 0xFFFF (maximum unsigned 2-byte value) means that the pin input has been unchanged for longer than the maximum pulse length. You might encounter this situation if you turn your RC transmitter off, for example. If you do not call pulse_in_update() often enough, the curPulseWidth value will overflow for pulses that are longer than the maximum pulse length, leading to unreliable results (e.g. you might get a very short pulse measurement for a pulse that is slightly longer than the maximum pulse length).

Please let me know how it works out for you. Given how late it’s getting and how much work I still have left to do tomorrow, it’s seeming less likely that I will get the PulseIn library documented tomorrow, so hopefully this information will be enough to get your project underway!

- Ben

Been a busy few days but this is how the code looks atm:

#include <pololu/orangutan.h>

const unsigned char pins[] = {IO_B0,IO_C4,IO_C5,IO_D0};

const int minPulseTime     = 156;  // 0.5 ms
const int neutralPulseTime = 469;  // 1.5 ms
const int maxPulseTime     = 782;  // 2.5ms
//const int maxLowPulseTime  = 3000; // 9.6ms

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

struct ChannelStruct ch[4];

int main()
{
	pulse_in_init(pins,4,MAX_PULSE_210MS);

	while(1)
	{
		pulse_in_update();

		for( int i = 0; i<4; i++)
		{
			struct PulseInputStruct pi = get_pulse_info(0);
			
			if (pi.curPulseWidth == 0xFFFF)
			{
				ch[i].error = 5; // timed out
			}
			else if (pi.newPulse == HIGH_PULSE)
			{
				if (pi.lastHighPulse <minPulseTime || pi.lastHighPulse > maxPulseTime)
				{
					ch[i].error = 5; // out of range
				}
				else
				{
					if (ch[i].error >0) //now in range reduce error on channel
					{
						ch[i].error--;
					}
					else
					{
						ch[i].pulse = pi.lastHighPulse; // set pulse length
					}
				}
			}
		}

		if (ch[0].error || ch[1].error || ch[2].error || ch[3].error)
		{
			// If either channel is not getting a good signal, stop
			set_motors(0, 0);
		}
		else
		{
			long speed1 = neutralPulseTime - (int)ch[0].pulse;
			long speed2 = neutralPulseTime - (int)ch[1].pulse;
			long speed3 = neutralPulseTime - (int)ch[2].pulse;

			long m1 = speed1 + speed2 + speed3;
			long m2 = speed1 + speed2 - speed3;
			long m3 = speed1 - speed2 + speed3;
			long m4 = speed1 - speed2 - speed3;

			set_motors(m1, m2); // set_motors(m3, m4)
		} 
		
	}
}

For the 4th input of the radio rx i am looking at outputting a modified servo command. Basically if the signal is >1.8ms then sent 2ms pulses on the servo for 300ms, then when the input is <1.3ms send 1ms pulses out for 300ms. When in fault and while waiting send out 1.5ms pulses.

What would be the easiest way to send out this pulse out signal and count the 300ms time for the change of pulse?

I am concerned that an other interupt would affect the motor outputs or the rc inputs.

Again any help or pointers with the current code would be welcome.

I thought i would include the modified 3pirx code i have been using up until now:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <pololu/3pi.h>

const int minPulseTime     = 156;  // 0.5 ms
const int neutralPulseTime = 469;  // 1.5 ms
const int maxPulseTime     = 782;  // 2.5ms
const int maxLowPulseTime  = 3000; // 9.6ms

struct ChannelStruct
{
	volatile unsigned int prevTime;
	volatile unsigned int lowDur;
	volatile unsigned int highDur;
	volatile unsigned char newPulse;

	unsigned int pulse;
	unsigned char error;
};

struct ChannelStruct ch[3];

/*
 * Pin Change interrupts
 * PCI0 triggers on PCINT7..0
 * PCI1 triggers on PCINT14..8
 * PCI2 triggers on PCINT23..16
 * PCMSK2, PCMSK1, PCMSK0 registers control which pins contribute.
 *
 * The following table is useful:
 *
 * AVR pin    PCINT #            PCI #
 * ---------  -----------------  -----
 * PB0 - PB5  PCINT0 - PCINT5    PCI0
 * PC0 - PC5  PCINT8 - PCINT13   PCI1
 * PD0 - PD7  PCINT16 - PCINT23  PCI2
 *
 */

// This interrupt service routine is for the channel connected to PD0
ISR(PCINT0_vect)
{
	// Save a snapshot of PIND at the current time
	unsigned char pinb = PINB;
	unsigned int time = TCNT1;

	if (pinb & (1 << PORTB0)) 
	{
		// PD0 has changed to high so record the low pulse's duration
		ch[0].lowDur = time - ch[0].prevTime;
	}
	else
	{
		// PD0 has changed to low so record the high pulse's duration
		ch[0].highDur = time - ch[0].prevTime;
		ch[0].newPulse = 1; // The high pulse just finished so we can process it now
	}
	ch[0].prevTime = time;
}

// This interrupt service routine is for the channel connected to PC5
ISR(PCINT1_vect)
{
	// Save a snapshot of PINC at the current time
	unsigned char pinc = PINC;
	unsigned int time = TCNT1;

	if (pinc & (1 << PORTC4))
	{
		// PC5 has changed to high so record the low pulse's duration
		ch[1].lowDur = time - ch[1].prevTime;
	}
	else
	{
		// PC5 has changed to low so record the high pulse's duration
		ch[1].highDur = time - ch[1].prevTime;
		ch[1].newPulse = 1; // The high pulse just finished so we can process it now
	}
	ch[1].prevTime = time;
}

ISR(PCINT2_vect)
{
	// Save a snapshot of PINC at the current time
	unsigned char pind = PIND;
	unsigned int time = TCNT1;

	if (pind & (1 << PORTD0))
	{
		// PC5 has changed to high so record the low pulse's duration
		ch[2].lowDur = time - ch[2].prevTime;
	}
	else
	{
		// PC5 has changed to low so record the high pulse's duration
		ch[2].highDur = time - ch[2].prevTime;
		ch[2].newPulse = 1; // The high pulse just finished so we can process it now
	}
	ch[2].prevTime = time;
}


/**
 * updateChannels ensures the recevied signals are valid, and if they are valid 
 * it stores the most recent high pulse for each channel.
 */ 
void updateChannels()
{
	unsigned char i;

	for (i = 0; i < 3; i++)
	{
		cli(); // Disable interrupts
		if (TCNT1 - ch[i].prevTime > 35000)
		{
			// The pulse is too long (longer than 112 ms); register an error 
			// before it causes possible problems.
			ch[i].error = 5; // wait for 5 good pulses before trusting the signal

		}
		sei(); // Enable interrupts

		if (ch[i].newPulse)
		{
			cli(); // Disable interrupts while reading highDur and lowDur
			ch[i].newPulse = 0;
			unsigned int highDuration = ch[i].highDur;
			unsigned int lowDuration = ch[i].lowDur;
			sei(); // Enable interrupts

			ch[i].pulse = 0;

			if (lowDuration < maxLowPulseTime ||
				highDuration < minPulseTime ||		
				highDuration > maxPulseTime)
			{
				// The low pulse was too short or the high pulse was too long or too short
				ch[i].error = 5; // Wait for 5 good pulses before trusting the signal
			}
			else
			{
				// Wait for error number of good pulses
				if (ch[i].error)
					ch[i].error--;
				else
				{
					// Save the duration of the high pulse for use in the channel mixing
					// calculation below
					ch[i].pulse = highDuration; 
				}
			}
		}
	}
}


int main()
{
	ch[0].error = 5; // Wait for 5 good pulses before trusting the signal
	ch[1].error = 5; 
        ch[2].error = 5; 

	DDRB &= ~(1 << PORTB0);	// Set pin PB0 as an input
	PORTB |= 1 << PORTB0;	// Enable pull-up on pin PB0 so that it isn't floating
	DDRC &= ~(1 << PORTC4); // Set pin PC5 as an input
	PORTC |= 1 << PORTC4;	// Enable pull-up on pin PC5 so that it isn't floating
	DDRD &= ~(1 << PORTD0);	// Set pin PD0 as an input
	PORTD |= 1 << PORTD0;	// Enable pull-up on pin PD0 so that it isn't floating

	delay_ms(1);			// Give the pull-up voltage time to rise
	
	PCMSK1 = (1 << PORTC4);	// Set pin-change interrupt mask for pin PC5
	PCMSK0 = (1 << PORTB0);	// Set pin-change interrupt mask for pin PB0
	PCMSK2 = (1 << PORTD0);	// Set pin-change interrupt mask for pin PD0
	PCIFR = 0xFF;			// Clear all pin-change interrupt flags
	PCICR = 0x07;			// Enable pin-change interrupt for masked pins of PORTD
							//  and PORTC and for PORTB
	sei();					// Interrupts are off by default so enable them

	TCCR1B = 0x03;	// Timer 1 ticks at 20MHz/64 = 312.5kHz (1 tick per 3.2us)

	while (1) // Loop forever
	{
		updateChannels();
		if (ch[0].error || ch[1].error|| ch[2].error)
		{
			// If either channel is not getting a good signal, stop
			set_motors(0, 0);
			red_led(1);
		}
		else
		{
			 red_led(0);

			long speed1 = neutralPulseTime - (int)ch[0].pulse;
			long speed2 = neutralPulseTime - (int)ch[1].pulse;
			long speed3 = neutralPulseTime - (int)ch[2].pulse;

long 				m1 = speed1 + speed2 + speed3;
long 				m2 = speed1 + speed2 - speed3;
long 				m3 = speed1 - speed2 + speed3;
long 				m4 = speed1 - speed2 - speed3;

				set_motors(m3, m4);
			}
		}
	

    // This part of the code is never reached.  A robot should
    // never reach the end of its program, or unpredictable behavior
    // will result as random code starts getting executed.  If you
    // really want to stop all actions at some point, set your motors
    // to 0,0 and run the following command to loop forever:
    //
	// set_motors(0,0);
    // while(1);
}

I have set up a simple test just to try out ideas. I have one channel of my rx into C0 reading in the pulses using the pulse_in library. Pin D1 is connected to a servo controller, if I remove all the servo control commands this code produces perfect movement on the motor outputs of the baby-O. However when i try using the servo output the led phases on and off and the servo output is similar. I am not sure what is causing this, is there some problems with the timers of the functions?

#include <pololu/orangutan.h>

const unsigned char pins[] = {IO_C0};
const unsigned char servoPins[] = {IO_D1};

const int minPulseTime     = 156;  // 0.5 ms
const int neutralPulseTime = 469;  // 1.5 ms
const int maxPulseTime     = 782;  // 2.5ms

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

struct ChannelStruct ch[1];

int main()
{
	pulse_in_init(pins,1,MAX_PULSE_210MS);
	servos_init(servoPins, 1);
	set_servo_target(0,1500);

	while(1)
	{
		pulse_in_update();
		

		for( int i = 0; i<1; i++)
		{
			struct PulseInputStruct pi = get_pulse_info(0);
			
			if (pi.curPulseWidth == 0xFFFF)
			{
				ch[i].error = 5; // timed out
			}
			else if (pi.newPulse == HIGH_PULSE)
			{
				if (pi.lastHighPulse <minPulseTime || pi.lastHighPulse > maxPulseTime)
				{
					ch[i].error = 5; // out of range
					red_led(1);
				}
				else
				{
					if (ch[i].error >0) //now in range reduce error on channel
					{
						ch[i].error--;
					}
					else
					{
						ch[i].pulse = pi.lastHighPulse; // set pulse length
						red_led(0);
					}
				}
			}
		}

		if (ch[0].error > 0)
		{
			set_motors(0, 0);
		}
		else
		{
			long speed1 = neutralPulseTime - ch[0].pulse;

			//set_motors(speed1, speed1);
			set_servo_target(0,speed1*6);
		} 
		
	}
}

Yes, there is a problem. The OrangutanPulseIn and OrangutanServos libraries can not be run in the same program because they both use timer 1 in conflicting ways.

I recommend generating the servo pulses yourself using OrangutanDigital and OrangutanTime. If you’re only generating pulses for one servo, it should be relatively easy.

-David

Many thanks for your help, i have all the parts working in my test program.