Monday, January 2, 2012

Vexduino Software Using Interrupt Timer

It's been some time since I worked with the Vexduino but I had some time over this winter break.  Here is an update of the code that uses the interrupt timers of the Arduino.  I did some study of PPM output using code from a PPM output joystick project located here.

This new should create a better PPM output.  I'm now working on integrating this with code from cellbots.com so that I can attach an android phone to the bot.


#include <Messenger.h>


#include 

/*
 * Vexduino 2.0 Interface
 * -------------------
 *
 * 12/31/2011
 * Copyleft 2011 Jeremy Espino MD
 * Licensed under the the Apache 2.0 License
 * espinoj@gmail.com
 * vexduino.blogspot.com
 * modified for vexplorer compatibility
 *
 */

#define PPM_OUT_PIN 12     // which pin to output the PPM signal on
#define FRAME_LENGTH 20  // length of PPM frame in ms
#define PULSE_START 300  // pulse start width
#define PULSE_MIN 600    // pulse minimum width in microsecs
#define PULSE_MAX 1700   // pulse max width in microsecs
#define SERVO_MAX 9      // max servo value
#define SERVO_CNTR 5     // servo center value
#define CHANNEL_NUM 8    // number of channels in the PPM 

// A pulse starts with a high signal of fixed width (0.3ms),
// followed by a low signal for the remainder of the pulse.
// Total pulse width is proportional to servo position (.6 to 1.7ms)
// conversion factor for servo to signal pulse width
const int conversionFactor = (PULSE_MAX - PULSE_MIN)/ SERVO_MAX; 

// A frame is a succession of pulses, in order of channels,
// followed by a synchronisation pulse to fill out the frame.
// A frame's total length is fixed (20ms)
int servo[CHANNEL_NUM];         // Values to set for motor speed 0 - 9, 5 is neutral
int channel[CHANNEL_NUM];       // Channel pulse width (duration of pulse minus start, in microseconds)
int mapping[CHANNEL_NUM];       // map of vex "channel" to PPM radio channels

ISR(TIMER1_COMPA_vect) {
    ppmoutput(); // Jump to ppmoutput subroutine
}



Messenger message = Messenger(); 
byte isAllStop = 0;             // flag to set all channels to center
int inputValue;                 // input value from the serial
int value[CHANNEL_NUM];
  
// initialize servo and channel values to center points
void allStop() {
  for (int i = 0; i < CHANNEL_NUM; i = i + 1 ) {
    servo[i] = SERVO_CNTR;
  }
  for (int i = 0; i < CHANNEL_NUM; i = i + 1 ) {
    channel[i] = (PULSE_MAX - PULSE_MIN) / 2;
  }
}

void setupPPM() {
  
  // Setup timer
  TCCR1A = B00110001; // Compare register B used in mode '3'
  TCCR1B = B00010010; // WGM13 and CS11 set to 1
  TCCR1C = B00000000; // All set to 0
  TIMSK1 = B00000010; // Interrupt on compare B
  TIFR1  = B00000010; // Interrupt on compare B
  OCR1A = 20000;      // 20mS PPM output refresh
  OCR1B = 1000;

  // setup mapping channel to pulse sequence
  mapping[6]=0;
  mapping[5]=1;
  mapping[4]=2;
  mapping[3]=4;
  mapping[2]=5;
  mapping[1]=6;

  pinMode(PPM_OUT_PIN, OUTPUT);   
}

void setup() {
  setupPPM();
  
  // Initiate Serial Communication
  Serial.begin(57600); 
  
  allStop();

  Serial.println("\nVexduino Transmitter Interface ready!");
}

void ppmoutput() { // PPM output sub

  // This for loop generates the pulse train
  for (int i = 0; i < CHANNEL_NUM; i = i + 1 ) {
    // Calculate pulse durations from servo positions
    channel[i] = int(servo[i]*conversionFactor) + PULSE_MIN;
    
    digitalWrite(PPM_OUT_PIN, HIGH);   // Initiate pulse start
    delayMicroseconds(PULSE_START);    // Duration of pulse start
    digitalWrite(PPM_OUT_PIN, LOW);    // Stop pulse start
    delayMicroseconds(channel[i]);     // Finish off pulse
  }
  digitalWrite(PPM_OUT_PIN, HIGH);     // Initiate synchronisation pulse
  delayMicroseconds(PULSE_START);      // Duration of start of synchronisation pulse
  digitalWrite(PPM_OUT_PIN, LOW);      // Stop synchronisation pulse start
  
}

void readSerialInput() {
    // read the next set of servo settings from serial input
  while ( Serial.available() ) {

    if ( message.process(Serial.read() ) ){
      int p = 1;
      
      while( message.available() ) {
        inputValue = message.readInt();
        
        // a value of 999 indicates all stop position
        if (inputValue == 999) isAllStop = 1;
        
        servo[mapping[p]] =  inputValue;
        
        // if invalid servo position set to center
        if ( servo[mapping[p]] > SERVO_MAX) servo[mapping[p]] = SERVO_CNTR;

        p = p + 1;
      }
      if (isAllStop == 1) {
        allStop();
        isAllStop = 0;
      }
    }
  }
  
}


