Thread Tools
Apr 25, 2011, 11:25 PM
Want heli used electonics
I have my new heli set but my efforts have been severely set back by a sudden hard drive failure. All my work is gone.

Michael
Sign up now
to remove ads between posts
May 01, 2011, 01:59 AM
Registered User
Hi -

I'm new to this, but I've done some testing and thought I would add my fuel to the fire

I've got a SYMA S107G helicopter that I want to control with an Arduino controller. The SYMA receiver board is labelled "S107R5" while the transmitter board is labelled "S107T2" and bears the legend "38KHZ | 57KHZ". I took the IR receiver from the heli and hooked it to the external interrupts on the Arduino. According to Darkstar, this should be the TSOP1738 IR receiver ( http://www.datasheetcatalog.org/data.../301092_DS.pdf ) so I'm assuming that the output is low when active and pulled high when inactive.

I capture interrupt pairs (one on the falling edge and one on the rising edge) and note the microseconds since the last interrupt service. These are as follows when barely applying throttle only:

549680 (time in us since starting the program and moving the control)
2024 1972 268 336 264 332 268 736
272 736 268 740 264 724 284 720
284 724 280 340 264 336 336 668
264 744 264 720 284 720 284 724
284 720 284 720 288 336 264 336
264 340 260 744 264 336 264 724
280 740 268 336 264 740 268 336
264 348 264 308 344 264 288 336
264 336 268 150700 2032 1956 284 336
264 332 268 736 268 740 268 740
264 740 264 724 284 740 264 340
264 336 264 740 268 720 280 724
284 720 284 720 284 724 284 720
284 340 340 224 300 720 284 340
264 740 264 740 264 724 284 336
264 724 280 340 264 336 264 336
264 352 264 312 288 336 264 150252
2024 1952 284 340 264 332 268 740
264 740 268 720 284 724 284 720
284 720 360 224 304 336 264 740
264 720 284 724 284 740 264 720
288 716 360 644 292 320 280 716
356 224 308 340 264 336 364 236
264 728 352 220 308 736 268 328
288 312 336 256 348 256 296 336
264 336 264 151104 2032 1952 284 340

Inspection led me to believe that the first two (2024 & 1972) were the start bits - each pair that follows appears to either add up to ~600us or ~1000us with the first (mark) bit approximately ~300us +/- 20% - there is an odd bit before the ~150000 end-of-packet to complete the rising/falling pulse sequence before returning to a high state.

Reformatting these into 4 byte packets, I get something that looks like this:

Starting ... low throttle
X: 00111111 00111111 10001011 01000000
X: 00111111 00111111 10001100 01000000
X: 00111111 00111111 10010000 01000000 // note the change from this line to the
X: 00111111 00111111 10010001 01000000 // next line - no evidence of a CRC bit
X: 00111111 00111111 10010001 01000000
X: 00111111 00111111 10010001 01000000

Starting ... low throttle - trim full right
X: 00101101 00111111 10001101 00000001
X: 00101101 00111111 10001101 00000001
X: 00101101 00111111 10001101 00000001
X: 00101101 00111111 10001101 00000001
X: 00101101 00111111 10001100 00000001
X: 00101101 00111111 10001100 00000001

Starting ... low throttle - trim full left
X: 01001111 00111111 10010000 01110110
X: 01001111 00111111 10010101 01110110
X: 01001111 00111111 10011100 01110110
X: 01001111 00111111 10011100 01110110
X: 01001111 00111111 10011100 01110110
X: 01001111 00111111 10011100 01110110

I used one of the IR LEDs from the controller and hooked it to an output pin on my Arduino. Noting the ~38kHz pulse rate, I toggle the LED 10 cycles with a 13us delay between state changes to generate a "Mark" and toggle it 75 cycles to generate a "Start". I then use the above bit patterns to drive the sequence of "Start" / "Mark" + data delays. It's flaky, but the helicopter starts up the blades and shuts down when the pulse train is terminated.

See video at
Arduino control for SYMA S107G helicopter (0 min 21 sec)


