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