IR Remote (Syma s026) dedicated board – V2 – AtTiny45


IRcmdS026_AtTiny45_Final_Side1

Right, the previous post was about creating a dedicated board that can do 2 things:

  1. read and decrypt the IR commands sent by the Syma s026 remote control
  2. send them via a serial link (to the “central” MCU or elsewhere)

Now it kept bothering me that, for such a simple task, I had to use as big an MCU as AtMega328. I love the small AtTiny13 but it has no serial port, and not enough memory to implement my own software serial protocol…

However, after moult research and playing around, I realised that “serial” does NOT mean “USART” and that, especially if we need it only 1 way, implementing your own software protocol is REALLY easy… it turns out, my IR Beacon project is doing exactly this, but over IR… it should be only simpler to do it over a wire !

So here it is, I start my project with the small and nice AtTiny13A, but will later replace it with AtTiny45V, simply because I’m using interrupts and this requires slightly more Flash memory (but I’m sure the code could be optimized to fit on to an attiny13).

TIMING is really the main lait-motif of this project and hence I use extensively one of the 2 timers on the tiny45:

  1. to time the incoming IR codes
  2. to time the outgoing serial pulses

Some useful stuff from the AtTiny45V docs :

AtTiny45V_TCCR0B

AtTiny45V_TCCR0B_Prescaler

More photos of the final product :

IRcmdS026_AtTiny45_Breadboard

IRcmdS026_AtTiny45_Final_Top

IRcmdS026_AtTiny45_Final_Side2

IRcmdS026_AtTiny45_Final_Back

The C code going on the AtTiny45 (compile using AVR Studio with g++) :

//#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#define LED_PIN PB0
#define SERIAL_PIN PB3
#define IR_PIN PB4
#define PORT PORTB
#define DDRD DRB
#define PIN PINB
#define TIMER_COUNT TCNT0
// SERIAL SIGNALS
#define BYTE_SIZE = 8
const unsigned long T0 = 500;        	// in microseconds
const unsigned long T1 = T0 * 3;
const unsigned long START = T0 * 8;
const unsigned long BETWEEN_BITS = T0;

// IR SIGNALS
#define SIGNAL_SIZE 26 // MAX is 29
#define SIGNAL_SPEED 7//from 0 to here
#define SIGNAL_DIR 13//from end of SignalSpeed to here
#define SIGNAL_BUTTONS 17//from end of SignalDir to here
#define SIGNAL_VERTICAL 21//from end of SignalButtons to here
#define SIGNAL_TRIMMER 26//from end of SignalVertical to here

#define PULSE_START_MIN 1500 // in Micro Seconds
#define PULSE_START_MAX 2000
#define PULSE_0_MIN 300
#define PULSE_0_MAX 750
#define PULSE_1_MIN 750
#define PULSE_1_MAX 1200

#define VALUE_LEFT_BUTTON 22
#define VALUE_RIGHT_BUTTON 26

void ledON(){
PORT |= _BV(LED_PIN); // turn on the LED
}

void ledOFF(){
PORT &= ~_BV(LED_PIN); // turn off the LED
}

/* TIMING STUFF */

void timerConf(){
TCCR0A = 0x00;
TCCR0B = 1 << CS01;  // prescaler 8. IF F_CPU is 8MHz then counting directly in uSecs
TIMSK = 1 << TOIE0; //Overflow Interrupt Enabled

//Enable Global Interrupts
sei();
}

// don't forget the VOLATILE, otherwise it will be seen as 0 from the outside !!!
volatile unsigned long _timerOverflow;
ISR(TIMER0_OVF_vect){
_timerOverflow++;
}

void resetTimer(){
TIMER_COUNT = 0;
_timerOverflow = 0;
}

unsigned long getTimer(){
return _timerOverflow * 256 + TIMER_COUNT;
}

void delay_us(unsigned long microSecs){
resetTimer();
while(getTimer() < microSecs) {};
}

/* TIMING STUFF - END */

void serialSendStart(){
PORT |= _BV(SERIAL_PIN);   // high
delay_us(START);
PORT &= ~_BV(SERIAL_PIN);  // low
delay_us(BETWEEN_BITS);
}

void serialSendBits(unsigned char data){
for(unsigned char i = 0; i < 8; i++){
PORT |= _BV(SERIAL_PIN); // high
if(data & 0x01) delay_us(T1);
else delay_us(T0);
PORT &= ~_BV(SERIAL_PIN);  // low
delay_us(BETWEEN_BITS);
data >>= 1;  // get the next most significant bit
}

}