My current problem is that I'm only guessing at the timing here. My o'scope died and I've been trying to use the Arduino as a generic pulse analyzer. I think I'm having issues with more exact timing from the LED pulses versus pulsetrain widths, but I'm shooting in the dark without a scope. Any additional thoughts, ideas, suggestions, etc., would be appreciated.
Last edited by Aqualung; May 01, 2011 at 10:34 AM. Reason: (updated TSOP part spec per Darkstar's notes)
May 01, 2011, 01:53 PM
Registered User
Additional testing of IR receiver - each sample is six sets of pulses received sequentially.

Some notes: there are two base settings for the transmitter for dual-copter operation - when set to the left, the 17th data bit is set to zero and the intra-packet pulse is ~90ms - when set to the right, the 17th data bit is set to one and the intra-packet pulse is ~150ms - not sure whether the carrier is 38kHz or 57kHz (sadly to say, I have no scope available) - the trim seems to offset the yaw component when applied - while striving to only affect one control at a time, the full-forward and full-reverse pitch appears to introduce a slight yaw component if not careful with the position of the lever.

Tentative analysis:

0YYYYYYY|0PPPPPPP|CTTTTTTT|0AAAAAAA
YAW PITCH THROT ADJUST

"C" is the channel bit - all values are 7-bit binary - yaw and pitch values are centered while the throttle is low-to-high only.
Code:
Starting ... minimal throttle
X: 00111111 00111111 10001100 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000

Starting ... medium throttle
X: 00111111 00111111 10110110 01000000 
X: 00111111 00111111 11001001 01000000 
X: 00111111 00111111 11001110 01000000 
X: 00111111 00111111 11001100 01000000 
X: 00111111 00111111 11001100 01000000 
X: 00111111 00111111 11001100 01000000

Starting ... full throttle
X: 00111111 00111111 10010011 01000000 
X: 00111111 00111111 11111101 01000000 
X: 00111111 00111111 11111101 01000000 
X: 00111111 00111111 11111101 01000000 
X: 00111111 00111111 11111101 01000000 
X: 00111111 00111111 11111101 01000000
Next readings use minimal throttle
Note that the process of pulling the
pitch lever back or pushing it forward
possibly introduces rotation vector
(i.e., sideways lever)
--------------------------------------
Code:
Starting ... full trim right 
X: 00101101 00111111 10001100 00000001 
X: 00101101 00111111 10001100 00000001 
X: 00101101 00111111 10001100 00000001 
X: 00101101 00111111 10001100 00000001 
X: 00101101 00111111 10001100 00000001 
X: 00101101 00111111 10001100 00000001

Starting ... trim at 3/4
X: 00110111 00111111 10001100 00100010 
X: 00110111 00111111 10001100 00100010 
X: 00110111 00111111 10001100 00100010 
X: 00110111 00111111 10001100 00100010 
X: 00110111 00111111 10001100 00100010 
X: 00110111 00111111 10001100 00100010

Starting ... trim at exactly 1/2
X: 00111111 00111111 10001100 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000 
X: 00111111 00111111 10001101 01000000

Starting ... trim at 1/4
X: 01001001 00111111 10001101 01100011 
X: 01001001 00111111 10001101 01100011 
X: 01001001 00111111 10001101 01100011 
X: 01001001 00111111 10001100 01100011 
X: 01001001 00111111 10001101 01100011 
X: 01001001 00111111 10001100 01100011

Starting ... trim at full left
X: 01001111 00111111 10001101 01110110 
X: 01001111 00111111 10001101 01110110 
X: 01001111 00111111 10001101 01110110 
X: 01001111 00111111 10001101 01110110 
X: 01001111 00111111 10001101 01110110 
X: 01001111 00111111 10001101 01110110

Starting ... slight rear
X: 00111111 01100100 10010000 01000000 
X: 00111111 01100100 10010101 01000000 
X: 00111111 01100100 10010101 01000000 
X: 00111111 01100100 10010101 01000000 
X: 00111111 01100100 10010010 01000000 
X: 00111111 01100100 10010010 01000001 

Starting ... nearly full rear
X: 00111111 01101011 10010000 01000000 
X: 00111111 01101011 10010001 01000000 
X: 00111111 01101011 10010010 01000000 
X: 00111111 01101011 10010010 01000000 
X: 00111111 01101011 10010101 01000000 
X: 00111111 01101011 10010100 01000000

Starting ... full rear 
X: 00111111 01110100 10010000 00111111 
X: 00111111 01110100 10010000 00111111 
X: 00111111 01110100 10010001 01000000 
X: 00111111 01110100 10010000 01000000 
X: 00111111 01110100 10010000 01000000 
X: 00111111 01110100 10010000 01000000

Starting ... slight forward
X: 00111111 00110010 10001100 00111111 
X: 00111111 00110010 10001101 01000000 
X: 00111111 00110010 10001100 00111111 
X: 00111111 00110010 10001100 00111111 
X: 00111111 00110010 10001100 00111111 
X: 00111111 00110010 10001100 00111111 

Starting ... nearly full forward
X: 01000010 00001011 10001100 01000000 
X: 01000010 00001011 10001101 01000000 
X: 01000010 00001011 10001101 01000000 
X: 01000010 00001011 10001101 01000000 
X: 01000010 00001011 10001101 01000000 
X: 01000010 00001011 10001101 01000000

Starting ... full forward
X: 01000010 00000001 10001100 00111111 
X: 01000010 00000001 10001011 00111111 
X: 01000010 00000001 10001011 01000000 
X: 01000010 00000001 10001011 01000000 
X: 01000010 00000001 10001011 01000000 
X: 01000010 00000001 10001011 01000000

Starting ... nearly full right
X: 00100110 00111111 10001100 01000000 
X: 00100110 00111111 10001101 01000000 
X: 00100101 00111111 10001101 01000000 
X: 00100110 00111111 10001101 01000000 
X: 00100110 00111111 10001101 01000000 
X: 00100110 00111111 10001101 01000001 

Starting ... full right
X: 00001011 00111111 10001011 01000000 
X: 00001011 00111111 10001100 01000000 
X: 00001011 00111111 10001101 01000000 
X: 00001011 00111111 10001100 01000000 
X: 00001011 00111111 10001101 01000000 
X: 00001011 00111111 10001101 01000000

Starting ... nearly full left 
X: 01010100 00111111 10001011 01000000 
X: 01010100 00111111 10001100 01000000 
X: 01010100 00111111 10001100 01000000 
X: 01010100 00111111 10001100 01000000 
X: 01010100 00111111 10001100 01000000 
X: 01010100 00111111 10001100 01000001 

Starting ... full left
X: 01101011 00111111 10001100 01000000 
X: 01101011 00111111 10001100 01000000 
X: 01101011 00111111 10001100 00111111 
X: 01101011 00111111 10001100 01000000 
X: 01101011 00111111 10001100 01000000 
X: 01101011 00111111 10001100 00111111
Last edited by Aqualung; May 06, 2011 at 11:47 PM. Reason: Added code blocks to improve readability.
May 01, 2011, 03:34 PM
Registered User
Hi Aqualung,
you did a good job! I always found it most easy to find out the correct code by watching the bits while moving the controls. If you have a look at the overall timing you will probably see that the long space (~150000us) always fills the whole sequence up to approximately the same length. If you record some data you can make a statistical analysis to find the correct timing for the bits and implement it in your software. VoilÓ, you are done!

Darkstar.
May 01, 2011, 08:04 PM
Registered User

Syma S107R3 IR Protocol


What a coincidence, I was working on the same thing this weekend for the R3 version.

Here are my initial results. The protocol seems to be very similar to that of the R5.

IR Protocol

S 0YYYYYYY 0PPPPPPP CTTTTTTT E

S - Start Symbol
Y - 7 bit yaw field - increasing yaw value turns left
P - 7 bit pitch field - increasing pitch value pitches up
C - 1 bit channel field - 0 is channel A, 1 is channel B
T - 7 bit throttle field - increasing throttle value applies more throttle
E - End Symbol

Pulse timings in microseconds

ZERO - 256 High 341 Low
ONE - 277 High 725 Low
START - 1941 High 2048 Low
END - 277 High 46059 Low *


Note that the definitions of ONE/ZERO were arbitrarily set. I had initially defined them the other way but then switched so that a larger throttle value increased the applied throttle.

Like Aqualung I notice a slight mixing between the pitch and yaw axes. The yaw value varies by about 8 counts across the range of the pitch stick where decreasing pitch value increased the yaw value. Also when the throttle is completely cut the transmitter sends five packets with a throttle value of zero and a pitch and yaw value of 63 independent of the position of the pitch/yaw stick.


* The Low time of the END symbol varies with the channel selected on the transmitter. This value corresponds to channel A, but will work when transmitting with the channel set to B. My suspicion is that there is a minimum low time between packets that must be observed and anything beyond that value will work. The reason for the difference between the two channels is to ensure that two transmitters can operate simultaneously without requiring any synchronization. Decreasing the duration of the END pulse will likely allow a corresponding increase in the update rate. An increase in the update rate could be beneficial with closed loop control.


A little background

I used a device called the USB IR Toy to record the transmitter output and send commands to the copter.

http://dangerousprototypes.com/docs/USB_Infrared_Toy

It has some quirks but I think that it is well worth the ~$20 shipped. Especially as it allowed me to send and receive IR signals without needing to deal with programming a microcontroller. Although it looks like any device that is compatible with lirc could serve the same function.

The Infrared Toy reports the length of high/low pulses in terms of clock counts, where each count is claimed to be 21.333 uS. I have not tested to verify if this value is correct. If this value is off the timings that I report will need to be adjusted by a scale factor. Even if the clock rate is correct it does not correspond to the carrier frequency of the transmitter. I believe that the transmitter works with a carrier of 38 KHz which would have a period of 26.32 uS. This mismatch leads to what I would consider quantization error in the measurements. I averaged multiple measurements to help reduce this error. The USB Infrared Toy is open source and could probably be modified to address these issues but I do not have any plans to do so at this time. The values I came up with are close enough to those of Aqualung to make me believe they are correct.

I am a little surprised that the protocol does not include a checksum. I understand that even the earlier Air Hogs/Picco Z copters used a checksum. There is a possibility that a R5 transmitter could be used with a R3 receiver as the protocol is the same up until the 4th byte.

Next up is do the same with the S026 protocol. Comparing those results with what Darkstar2000 reported should shed further light on the accuracy of the USB Infrared Toy. I will probably also do the Air Hogs Havoc and Tandem as well. My goal is to build a generic library that can be used to generate or decode the protocol for any of the copters that I have access to. The library should be adaptable to any device that deals with IR pulses in terms of high/low pulse durations.
May 01, 2011, 10:59 PM
Registered User
Some additional notes about my data ...

I'm using an Arduino Uno (ATmega328) to capture the pulses using the two interrupt pins (2 & 3) - the input is fed into both pins simultaneously and then interrupt 0 is set to trigger on the falling edge (down) and interrupt 1 is set to trigger on the rising edge (up) - the two interrupt routines (ISRs) each get a snapshot of the current time in microseconds and store the time since the last interrupt in an array:
Code:
void up()
{
  t = micros();
  times[i++] = t - timer;
  timer = t;
}

void down()
{
  t = micros();
  times[i++] = t - timer;
  timer = t;
}
Seems like simple enough code to get relatively accurate results, right? It wasn't until I tried sampling the raw output from the LED in the transmitter that I noticed that the values were always multiples of four - I went back through my previous captures and noticed that this was the case with all the data I collected - so this +/-2 error is apparently some sort of artifact introduced by the method I've chosen to analyze the pulse-trains.

Sampling from the transmitter LED cathode (low = on):
Code:
Starting ... medium throttle - start + 10 bits
3531104 - high time until first low pulse
16	12	16	12	12	16	12	12
12	12	16	12	12	16	12	12
12	16	12	12	12	16	12	12
12	16	12	12	12	16	12	12
12	16	12	12	12	16	12	12
12	16	12	12	12	16	12	12
16	12	12	12	12	20	16	12
12	12	12	16	12	12	12	16
12	12	12	16	12	12	12	16
12	12	12	16	12	12	12	16
12	12	12	16	12	12	12	16
12	12	12	16	12	12	12	16
12	12	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	16	12	12	16
12	12	16	12	12	16	12	12
12	16	12	12	16	12	12	1996
16	12	12	12	16	12	12	12
16	12	12	12	12	16	12	12
12	16	12	12	12	16	12	12
12	280	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	16	12	12	16
12	12	16	272	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	692	12	12	16
12	12	12	16	12	12	12	16
12	12	12	16	12	12	12	16
12	12	16	12	12	288	12	16
12	12	12	16	12	12	16	12
12	16	12	12	16	12	12	16
12	12	16	12	12	16	684	12
12	16	12	12	12	16	12	12
16	12	12	16	12	12	16	12
12	16	12	12	16	12	12	684
16	12	12	12	16	12	12	12
20	12	16	12	12	12	16	12
12	12	16	12	12	12	16	12
284	12	12	16	12	12	12	16
12	12	12	16	12	12	12	12
16	12	12	12	16	12	12	12
16	692	12	12	16	12	12	12
16	12	12	12	16	12	12	12
16	12	12	12	12	16	12	12
12	16	288	12	12	12	16	12
12	12	16	12	12	12	16	12
12	12	16	12	12	12	16	12
12	12	16	288
This was about what I was expecting to see, but the multiples of four were not expected ;(

When trying to perform the inverse operation to generate LED pulses that will be properly interpreted by the S107R5 receiver, I can easily see that I need to pulse the LED 76 times with a 13us/13us pulse, wait 2000us, send another 13 13us/13us pulses followed by a fill time to 600us for a zero or a fill time of 1000us for a one - and, as Darkstar previously pointed out, a fill time at the end of the packet to insure even packet-to-packet transmission intervals.

Unfortunately, I believe that this is beyond the means of a simple program in the Arduino. While I can synchronize the pulse-train to real-time by using delta real-time values for the fillers, experimental testing indicates that I can't reliably generate the 13us/13us pulses by simpy toggling an output pin in a loop. This is not to say that the ATmega328 can't be programmed to handle it, but that the means to do so are beyond the scope of my weekend project (my original goal is to use an SN754410 with the PWM output to drive the motors directly - a co-worker suggested using the LEDs as a challenge).

If I continue, I think I'll probably use an oscillator circuit to precisely generate the 38kHz pulses and control the period with the Arduino - I believe that arrangement would get around the timing issues at these frequencies.
Last edited by Aqualung; May 06, 2011 at 11:48 PM. Reason: changed "anode" to "cathode" - added code blocks to improve readability.
May 02, 2011, 02:17 AM
Registered User
Hi Aqualung,

You seem to be close to having everything working.

I'll share a couple of thoughts that I had while reading your post.

Your routing of the receiver signal to two pins is clever. I had not thought of that. I typically use a more complicated approach.

The reason that your measured times are a multiple of four is that everything that is returned by micros() is a multiple of four. My guess that the timer behind the micros function only runs at 250 kHz. The good news is that I had complete success with measurements made at about 50 KHz so you should be fine.

As for driving the LED I think you can do what you need by diving under the hood of the arduino. Using a timer interrupt that triggers every 13 uS will provide a very stable time base. The only tricky part is that you really only have about 200 clock cycles (13 uS * 16 MHz) to do everything that you need. The fact that your measurement interrupts were able to keep up at this rate indicates this should work. Driving the LED is not a lot more complicated than the receiver measurements. One tip is to write directly to the port instead of using the digitalWrite function to save time. There should be loads of examples showing how to configure the arduino timers on the web if you choose to try this route.

One benefit of doing the timing on the microcontroller is that it makes it easier for others to use your code if you decide to share it.
May 02, 2011, 04:45 AM
Registered User
Hi,
good job! As my laptop (the docking station actually) still has a serial input I used a simple IR receiver (like this) to capture the output of the receiver. The lirc software under linux gives an output very easy to interprete: space XXX, pulse XXX, space XXX and so on.

Quote:
Originally Posted by RottenFishHead
IR Protocol

S 0YYYYYYY 0PPPPPPP CTTTTTTT E

S - Start Symbol
Y - 7 bit yaw field - increasing yaw value turns left
P - 7 bit pitch field - increasing pitch value pitches up
C - 1 bit channel field - 0 is channel A, 1 is channel B
T - 7 bit throttle field - increasing throttle value applies more throttle
E - End Symbol

Pulse timings in microseconds

ZERO - 256 High 341 Low
ONE - 277 High 725 Low
START - 1941 High 2048 Low
END - 277 High 46059 Low *
This is quite the same what I found out by using a simple frequency graph:

Haeder properties
Pulse 2090Ás
Space 1926Ás
Pulse length
0,1, foot: 397 (main peak)
Space length
0: 231Ás
1: 608Ás
Footer properties
Pulse: 397Ás (see above)
Space: as required to make a package lenth of 120500Ás

If you have a look on my software you will find a usb_factor which I applied to adjust the output of the USB transmitter circuit. It's very good to have a scope at hand...
To generate the carrier I had to use an external hardware as the FTDI USB2Serial chip I use is unable due to a design error which is only corrected in the newer high-speed versions. I use a simple 555 timer chip for that with an adjustable resistor and a scope to tune it to the exact frequency. I hope I will be able to generate a stable carrier with the newer chip that is already sitting on my desk somewhere...

I still have a SH6020 without gyro and a S107R5 tuning PCB. As my S107 transmitter is an older version I will need the info from this thread to make it work.
May 02, 2011, 09:26 AM
Registered User
Thanks for the additional insight, RottenFishHead: "The reason that your measured times are a multiple of four is that everything that is returned by micros() is a multiple of four."

So ... basically, you're suggesting that I should RTFM: "On 16 MHz Arduino boards (e.g. Duemilanove and Nano), this function has a resolution of four microseconds (i.e. the value returned is always a multiple of four)."

Doh!

On the other hand, you've talked me into pursuing things further ... I had hoped to avoid straying beyond the C-like code realm and staying away from the lower level tinkering, but sometimes you gotta do what you gotta do. Thirty years ago I did some similar stuff with an Intel 8051 and I was trying to convince myself that we've moved away from having to manually bit-bang directly on ports anymore.

LoL!

PS: Thanks to you and Darkstar for the tips and support.
May 02, 2011, 10:09 AM
Registered User
Quote:
Originally Posted by RottenFishHead
(snip) ... I notice a slight mixing between the pitch and yaw axes. The yaw value varies by about 8 counts across the range of the pitch stick where decreasing pitch value increased the yaw value. Also when the throttle is completely cut the transmitter sends five packets with a throttle value of zero and a pitch and yaw value of 63 independent of the position of the pitch/yaw stick. (snip)
I have some thoughts about the yaw component ... when the helicopter is first started up in a horizontal position, the blades and the flyweight (flywheel?) both spin at high-speed and function mechanically as a gyroscope - when tilted forward or back, this induces gyroscopic precession which tends to rotate the gyroscope (helicopter) in one direction or the other - the flyweight remains horizontal during this time and is used to produce a compensating change in angular momentum via the yaw offset as seen in the transmitted pulses. It would appear that this is programmed into the controller and is probably tweaked to different values depending on the model of helicopter that is being controlled - unfortunately, this also means that it will probably vary from one model of heli to another and make it more difficult to compare notes.

I probably didn't explain this very well ... more info can be found at http://en.wikipedia.org/wiki/Precession
May 02, 2011, 10:32 AM
Registered User
Quote:
Originally Posted by Aqualung
I have some thoughts about the yaw component ... when the helicopter is first started up in a horizontal position, the blades and the flyweight (flywheel?) both spin at high-speed and function mechanically as a gyroscope - when tilted forward or back, this induces gyroscopic precession which tends to rotate the gyroscope (helicopter) in one direction or the other - the flyweight remains horizontal during this time and is used to produce a compensating change in angular momentum via the yaw offset as seen in the transmitted pulses. It would appear that this is programmed into the controller and is probably tweaked to different values depending on the model of helicopter that is being controlled - unfortunately, this also means that it will probably vary from one model of heli to another and make it more difficult to compare notes.

I probably didn't explain this very well ... more info can be found at http://en.wikipedia.org/wiki/Precession
I don't think so:
A) The axis of the precession is 90░ to the rotation axis of the rotor and 90░ to the torque axis (pitch axis). Thus the pitch movement adds a rolling momentum to the helicopter, not yaw.

B) A yaw momentum should be compensated by the gyro, shouldn't it?

