Friday, September 9, 2016

Arduino is fun - great for custom Radio Control signal handling

I've been having some fun with arduino. They're quite cheap boards, with arduino nano coming in at ~$3 per board. That's $3 - THREE. That's pretty awesome for a small amount of custom processing power. Unlike a board like the Raspberry PI which has to run a full Linux kernel, this just runs whatever script you write. As such, the timings are very consistent - it's excellent for handling servo PWN signals where you need to measure pulses from ~1000-2000 microseconds accurately. From my experience the resolution was 4μs.

This makes it excellent for managing radiocontrol PWM signals in complex RC vehicles. 



That wrapped up red component in my latest RC vehicle (another hobby, I build RC cars...) is an arduino that reads in steering and another channel and processes it to output a new signal that lets me choose if I want the truck to have 4 wheel steering, or crabwalk (both wheels point the same way making it go mostly sideways).




Definitely one of the more interesting vehicles I've built, and a decent summer project (though I did start last year wrecking the transmission pushing myself in a chair). 

This actually started out with me receiving a non-working unit for handling quadsteer that I had purchased on amazon. When using the unit, the servos lacked power, wouldn't center, and were slow. I used an oscilloscope to diagnose the signal and saw the pulses were 40 milliseconds apart. For Radio control PWM signals, the leading edge should be 20 milliseconds apart. What was essentially happening was the server would get a signal and act on it for 20 milliseconds, and then not do anything for the next 20 milliseconds.

So I turned to arduino nano in the hopes to making my own. Below I've setup that arduino nano on the breadboard to mix signals as I'd like. Each square on the oscilloscope is 10 ms, so my working prototype is showing the proper waveform. This is what was wrapped up in electrical tape in the first picture.




Below is the code I wrote to handle this. The idea is to read two channels - the steering and a spare channel to know if to invert the steering between front and rear. Since I'm reading the time of the pulse, I need a little math to normalize 1000-2000μs to -1 to 1 and back again. This lets me multiply the channels so I can then smoothly transition between crabwalk/quadsteer. 

Should be noted we're using interrupts to gather input channel data. This is a non-blocking method for gathering the input. I just requires us to determine the time of the pulse by subtracting the time the clock had at the signal's leading edge.

Edit: 20200907 - code had some bugs, turning off/on interrupts wasn't needed and led to jerky servo output. Tidied it up a bit.


#include <Servo.h> 

volatile unsigned long leadingedge1;
volatile unsigned long leadingedge2;
volatile int pulsetime1; 
volatile int pulsetime2;


//declare servo pins
int servoin1  = 2; // pin 2 - steering
int servoin2  = 3; // pin 3 - inversion channel
int servoout1 = 9; // output read servo is pin 9, front is pin 2
Servo rearservo;


int ch1_hist[3];
int ch2_hist[3];

int execute=0;
long RServo=1500; //value to write to rear servo

void setup()
{
  pinMode(servoin1, INPUT);      // sets the digital pin 1 as input
  pinMode(servoin2, INPUT);      // sets the digital pin 1 as input
  pinMode(servoout1, OUTPUT);    // sets the digital pin 9 as output
//  pinMode(servoout2, OUTPUT);    // sets the digital pin 9 as output
  leadingedge1 = 0;
  leadingedge2 = 0;
  pulsetime1 = 1500;
  pulsetime2 = 1500;
  attachInterrupt(0, chan1, CHANGE);
  attachInterrupt(1, chan2, CHANGE);
  rearservo.attach(servoout1);
  Serial.begin(115200);        // for debugging
}

int amode(int a[]){ //mode or average of an array - use to smooth glitches
  if (a[0]==a[1]) { return a[0]; }
  if (a[0]==a[2]) { return a[0]; }
  if (a[1]==a[2]) { return a[1]; }
  return (a[0]+a[1]+a[2])/3;
}

int pusharr(int a[],int pushval) {
  a[2]=a[1];
  a[1]=a[0];
  a[0]=pushval;
  return 0;
}

void dostuff(){
  execute=0;
  RServo = 1500+((((long)pulsetime1-1500)*((long)pulsetime2-1500))/500);
  if (RServo > 2050) {
    RServo=2050;
  } else {
    if (RServo < 950) {
      RServo=950;
    }
  }
  Serial.print("P1.");
  Serial.print(pulsetime1);
  Serial.print("--P2.");
  Serial.print(pulsetime2);
  Serial.print("--SRear.");
  Serial.print(RServo);
  Serial.print("\n" );
  execute=1;
}

void chan1()
{
  if(digitalRead(servoin1) == HIGH)
  {
    leadingedge1 = micros();
  } else {
    if (leadingedge1 > 0)
    {
      pulsetime1 = ((volatile long)micros() - leadingedge1)-14;
      //14 us added from other operations? center needed normalizing to 1500
      if ((pulsetime1 > 800 and pulsetime1 < 2200)) {pusharr(ch1_hist,pulsetime1);}
      leadingedge1 = 0;
      pulsetime1= amode(ch1_hist);
    }
  }
}

void chan2()
{
  if(digitalRead(servoin2) == HIGH)
  {
    leadingedge2 = micros();
  }
  else
  {
    if(leadingedge2 > 0)
    {
      pulsetime2 = ((volatile long)micros() - leadingedge2)-14;
      if ((pulsetime2 > 800 and pulsetime2 < 2200)) {pusharr(ch2_hist,pulsetime2);}
      leadingedge2 = 0;
      pulsetime2= amode(ch2_hist);
    }
  }
}

void loop()
{  
  delay(2);  //delay is non blocking
  dostuff();  //do stuff after the receiver has sent all pulses
  if (execute==1) { rearservo.write(RServo);}
}

No comments:

Post a Comment