void outputCurrentSettings() {
  // show current settings
  // skip vex "channels" 0 and 7 since they are numbered 1 thru 6
  Serial.print("\r");
  for (int i = 1; i < 7; i = i + 1 ) {

    Serial.print(i);
    Serial.print(":");
    Serial.print(servo[mapping[i]]);
    Serial.print(" ");

  }
  Serial.print("  ");
  for(int x=0; x<=CHANNEL_NUM-1; x++)  //Loop to print and clear all the channel readings
  {
      Serial.print(x);
      Serial.print(":");
      Serial.print(value[x]); //Print the value
      Serial.print(" ");
      value[x]=0; //Clear the value after is printed
  }
  Serial.print("   "); //Start a new line
}

void loop() {
  readSerialInput();
  outputCurrentSettings();
  delay(10);
} 


Sunday, March 29, 2009

Vexduino Battery Eliminators

To reduce battery consumption for Vexduiono I picked up a 9.6v NiMH Battery pack and charger. Many users on the vex forums note that the power jack on the vexplorer receiver is the standard type jack you'll find on r/c racing battery packs.
I finally sprung for a proper soldering station--a weller digital! I shouldn't be melting any pads anymore.

To eliminate battery usage at the transmitter I also picked a normally closed barrel socket and connector. The socket was installed in the transmitter in a similar way to the PPM jack. The wire to the negative terminal of the battery holder was snipped and connected to the sheath of the barrel socket. The ground of the transmitter was connected to the by pass of the socket. I dremelled out another hole on the right side of the transmitter to accommodate the socket. The leads to this plug are connected to the Arduino power rails which is in turn powered by the USB connection.

Saturday, March 28, 2009

Arduino to Vexplorer Transmitter Interface Software

The first code I worked on was creating the software to send commands from the arduino to the robot over wireless. This would also allow computer control. There were plenty of pages on the net that show how to read PPM data with the arduino but none covering writing PPM data to a vexplorer radio using an Arduino. I started out with the code on http://ban-sidhe.com/blog/?p=1465 but it didn't work at all for the Arduino. I looked profmason's scope traces again and noted differences in the phases of the signal. On a hunch I reversed the pulse phase such that high was low and low was high. It worked!

Note that 8 signals are sent out by the transmitter and only six signals are used.

So now I have code using the arduino to control the robot wirelessly. To use it, run a terminal on the serial port you are connected to at 57600 baud. For example on my mac I used screen as a terminal i.e., screen /dev/tty.usbserial-A8004IJh 57600

Enter a value of 0 - 9 separated by spaces to set the value of each successive channel followed by a carriage return. (note: entering values into the arduino terminal will not work since it does not pass carriage returns) Entering a value of 999 will set an ALL STOP condition for all channels.

ex: 5 5 5 5 5 5 - all motors not moving
ex: 5 5 5 9 5 9 - full speed ahead because I have channels 4 and 6 connected to the wheels
ex: 999 - ALL STOP

#include <Messenger.h>

/*
* Vexduino Interface
* -------------------
*
* 03/28/2009
* Copyleft 2008 Jeremy Espino MD
* Licensed under the the Apache 2.0 License
* espinoj@gmail.com
* vexduino.blogspot.com
* modified for vexplorer compatibility
*
*/

#define SERVO_PIN 12 // which pin to output the PPM signal on
#define FRAME_LENGTH 20 // length of PPM frame in ms
#define PULSE_START 300 // pulse start width
#define PULSE_MIN 600 // pulse minimum width in microsecs
#define PULSE_MAX 1700 // pulse max width in microsecs
#define SERVO_MAX 9 // max servo value
#define SERVO_CNTR 5 // servo center value
#define CHANNEL_NUM 8 // number of channels in the PPM


// Instantiate Messenger object with the default separator (the space character)
Messenger message = Messenger();

// A pulse starts with a high signal of fixed width (0.3ms),
// followed by a low signal for the remainder of the pulse.
// Total pulse width is proportional to servo position (.6 to 1.7ms)
// conversion factor for servo to signal pulse width
int conversionFactor = (PULSE_MAX - PULSE_MIN)/ SERVO_MAX;

// A frame is a succession of pulses, in order of channels,
// followed by a synchronisation pulse to fill out the frame.
// A frame's total length is fixed (20ms)
long lastFrame = 0; // The time in millisecs of the last frame
int servo[CHANNEL_NUM]; // Values to set for the servos in degrees
int channel[CHANNEL_NUM]; // Values to send on channels (duration of pulse minus start, in microseconds)
int mapping[CHANNEL_NUM]; // map of vex "channels" to radio channels
int i; // Counter in for loop
int j = 0; // Counter for servo updates
byte isAllStop = 0; // flag to set all channels to center
int inputValue; // input value from the serial

// initialize servo and channel values to center points
void allStop() {
for ( i = 0; i < CHANNEL_NUM; i = i + 1 ) {
servo[i] = SERVO_CNTR;
}
for ( i = 0; i < CHANNEL_NUM; i = i + 1 ) {
channel[i] = (PULSE_MAX - PULSE_MIN) / 2;
}
}