The yaw component probably comes from the cheap components and poor alignment with the housing: There is only one spring that move the stick back to the center position. That makes it very difficult to move the stick only in one axis. The more expensive transmitter always have two springs to center each axis separately.

Darkstar.
May 06, 2011, 11:05 PM
Registered User
Quote:
Originally Posted by Aqualung
On the other hand, you've talked me into pursuing things further ... I had hoped to avoid straying beyond the C-like code realm and staying away from the lower level tinkering, but sometimes you gotta do what you gotta do.
I spent most of the past few evenings reading (and re-reading) the ATmega328 tech sheet, the AVR reference, the Arduino reference, and doodling with different code designs. Using some sample timer code provided by Sebastian Wallin, I was able to generate a 13us heartbeat interrupt.

Within the ISR, I implemented a state machine that takes care of encoding and transmitting the four control values (yaw, pitch, throttle, and adjustment). A "busy" flag is set while transmitting the data and then cleared when entering the timing space between packets. This allows a simple routine in the main loop to know when it's safe to update new values that will be sent in the next packet. For convenience, one packet is sent every 200ms (5 per second). I also included an "idle" state so that the transmitter can be stopped / started in the main loop.

The four values are stored in a four-element byte array - these values can be changed at any time when the busy flag isn't set. I tested the throttle (only) by incrementing the throttle value from 0 to 127 and then decrementing it back to 0 again:

