Go Down

Topic: Arduino V-Tail Mixing (Read 10459 times) previous topic - next topic

reverendrichie

I under V-Tail mixers are fairly cheap however I have a special need for a custom Onboard V-Tail mixer. I can set the mixing in my transmitter but I need for the mixing to be done on my model aircraft. My question is, do anyone have some sample code of how to do servo mixing with an Arduino?

thank you.

vinceherman

Hi R.R,
I was working on a piece that used multiple RC channel inputs and RC servo outputs.  Your question got me off my duff to at least make it read the receiver and move the servos.  From there, you can add a little math and your mixer should be done!  (famous last words)
Let us know if this helps (and ask lots of questions)

Code: [Select]
#include <math.h>
#include <Servo.h>

int Chan1Interrupt = 5; // pin 18
int Chan2Interrupt = 4; // pin 19
int Chan3Interrupt = 3; // pin 20
int Chan4Interrupt = 2; // pin 21
int Chan5Interrupt = 1; // pin 3
unsigned long Chan1_startPulse, Chan2_startPulse, Chan3_startPulse, Chan4_startPulse, Chan5_startPulse;
volatile double Chan1_val, Chan2_val, Chan3_val, Chan4_val, Chan5_val;
volatile double Chan1_val_last, Chan2_val_last, Chan3_val_last, Chan4_val_last, Chan5_val_last;
long StartMillis=0;
long FrameCounter=0;

Servo ServoArray[4];

void serviceServos(long pFrameCounter)
{
 ServoArray[1].writeMicroseconds(Chan1_val);
 ServoArray[2].writeMicroseconds(Chan2_val);
 ServoArray[3].writeMicroseconds(Chan3_val);
 ServoArray[4].writeMicroseconds(Chan4_val);
}

void setup()
{
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
 attachInterrupt(Chan3Interrupt, Chan3_begin, RISING);
 attachInterrupt(Chan4Interrupt, Chan4_begin, RISING);
 attachInterrupt(Chan5Interrupt, Chan5_begin, RISING);
 
   int i;          
 for (uint8_t i=0;i<4;i++)
   ServoArray[i].attach(22+i, 700, 2300);

   StartMillis = millis();
}

void loop()
{
 
 //Chan1_val has the value for channel 1 from your receiver.
 //same for Chan2_val, Chan3_val, Chan4_val and Chan5_val
 
 long LocalMillis;
 long LocalFrameCounter;
 LocalMillis = millis();
 LocalFrameCounter = (LocalMillis - StartMillis) / 20;

 if (LocalFrameCounter > FrameCounter)
 {
   FrameCounter = LocalFrameCounter;
   serviceServos(FrameCounter);
 }
 
}

void Chan1_begin()           // enter Chan1_begin when interrupt pin goes HIGH.
       {
         Chan1_startPulse = micros();     // record microseconds() value as Chan1_startPulse
         detachInterrupt(Chan1Interrupt);  // after recording the value, detach the interrupt from Chan1_begin
         attachInterrupt(Chan1Interrupt, Chan1_end, FALLING); // re-attach the interrupt as Chan1_end, so we can record the value when it goes low
       }

void Chan1_end()
       {
        Chan1_val = micros() - Chan1_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan1Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
        if (Chan1_val < 1000 || Chan1_val > 2000) { Chan1_val = Chan1_val_last;}
        else {Chan1_val_last = Chan1_val;}
       }

void Chan2_begin()           // enter Chan2_begin when interrupt pin goes HIGH.
       {
         Chan2_startPulse = micros();     // record microseconds() value as Chan2_startPulse
         detachInterrupt(Chan2Interrupt);  // after recording the value, detach the interrupt from Chan2_begin
         attachInterrupt(Chan2Interrupt, Chan2_end, FALLING); // re-attach the interrupt as Chan2_end, so we can record the value when it goes low
       }

void Chan2_end()
       {
        Chan2_val = micros() - Chan2_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan2Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
        if (Chan2_val < 1000 || Chan2_val > 2000) { Chan2_val = Chan2_val_last;}
        else {Chan2_val_last = Chan2_val;}
       }
       
void Chan3_begin()           // enter Chan3_begin when interrupt pin goes HIGH.
       {
         Chan3_startPulse = micros();     // record microseconds() value as Chan3_startPulse
         detachInterrupt(Chan3Interrupt);  // after recording the value, detach the interrupt from Chan3_begin
         attachInterrupt(Chan3Interrupt, Chan3_end, FALLING); // re-attach the interrupt as Chan3_end, so we can record the value when it goes low
       }

