Go Down

Topic: Arduino-Controlled RC Transmitter (Read 49907 times) previous topic - next topic


Mar 28, 2010, 06:43 pm Last Edit: Mar 29, 2010, 12:44 pm by IanJohnston Reason: 1

Tried your timer test routine and it works fine, a nice clean 50Hz squarewave at pin 10 on the Nano.

However, I notice that timer.pde in your zip has nothing in it at all, i.e. empty file.......is that the problem, no timer setup?



Mar 30, 2010, 12:12 pm Last Edit: Mar 30, 2010, 12:13 pm by IanJohnston Reason: 1
Hi all,

In the end I ended up writing my own sketch.
A bit of a hack (I'm not a real programmer!) but it does work, at least on the workbench with my Futaba T9.

Still got the switches on  the joystick to do, i.e. better dual rates and trim. For the moment there is 1 switch on the joystick which is read to adjust dual rates.


Code: [Select]

// PPM Encoder Joystick to Futaba Trainer Port
// For use with Arduino Nano V3.0
// Ian Johnston 30/03/2010

int AI_Pin0 = 0;    // Ana In Ch.0 pin - Aeleron potentiometer
int AI_Pin1 = 1;    // Ana In Ch.1 pin - Elevator potentiometer
int AI_Pin2 = 2;    // Ana In Ch.2 pin - Throttle potentiometer
int AI_Pin3 = 3;    // Ana In Ch.3 pin - Rudder potentiometer
int AI_Raw0;        // Ana In Ch.0 raw var - 0->1023
int AI_Raw1;        // Ana In Ch.1 raw var - 0->1023
int AI_Raw2;        // Ana In Ch.2 raw var - 0->1023
int AI_Raw3;        // Ana In Ch.3 raw var - 0->1023
int AI_0;           // Ana In Ch.0 raw var - 0->1023 compensated
int AI_1;           // Ana In Ch.1 raw var - 0->1023 compensated
int AI_2;           // Ana In Ch.2 raw var - 0->1023 compensated
int AI_3;           // Ana In Ch.3 raw var - 0->1023 compensated
int Aeleron_uS = 700;     // Ana In Ch.0 uS var - Aeleron
int Elevator_uS = 700;    // Ana In Ch.1 uS var - Elevator
int Throttle_uS = 700;    // Ana In Ch.2 uS var - Throttle
int Rudder_uS = 724;      // Ana In Ch.3 uS var - Rudder
int Spare1_uS = 1225;     // uS var - Spare1 (set to mid)
int Spare2_uS = 1225;     // uS var - Spare2 (set to mid)

int Fixed_uS = 300;       // PPM frame fixed LOW phase
unsigned long SynchroFrameAdj = 0; // Timing adjustment for Synchro blank time in uS
unsigned long SynchroFrameAdjDiff = 0; // Timing adjustment for Synchro blank time in uS
unsigned long TotalFrameLengthAllowed = 20000; // Total Frame Length allowed in uS

int pulseMin = 750;            // pulse minimum width minus start in uS
int pulseMax = 1700;        // pulse maximum width in uS

float DualrateMultAel = 0.9; // Dual rate mult
float DualrateMultEle = 0.9; // Dual rate mult
float DualrateMultThr = 0.9; // Dual rate mult
float DualrateMultRud = 0.9; // Dual rate mult
int DualrateAdjAel = 0;   // Dual rate mid adjustment
int DualrateAdjEle = 0;        // Dual rate mid adjustment
int DualrateAdjThr = 0;        // Dual rate mid adjustment
int DualrateAdjRud = 0;        // Dual rate mid adjustment

int outPin = 10;          // digital pin 10
int inPinD6 = 6;          // digital pin 6

void setup() {

 pinMode(outPin, OUTPUT);      // sets the digital pin as output
 pinMode(inPinD6, INPUT);      // sets the digital pin as input
 digitalWrite(inPinD6, HIGH);  // turn on pull-up resistor


void loop() { // Main loop

// Frame start save, used to determine required 20mS PPM frame interval
  SynchroFrameAdj = micros();
// Read 4 analogue ports and convert to mS
  AI_Raw0 = analogRead(AI_Pin0);
  AI_Raw1 = analogRead(AI_Pin1);
  AI_Raw2 = analogRead(AI_Pin2);
  AI_Raw3 = analogRead(AI_Pin3);

// Compensate for discrepancy in pot inputs, also use this to invert inputs if necessary
  AI_0 = map(AI_Raw0, 0, 1023, 0, 1200) - 50; // y=mx+c, x to y scales to x1 to y1
  AI_1 = map(AI_Raw1, 0, 1023, 0, 1200) - 50; // y=mx+c, x to y scales to x1 to y1
  AI_2 = map(AI_Raw2, 0, 1023, 0, 1023) + 0; // y=mx+c, x to y scales to x1 to y1
  AI_3 = map(AI_Raw3, 0, 1023, 0, 1023) + 0; // y=mx+c, x to y scales to x1 to y1
// Map analogue inputs to PPM rates for each of the channels
  Aeleron_uS = (AI_0 * DualrateMultAel) + pulseMin + DualrateAdjAel;
  Elevator_uS = (AI_1 * DualrateMultEle) + pulseMin + DualrateAdjEle;
  Throttle_uS = (AI_2 * DualrateMultThr) + pulseMin + DualrateAdjThr;
  Rudder_uS = (AI_3 * DualrateMultRud) + pulseMin + DualrateAdjRud;

// Check limits
 if (Aeleron_uS <= 750) Aeleron_uS = 750;     // Min
 if (Aeleron_uS >= 1700) Aeleron_uS = 1700;   // Max  
 if (Elevator_uS <= 750) Elevator_uS = 750;   // Min
 if (Elevator_uS >= 1700) Elevator_uS = 1700; // Max
 if (Throttle_uS <= 750) Throttle_uS = 750;   // Min
 if (Throttle_uS >= 1700) Throttle_uS = 1700; // Max
 if (Rudder_uS <= 750) Rudder_uS = 750;       // Min
 if (Rudder_uS >= 1700) Rudder_uS = 1700;     // Max  
// Channel 1
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Aeleron_uS);  // Hold for Aeleron_uS microseconds      

// Channel 2  
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Elevator_uS); // Hold for Elevator_uS microseconds      

// Channel 3  
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Throttle_uS); // Hold for Throttle_uS microseconds      