Arduino control for SYMA S107G helicopter II (0 min 41 sec)


I then tested the tail rotor by first decrementing the pitch value from 63 to 0 and then incrementing it to 127 and then decrementing it back to 63 again (the "center" point):

Arduino control for SYMA S107G helicopter IIa (0 min 58 sec)


Since I haven't done much of anything with the yaw, I thought I'd try cycling through its values. I first set the throttle to 15 and then decreased the yaw from 63 down to 0. Next I increased it to 127 and then back down to 63. Not sure what's actually happening here:

Arduino control for SYMA S107G helicopter IIb (1 min 3 sec)


I finally got brave enough to try actually flying the helicopter by incrementing the throttle by two's up to 80 and then back down again:

Arduino control for SYMA S107G helicopter III (0 min 23 sec)


I'm not sure what approaches have already been tried and trued, but the code that I developed seems to be robust and reliable as far as I've been able to test it. Note that this is specifically tweaked for an Arduino Uno running at 16MHz (your mileage may vary).

The code framework for this is below:

Code:
// S107G IR packet transmitter (ver 1.0)
// 7 MAY 2011 (Aqualung)

#define LED 8
#define STARTL 153
#define STARTH 153
#define MARKL 23
#define DATAC0 21
#define DATAC1 51
#define STOPL 23
#define SPACEH 15385
#define TCOUNT 92

