Pages: 1 [2] 3 4 ... 6   Go Down
Author Topic: Arduino-Controlled RC Transmitter  (Read 22322 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 9
a r d u i n o
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, I have it from an experienced RC source that the Futaba buddy system is 0 volts for Low and 5 volts for High, However anything over 4 volts will be considered a High pulse. I haven't tested this yet but I will later this week and report back. However this should line up well with the arduino output capabilities.
Thanks again Matt for all you help.
Casey
Logged


0
Offline Offline
Newbie
*
Karma: 0
Posts: 9
a r d u i n o
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

OK, Just got the arduino to talk to my rc set up,

Which consists of a Futaba T4YF transmitter and an Alpex APR-4FM receiver.
I hooked up the trainer output from the Transmitter to the oscilloscope program (the electroacoustic Toolbox from faber acoustical) and discovered that ppm was essentially the same as what Matt described with slightly different values.

For some reason the PPM was inverted on the Oscilloscope but I just tweaked the Arduino until I had some thing that matched the signal coming out of the transmitter.

So the Futaba T4YF transmitter uses a frame length of 18 milliseconds, a 400 microsecond low pulse before each of the high pulses and for me the high pulses were 1050 microseconds for dead center.

So this code keeps everything in the center
Code:
int outPin = 12;                 // digital pin 12

void setup()
{
  pinMode(outPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds        
  
  digitalWrite(outPin, LOW);    // sets the pin off
    delayMicroseconds(400);        // pauses for 400 microseconds
  digitalWrite(outPin, HIGH);    // sets the pin off
  delayMicroseconds(10350);        // pauses for 10650 microseconds          
}

So my question for everyone now is I'm controlling 6 of these transmitters which should be no problem with the Arduino, However, I want to have global control of the Arduino from Max/MSP. I can't use the Max/MSP message systems because the timing in Max is only millisecond (not microsecond) resolution. How can I send the Arduino global messages from Max while it keeps running its microsecond routines? Is that clear?
Thanks everyone for your help.
Casey
Logged


New Jersey
Offline Offline
Full Member
***
Karma: 0
Posts: 193
Ard at work
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for putting the details on a different radio setup here! Should make lifer easier for the next person, I'm sure :-)

The signal may be reversed because, at least on US radios, you can have positive or negative shift, while on European ones, it's only negative (I think), so that may indeed happen. The Royal Evo, with US firmware, lets you choose which shift polarity you want.

As for timing, if you're only running 6 channels, you should have a fairly long synch pulse... any chance you can handle the rest of what you need to do during the synch pulse? If not, you'd have to use interrupts for the timing, at a guess, but I haven't tried that yet.
« Last Edit: February 09, 2008, 08:32:22 pm by Syvwlch » Logged

----------
Mathieu

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
So my question for everyone now is I'm controlling 6 of these transmitters which should be no problem with the Arduino, However, I want to have global control of the Arduino from Max/MSP. I can't use the Max/MSP message systems because the timing in Max is only millisecond (not microsecond) resolution. How can I send the Arduino global messages from Max while it keeps running its microsecond routines? Is that clear?
Thanks everyone for your help.
Casey
Casey, you could implement your app by sending serial packets from Max with an array of integers holding the millisecond pulse width for each of the channels for all the transmitters.

The easiest to implement on the arduino side would be to use a single packet for all the data from your six transmitters.

You could us a two dimensional array like the following or define a structure if you are familiar with that construct.

#define NBR_TRANSMITTERS  6
#define NBR_CHANNELS 4
int  channelData[ NBR_TRANSMITTERS ] [NBR_CHANNELS];

In your arduino code you would wait until you had NBR_TRANSMITTERS times NBR_CHANNELS times two data bytes available and read this into the two dimensional array. (times two because there are two bytes oer integer)

You could then loop through each transmitters data to send your pulses.

If its possible that Max could send packets more quickly than the transmitters can handle ( it takes 120 milliseconds to process all the data if you handle each transmitter in sequence) there are a few things you could do. The simplest is to only process the latest data,  but if throwing away older data is not acceptable then you could process all of the transmitter frames in parallel by starting pulses for each sequential channel for all transmitters at the same time and turning them off as per each transmitters received pulse time.

For example, turn on a pulse for channel 1 on all transmitters and as the time for the shortest pulse is reached, turn that one off. Wait for the next shortest pulse time to be reached and turn that one off etc until all transmitters have stopped pulsing channel 1. Then to the next channel until all channels are done.

The delay between channels will look a little different from what you observed on your transmitter but any radio control receiver will be able to cope.

Implimentation gets a little more complicated if you really do neeed millisecond accuracy because digitalWrite takes around five milliseconds to start and stop a pulse so you either need to allow for that or use the faster but more complicated direct port i/o.
« Last Edit: February 10, 2008, 12:10:51 am by mem » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 9
a r d u i n o
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Wow, thanks that is quite helpful,

I got the part about having max send the Arduino messages via the serial port. And I determined that I do indeed need to address the receivers in Parallel ( a super long sync pulse that results from serial communication results in an undesirable pulsing of the motors). And I understand conceptually how you described addressing all of the pins concurrently, however I'm have a hard time putting that into code. Would I still be using the delayMicroseconds function? Thanks
Casey
Logged


London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

To output the pulses in parallel lets assume you will start pulsing all Tx output pins at the same time and stop pulsing as per each transmitters pulse width. (we will ignore the alternative of turning on as per the pulse width and turning them all off at the same time, either will work)

Anyway, you need to sort the six pulse width values for the current channel you are pulsing. This lets you easily determine how long to wait until turning off the first pulse (you delay for the pulse width of the shorted pulse). You then delay for the difference between that pulse width and the pulse width of the next shortest pulse. And so on until all pulses have been turned off.

Have a search of quicksort or qsort to see if there is a built in function you could use. If not, there should be plenty of algorithms for C implementations of sort functions on the net.

It may not be too critical timing when to start the next sequence of pulses because receivers and servos are tolerant of small variations in frame rate but you could use one of the arduino timers to tell you when to start pulsing the next frame. I can post code you could use if you want to do this and are not familiar with the timers.

I hope that helps. I don't know your experience level so do please say if you need more detail on turning this into code.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 9
a r d u i n o
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I think that does help, thanks,
I am pretty new to arduino and coding for it, any example in code would be great if you have the time,
Thanks again
Casey
Logged


London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I am not sure if the following fragment will help or not. I threw it together a little to quickly and not only is it incomplete and certainly buggy, i expect the array indirections I have used could confuse more than clarify. But unfortunatly I don't have any more time at the moment to do anyting with it so FWIW here it is. Let me know what you think and I will get back to you later.
Code:
#define NBR_TRANSMITTERS  6
#define MAX_TRANSMITTERS  8          // we use this only if we want the packet size to be 64 bytes, the last two tx values are ignored in this version
#define NBR_CHANNELS 4
int  channelData[NBR_CHANNELS] [ MAX_TRANSMITTERS ] ;
int SortIndex[NBR_TRANSMITTERS]; // the first element will point the transmitters with the sortest pulse for the given channel,  
int TxPins[NBR_TRANSMITTERS];     // init this with your pin numbers
 
void setup(){
  Serial.begin(19200);
  for(int i = 0; i <  NBR_TRANSMITTERS; i++)
     pinMode(TxPins[i], OUTPUT);
}

void Sort(int chan){
  // TODO - this function needs to sort the data for the given channel (note that 0 is what futaba calls channel 1)  
  // you can impliment your own algorythm or use a library sort function
  // The pseudocode below assumes you have populated the SortIndex array with indexs to increasing pulse width for the given channel  
}

void PulseChannel(int chan){
  
  for( int tx=0; tx < NBR_TRANSMITTERS; tx++)  // turn on pulse (but only if pulse width is greater than 0)
     if( channelData[chan][SortIndex[0]] > 0 )  // valid pulses must be greater than 0  
        digitalWrite(TxPins[tx], HIGH);     // HIGH or LOW depends if your tx pulses high or low, change as necessary
   delayMicroseconds( channelData[chan][SortIndex[0]]);    // the first element pointed to in the SortIndex is the shortest pulse width so we delay that amount first  
   digitalWrite(TxPins[SortIndex[0]], LOW);        // now turn the pulse for this tx off
   for( int tx=1; tx < NBR_TRANSMITTERS; tx++) {   // here we do all the other elements  
      delayMicroseconds(channelData[chan][SortIndex[tx-1]] - channelData[chan][SortIndex[tx]]); // wait however much longer to reach this channels pulse width    
      digitalWrite(TxPins[tx], LOW);      
   }  
}

void loop(){
   int buffersize = sizeof(channelData) * sizeof(byte) ;
   char * ptr = (char*)channelData;
   if( Serial.available() >= buffersize ) {
       for(int i =0; i < buffersize; i++)
           ptr[i]  = Serial.read();   // fill the buffer with the serial data
   }  
  
   for( int chan=0; chan < NBR_CHANNELS; chan++) {
          Sort(chan);          // get an array sorted so the shortest pulse is first.
          PulseChannel(chan);  // send the correct channel pulse to all transmitters              
   }
   delay(12);  // this is the frame delay , each frame should be around 20 ms and the channel code above will have taken up to 8ms      
               // you can do a more sophisticated calculation later if you want.    
  
}
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The code below has the sort implimented. None of this has been tested !
Code:
#define NBR_TRANSMITTERS  6
#define MAX_TRANSMITTERS  8          // we use this only if we want the packet size to be 64 bytes, the last two tx values are ignored in this version
#define NBR_CHANNELS 4

int TxPins[NBR_TRANSMITTERS];     // init this with your pin numbers

int channelData[NBR_CHANNELS] [ MAX_TRANSMITTERS ] ;  // this holds the data for all channels for all transmitters
int Index[NBR_TRANSMITTERS] = {0,1,2,3,4,5,}; // after sorting, the first element will point the transmitters with the sortest pulse for a given channel
 
void setup(){
  Serial.begin(19200);
  for(int i = 0; i <  NBR_TRANSMITTERS; i++)
        pinMode(TxPins[i], OUTPUT);
}

void Sort(int data[]) // sort the channel data in ascending order using Index array
{
  int i, j, index;
  for (i=1; i < NBR_TRANSMITTERS; i++)
  {
    index = Index[i];
    j = i;
    while ((j > 0) && (data[Index[j-1]] > data[index]))
    {
       Index[j] = Index[j-1];
       j = j - 1;
     }
     Index[j] = index;
   }
}

void PulseChannel(int chan){
  
  for( int tx=0; tx < NBR_TRANSMITTERS; tx++)  // turn on pulse (but only if pulse width is greater than 0)
      if( channelData[chan][Index[0]] > 0 )  // valid pulses must be greater than 0  
           digitalWrite(TxPins[tx], HIGH);     // HIGH or LOW depends if your tx pulses high or low, change as necessary
   delayMicroseconds( channelData[chan][Index[0]]);    // the first element pointed to in the SortIndex is the shortest pulse width so we delay that amount first  
   digitalWrite(TxPins[Index[0]], LOW);        // now turn the pulse for this tx off
   for( int tx=1; tx < NBR_TRANSMITTERS; tx++) {   // here we do all the other elements  
      delayMicroseconds(channelData[chan][Index[tx-1]] - channelData[chan][Index[tx]]); // wait however much longer to reach this channels pulse width      
      digitalWrite(TxPins[tx], LOW);      
   }  
}

void loop(){
   int buffersize = sizeof(channelData) * sizeof(byte) ;
   char * ptr = (char*)channelData;
   if( Serial.available() >= buffersize ) {
       for(int i =0; i < buffersize; i++)
       ptr[i]  = Serial.read();   // fill the buffer with the serial data
   }  
  
   for( int chan=0; chan < NBR_CHANNELS; chan++) {
      Sort(channelData[chan]);    // sort this channel so the shortest pulse is first.
      PulseChannel(chan);  // send the correct channel pulse to all transmitters              
   }
   delay(12);  // this is the frame delay , each frame should be around 20 ms and the channel code above will have taken up to 8ms      
    // you can do a more sophisticated calculation later if you want.    
  
}
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 9
a r d u i n o
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Wow, mem thanks this is a huge help.
I'll wire things up later today and see if I can get it worked out.

Logged


London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I simplifed the syntax of the PulseChannel function by passing the channel array rather then the channel number. also fixed a small bug in one of the for loops.  I expect there will be a few more things waiting for you to debug smiley-wink
Code:
#define NBR_TRANSMITTERS  6
#define MAX_TRANSMITTERS  8          // we use this only if we want the packet size to be 64 bytes, the last two tx values are ignored in this version
#define NBR_CHANNELS 4

int TxPins[NBR_TRANSMITTERS];     // init this with your pin numbers

int channelData[NBR_CHANNELS] [ MAX_TRANSMITTERS ] ;  // this holds the data for all channels for all transmitters
int Index[NBR_TRANSMITTERS] = {0,1,2,3,4,5,}; // after sorting, the first element will point the transmitters with the sortest pulse for a given channel
 
void setup(){
  Serial.begin(19200);
  for(int i = 0; i <  NBR_TRANSMITTERS; i++)
      pinMode(TxPins[i], OUTPUT);
}

void Sort(int data[]) // sort the channel data in ascending order using Index array
{
  int i, j, index;
  for (i=1; i < NBR_TRANSMITTERS; i++)
  {
    index = Index[i];
    j = i;
    while ((j > 0) && (data[Index[j-1]] > data[index]))
    {
       Index[j] = Index[j-1];
       j = j - 1;
     }
     Index[j] = index;
   }
}

void PulseChannel(int data[]){ // pulse all transmitter using pulse width data in the given channel array
// these are pulsed in the order of shortest to longest pulse width as determined by the sorted Index array  
  for( int tx=0; tx < NBR_TRANSMITTERS; tx++)  // turn on pulse (but only if pulse width is greater than 0)
      if( data[Index[tx]] > 0 )  // valid pulses must be greater than 0  
          digitalWrite(TxPins[tx], HIGH);   // HIGH or LOW depends if your tx pulses high or low, change as necessary
   delayMicroseconds( data[Index[0]]);   // the first element pointed to in the SortIndex is the shortest pulse width so we delay that amount first  
   digitalWrite(TxPins[Index[0]], LOW);   // now turn the pulse for this tx off
   for( int tx=1; tx < NBR_TRANSMITTERS; tx++) {   // here we do all the other elements  
      delayMicroseconds(data[Index[tx-1]] - data[Index[tx]]); // wait however much longer to reach this channels pulse width      
      digitalWrite(TxPins[tx], LOW);      
   }  
}

void loop(){
   int buffersize = sizeof(channelData) * sizeof(byte) ;
   char * ptr = (char*)channelData;
   if( Serial.available() >= buffersize ) {
       for(int i =0; i < buffersize; i++)
            ptr[i]  = Serial.read();   // fill the buffer with the serial data
   }  
  
   for( int chan=0; chan < NBR_CHANNELS; chan++) {
      Sort(channelData[chan]);    // sort this channel so the shortest pulse is first.
      PulseChannel(channelData[chan]);  // send the correct channel pulse to all transmitters              
   }
   delay(12);  // this is the frame delay , each frame should be around 20 ms and the channel code above will have taken up to 8ms      
     // you can do a more sophisticated calculation later if you want.    
  
}
« Last Edit: February 12, 2008, 09:31:00 am by mem » Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I'll wire things up and see if I can get it worked out.

Remember this code is not even half baked. Even though it looks it may have all the ingredients, the output pulses may not be servo friendly yet, so run it with debugging code first to check the pulse width calculations

A good place for some Serial.print statements is in the PulseChannel function.
In the for loop, you could print the value of data[Index[tx]] (this is the pulse delay for a transmitter).
If you also print the value you are passing to delayMicroseconds and the sum of these delays you can confirm that the actual pulsing matches the data in the array.

If you don't have the communication link to your control program to populate the data, now may be good time to temporarily substitute some hard coded data in your sketch for testing. This enables you to see what happens when out of range values are processed (the posted code does not do much checking).
« Last Edit: February 13, 2008, 05:46:06 am by mem » Logged

Glasgow, UK
Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Instead of using the trainer connector, has anybody tried to hook the Arduino between the transmitter and the HF module? That should work also with old and cheap transmitters, and theoretically, one could even extend the number of transmitter channels that way. Just a thought - but I might be onto a dead-end here just as well...
Logged

New Jersey
Offline Offline
Full Member
***
Karma: 0
Posts: 193
Ard at work
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I haven't tried it, mostly because I wanted to mix manual input with arduino input... including with some mixing done by the radio.

As far as I know, your idea should work because the HF module takes the unmodulated pulse series as input and just modulates it by the carrier wave. IOW, yes, you should be able to hook the arduino straight to the HF module for the win.

I, unfortunately, am too chicken to try it with my HF modules as they are rather pricey, and require some add'l interaction to set the carrier frequency (synth modules, not crystal). If I can get my hands on an inexpensive toy with radio included, I'll give it a shot!
Logged

----------
Mathieu

Netherlands
Offline Offline
Sr. Member
****
Karma: 0
Posts: 414
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I have been playing with Mathieu's code and apart from having to change the 'shift' it is working perfectly well.

Mathieu uses 'delayMicroseconds' in his code. I had the impression that using just 'delay' is better because delaymicroseconds turns of interrupts. However, when I changed this in code (from delaymicroseconds to delay), the receiver didn't respond anymore.

Anyone can explain me why?
Logged

Pages: 1 [2] 3 4 ... 6   Go Up
Jump to: