geektimes

Реле с дистанционным ИК управлением на ATtiny13a

  • пятница, 28 ноября 2014 г. в 02:10:37
http://habrahabr.ru/post/244349/

Привет, Хабр!

Появилась необходимость выключения старой, но вполне рабочей акустической системы с пульта от телевизора, не вставая с дивана. Подумав, я решил использовать ИК приёмник, некогда выкрученный со старого телевизора. ИК приёмник оказался без опознавательных знаков. Определив выходы методом тыка выяснил, что он из серии TSOP4xxx, если верить картинке:



Погуглив и потренировавшись на Arduino UNO, используя этот код и удостоверившись в работоспособности датчика, я перешёл к переписыванию кода на ATtiny13. Перейдя к ней, понял, что очень сильно ограничен ресурсах, как во флеше, так и в оперативной памяти. По началу с трудом оптимизировал по размеру прошивки, контроллер все еще не работал, а когда понял, что памяти в коде используется намного больше 64 байт, пришлось конкретно взяться за оптимизацию. В итоге с горем пополам оптимизировал код и собрал прототип на макетке. Радовался, как ребенок! Оно мигало лампочками так, как мне надо.

Общий вид:
Макетка


Схема:
Спойлер


Пришло время переводить всю макетку в текстолит. Плату изготовлял методом ЛУТ. Первый блин, как говорится, комом. Сделав первую схему, напечатав и собрав, понял, что ничего не работает. Я неправильно подключил LM317T. К тому же, поломав пятаки и оторвав некоторые слишком тонкие дорожки, решил сделать вторую плату. В ней сделал дорожки 0,7мм и, увеличив пятаки, кое как справился с частью проблем. Тут тоже не обошлось без проблем, так как опять неправильно подключил LM317T, да еще и в прошлой версии платы сжег приёмник, подав на него 12В.

Кстати, питается это дело от 12В (был у меня маломощный трансформатор, вот его и задействовал). Выбор напряжения был обусловлен так же имеющимся в наличии реле на 12В. Для понижения напряжения для микроконтроллера до 5В используется стабилизатор LM317T, а для управления реле используется имеющийся под рукой npn транзистор КТ819.

Окончательная плата в SprintLayout:
Спойлер


Используемые детали:
  • Микроконтроллер ATtiny13A;
  • Резисторы номиналом 470, 1300, 2x330 и 90 Ом;
  • Транзистор КТ819;
  • Стабилизатор LM317T;
  • 2 светодиода красный и зеленый;
  • Приёмник серии TSOP4XXX или совместимый;
  • 2 конденсатора для фильтрации по питанию примерно на 200-220мкФ.


Что касается кода.

Исходный вариант был очень «увесистый» и на ATtiny13 ни как не мог работать. Необходимо было избавиться от тяжелого двумерного массива. Код был очень странным: записывались так же «низкие» импульсы, но они никак не использовались. В общем, выкинул двумерный массив и этим освободил как минимум 64 байта оперативной памяти. Высчитывал сигналы на лету, но этого было мало и после добавления функционала таймера пришлось максимально урезать переменные.

Код для Arduino IDE
#define IRpin_PIN PINB
#define IRpin 2

#define rLedPin 3
#define gLedPin 4
#define relayPin 1

#define MAXPULSE 5000
#define NUMPULSES 32
#define RESOLUTION 2

#define timeN1 1800000
#define timeN2 3600000

#define timerInterval 500


bool relayState = false;
unsigned long timer = 0;
unsigned long shift = timeN1;//30 min timer by default
unsigned long previousMillis = 0;
bool timerN = false;
byte i = 0;


void setup() {
  //default states
  DDRB |= (1<<relayPin);
  DDRB |= (1<<rLedPin);
  DDRB |= (1<<gLedPin);

  PORTB &= ~(1<<relayPin);//relay off
  PORTB &= ~(1<<rLedPin);//red led off
  PORTB |=  (1<<gLedPin);//green led on

  /*
  //for debug
  Serial.begin(9600);
  Serial.println("Start | "+String(millis()));
  //*/

  /*
  //for debug without ir receiver
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  //*/


}

void shutDown(){
  relayState = true;
  PORTB |=  (1<<relayPin);
  PORTB &= ~(1<<gLedPin);
  PORTB |=  (1<<rLedPin);
  //Serial.println("turining off |"+String(millis()));
}

void startUp(){
  relayState = false;
  PORTB &= ~(1<<relayPin);
  PORTB |=  (1<<gLedPin);
  PORTB &= ~(1<<rLedPin);
  //Serial.println("turining on |"+String(millis()));
}

