IR Remote (Syma s026) dedicated board


Syma s026 Remote IR protocol decryptor

I’ve talked in at least 1 other post about how to use the Infra Red remote control of a toy helicopter (Syma s026) for other projects : once the protocol reverse engineered, it’s very easy to program a micro controller to listen to it and I find it very useful to integrate this into any project that needs some wireless remote controlling.

It’s also quite cheap, all you need is an IR receiver and the microcontroller that you alreay use in the given project…

BUT, the more I used it the more I hit one problem : it uses far too much of the MCU ressources, because the remote keeps sending 29 bytes signals all the time and you have to try to intercep them all to check if anything has changed in the commands sent !

Even after migrating to the FEZ Domino board which is much more powerful, everything is ok, until you need to add other threads or interrups to your program, and then, “all of a sudden” the IR command routine does NOT work anymore !! Very frustrating as you have to come back to a problem that you though was solved… it turns out it’s simply the more work you add to your MCU the more chances that the critical timing parts of the IR remote protocol routine are going to be measured wrong and the remote signals ignored…

So, there’s 1 obvious solution : use a stand alone MCU that does only 1 job but does it well : listen for commands from the IR Remote and as soon as it has received one, send it by serial link to the master MCU.

This way, the master MCU is free to do its job, and especially doesn’t have to be carefull about timing issues, etc. …

Here’s my take at solving this, using an ATMega328. I would have loved to be able to use an ATTiny13A, one of my preferred MCUs given its size, but it had no serial capability (hardware that is, now I think more and more that maybe I could have used a software serial library ?) .

I initially wanted to use the I2C (TWI) protocol, but gave up as the master can’t do a blocking read or wait on an interrupt from the slave, and I didn’t want it to spend too much time checking for new data on the slave (and for the slave responding).

From left to right:

  1. ISP programmer
    1. I was initially using AVRStudio and gcc to program it
    2. but I hit issues when trying to find an “easy to use” UART or I2C library
    3. so ended up using the Arduino IDE, from which I can program this using the Pololu Orangutan USB Programmer (great product !)
  2. 28 pins socket for the ATMega328 (or 168)
  3. Serial plug, compatible with the TTL 232R USB cable from FTDI
  4. Big blue socket : I2C socket – unused for now
  5. Smaller blue socket : socket for the IR Receiver (TSOP2438 from Vishay)
  6. I was initially using the internal oscillator at 8MHz (dividen internally by 8 for a 1MHz CPU freq) but ended up adding an “funky” external one at 11.0592MHz) – no capacitors used as I couldn’t be bothered 🙂

Here’s a picture of the ISP programmer used (both with the  Arduino IDE and directly with AVR Studio):

Pololu Orangutan USB Programmer - PGM02B - 2008

Configuration for Arduino IDE:

boards.txt

dan1.name=ATmega328 with Orangutan Pololu Programmer
dan1.upload.maximum_size=30768
dan1.build.mcu=atmega328p
dan1.build.f_cpu=11059200L
dan1.build.core=arduino
dan1.upload.using=avrispv2

programmers.txt

avrispv2.name=AVR ISP v2
avrispv2.communication=serial
avrispv2.protocol=avrispv2

Arduino code :

#define F_CPU 11059200
const double TICKS_US_FACT = 11.059200;  // F_CPU / 1 000 000
const long PULSE_TIMEOUT = 10000 * TICKS_US_FACT; // microSecs * ticks per microSec

const byte LED_PIN = 13;
const byte IR_PIN_SYMA = 8;
const byte SIGNAL_SIZE = 26; // MAX is 29
const byte SIGNAL_SPEED = 7;//from 0 to here
const byte SIGNAL_DIR = 13;//from end of SignalSpeed to here
const byte SIGNAL_BUTTONS = 17;//from end of SignalDir to here
const byte SIGNAL_VERTICAL = 21;//from end of SignalButtons to here
const byte SIGNAL_TRIMMER = 26;//from end of SignalVertical to here
const long PULSE_START_MIN = 1500 * TICKS_US_FACT; // microSecs * ticks per microSec
const long PULSE_START_MAX = 2000 * TICKS_US_FACT;
const long PULSE_0_MIN = 400 * TICKS_US_FACT;
const long PULSE_0_MAX = 650 * TICKS_US_FACT;
const long PULSE_1_MIN = 800 * TICKS_US_FACT;
const long PULSE_1_MAX = 1050 * TICKS_US_FACT;

const byte VALUE_LEFT_BUTTON = 22;
const byte VALUE_RIGHT_BUTTON = 26;