void Chan3_end()
       {
        Chan3_val = micros() - Chan3_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan3Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan3Interrupt, Chan3_begin, RISING);
        if (Chan3_val < 1000 || Chan3_val > 2000) { Chan3_val = Chan3_val_last;}
        else {Chan3_val_last = Chan3_val;}
      }
       
void Chan4_begin()           // enter Chan4_begin when interrupt pin goes HIGH.
       {
         Chan4_startPulse = micros();     // record microseconds() value as Chan4_startPulse
         detachInterrupt(Chan4Interrupt);  // after recording the value, detach the interrupt from Chan4_begin
         attachInterrupt(Chan4Interrupt, Chan4_end, FALLING); // re-attach the interrupt as Chan4_end, so we can record the value when it goes low
       }

void Chan4_end()
       {
        Chan4_val = micros() - Chan4_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan4Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan4Interrupt, Chan4_begin, RISING);
        if (Chan4_val < 1000 || Chan4_val > 2000) { Chan4_val = Chan4_val_last;}
        else {Chan4_val_last = Chan4_val;}}
       
void Chan5_begin()           // enter Chan5_begin when interrupt pin goes HIGH.
       {
         Chan5_startPulse = micros();     // record microseconds() value as Chan5_startPulse
         detachInterrupt(Chan5Interrupt);  // after recording the value, detach the interrupt from Chan5_begin
         attachInterrupt(Chan5Interrupt, Chan5_end, FALLING); // re-attach the interrupt as Chan5_end, so we can record the value when it goes low
       }

void Chan5_end()
       {
        Chan5_val = micros() - Chan5_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan5Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan5Interrupt, Chan5_begin, RISING);
        if (Chan5_val < 1000 || Chan5_val > 2000) { Chan5_val = Chan5_val_last;}
        else {Chan5_val_last = Chan5_val;}
       }

reverendrichie

Wow, THANK YOU, THANK YOU and another Thank you!!!

vinceherman

I guess I should comment a little about the configuration.
I am using a Mega.
I have a prototyping shield that lets me run the servos under separate power.
I have the serves hooked up to pins 22-25
I have the receiver channels hooked up to pins 18-21.
There is code there to read a fifth receiver channel but I have not tested that.

reverendrichie

Thank you, I have an Arduino Duemilanove ATmega168 AVR Development board/1 however, I will be purchasing a few Nano 328 here very soon. I think I can adapt your code to work, right now I have sleep in my eyes but first thing in the morning, I am going to jump right on this.  :) :) :)

vinceherman

I hope it gets you off to a good start.

I am curious.  What custom mixing do you want to do on-board the aircraft?

reverendrichie

I will post the modifications once done.

reverendrichie

#7
Jul 07, 2010, 01:54 am Last Edit: Jul 07, 2010, 02:04 am by reverendrichie Reason: 1
VinceHerman,

I looked over your code but have not loaded it onto my Audino yet. I noticed you are not using the following code segment:

Code: [Select]

int pin = 7;
unsigned long duration;

void setup()
{
 pinMode(pin, INPUT);
}

void loop()
{
 duration = pulseIn(pin, HIGH);
}

http://www.arduino.cc/en/Reference/PulseIn

Does your code read in a PWM signal?

vinceherman

#8
Jul 07, 2010, 04:40 am Last Edit: Jul 07, 2010, 12:45 pm by vinceherman Reason: 1
I'll try to explain.  But be forewarned that I do not always use the proper terminology.  And I am still learning from the masters on this forum.  :)

I do read the signal from the receiver.
But I believe it is more accurate to call it PPM, Pulse Position Modulation.  Here is a discussion of the matter:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1253149521 Post #6

pulsein is not ideal for this application.  It is blocking, meaning that other actions cannot occur while waiting for the signal to complete.
Instead, I use the pairs of interrupt routines for each RC channel.

Lets look at one RC channel.
Code: [Select]
void setup()
{
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);

This tells the Arduino to fire the Chan1_begin routine when the pin changes from low to high.  Read the reference for which pins.  My Mega has 6, others have 2.  http://www.arduino.cc/en/Reference/AttachInterrupt
So, I have channel 1 from my receiver connected to pin 18.  When that pin changes from low to high, the Arduino fires the Chan1_begin routine.
Code: [Select]
void Chan1_begin()           // enter Chan1_begin when interrupt pin goes HIGH.
       {
         Chan1_startPulse = micros();     // record microseconds() value as Chan1_startPulse
         detachInterrupt(Chan1Interrupt);  // after recording the value, detach the interrupt from Chan1_begin
         attachInterrupt(Chan1Interrupt, Chan1_end, FALLING); // re-attach the interrupt as Chan1_end, so we can record the value when it goes low
       }