// Channel 4  
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Rudder_uS);   // Hold for Rudder_uS microseconds

// Channel 5  
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Spare1_uS);   // Hold for xxxx microseconds        

// Channel 6
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 uS
 digitalWrite(outPin, HIGH);
 delayMicroseconds(Spare2_uS);   // Hold for xxxx microseconds  
// Synchro pulse
 digitalWrite(outPin, LOW);
 delayMicroseconds(Fixed_uS);    // Hold for 300 microseconds
 digitalWrite(outPin, HIGH);     // Start Synchro pulse

 SwitchesRates();                // Now is the time to jump to subroutine whilst we have some free time

 SynchroFrameAdjDiff = micros() - SynchroFrameAdj;  // Calculate remainder and adjust (fails after 70mins, but more than enough as who flies that long?)
 SynchroFrameAdj = TotalFrameLengthAllowed - SynchroFrameAdjDiff;
 delayMicroseconds(SynchroFrameAdj);   // Hold for remainder (uS), Synchro blank time


void SwitchesRates() {

   if (digitalRead(inPinD6) == 0) { // Low rate
          DualrateMultAel = 0.5;
           DualrateMultEle = 0.5;
           DualrateMultThr = 0.9;
           DualrateMultRud = 0.7;
           DualrateAdjAel = 200;
         DualrateAdjEle = 200;
           DualrateAdjThr = 0;
           DualrateAdjRud = 100;
   if (digitalRead(inPinD6) == 1) { // Normal/high rate
       DualrateMultAel = 0.9;
       DualrateMultEle = 0.9;
           DualrateMultThr = 0.9;
           DualrateMultRud = 0.9;
       DualrateAdjAel = 0;
       DualrateAdjEle = 0;
           DualrateAdjThr = 0;
           DualrateAdjRud = 0;



Hi Ian,

The missing timer file would certainly explain why is wasn't working!

If your code works then it isn't a hack, it's a solution! :D

I have re-uploaded my code if you want to give it another go. This is the version that is currently working for me.



Thanks Chris......

File is intact now. I'm putting together another stick so will take a look at your code again.

Ya go build one for yourself and all your mates want one......!



Isn't that just the way it always goes?

I took delivery of some ferric chloride this morning so I am hoping to make the PPM boad in the next few days. Eagle files coming soon! :)

The new code also includes functions for saving settings to EEPROM and reading them again. It isn't very well documented yet, but it will be soon.

I am currently working on a serial programming interface for it as well, so that you can make changes to the setup without having to re-upload the whole program. As soon as I have done I will upload the new code.



>ferric chloride

That takes me back........gotta admit I'm too lazy now, I just send my Eagle files to China to get boards made.....far cheaper than UK, even for low quantities.





Thanks Ian, I will check that out. I don't trust me to get it right first time tho, so I will still make one by hand first, as (so far) it is only single sided.



Hi all, i know this is an old thread, but its still relevant.
I saw this today and ive just ripped out the transmitter board out of a HobbyKing HK-T6A and attempted to interface it with an arduino. it has 4 interfae pins, VCC_+5v , GND, SW1 and PPM. interfacing is simple using the code mentioned previously, connect PPM pin to pin 10 on arduino, connect power and gnd up to +5v and gnd respectively and leave SW1 alone, its essentially just used to bind the transmitter when grounded.
So far it looks promising, but i have yet to connect it to a proper serial setup so i can control it using my PC.


Sounds good. Let us know how you get on!



Apr 22, 2010, 11:36 pm Last Edit: Apr 23, 2010, 12:18 am by IanJohnston Reason: 1

I did write my own code, but it's seems to be glitching a wee bit, not sure why so have decided to give yours a serious go.

So, I'm on it now.....and have got it basically working, i.e. Ael, Ele, Thr & Rud.

Have got some questions, appreciate if you can help.

4 switches are read and as I understand,
0 = Ael dual rate set
1 = Ele dual rate set
.......So, what are switches 2 & 3 for?

I need to use PPM Ch.5 for a switch input.
I also need to use PPM Ch.6 for an analogue input (a pot on my Tx).
This is for special use on a QuadCopter, basically to be able to turn a feature on/off and when it's on have some control over it via the pot.
Not sure what you are using Ch.5 & 6 for at the moment.
.......Any ideas?.....I don't mind hacking it in somewhere, just need a pointer or two.

What about trim?.....is that hardcoded or is the code reading inputs somewhere.

//stick limits, given as top, center and bottom
What is the range of the numbers given? i.e.
604, 501, 396
I was expecting this in mS i.e. in the range 750 to 1800 or whatever your min/max range is, but that doesn't look to be the case obviously.


PS. Here is my home made Tx (prior to wiring it), Arduino & 430mHz RF board inside:-


Apr 23, 2010, 12:42 am Last Edit: Apr 23, 2010, 12:48 am by cjparish Reason: 1
Hi Ian,

At the moment switches 3 and 4 don't do anything. They are there to be used as you like and can be read with get_switch_state(switch number)

At current I am using outputs 0,1,2,3 with 4 used for differential ailerons, but feel free to remap them however you like.
You can use PPM5 and 6 by simply updating the pulse array with the required pulse length in microseconds. Bare in mind that the channels are numbered from 0, not from 1!
[edit]Alternativly you can set the requoired values as a persentage offset from center in the channels array, this might be better as it allows you to use the throw adjustment and reverse functions built into the ouput processor[/edit]

At the moment trims are hard set in the code, as the trims on my current transmitter are mechanical rather than electronic. If you look in the sticks_include.h file you will find the lines:
Code: [Select]

int stick_trims[4] = {
 0,0,0,0}; //stick trims

The values used for the trims should be the trim offset from center, as measured by the ADC (which gives values bewteen 0 and 1023)

The stick limits: The range of values is 0 to 1023 as read by the ADC.

To set the throw limits on the servo you can change the OUTPUT_TOP, OUTPUT_CENTER and OUTPUT_BOTTOM values, which are set in microseconds.

With the help of Santiago Saldana I have developed a better exponential function if you need it:
Code: [Select]

int expo(float pos, float amount)
 pos = pos / 100;
 amount = amount / 100;
 if (amount < 0) {
   float temp = pow((float)pos,(float)(1.0/3.0));
   return (((1-amount)*pos)+((amount*temp)))*100;
 } else {
   return (((1-amount)*pos)+((amount*pow(pos,3))))*100;

Any other questions, don't hesitate to post.
I am really pleased that my code is proving usefull.

I am currently working on version 2 of the code, and I will post it as and when.


Oh yeah, I forgot: Where the processing is done on a timer induced loop, if you want to add your own functions to be run automatically then add calls to them in the process_loop() function in the main file, just add them before run_outputs(); if you are setting output values using the channels array or after run_outputs() if you are setting the output values directly to the pulses array


Apr 24, 2010, 02:08 am Last Edit: Apr 24, 2010, 12:00 pm by IanJohnston Reason: 1

I had a go at your code for a few hours and whilst I got the sticks working I just couldn't see where to modify the code to get my additional channels working (see below).

I understand what your saying in your post above, it's just that your code is just too advanced for me. You'll see my code is just very basic.

Maybe your V2 will be easier.

Here's the I/O I was trying to implement:-

Ana ch.0 = Ele
Ana ch.1 = Ael
Ana ch.2 = Rud
Ana ch.3 = Thr
Ana ch.4 = TI Adjust pot (Quadcopter use)
Ana ch.5 = battery voltage
Dig ch.11 = TI enable sw (Quadcopter use)
Dig ch.2 = dual rates
Dig ch.3 = ?
Dig ch.4 = ?
Dig ch.5 = ?

Dig ch.12 = low battery buzzer
Dig ch.10 = PPM output

PPM order req'd for quadcopter:-



Hi Ian,

I'm at work at the mo, but when I get home I'll have a shot at re-writing it.



Apr 28, 2010, 08:59 pm Last Edit: Apr 28, 2010, 09:00 pm by IanJohnston Reason: 1
Hi all,

Managed to get a stable version of my own code running.....using timers the PPM stream is FAR more stable. Chris's code showed me the light....:)

The LCD & dual rates implementation is a bit of a hack at the mo, also there is no stick trimming yet....need to work out how to do that, i.e. probably need to have y=mx+c twice....with the centre position the transition between the 2 graphs so can then have a finite point to offset (trim)............or maybe there's another way.

Code to big to post here, so here the link to my page:-



hi chris! i have been using ian's code with good results, but i think yours is a little better suited for my application. i have a question about pin 10... is using pin 10 required? i blew up pin 10 on my arduino mini pro so i would like to use pin 9 since it is still alive. i can't seem to locate where this is controlled other than the line pinMode(10, OUTPUT);. any help will be much appreciated! great job btw! cheers!

Go Up