Saturday, August 3, 2013

Part 2: Integrating rc transmitter and receiver with Arduino

List of parts to blog:
Part 1: Quadcopter Design
Part 2: Integrating rc Transmitter and Receiver with Arduino
Part 3: ESC and motors (wiring and code)
Part 4: Sensors (reading in raw data and applying calibration)
Part 5: IMU - Kalman Filter Orientation Estimator
Part 6: Discussion on Arduino (boards and oddities)
Part 7: Designing a shield for the Due
Part 8: Control Laws!!!!!



Part 2: Integrating transmitter and receiver with Arduino
The following video is ultimately what you should end up with after this post and the next (parts 2 and 3).  Part 2 is the transmitter and receiver, part 3 will be the esc and motor setup. Basically what I have is the transmitter in my hand is sending signals to the receiver which then sends that to the Arduino mico controller which read it in then sends a command back out to the ESC which controls the motor. In this post I'll detail how to wire all of this up and also include the c-code that I used on the Arduino.




Part 2A: Wiring up Receiver -> Arduino
First thing that needs to be said is the Arduino Due is a 3.3volt system and the receiver is a 5v system. So a 0 signal from the receiver is 0 volts and a 1 signal is 5v's. This 5v signal will destroy the arduino Due, it requires 0 to 3.3v. This is very very important and there is a very simple way to step down the 5v signal to 3.3v because 3.3 = 5*2/3. So the following circuit will do the job:

Where R1 = half of R2. You can pick R1 = 10k ohms then R2 would be 20k. Or R1 = 5k ohms and R2 = 10k. Instead of doing this yourself, you can get these tiny logic level shifters from sparkfun that do exactly this. Beware they have made a mistake and use 10k Ohms for each. This will be close but not exact but I've used them for a while now and have had no issue with frying the due. But I later fixed this anyways. I basically did this for all 6 channels. Here is 2 channels with a logic level board from sparkfun wired up:


Here is how its wired to the arduino. Sorry for the messy image, this was taken from a graphic of the entire wiring diagram for the quad. Here green signals are 0to5v, yellow are 0to3.3, orange is 3.3volt power, red is 5v power, black is ground. (please don't hate me for the color inconsistency between this and the picture I took above...)


Part 2B: Arduino code for reading in data from rc receiver.
Some quick background. The signal from the receiver is a PWM (or actually more accurately PPM). The receiver sends a pulse for each channel every so often thats about 1000 to 2000 microseconds long. So for the throttle a pulse that lasts 1000 microseconds means zero throttle, 2000 microseconds means full. Same for all channels, so with Elevator and aileron sticks centered the receiver sends a signal every so often that is 1500 microseconds long. If you push up, it will go to 2000 microseconds long, 1000 if you pull back on the stick. Fairly simple. The problem is when we connect that to the Arduino.  Who knows when that signal is going to arrive and we need to very accurately measure exactly when it started and when it ended then we can calculate how long it was. To do this we use interrupt routines. Basically what this does is any time that pin goes from 0 to 1, the arduino stops what its doing and tells us "hey this pin just changed" so then we record the time of that event. If it changes from 1 to 0, we know thats the end of the signal then we can calculate how long it lasted. Again fairly straight forward. Here is the code to implement this.  I stole a lot of this code from: http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html I recommend reading this guys blog. He explains it wayyy better than me. The basics are: attach interrupt, write a intrupt routine that checks if pin went high or low and records time, then some fancy volatile variable use to store the recorded variable (fanciness is incase you get interrupted while writing to the variable, you will write half then write other half but the 2 halfs might not match so you do some programming logic to stop that)

Here is the code: (for just throttle and pitch stick)
=======================================================================
#define THROTTLE_PIN 53 // thro receiver signal connected to pin 53
#define ELEV_PIN 49  // ELEV receiver signal connected to pin 49

volatile boolean bNewSignal_throttle = false;  //Used to determine if a new command w
volatile boolean bNewSignal_pitch = false; 
volatile unsigned long ulStartPeriod_throttle = 0;
volatile unsigned long ulStartPeriod_pitch = 0;
volatile int throttle_pos_v = 1000; // _v set in ISR, stored for use in throttle_pos without _v
volatile int pitch_pos_v = 1500;
static int throttle_pos = 1000; //actual variable to use in code as the command
static int pitch_pos = 1500;

void setup() {
  Serial.begin(19200);

  attachInterrupt(THROTTLE_PIN,ISR_throttle,CHANGE); //(Pin, code to run when triggered - see
 // ...function at end named ISR_throttle, type of trigger ie when pin changes)

attachInterrupt(ELEV_PIN,ISR_pitch,CHANGE);

}

void loop() {

//Update Input commands (trickery to prevent being interrupted while writing to the same variable)
  noInterrupts();
  if(bNewSignal_throttle)
  {
    throttle_pos = throttle_pos_v;
    bNewSignal_throttle = false;
  }

    if(bNewSignal_pitch)
  {
    pitch_pos = pitch_pos_v;
    bNewSignal_pitch = false;
  }
 interrupts();

  Serial.print("Throttle:");
  Serial.print(throttle_pos);
  Serial.print("\t");
  Serial.print("Pitch:");
  Serial.print(pitch_pos);
  Serial.print("\t");
  Serial.println();
}


// Interrupt Routines
void ISR_throttle() {
  if(digitalRead(THROTTLE_PIN) == HIGH) //means it went 0 to 1
    ulStartPeriod_throttle = micros();
  else // it went from 0 to 1, thus end of pulse
  {
    if(ulStartPeriod_throttle && (bNewSignal_throttle == false))
    {
      throttle_pos_v = (int)(micros() - ulStartPeriod_throttle);
      ulStartPeriod_throttle = 0;
      bNewSignal_throttle = true;
}  }}
void ISR_pitch() {
  if(digitalRead(ELEV_PIN) == HIGH)
    ulStartPeriod_pitch = micros();
  else
  {
    if(ulStartPeriod_pitch && (bNewSignal_pitch == false))
    {
      pitch_pos_v = (int)(micros() - ulStartPeriod_pitch);
      ulStartPeriod_pitch = 0;
      bNewSignal_pitch = true;
}  }}
============================================================


That should do it, you should see the data on the serial display. Copy and paste code to add the other channels. Next up, using that data to then send a command to spin the motor.

No comments:

Post a Comment