Go Down

Topic: Using RC controller with Arduino to interface to wheelchair controller (Read 284 times) previous topic - next topic

patracy

Has anyone worked on anything similar?  I have the remains of a Invacare Pronto wheelchair (heavy duty unit).  I was going to just hack up the wheelchair and put in a motor controller.  But that would be more expensive than just using the controller already in place.  I would like to eventually turn this into a base for a raspberry pi autonomous robot with GPS/wifi/exc.  But to start with, I'm just wanting to make it remote controlled.  I have a 6ch radio on the way.  I've been picking at the controller and the easiest means to interface it I think will be tapping into the joystick control of the wheelchair.  (The only other way of reusing the hardware would be trying to interface with the can-bus it has perhaps)  There's two wires that feed X and Y to the controller.  The input voltage to the joystick is 5V.  "Neutral" is when the two wires are at 2.5V each.  And then you have a range to actuate the motors from 1V to 4V.  IE: forward full throttle would be 4V.  Reverse full throttle would be 1V.  And the same for turning left and right.  (Hard at least, steering at speed would be a mixture)

I'd like to see about using the PWM from the RC controller as a input to the uno.  From what I gather, it would need to analogwrite that out as a map? 

So let's take the following example:

Sitting still the input would be at 128 and 128.  Moving the joystick forward, it would then shift to 255.  Moving the stick backwards would shift to 0.  At least this is what the arduino would see from the RC controller I guess.  The output I need from the arduino needs to be "tuned".  That 255 value for full throttle would exceed what the wheelchair joystick actually outputs.  So it actually needs to output 204.  And to explain the reverse throttle the output needs to be 51.  I need to output the PWM to a DAC to smooth the squarewave into a voltage.  (I'll be using a op amp for this)

Does anyone know of any sketches that convert PWM like that?  Or anyone able to help? 

patracy

Well I tried this code, and the left and right seem to be useable.  But nothing out of the forward and back axis.  

Code: [Select]

int joystickX= 10;
int joystickY= 11;
int rc_channel1 = 5;
int rc_channel2 = 6;

void setup() {
  pinMode(rc_channel1, INPUT);
  pinMode(rc_channel2, INPUT);
  pinMode(joystickX, OUTPUT);
  pinMode(joystickY, OUTPUT);
  Serial.begin(9600);
}

void loop() {
 
  int pwm = 127;
  int rc1 = pulseIn(rc_channel1, HIGH, 25000);
  int rc2 = pulseIn(rc_channel2, HIGH, 25000);
 
  Serial.print(" raw channel1: ");
  Serial.print(rc1);
  Serial.print(" raw channel2: ");
  Serial.print(rc2);
 
  if(rc1==0){
      Serial.println(" no signal");
      analogWrite(joystickX, 127);
  }
  else if(rc1 > 1530){ //right stick
      pwm = map(rc1, 1530, 2000, 128, 255); //map our speed to 127-255 range
      analogWrite(joystickX, pwm);
      Serial.print(" right stick speed: ");
      Serial.println(pwm);
  }
  else if(rc1 < 1460){ //left stick
      pwm = map(rc1, 993, 1460, 1, 126); //map our speed to 1-126 range
      analogWrite(joystickX, pwm);
      Serial.print(" left stick speed: ");
      Serial.println(pwm);
  }else{
      Serial.println(" stick centered");
      analogWrite(joystickX, 127);
  }
 
  if(rc2==0){
      Serial.println(" no signal");
      analogWrite(joystickY, 127);
  }
  else if(rc2 > 1530){ //forward stick
      pwm = map(rc2, 1530, 2000, 128, 255); //map our speed to 127-255 range
      analogWrite(joystickY, pwm);
      Serial.print(" forward stick speed: ");
      Serial.println(pwm);
  }
  else if(rc2 < 1460){ //back stick
      pwm = map(rc2, 993, 1460, 1, 126); //map our speed to 1-126 range
      analogWrite(joystickY, pwm);
      Serial.print(" back stick speed: ");
      Serial.println(pwm);
  }else{
      Serial.println(" stick centered");
      analogWrite(joystickY, 127);
  }
 
  delay(10);
}

patracy

Well, useable, but I'll have to play around with the mapping to get my outputs through the DAC to the voltages I need.  But not sure how to get both channels to respond in real time.

patracy

So I had another person take a swing at it and this is what he came up with.  I can "tune" the outputs with the mapping values.  But it seems like it will only work from ch1 alone.  Or if I pull ch1, it will work for ch2.

Code: [Select]
/*
  simplified interface
*/
#define deadband 30 // R/C deadband value Note:- The pod has it's own built in deadband value
#define joystickX 10 // pin 10
#define joystickY 11 // pin 11
#define rc_channel1  5 //pin 5
#define rc_channel2  6// pin6
#define neutral 127 //neutral value
int pwm1;
int pwm2 ;
void setup() {
  pinMode(rc_channel1, INPUT);
  pinMode(rc_channel2, INPUT);
  pinMode(joystickX, OUTPUT);
  pinMode(joystickY, OUTPUT);
  Serial.begin(57600);
}

void loop() {
  int rc1 = pulseIn(rc_channel1, HIGH, 25000);
  int rc2 = pulseIn(rc_channel2, HIGH, 25000);

  Serial.print(" R/C channel 1: ");
  Serial.print(rc1);
  Serial.print ("::");
  Serial.print ("Turn");
  Serial.print ("::");
  Serial.print (pwm1);
  Serial.print ("::");
  Serial.print(" R/C channel 2: ");
  Serial.print(rc2);
  Serial.print ("::");
  Serial.print ("Fore/Aft");
  Serial.print ("::");
  Serial.print (pwm2);
  Serial.println (":");


  if (rc1 < 200) { // zero is a null input ... I'd add a 100k bias to the gnd from the RC pins to make sure it reads LOW on disconnect and alter the rule to .....  if ( rc1 < 200 )
    Serial.println(" RC1 no signal");
    analogWrite(joystickX, neutral );
  }
  else if (rc1 > 1500 + deadband) { //right stick direction L/R or turn... signals are "mixed" by the pod to combine the two stick commands.
    pwm1 = map(rc1, 1500 + deadband, 2000, 128, 206); //map our turn speed to 128-255 range
    analogWrite(joystickX, pwm1 );
  }
  else if (rc1 < 1500 - deadband) { //left stick
    pwm1 = map(rc1, 1000, 1500 - deadband, 51, 126); //map our turn speed to 1-126 range
    analogWrite(joystickX, pwm1 );
  } else {
    analogWrite(joystickX, neutral);
  }

  if (rc2 < 200 ) {
    Serial.println(" RC2 no signal");
    analogWrite(joystickY, neutral);
  }
  else if (rc2 > 1500 + deadband) { //forward stick speed fore/aft
    pwm2 = map(rc2, 1500 + deadband, 2000, 128, 206); //map our speed to 128-255 range
    analogWrite(joystickY, pwm2 );
  }
  else if (rc2 < 1500 - deadband) { //back stick
    pwm2 = map(rc2, 1000, 1500 - deadband, 51, 126); //map our speed to 1-126 range
    analogWrite(joystickY, pwm2 );
  } else {
    analogWrite(joystickY, neutral );
  }

  delay(10);
}

vinceherman

Quote
But it seems like it will only work from ch1 alone.  Or if I pull ch1, it will work for ch2.
Can you elaborate this a bit?  I did not follow.

patracy

It's ending up in a race condition.  The RC controller is sending the pulses at the same time.  PulseIn is trying to read the PWM of the first channel, then ties up the program until that's complete.  By then, it's missed ch2. 

I did find this bit of code out there that works around this by interrupts.  But I'm not sure how to merge the two together to make it work.

Code: [Select]
/*
 * Read 6 PWM signals from an RC receiver.
 *
 * Channels 0-5 are on digital pins 2-7 (i.e. PD2-PD7 = PCINT18-23).
 */

// This always contains the last length measured on each channel.
volatile uint16_t last_pulse_lengths[6];

// Interrupt triggered on every change in pins 2-7.
ISR(PCINT2_vect)
{
    static uint8_t last_pin_states;
    static uint16_t start_times[6];  // start time for each channel
    uint8_t pin_states = PIND >> 2;  // shifted to bits 0-5
    uint16_t now = micros();

    // Check every changed bit of the port.
    for (int i = 0; i < 6; i++) {
        uint8_t mask = _BV(i);

        // Record start time on rising edge.
        if ((pin_states & mask) && !(last_pin_states & mask))
            start_times[i] = now;

        // Record pulse length on falling edge.
        if (!(pin_states & mask) && (last_pin_states & mask))
            last_pulse_lengths[i] = now - start_times[i];
    }
    last_pin_states = pin_states;
}

// Return the pulse lengths avoiding race conditions.
static void get_pulse_lengths(uint16_t lengths[6])
{
    for (int i = 0; i < 6; i++) {
        noInterrupts();
        uint16_t tmp = last_pulse_lengths[i];
        interrupts();
        lengths[i] = tmp;
    }
}

void setup()
{
    // Setup the pin change interrupt.
    PCIFR  = _BV(PCIF2);  // clear pin change interrupt flag
    PCICR  = _BV(PCIE2);  // enable pin change interrupt
    PCMSK2 = 0xfc;        // sense changes on pins 2-7 (i.e. PD2-PD7)

    Serial.begin(9600);
}

void loop()
{
    // Report the pulse lengths on the serial console.
    uint16_t pulse_lengths[6];
    get_pulse_lengths(pulse_lengths);
    for (int i = 0; i < 6; i++) {
        Serial.print(pulse_lengths[i]);
        if (i < 5) Serial.print(", ");
        else Serial.println();
    }

    delay(100);
}


What would be great is if this could output into a value for each channel.  Then I could just reuse the previous code to output the PWM needed (mapped).

vinceherman

I certainly agree that using interrupts is a good way to read the output of an RC receiver.
But I disagree that PulseIn misses channels.
Is it slow, blocking and inefficient?  Yes.
But it reads channel 1.
Then you tell it to read channel 2.  PulseIn simply waits for the next signal.

Take out all of your code attempting to drive the robot.
Just do PulseIn on both channels and print the result to the serial monitor.


Go Up