Orangutan X2 with LCD and VHN2 wiring

Pardon my simplistic questions, but I am quite new to this particular microcontroller.

I bought an Orangutan X2 with included LCD and VHN2 motor controller. I intend to run one motor using joint motor control. I followed the Orangutan X2 Command Documentation v1.01 page 7 and wired the motor with one terminal connected to two M1 outputs and the other wired to two M2 outputs. Please let me know if this is correct.

I have been reviewing the datasheets and can not figure out for certain where to attach the quadrature encoder to the VHN2 board. Looking at provided sample motor controller code it uses PC2, PC3, PC4 and PC5 - all of which are used by the LCD. I will assume that to use that program as written I would have to give up the LCD. Is that correct?

My encoder has the following output lines.

1 - ground
2 - index, which I will not use for now
3 - Channel A
4 - Vcc at 5 volts
5 - Channel B

I assume these will connect to the 3 x 16 socket to get both power for the encoder and inputs from both channels.

Do I connect lines 1 and 4 to the sockets directly under PA0 (i.e 1 to - and 4 to the + socket) to get 5v of power?

I then believe I would need to connect Channel A to PA6 and Channel B to PA7.

Does that sound reasonable?

Thanks for the help.

Ken

Hello.

You are correct about your understanding of joint motor control. The two M1 motor outputs should both be connected to one of your motor’s terminals, and the two M2 motor outputs should both be connected to your motor’s other terminal. Make sure you send the command to set the Orangutan X2 to joint motor mode before trying to use it in this configuration, though! Also, if you don’t need the extra power you get from using the X2 in joint motor mode, you could always just use one of the motor outputs and leave the other motor output disconnected, which would allow you to use the X2’s current-sensing and current-limiting features.

It sounds like you are trying to use the encoder section of the Pololu AVR library with your X2, but the Pololu AVR library does not currently support this platform. You will need to modify the library’s encoder code to work on your X2.

The example code uses pins PC2 - PC5 because these pins are available on the Orangutans supported by the Library; on the X2, these pins are used for the LCD and the free pins are PA0 - PA7 and PD0 - PD7. You should use these free pins for your encoder connections (i.e. you don’t need to give up the LCD).

Pin 1 of your encoder should go to the X2’s ground (any pin in the - column of the 3x16 user I/O header; this is the column that is along the outside edge of the board). Pin 4 should connect to any pin in the + column of the 3x16 header (this is the middle column). Pins 3 and 5 (channels A and B) can then connect to any I/O pin (the interior column of the 3x16 header), but I recommend against using pins PA6 and PA7 since these are connected to the battery voltage and user trimpot, respectively, by SMT jumpers on the back of the bottom PCB. All of the other user I/O pins are completely free by default.

Now we just need some code that will read the encoders on the X2’s mega644 microcontroller. The following code has been ported from the Pololu AVR library:

#include <avr/io.h>
#include <avr/interrupt.h>
#include "LCD.h"
#include "SPI.h"

/*
 * Pin Change interrupts on the ATmega644
 * PCI0 triggers on PCINT7..0
 * PCI1 triggers on PCINT14..8
 * PCI2 triggers on PCINT23..16
 * PC13 triggers on PCINT31..24
 * PCMSK3, PCMSK2, PCMSK1, PCMSK0 registers control which pins contribute.
 *
 * The following table is useful:
 *
 * pin enumeration  AVR pin    PCINT #            PCI #
 * ---------------  ---------  -----------------  -----
 * 0 - 7            PA0 - PA7  PCINT0 - PCINT7    PCI0
 * 8 - 15           PB0 - PB7  PCINT8 - PCINT15   PCI1
 * 16 - 23          PC0 - PC7  PCINT16 - PCINT23  PCI2
 * 24 - 31          PD0 - PD7  PCINT24 - PCINT31  PCI3
 *
 */

static char global_M1A;
static char global_M1B;
static char global_M2A;
static char global_M2B;

static int global_counts_m1;
static int global_counts_m2;

static char global_error_m1;
static char global_error_m2;

static char global_last_M1A_val;
static char global_last_M1B_val;
static char global_last_M2A_val;
static char global_last_M2B_val;

inline unsigned char get_val(unsigned char pin)
{
	if(pin < 8)
		return (PINA >> pin) & 1;
	if(pin < 16)
		return (PINB >> (pin - 8)) & 1;
	if(pin < 24)
		return (PINC >> (pin - 16)) & 1;
	return (PIND >> (pin-24)) & 1;
}

ISR(PCINT0_vect)
{
	unsigned char M1A_val = get_val(global_M1A);
	unsigned char M1B_val = get_val(global_M1B);
	unsigned char M2A_val = get_val(global_M2A);
	unsigned char M2B_val = get_val(global_M2B);

	char plus_m1 = M1A_val ^ global_last_M1B_val;
	char minus_m1 = M1B_val ^ global_last_M1A_val;
	char plus_m2 = M2A_val ^ global_last_M2B_val;
	char minus_m2 = M2B_val ^ global_last_M2A_val;

	if(plus_m1)
		global_counts_m1 += 1;
	if(minus_m1)
		global_counts_m1 -= 1;

	if(plus_m2)
		global_counts_m2 += 1;
	if(minus_m2)
		global_counts_m2 -= 1;

	if(M1A_val != global_last_M1A_val && M1B_val != global_last_M1B_val)
		global_error_m1 = 1;
	if(M2A_val != global_last_M2A_val && M2B_val != global_last_M2B_val)
		global_error_m2 = 1;

	global_last_M1A_val = M1A_val;
	global_last_M1B_val = M1B_val;
	global_last_M2A_val = M2A_val;
	global_last_M2B_val = M2B_val;
}

