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);
}
}


2 comments:

  1. Hello!

    Thanks for sharing this code! I am trying to do the same thing but not having any luck. I've got the code running and hooked up, but nothing is happening to the motors. I cut the connection and soldered in two wires like you describe, and when I bridge them the remote works like normal. Just to clarify, I have the pin 20 i believe of the chip inside the remote connecting to pin 12 of the arduino (SERVO_PIN in the code). I then turn on the controller but no luck. I can see the values changes in the serial output so I imagine the arduino is sending out what it should. Any ideas on what I could do to figure out whats wrong?

    Thanks!!

    ReplyDelete
  2. I figured it out! All I needed to do was share the ground!!

    Thanks for sharing your code!

    ReplyDelete