First, I want to thank you for making this post. I have a few remote-controlled cars/boats/helicopters, and I've often wondered how the remote control works. :)
To answer your question - and because I was too lazy to work out how to connect to an actual controller - I made a "remote simulator" like this:
// PPM generator
const byte SIGNAL_PIN = 3;
const int SIGNAL_COUNT = 6;
const unsigned long PULSE_WIDTH = 300; // µs
const unsigned long BLANK_TIME = 25; // ms
const int widths [SIGNAL_COUNT] = { 600, 2000, 1000, 1500, 2200, 2400 };
void setup ()
{
pinMode (SIGNAL_PIN, OUTPUT);
} // end of setup
void loop ()
{
delay (BLANK_TIME);
digitalWrite (SIGNAL_PIN, HIGH); // start
delayMicroseconds (PULSE_WIDTH);
digitalWrite (SIGNAL_PIN, LOW); // end of start pulse
for (byte i = 0; i < SIGNAL_COUNT; i++)
{
delayMicroseconds (widths [i] - PULSE_WIDTH);
digitalWrite (SIGNAL_PIN, HIGH); // start of pulse
delayMicroseconds (PULSE_WIDTH);
digitalWrite (SIGNAL_PIN, LOW); // end of pulse
} // end of for loop
delay (1000);
} // end of loop
This is intended to generate a series of pulses (six in this case) which might be generated by some fancy toy fun device.
Output from it:
As you can see from the cursor, it is doing what it is bid. The pulse width shown by the scope cursor is indeed 2000 µs, as per the table of widths.
Note: Output on pin 3 on the Uno.
Now we need a decoder. So let's use interrupts:
const byte SIGNAL_PIN = 2;
const int SIGNAL_COUNT = 6;
volatile unsigned long widths [SIGNAL_COUNT + 1];
volatile byte count;
unsigned long lastPulse;
// ISR
void gotPulse ()
{
unsigned long now = micros ();
// a long gap means we start again
if ((now - lastPulse) >= 25000)
count = 0;
lastPulse = now;
if (count >= (SIGNAL_COUNT + 1))
return;
widths [count++] = now;
}
void setup ()
{
Serial.begin (115200);
Serial.println ();
attachInterrupt (0, gotPulse, RISING);
EIFR = bit (INTF0); // clear flag for interrupt 0
} // end of setup
void loop ()
{
if (count >= SIGNAL_COUNT)
{
Serial.println (F("Got sequence."));
for (int i = 1; i < SIGNAL_COUNT + 1; i++)
{
Serial.print ("Channel ");
Serial.print (i);
Serial.print (" = ");
Serial.println (widths [i] - widths [i - 1]);
}
count = 0;
}
} // end of loop
This uses an interrupt (on pin 2 on the Uno) on the rising edge to detect the start of the pulses. It stores them, and then works out their widths later.
Results?
Got sequence.
Channel 1 = 612
Channel 2 = 2020
Channel 3 = 1016
Channel 4 = 1512
Channel 5 = 2220
Channel 6 = 2428
Got sequence.
Channel 1 = 612
Channel 2 = 2020
Channel 3 = 1016
Channel 4 = 1512
Channel 5 = 2220
Channel 6 = 2424
Got sequence.
Channel 1 = 616
Channel 2 = 2020
Channel 3 = 1012
Channel 4 = 1516
Channel 5 = 2220
Channel 6 = 2424
Got sequence.
Channel 1 = 616
Channel 2 = 2020
Channel 3 = 1012
Channel 4 = 1516
Channel 5 = 2216
Channel 6 = 2432
Highly consistent results, with a small error factor of around 16 µs compared to 600, 2000, 1000, 1500, 2200, 2400
In case it isn't clear, this test required two Unos. One to generate the pulses and a second one to decode them:
The one with the USB cable is the decoder, which writes to the serial port. The other one is powered by 5V/Gnd from the first one, and the white wire is the data with the pulses.