unsigned char irState(){
return PIN & _BV(IR_PIN); // get the value of the IR PIN, 0 or 1
}

static unsigned char 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
}

unsigned long _currTimer;
int getIRLowPulse(unsigned int timeoutMicroSecs){
resetTimer();

// wait until it gets LOW or timesout
_currTimer = getTimer();
while(irState() && _currTimer < timeoutMicroSecs) {
_currTimer = getTimer();
}

// time out
if(_currTimer >= timeoutMicroSecs) return -1;

// start conting the pulse
resetTimer();
_currTimer = getTimer();

while(! irState() && _currTimer < timeoutMicroSecs) {
_currTimer = getTimer();
}

// pulse too long
if(_currTimer >= timeoutMicroSecs) return -2;

return _currTimer;
}

int _pulseCount = -1;
unsigned char _bit = 0;
unsigned char _bits[SIGNAL_SIZE];
int _pulse;
unsigned char i = 0;
unsigned char _speed, _dir, _buttons, _vertical, _trimmer;
unsigned char _speedPrev, _dirPrev, _buttonsPrev, _verticalPrev, _trimmerPrev;

void send(){
ledON();

_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;

serialSendStart();

serialSendBits(_speed);
serialSendBits(_dir);
serialSendBits(_vertical);
serialSendBits(_trimmer);
serialSendBits(_buttons);
}

ledOFF();
}

void init(){
timerConf();

// make both LED and SERIAL pins outputs
DDR |= _BV(LED_PIN) | _BV(SERIAL_PIN);
// IR_LED is an input
DDR &= ~_BV(IR_PIN);
// SERIAL PORT start LOW
PORT &= ~_BV(SERIAL_PIN);
}

int main(void){
init();

while(1){
_pulse = getIRLowPulse(10000);
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 caller know !

if (_pulseCount == SIGNAL_SIZE) {
send();
// end of this command, so reInit so that we wait for another start
_pulseCount = -1;
}
}
}
}

return 0;
}

14 Responses to IR Remote (Syma s026) dedicated board – V2 – AtTiny45

  1. Maks says:

    Thanks for this article!

    Is there a simple way to convert this to Arduino ?
    I would like to make a tiny receiver that uses ATTINY 45 and Syma transmitter to control it.

  2. Jan Palounek says:

    Its not public yet 🙂 (its about remote controll signals recognition), I’ll write a blogpost about it and I’ll put here a link.

  3. Jan Palounek says:

    Where I can get a circuit? (I should know how its connected together) Its not placed there… or I dont see it?

    • trandi says:

      I’m not sure I understand your question..
      This is a circuit that I built by myself from scratch. It’s NOT based on any schematic that you could find somewhere, I have simply put together the MCU with 2 IR sensors (and a LED and it’s resistor simply to get some visual feedback).
      I have not taken the time to make a diagram, you should be able to figure out the connections from the code.

      Dan

      • Jan Palounek says:

        Connections are exactly what I need. Tell me please which pins are connected to what. I know I can extract it from code, but if you have it in mind you will save me a time.

        Thanks

      • trandi says:

        If you look at the beginning of the code you’ll see:

        #define LED_PIN PB0 #define SERIAL_PIN PB3 #define IR_PIN PB4

        Which means : – connect the LED to the PB0 pin (through a 3-500Ohms resistor in series) – the output serial pin is PB3 – connect BOTH IR sensors in PARALLEL to PB4 (their “signal” pin, and obviously their GND and VCC to the corresponding MCU pins)

        And that’s it, it’s really simple as you can see.

        Could you please post a link or give more explanations about what project you need this for ? I’d be curious to find out more…

        Dan

  4. Pingback: ATtiny Hacks: Infrared guidance and navigation « FOOTBALL, SEX & ALCOHOL

  5. Pingback: ATtiny Hacks: Infrared guidance and navigation - Hack a Day

  6. Pingback: Quadcopter – home made « Robotics / Electronics / Physical Computing

  7. Pingback: Lego NXT – Arduino Truck « Robotics / Electronics / Physical Computing

  8. tibi says:

    Just found your post and it’s great ! Simple enough for an intermediate-level guy like me, great idea and great explanations !

    Have you tried using a PIC MCU rather than an AVR ?

  9. visitor says:

    Very detailed explanation and great idea !
    Thanks for sharing !

Leave a comment