void setup() {
// Initiate Serial Communication
Serial.begin(57600);

// setup mapping channel to pulse sequence
mapping[6]=0;
mapping[5]=1;
mapping[4]=2;
mapping[3]=4;
mapping[2]=5;
mapping[1]=6;

pinMode(SERVO_PIN, OUTPUT); // Set servo pin as an output pin

allStop();

Serial.println("\nVexduino Transmitter Interface ready!");
}

void loop() {

// Save the time of frame start
lastFrame = millis();

// This for loop generates the pulse train, one per channel
for ( i = 0; i < CHANNEL_NUM; i = i + 1 ) {
digitalWrite(SERVO_PIN, HIGH); // Initiate pulse start
delayMicroseconds(PULSE_START); // Duration of pulse start
digitalWrite(SERVO_PIN, LOW); // Stop pulse start
delayMicroseconds(channel[i]); // Finish off pulse
}
digitalWrite(SERVO_PIN, HIGH); // Initiate synchronisation pulse
delayMicroseconds(PULSE_START); // Duration of start of synchronisation pulse
digitalWrite(SERVO_PIN, LOW); // Stop synchronisation pulse start


// read the next set of servo settings from serial input
while ( Serial.available() ) {

if ( message.process(Serial.read() ) ){
int p = 1;

while( message.available() ) {
inputValue = message.readInt();

// a value of 999 indicates all stop position
if (inputValue == 999) isAllStop = 1;

servo[mapping[p]] = inputValue;

// if invalid servo position set to center
if ( servo[mapping[p]] > SERVO_MAX) servo[mapping[p]] = SERVO_CNTR;

p = p + 1;
}
if (isAllStop == 1) {
allStop();
isAllStop = 0;
}
}
}


// Calculate pulse durations from servo positions
for ( i = 0; i < CHANNEL_NUM; i = i + 1 ) {

channel[i] = int(servo[i]*conversionFactor) + PULSE_MIN;
}

// show current settings
// skip vex "channels" 0 and 7 since they are numbered 1 thru 6
Serial.print("\r");
for ( i = 1; i < 7; i = i + 1 ) {

Serial.print(i);
Serial.print(":");
Serial.print(servo[mapping[i]]);
//Serial.print(":");
//Serial.print(channel[mapping[i]]);
Serial.print(" ");

}
Serial.print(" ");


// We're ready to wait for the next frame
// Some jitter is allowed, so to the closest ms
while (millis() - lastFrame < FRAME_LENGTH) {
delay(1);
}
}


Wednesday, March 11, 2009

Adding a PPM jack to the Radio Transmitter

After reading through the links in my previous post, I added a 1/8 stereo normally closed headphone jack that is connected to the PPM output of the remote's microcontroller and the PPM input of the remote's radio transmitter. Since the jack is normally closed, I should be able to remove the headphone plug from the jack and the remote control will work normally.

First, I cut the trace between pin 20 of EM78P458 and surface mount resistor. I then soldered a wire to pin 20 and another wire to the resistor. In the picture you'll notice that, I ended up soldering the second wire to the spot between the resistor and cap. Alas, I'm not an soldering expert, the wire I used was too thick and the pad on the left side of the resistor lifted. I ended up adding a 1k resistor to the circuit to correct this.



I ended up putting the jack on the right side of the antenna so that when used, it would stay out of the way.

The mod looks really clean and I works like I thought it would. Without anything plugged in, the remote works like it did out of the box!

An Interface between the Arduino and the Vexplorer


I picked up a vexplorer from woot! recently with the goal of using my arduinos with it. The vexplorer has a wireless color camera as well as a gripper. The vexplorer is designed to be more like a remote control toy than a autonomous robot and if I had any chance of using my arduinos with it it would be good to have a flexible interface to the unit.

There are several internet resources that describe how to take the vexplorer hardware and interface it with alternate forms of control. Basically, there are two options 1) interface using pulse position modulation (PPM) signals or 2) discard the radio transmitter and receivers and interface directly to the motors of the vexplorer using H bridges.

Here are a few links describing a PPM interface:

Here is a link to using H bridges - http://www.instructables.com/id/How-to-Program-the-Vexplorer-Using-Arduino/ and an Arduino shield that provides motor control http://www.adafruit.com/index.php?main_page=product_info&cPath=17_21&products_id=81

I wanted to be able to control the vexplorer wirelessly using an arduino and do so in a low cost way. Specifically, I wanted the arduino to receive input from the joysticks and buttons of the radio transmitter, send motor commands to the vexplorer, receive commands from the radio transmitter and finally control the motors of the vexplorer. Thus I opted for a two way PPM interface at both the transmitter and receiver. This should allow for several cool projects including:
  • translation of a single joystick's X/Y position such that it controls both drive motors of the vexplorer i.e., use a single joystick to control the vexplorer's movements instead of two joysticks in tank control mode. I should be able to locate the arduino at the transmitter or the receiver
  • image processing on my computer with feedback to the vexplorer as described at profmason.com
  • movement macros
  • autonomous behaviors but still have the option of radio control to override the robot