This captures the time in a variable Chan1_startPulse.
Then it takes off the interrupt watching for the pin to go high, and attaches a different interrupt watching for the pin to change from high to low (falling).  That routine is Chan1_end
Code: [Select]
void Chan1_end()
       {
        Chan1_val = micros() - Chan1_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan1Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
        if (Chan1_val < 1000 || Chan1_val > 2000) { Chan1_val = Chan1_val_last;}
        else {Chan1_val_last = Chan1_val;}
       }

Capture the time, swap the interrupts back, do one bit of error checking to make sure the range is withing acceptable bounds, and place the result in Chan1_val (declared Volatile).  The error checking might not belong here.  But it works for now.

So, this entire process consists of:
1. start watching the pin to go high
2. recording the time when it does
3. start watching for the pin to go low
4. computing the duration between when it went high and when it went low

That process happens 50 times a second (the frame rate of a typical RC servo signal)
And this method only uses a few CPU cycles.  OK, I do not know the actual overhead of calling Interrupt Service Routines, but the number is relatively low.

Pulsein locks up the CPU for the entire duration of the signal.


For your code, since you do not have a Mega, you will likely want to use these pin definitions (from the reference)
numbers 0 (on digital pin 2) and 1 (on digital pin 3)  I am using number 1 for RC channel 5, but truthfully I have not looked at the results.  I will try a 2 channel input using just numbers 0 and 1 to see what it does.  But not tonight.   zzzzz.........
Gosh, I wrote a book!  :)

vinceherman

OK, who can sleep when there is Arduino-ie goodness to play with?!?

Code: [Select]
#include <math.h>
#include <Servo.h>

int Chan1Interrupt = 0; // pin 2
int Chan2Interrupt = 1; // pin 3

unsigned long Chan1_startPulse, Chan2_startPulse;
volatile double Chan1_val, Chan2_val;
volatile double Chan1_val_last, Chan2_val_last;
long StartMillis=0;
long FrameCounter=0;

Servo ServoArray[2];

void serviceServos()
{
 ServoArray[0].writeMicroseconds(Chan1_val);
 ServoArray[1].writeMicroseconds(Chan2_val);
}

void setup()
{
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
       
 for (uint8_t i=0;i<2;i++)
   ServoArray[i].attach(22+i, 700, 2300);

 StartMillis = millis();
}

void loop()
{
 long LocalMillis;
 long LocalFrameCounter;
 LocalMillis = millis();
 LocalFrameCounter = (LocalMillis - StartMillis) / 20;

 if (LocalFrameCounter > FrameCounter)
 {
   FrameCounter = LocalFrameCounter;
   serviceServos();
 }
}

void Chan1_begin()           // enter Chan1_begin when interrupt pin goes HIGH.
       {
         Chan1_startPulse = micros();     // record microseconds() value as Chan1_startPulse
         detachInterrupt(Chan1Interrupt);  // after recording the value, detach the interrupt from Chan1_begin
         attachInterrupt(Chan1Interrupt, Chan1_end, FALLING); // re-attach the interrupt as Chan1_end, so we can record the value when it goes low
       }

void Chan1_end()
       {
        Chan1_val = micros() - Chan1_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan1Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
        if (Chan1_val < 1000 || Chan1_val > 2000) { Chan1_val = Chan1_val_last;}
        else {Chan1_val_last = Chan1_val;}
       }

void Chan2_begin()           // enter Chan2_begin when interrupt pin goes HIGH.
       {
         Chan2_startPulse = micros();     // record microseconds() value as Chan2_startPulse
         detachInterrupt(Chan2Interrupt);  // after recording the value, detach the interrupt from Chan2_begin
         attachInterrupt(Chan2Interrupt, Chan2_end, FALLING); // re-attach the interrupt as Chan2_end, so we can record the value when it goes low
       }

void Chan2_end()
       {
        Chan2_val = micros() - Chan2_startPulse;  // when interrupt pin goes LOW, record the total pulse length by subtracting previous start value from current micros() vlaue.
        detachInterrupt(Chan2Interrupt);  // detach and get ready to go HIGH again
        attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
        if (Chan2_val < 1000 || Chan2_val > 2000) { Chan2_val = Chan2_val_last;}
        else {Chan2_val_last = Chan2_val;}
       }


Trimmed down to just 2 channels input, using the interrupt numbers 0 and 1 (on digital pins 2 and 3) that I believe are supported by all Arduinos.  Servos are connected to pins 22 and 23.  RC receiver connected to digital pins 2 and 3.
I fixed an index problem in my servo array (ZERO based arrays, ZERO based arrays).

reverendrichie

Awesome, Absolutely Awesome and Thank you for the great explanation. Back in the old days of assembly language, interrupt driven applications was far superior.

reverendrichie

Just a very quick update, I was able to load the above code to my Arduino. We are off to the races.  :)

Go Up