void loop() {
  unsigned long irCode = listenForIR(); // Wait for an IR Code
  //Serial.println("ir code: "+String(irCode));
  if(irCode == 3359105948){//green button
    //Serial.println("Pressed green btn |"+String(millis()));
    if(timer == 0){//on off mode
      if(relayState == true){
        startUp();
      }else{
        shutDown();
      }
    }else{//cancel timer mode
      timer = 0;
      PORTB &= ~(1<<rLedPin);//turn off red led
      //Serial.println("timer canceled |"+String(millis()));
    }
  }//end green btn


  if(3359101868 == irCode){//red btn
    //Serial.println("pressed red btn |"+String(millis()));
    if(timer == 0){
      if(relayState == 0){
        timer = millis();
        //Serial.println("timer started |"+String(millis()));
      }/*else{
        Serial.println("already shutdown |"+String(millis()));
      }
      //*/

    }else{//changing time mode
      timerN = !timerN;
      if(timerN){
        //Serial.println("change 30sec |"+String(millis()));
        shift = timeN1;//30 min
      }else{
        //Serial.println("change 60sec |"+String(millis()));
        shift = timeN2;//60 min
      }
    }
  }//end red btn
} // loop end

void checkTimer(){
  unsigned long time = millis();
  if(time - previousMillis >= timerInterval || previousMillis > time ) {
    previousMillis =time;
    timer1();
  }
}

unsigned long  listenForIR() {// IR receive code
  byte currentpulse = 0; // index for pulses we're storing
  unsigned long irCode = 0; // Wait for an IR Code
  irCode = irCode << 1;

  while (true) {
    unsigned int pulse = 0;// temporary storage timing
    //bool true (HIGH)
    while (IRpin_PIN & _BV(IRpin)) { // got a high pulse (99% standby time have HIGH)
      if(++i > 150){//check timer every 150 iterations (high frequency break ir code timing)
        i = 0;
        checkTimer();
      }
      pulse++;
      delayMicroseconds(RESOLUTION);
      if (((pulse >= MAXPULSE) && (currentpulse != 0)) || currentpulse == NUMPULSES ) {
        return irCode;
      }
    }

	//make irCode
    irCode = irCode << 1;
    if ((pulse * RESOLUTION) > 0 && (pulse * RESOLUTION) < 500) {
      irCode |= 0;
    }else {
      irCode |= 1;
    }
    currentpulse++;
	pulse = 0;
        //bool false (LOW)
    while (!(IRpin_PIN & _BV(IRpin))) {//wait before new pulse
      //checkTimer();
      pulse++;
      delayMicroseconds(RESOLUTION);
      if (pulse >= MAXPULSE || currentpulse == NUMPULSES ) {
        //Serial.println(irCode);
        return irCode;
      }
    }
  }//end while(1)
  
}//end listenForIR 


//executing every timerInverval
void timer1() {
  if(timer != 0){
    if(timerN == true){//timeN1 or timeN2
      PORTB |= (1<<rLedPin);
    }else{//blinking 30min	
      PORTB ^= (1<<rLedPin);//invert
    }
    //Serial.println(String((timer+shift - millis())/1000));
  }

  if(timer != 0 &&(timer+shift < millis() || timer > millis())){
    timer = 0;
    shutDown();
  }
}



Видеодемонстрация:

Прошивал ATtiny13 я с помощью Arduino UNO, используя его как программатор, руководствуясь публикацией «Прошивка и программирование ATtiny13 при помощи Arduino». Для прошивки использовал 9,6МГц конфигурацию.

Фотографии почти не делал, но что есть, то есть:
Фото
Из за другой распиновки запасного TSOP на второй версии, пришлось его перенести на проводки и закрепить клеем (позже просто прикрепил к корпусу).

Вторая плата сбоку:



Вторая плата сверху (ик датчик перенес):



Вторая плата снизу:



Конечное устройство:




Исходники и прошивка на яндекс диске.

Используемые материалы


myrobot.ru/wiki/index.php?n=Components.TSOP Всё об ИК-приёмнике «TSOP»
www.atmel.com/images/doc2535.pdf Даташит по ATtiny13
habrahabr.ru/post/234477/ Инструкция по прошивке ATtiny13
payalo.at.ua/c_fuse/calc.html?part=ATtiny13A калькулятор фьюзов для ATtiny13
github.com/nathanchantrell/TinyPCRemote/tree/master/TinyPCRemote_CodeReader, код читалки кодов пульта который я взял за основу.