szty's diary

電子工作、プログラミングなど

AVRで赤外線リモコン送信機を作る

送信機にはAVR(ATtiny2313)、受信機にはArduino Unoを使用した。

受信機はArduinoに接続した赤外線リモコン受信モジュールからシリアル通信でPCに信号を表示する形にした。

最終的にモニタのリモコンの信号を読み取って送信機に組み込むことができた。

使用した部品

送信機

このリモコンはNECフォーマットでデータを送信する。

そのまま握ると配線に接触するので、裏面にアクリルパネルを付けてみた。

NECフォーマットについて。

support.renesas.com

回路

f:id:szty1012:20180607200450j:plain

入力: PB0~PB3

出力: PD5

PB0~PB3にタクトスイッチを接続。

PD5にトランジスタのベースを接続し、信号を増幅してから赤外線LEDに流すようにした。

プログラム

ATtiny2313は工場出荷時のまま使用した。

送信したいデータをdata配列にセットする。(ひとまず0xA、0xB、0xC、0xDとした。)

ATtiny2313のPWM機能で38kHz、1/3Dutyの搬送波を作る。

その搬送波をHigh/Lowにすることで被変調波を出力する。

消費電流を少なくするために無操作時はアイドル動作にしている。

#define SBI(reg,bit) reg |= (1 << bit)
#define CBI(reg,bit) reg &= ~(1 << bit)
#define SW() (PINB & 0x0f)
#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

void send(unsigned long *data);

unsigned long data[4] = { 0xA, 0xB, 0xC, 0xD };

EMPTY_INTERRUPT(PCINT_vect);

int main(void) {
    DDRD = (1 << DDD5);
    PORTB = 0x0f;

    TCCR0A = (1 << WGM01) | (1 << WGM00);
    TCCR0B = (1 << WGM02) | (1 << CS00);
    OCR0A = 25;
    OCR0B = 8;

    PCMSK = 0x0f;
    GIMSK = (1 << PCIE);

    set_sleep_mode(SLEEP_MODE_IDLE);

    sei();

    while (1) {
        sleep_mode();

        while (SW() == 0b1111);
        _delay_ms(10);

        switch (SW()) {
            case 0b1110:
            send(&data[0]); break;
            case 0b1101:
            send(&data[1]); break;
            case 0b1011:
            send(&data[2]); break;
            case 0b0111:
            send(&data[3]); break;
        }
    }
}

void send(unsigned long *data) {
    // reader code
    SBI(TCCR0A, COM0B1);
    _delay_us(9000);
    CBI(TCCR0A, COM0B1);
    _delay_us(4500);

    // custom code and data code
    for (unsigned char i = 0; i < 8 * sizeof(unsigned long); i++) {
        if ((*data >> i) & 1) {
            SBI(TCCR0A, COM0B1);
            _delay_us(600);
            CBI(TCCR0A, COM0B1);
            _delay_us(1500);
        }
        else {
            SBI(TCCR0A, COM0B1);
            _delay_us(600);
            CBI(TCCR0A, COM0B1);
            _delay_us(600);
        }
    }

    // stop bit
    SBI(TCCR0A, COM0B1);
    _delay_us(600);

    // frame space
    CBI(TCCR0A, COM0B1);
    _delay_ms(50);

    while (SW() != 0b1111) {
        // repeat code
        SBI(TCCR0A, COM0B1);
        _delay_us(9000);
        CBI(TCCR0A, COM0B1);
        _delay_us(2250);

        // stop bit
        SBI(TCCR0A, COM0B1);
        _delay_us(600);

        // frame space
        CBI(TCCR0A, COM0B1);
        _delay_ms(50);
    }

    return;
}

送信している様子

スイッチを押すと紫色に光るので、信号は出力されていることが分かる。(iPhoneのインカメを使うと赤外線を見ることができる。)

これから受信機で正しく送信できているか確かめてみる。

受信機

受信したデータをシリアルモニタに"2進数 (16進数)"のフォーマットで表示する。

回路

入力: PIN2

出力: USB

赤外線リモコン受信モジュールの出力をPIN2に接続し、Arduino UnoとPCをUSBケーブルで接続した。

受信モジュールをArduinoに繋いだだけなので、回路図は省略する。

プログラム

受信モジュールから入力されるまで待つ。

入力されたらリーダコードを読み飛ばす。

入力の状態が変化した時の2つの時間差⊿tが1msより大きいならHigh、そうでないならLowと判定する。

受信したbitはdataのMSBから詰めて代入する。(LSBからだとMSB/LSBが逆になってしまうため。)

#define SENSOR 2

unsigned long begin_t, dt;
unsigned long data;
int state, old_state;

void setup() {
  Serial.begin(115200);
  Serial.println("reset");
}

void loop() {
  // wait for input
  while (digitalRead(SENSOR) == HIGH);

  // reader code
  while (digitalRead(SENSOR) == LOW);
  while (digitalRead(SENSOR) == HIGH);

  data = 0;
  dt = 0;
  old_state = LOW;
  begin_t = micros();

  // read from custom code
  while (dt < 2000) {
    state = digitalRead(SENSOR);
    dt = micros() - begin_t;

    if (old_state != state) {
      begin_t = micros();
      if (state == LOW) {
        if (dt > 1000) {
          data = (data >> 1) | 0x80000000;
        } else {
          data = (data >> 1);
        }
      }
    }

    old_state = state;
  }

  if (data != 0) {
    Serial.print(data, BIN);
    Serial.print(" (");
    Serial.print(data, HEX);
    Serial.println(")");
  }
}

受信した様子

送受信に成功した。

f:id:szty1012:20180422230756j:plain

応用

実際のリモコンの信号を解析し、組み込んでみた。

以下のプログラムでNECフォーマットであるか判定し、上記のプログラムで信号を読み取った。

プログラム

ロジックは上記の受信した信号を出力するプログラムと同じ。

このプログラムはHigh/Lowの時間を検出し、そのまま時間を出力する。

出力は1周期毎に改行している。

#define SENSOR 2

unsigned long begin_t, dt;
int state, old_state;

void setup() {
  Serial.begin(115200);
  Serial.println("reset");
}

void loop() {
  // wait for input
  while (digitalRead(SENSOR) == HIGH);

  dt = 0;
  old_state = LOW;
  begin_t = micros();

  // read from reader code
  while (dt < 10000) {
    state = digitalRead(SENSOR);
    dt = micros() - begin_t;

    if (old_state != state) {
      begin_t = micros();
      Serial.print(dt);
      if (state == LOW) {
        Serial.print("\n");
      } else {
        Serial.print(",");
      }
    }

    old_state = state;
  }

  Serial.print("\n");
}

出力例

先頭(リーダコード)の約9ms、約4.5msからNECフォーマットであると判断できる。

f:id:szty1012:20180430231211j:plain

モニタを操作している様子

メモ

次にできそうなこと

  • 今回作った送受信機を合わせて学習リモコンを作る
    • 対応するフォーマットを増やす
  • 液晶を使って独立した受信機を作る
  • IoT化