ISR(PCINT1_vect,ISR_ALIASOF(PCINT0_vect));
ISR(PCINT2_vect,ISR_ALIASOF(PCINT0_vect));
ISR(PCINT3_vect,ISR_ALIASOF(PCINT0_vect));

static void enable_interrupts_for_pin(unsigned char p)
{
	// check what block it's in and do the right thing
	if (p < 8)
	{
		PCICR |= 1 << PCIE0;
		DDRA &= ~(1 << p);
		PCMSK0 |= 1 << p;
	}
	else if (p < 16)
	{
		PCICR |= 1 << PCIE1;
		DDRB &= ~(1 << (p - 8));
		PCMSK1 |= 1 << (p - 8);
	}
	else if (p < 24)
	{
		PCICR |= 1 << PCIE2;
		DDRC &= ~(1 << (p - 16));
		PCMSK2 |= 1 << (p - 16);
	}
	else if (p < 32)
	{
		PCICR |= 1 << PCIE3;
		DDRD &= ~(1 << (p - 24));
		PCMSK3 |= 1 << (p - 24);
	}
}

void encoders_init(unsigned char M1A, unsigned char M1B, unsigned char M2A, unsigned char M2B)
{
	global_M1A = M1A;
	global_M1B = M1B;
	global_M2A = M2A;
	global_M2B = M2B;

	// disable interrupts while initializing
	cli();

	enable_interrupts_for_pin(M1A);
	enable_interrupts_for_pin(M1B);
	enable_interrupts_for_pin(M2A);
	enable_interrupts_for_pin(M2B);

	// initialize the global state
	global_counts_m1 = 0;
	global_counts_m2 = 0;
	global_error_m1 = 0;
	global_error_m2 = 0;

	global_last_M1A_val = get_val(global_M1A);
	global_last_M1B_val = get_val(global_M1B);
	global_last_M2A_val = get_val(global_M2A);
	global_last_M2B_val = get_val(global_M2B);

	// clear the interrupt flags in case they were set before for some reason
	PCIFR |= (1 << PCIF0) | (1 << PCIF1) | (1 << PCIF2) | (1 << PCIF3);

	// enable interrupts
	sei();
}

int encoders_get_counts_m1()
{
	cli();
	int tmp = global_counts_m1;
	sei();
	return tmp;
}

int encoders_get_counts_m2()
{
	cli();
	int tmp = global_counts_m2;
	sei();
	return tmp;
}

int encoders_get_and_reset_counts_m1()
{
	cli();
	int tmp = global_counts_m1;
	global_counts_m1 = 0;
	sei();
	return tmp;
}

int encoders_get_and_reset_counts_m2()
{
	cli();
	int tmp = global_counts_m2;
	global_counts_m2 = 0;
	sei();
	return tmp;
}

unsigned char encoders_check_error_m1()
{
	unsigned char tmp = global_error_m1;
	global_error_m1 = 0;
	return tmp;
}

unsigned char encoders_check_error_m2()
{
	unsigned char tmp = global_error_m2;
	global_error_m2 = 0;
	return tmp;
}

// test program
int main()
{
	encoders_init(0, 1, 32, 32);  // connect encoder A and B outputs to PA0 and PA1
	LCDInit();
	LCDClear();

	while(1)
	{
		LCDMoveCursor(LCD_ROW_0);
		LCDAddString("Motor 1: ");
		LCDInt(encoders_get_counts_m1());
		LCDAddString("     ");

		LCDMoveCursor(LCD_ROW_1);
		LCDAddString("Motor 2: ");
		LCDInt(encoders_get_counts_m2());
		LCDAddString("     ");
		
		if (encoders_check_error_m1())
		{
			LCDMoveCursor(LCD_ROW_0 + 15);
			LCDAddString("Err A");
		}
		if (encoders_check_error_m2())
		{
			LCDMoveCursor(LCD_ROW_1 + 15);
			LCDAddString("Error B");
		}

		delay_ms(50);
	}	
}

I have attached a zipped AVR Studio project that contains the above code: X2EncoderTest.zip (98.9 KB)

The project includes the files SPI.c/SPI.h and LCD.c/LCD.h (you can download these files from the resources tab of the Orangutan X2 product page). The delay_ms() function used in the test program’s main loop comes from SPI.c and the LCD functions in the main loop come from LCD.c.

The sample program initializes the encoder code to accept input from a single encoder whose A and B outputs are connected to PA0 and PA1. The outputs for the second encoder are set to invalid pins 32 (this means it is only looking for inputs from a single encoder).

Please let me know if you have any more questions or if anything I’ve written doesn’t make sense.

- Ben