volatile byte state,startH,startL,markL,stopL,dataC,countP,countR;
volatile unsigned int spaceH;
volatile bool busy;
// maskB[] contains bit mask values
const volatile byte maskB[] = {128,64,32,16,8,4,2,1};
// dataP[] has four 7-bit packet values
//   dataP[0] = yaw       63->0 / 64->127
//   dataP[1] = pitch     63->0 / 64->127
//   dataP[2] = throttle  0->127
//   dataP[3] = offset    63->0 / 64->127
volatile byte dataP[] = {63,63,0,63};

void setup()
{   
  pinMode(LED,OUTPUT);
  digitalWrite(LED,HIGH);
  Serial.begin(9600);
// Disable the timer2 overflow interrupt 
  TIMSK2 &= ~(1<<TOIE2);   
// Configure timer2 in normal mode
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));   
  TCCR2B &= ~(1<<WGM22);   
// Select internal clock source
  ASSR &= ~(1<<AS2);   
// Disable compMatchA interrupt
  TIMSK2 &= ~(1<<OCIE2A);   
// Disable pre-scaler (use system clock)
  TCCR2B |= (1<<CS20);
  TCCR2B &= ~((1<<CS22) | (1<<CS21));
// Set the initial values - all cycle count values must be odd 
  startL = STARTL;
  startH = STARTH;
  markL = MARKL;
  stopL = STOPL;
  spaceH = SPACEH;
