Arduino – 3chanel IR remote control from mini helicopter – processing
September 20, 2009 6 Comments
The other day I got this from my “local” toys store (Hamleys, on Regent Street in London):
After some frustration with controlling it (yes, even these supposedly “simple” ones are still not so obvious to master…) I started to be more and more intrigued by its remote control:
It’s a 3 channels, 3 selectable frequencies IR one.
I’ve been planning for soooo long to add some sort of remote controlling abilities to my rover (see the corresponding post here) and the “TV remote control” type as I did for my LEGO truck was pretty frustrating : you had to keep a button pressed to increase the speed, etc. … Now I want something progressive and smooth, like any “proper” RC toy… like the remote for this new mini helicopter…
Step 1: IR receiver compatibility
See if the IR receiver module that I have (used for the other projects, the most basic one that you can find, 38kHz bought from Radioshack while I was still in the US…) does manage to receive the IR signals from the new remote (frequencies close enough)
– YES, lucky me…
I used my Freeduino and this program that I found onthe Arduino website and I was getting nice signals back …
#define TIMER_RESET TCNT1 = 0 #define SAMPLE_SIZE 80 #define SIGNAL_SIZE 28 int IRpin = 7; unsigned int TimerValue[SAMPLE_SIZE]; unsigned int SignalValue[SIGNAL_SIZE]; char direction[SAMPLE_SIZE]; byte change_count; long time; void setup() { Serial.begin(115200); Serial.println("Analyze IR Remote"); TCCR1A = 0x00; // COM1A1=0, COM1A0=0 =Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 = PWM Operation disabled // ICNC1=0 = Capture Noise Canceler disabled -- ICES1=0 =Input Capture Edge Select (not used) -- CTC1=0 =Clear Timer/Counter 1 on Compare/Match // CS12=0 CS11=1 CS10=1 =Set prescaler to clock/64 TCCR1B = 0x03; // 16MHz clock with prescaler means TCNT1 increments every 4uS // ICIE1=0 = Timer/Counter 1, Input Capture Interrupt Enable -- OCIE1A=0 =Output Compare A Match Interrupt Enable -- OCIE1B=0 =Output Compare B Match Interrupt Enable // TOIE1=0 =Timer 1 Overflow Interrupt Enable TIMSK1 = 0x00; pinMode(IRpin, INPUT); } void loop() { Serial.println("Waiting..."); change_count = 0; while(digitalRead(IRpin) == HIGH) {} TIMER_RESET; TimerValue[change_count] = TCNT1; direction[change_count++] = '0'; while (change_count < SAMPLE_SIZE) { if (direction[change_count-1] == '0') { while(digitalRead(IRpin) == LOW) {} TimerValue[change_count] = TCNT1; direction[change_count++] = '1'; } else { while(digitalRead(IRpin) == HIGH) {} TimerValue[change_count] = TCNT1; direction[change_count++] = '0'; } } Serial.println("Bit stream detected!"); change_count = 1; time = (long) TimerValue[change_count] * 4; Serial.print(time); Serial.print("\t"); Serial.println(direction[change_count++]); while (change_count < SAMPLE_SIZE) { if(direction[change_count] == '1'){ time = (TimerValue[change_count] - TimerValue[change_count-1]) * 4; Serial.println(time); } change_count ++; } Serial.println("Bit stream end!"); delay(2000); }
Step 2: What to do with those signals…
The most complex part, it took me almost an entire Sunday…
Try to do some graphs (thanks Gnuplot, much better than Excel !) and figure out what the used protocol is.
So now, from this and the raw data I know that:
1) there are 300μs HIGH pulses that separate the LOW ones, of variable length -> the low ones must encode the data, the other are just separators
2) I found 3 different lengths of LOW pulses, and here are their meaning (from the data and my “feeling”):
– around 500μs = binary 0
– around 900μs = binary 1
– around 1600μs = start of encoding
3) The pulses come in groups of 29 (in between the 1600μs ones, that serve as delimiters)
Soo good news for now, it’s digital, at some point I was afraid it was analogic, much trickier to dechipher…
Interesting note: when you point the remote not directly to the receiver but to a wall or something, the length of the pulses becomes longer.
I initially thought about something like the Doppler effect, but that would be if I was to run away very quickly from the receiver…:) so I think it’s simply some interference or noise…
So make the range for 0 and 1 quite large. For example, I initially used 450 – 550μs which was enough if pointing the remote directly at the receiver, but I had to modify it to 400-600μs to be really usable…
Step 3 : This isn’t enough
I’m getting there, but plotting this pulses is not enough I need something more dynamic, to see how they evolve in response to me moving the sticks on the remote.
Here comes Processing and the following sketch:
import processing.serial.*; Serial myPort; int[] mPulses = new int[29]; static int PULSE_WIDTH = 30; void setup() { size(1000, 600); // List all the available serial ports: println(Serial.list()); String portName = Serial.list()[1]; myPort = new Serial(this, portName, 115200); } void draw() { background(0); stroke(255); for(int i=0; i<29; i++){ rect(i*(PULSE_WIDTH + 3), 0, PULSE_WIDTH, mPulses[i] - 400); } } void serialEvent(Serial myPort) { String oneLine = myPort.readStringUntil('\n'); if(oneLine != null){ String[] lineElements = oneLine.split("\t"); //println(lineElements); if(lineElements.length == 30){ for(int i=0; i<29; i++){ mPulses[i] = Integer.valueOf(lineElements[i]); } println("OK " + asInt(mPulses, 0, 6) + " - " + asInt(mPulses, 7, 12)); } } } private int asInt(int[] pulses, int start, int end){ StringBuilder strB = new StringBuilder(); for(int i=start; i<end; i++){ strB.append(getBit(pulses[i])); } return Integer.parseInt(strB.toString(), 2); } private int getBit(int pulse){ if(pulse > 450 && pulse < 550) return 0; if(pulse > 850 && pulse < 950) return 1; return -1; }
Now at the other end of the serial cable I need Arduino to provide real time data, so I can see the signal diagram evolving.
However I’m not very happy with the code previously used (too complex for me, I still don’t fully understand what TCCR1A, TCCR1B, TIMSK1 and all these “obscure”, timer related parameters are) so here comes the my simplified version (oh so much more elegant…):
const int IRpin = 7; #define SIGNAL_SIZE 29 unsigned int SignalValues[SIGNAL_SIZE]; void setup(){ Serial.begin(115200); pinMode(IRpin, INPUT); } void loop(){ while(pulseIn(IRpin, LOW) < 1600){ } for(int i=0; i < SIGNAL_SIZE; i++){ SignalValues[i]=pulseIn(IRpin, LOW); } unsigned int last = pulseIn(IRpin, LOW); if(last < 1600){ Serial.print(last); Serial.println(" PROBLEM"); }else{ //Serial.println("New Data:"); for(int i=0; i < SIGNAL_SIZE; i++){ Serial.print(SignalValues[i]); Serial.print("\t"); } Serial.println(); } }
And here are the results (these bars were moving in real time):
So now, while seeing the bars (shortone means a 0, long one 1) moving while I play with the sticks on the remonte I infer the following:
1) the first 7 bars ONLY move when I move the gas
2) the next 6 bars ONLY move when I move the left-right stick
3) it’s a little bit trickier for the forward-backward stick, it seems to move more than the last ones, and I don’t get why still 16 remaining… too much information… but for now, the 7 + 6 bits seems to be pretty accurate, so I will be able to use 2 of the channels at least.
Here is the console output of the Processing program:
Stable Library ========================================= Native lib Version = RXTX-2.1-7 Java lib Version = RXTX-2.1-7 [0] "COM3" [1] "COM5" OK 7 - 16 OK 17 - 16 OK 21 - 16 OK 22 - 16 OK 19 - 16 OK 30 - 16 OK 15 - 16 OK 0 - 16 OK 0 - 16 OK 6 - 16 OK 7 - 16 OK 10 - 16 OK 19 - 16 OK 4 - 16 OK 0 - 16 OK 11 - 16 OK 15 - 16 OK 14 - 16 OK 14 - 16 OK 0 - 16 OK 0 - 16 OK 18 - 16
Which confirms that my “theories”, and shows that the GAS is variable between 0 and 55 and the left-right from 0 to 31 (16 in the middle).
Now I’ll have to integrate this with the Rover… to be followed in that post…
Hi. I realize this is an old post but wondering if you can help me. I’m working w/ my son to build a simple auto-pilot to control this exact helicopter (well, the one I ordered has a gyroscope). In the body of the copter we’ll add an arduino Nano, tilt-compensated compass, ultrasonic distance sensor, and an IR transmitter (all powered by a very small 12v battery) — I’m thinking w/ two motors it should be able to lift all of this.
I’ve found several posts that have code for the Syma S017G copter (http://www.jimhung.co.uk/?p=1138). Do you know if the IR code is the same for this model? Do you still have the arduino compatible code for this model?
Thanks for any help/advice!!
No worries, I’m glad if I can help.
I have no idea regarding if the IR code is the same or not, but if you want the code for my model, have a look at these 2 later posts:
– https://trandi.wordpress.com/2010/06/13/ir-remote-syma-s026-atmega328-serial/ this will give you the Arduino code
– https://trandi.wordpress.com/2010/06/19/ir-remote-syma-s026-dedicated-board-v2/ this is an optimized version of the previous board, that uses a smaller AtTiny controller and hence gives you the same code but in C directly used by AVR Studio
You can use either, and I found them working quite reliably.
Dan
Pingback: ATtiny Hacks: Infrared guidance and navigation « FOOTBALL, SEX & ALCOHOL
Pingback: ATtiny Hacks: Infrared guidance and navigation | You've been blogged!
Pingback: ATtiny Hacks: Infrared guidance and navigation - Hack a Day
Pingback: IR Remote (Syma s026) dedicated board « Robotics / Electronics / Physical Computing