void setup(){
 Serial.begin(115200);

 pinMode(IR_PIN_SYMA, INPUT);
 pinMode(LED_PIN, OUTPUT);
 digitalWrite(LED_PIN, LOW);

 setTimingConf();
}

int _pulseCount = -1;
byte _bit = 0;
byte _bits[SIGNAL_SIZE];
long _pulse;

void loop(){
 _pulse = getLowPulse(IR_PIN_SYMA);

 if (_pulse > PULSE_START_MIN && _pulse < PULSE_START_MAX){
 //start counting pulses
 _pulseCount = 0;
 }else if (_pulseCount >= 0)
 {   //continue counting pulses
 _bit = getBit(_pulse);

 // if invalid bit, stop here
 if(_bit > 1){
 _pulseCount = -1;
 }else{
 _bits[_pulseCount] = _bit;

 // good, wait for next bit
 _pulseCount++;

 // FINAL, we've read what we wanted let our creator know !
 if (_pulseCount == SIGNAL_SIZE) {
 sendSerial();

 // end of this command, so reInit so that we wait for another start
 _pulseCount = -1;
 }
 }
 }
}

byte i = 0;
byte _speed, _dir, _buttons, _vertical, _trimmer;
byte _speedPrev, _dirPrev, _buttonsPrev, _verticalPrev, _trimmerPrev;

void sendSerial(){
 digitalWrite(LED_PIN, HIGH);
 _speed = 0; _dir = 0; _buttons = 0; _vertical = 0; _trimmer = 0;
 for(i=0; i<SIGNAL_SIZE; i++){
 if (_bits[i] == 1){
 if (i < SIGNAL_SPEED) _speed += (1 << (SIGNAL_SPEED - i));
 else if (i < SIGNAL_DIR) _dir += (1 << SIGNAL_DIR - i);
 else if (i < SIGNAL_BUTTONS) _buttons += (1 << SIGNAL_BUTTONS - i);
 else if (i < SIGNAL_VERTICAL) _vertical += (1 << SIGNAL_VERTICAL - i);
 else if (i < SIGNAL_TRIMMER) _trimmer += (1 << SIGNAL_TRIMMER - i);
 }
 }

 if(_buttons == VALUE_LEFT_BUTTON) _buttons = 1;
 else if (_buttons == VALUE_RIGHT_BUTTON) _buttons = 2;
 else _buttons = 0;

 if((_speed != _speedPrev) || (_dir != _dirPrev) || (_buttons != _buttonsPrev) || (_vertical != _verticalPrev) || (_trimmer != _trimmerPrev)){
 _speedPrev = _speed; _dirPrev = _dir; _buttonsPrev = _buttons; _verticalPrev = _vertical; _trimmerPrev = _trimmer;

//        Serial.print(_speed, DEC);
//        Serial.print(" - ");
//        Serial.print(_dir, DEC);
//        Serial.print(" - ");
//        Serial.print(_buttons, DEC);
//        Serial.print(" - ");
//        Serial.print(_vertical, DEC);
//        Serial.print(" - ");
//        Serial.print(_trimmer, DEC);
//        Serial.println();

 Serial.write(_speed);Serial.write(_dir);Serial.write(_vertical);Serial.write(_trimmer);Serial.write(_buttons);
 }
 digitalWrite(LED_PIN, LOW);
}

static byte getBit(int pulse){
 if(pulse > PULSE_0_MIN && pulse < PULSE_0_MAX) return 0;
 if(pulse > PULSE_1_MIN && pulse < PULSE_1_MAX) return 1;
 return 111; // BAAAD
}

/* TIMING STUFF*/
#define TIMER_COUNT TCNT1

long getLowPulse(byte pin){
 TIMER_COUNT = 0; // timer reset
 // wait until it gets LOW or timesout
 while(digitalRead(pin) == HIGH && TIMER_COUNT < PULSE_TIMEOUT) {}

 // time out
 if(TIMER_COUNT >= PULSE_TIMEOUT) return -1;

 // start conting the pulse
 TIMER_COUNT = 0; // timer reset
 while(digitalRead(pin) == LOW && TIMER_COUNT < PULSE_TIMEOUT) {}

 // pulse too long
 if(TIMER_COUNT >= PULSE_TIMEOUT) return -2;

 return TIMER_COUNT; // counts in Ticks
}

void setTimingConf(){
 // TIMER 1  Pins 9 & 10
 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
 TCCR1B = 1<<CS10;   // NO prescaler count at the frequency of the proc FREQ_CPU
 // 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
 TIMSK1 = 1<<TOIE1;   // TOIE1=0 =Timer 1 Overflow Interrupt Enable
}

Leave a comment