//------------
  countP = 4;
  countR = 8;
  state = 1;
  busy = true;
  Serial.println("Starting ... 5 second delay");
  delay(5000);
// Load timer counter and enable the timer
  TCNT2 = TCOUNT;   
  TIMSK2 |= (1<<TOIE2);   
}   
  
void loop()
{
  if(!busy)
  {
    // --- approximately 165ms is available ---
    // set dataP[0] to new yaw value
    // dataP[0] = 77;
    // set dataP[1] to new pitch value
    // dataP[1] += 3;
    // set dataP[2] to new throttle value
    // dataP[2]--;
    // set dataP[3] to new adjustment value
    // dataP[3] = dataP[2] / 32;
    // make sure to set the busy flag to avoid the
    // next pass through the loop
    busy = true;
  }
}

ISR(TIMER2_OVF_vect)
{
  TCNT2 = TCOUNT;
  switch(state)
  {
    case 0: // idle state
    spaceH = SPACEH;
    break;

    case 1: // startL (low)
    spaceH--;
    if(startL--)
    {
      if(startL & 1)
      {
        digitalWrite(LED,HIGH);
      }
      else
      {
        digitalWrite(LED,LOW);
      }
    }
    else
    {
      digitalWrite(LED,HIGH);
      startL = STARTL;
      state = 2;
    }
    break;

    case 2: // startH (high)
    spaceH--;
    if(!startH--)
    {
      startH = STARTH;
      countP = 4;
      state = 3;
    }
    break;

    case 3: // markL (low)
    spaceH--;
    if(markL--)
    {
      if(markL & 1)
      {
        digitalWrite(LED,HIGH);
      }
      else
      {
        digitalWrite(LED,LOW);
      }
    }
    else
    {
      digitalWrite(LED,HIGH);
      if(dataP[4-countP] & maskB[8-(countR--)])
      {
        dataC = DATAC1;
      }
      else
      {
        dataC = DATAC0;
      }
      markL = MARKL;
      state = 4;
    }
    break;

    case 4: // dataH (high)
    spaceH--;
    if(!dataC--)
    {
      if(!countR)
      {
        countR = 8;
        countP--;
      }
      if(countP)
      {
        state = 3;
      }
      else
      {
        state = 5;
      }
    }
    break;

    case 5: // stopL (low)
    spaceH--;
    if(stopL--)
    {
      if(stopL & 1)
      {
        digitalWrite(LED,HIGH);
      }
      else
      {
        digitalWrite(LED,LOW);
      }
    }
    else
    {
      digitalWrite(LED,HIGH);
      stopL = STOPL;
      busy = false;
      state = 6;
    }
    break;

    case 6: // spaceH (high)
    if(!spaceH--)
    {
      spaceH = SPACEH;
      busy = true;
      state = 1;
    }
    break;
  }
}
Feel free to pick apart my code; however, please note that I don't adhere to K&R programming practices
Last edited by Aqualung; May 08, 2011 at 03:14 AM. Reason: Updated code (replaced constants with #defines and reversed some logic to eliminate null routines)
May 09, 2011, 10:13 PM
Registered User
Hi!
I just got my S107 flying with the arduino uno board, and created an Arduino and Processing program to control it (and also to track it with the webcam and make it hover!)

I decoded the protocol, it seems to be a 32 bit pulse with the following format:
Byte 1: Yaw
Byte 2: Pitch
Byte 3: Throttle
Byte 4: Yaw correction (little knob in the remote)

They all use the last 7 bits of the byte.
Throttle goes from 0 to 126, while pitch and yaw start centered in 63.

The details can and source code be found here!
May 09, 2011, 11:54 PM
Registered User
Nice work, avergottini

I like how you allow for the serial control of the bits using the ASCII codes -- do I understand that you can update one or many as needed? So you don't need to send everything, just those that change?

I had convinced myself that it should be reasonably easy to have the helicopter pre-programmed for a simple flight path, but as Darkstar pointed out in another thread elsewhere, there are too many random things transpiring to do so without some sort of feedback loop to let you know what's going on.

I've uploaded a "bloopers & outtakes" video which shows the result of running the same packet sequences under various battery levels - the heli is supposed to take off, move forward, turn 180 degrees, move forward again, turn 180 degrees, and then land - the results as shown were not quite what I had in mind

Arduino control for SYMA S107G helicopter - Bloopers & Outtakes (1 min 8 sec)


Needless to say, I am quite impressed with the quality and durability of these S107s!
May 10, 2011, 12:00 AM
Registered User
Avergottini-Excellent and thanks for sharing. I've been wanting to play around with an IR project in line with what you've done...but just haven't set aside the time. Bookmarked your site to do a deeper read over the coming week. Thx again!

Edit: I just saw Aqualung's blooper video...this makes me rethink my approach in that why do I need a controller to do the work when that's how my flying already looks LOL.


Thread Tools

Similar Threads
Category Thread Thread Starter Forum Replies Last Post
Discussion Coaxial IR protocol (SH, Syma, etc...) Darkstar2000 Coaxial Helicopters 248 Aug 16, 2014 05:35 AM
Discussion S107 IR Protocol and Schematic campeck Coaxial Helicopters 2 Dec 28, 2010 06:32 PM
Discussion Syma s107 swaying Pitnut Micro Helis 2 Sep 16, 2010 08:31 AM
Question Syma s107 swaying Pitnut Coaxial Helicopters 1 Sep 01, 2010 05:43 PM
Discussion Syma s107 sway Pitnut Coaxial Helicopters 1 Sep 01, 2010 04:28 AM