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

はじめに

赤外線とは、波長が780nm~1000umの光(電磁波)である。赤外線は波長によって3種類に分類され、可視光に近い順から「近赤外線」、「中赤外線」、「遠赤外線」と呼ぶ。一般的に赤外線リモコンには900nm帯の「近赤外線」が使用されている。

今回は、送信機にAVR(ATtiny2313)、受信機にArduino Unoを使用して赤外線リモコン送・受信機を製作した。受信機は、Arduinoに接続した赤外線リモコン受信モジュールから信号を読み取り、Arduino-PC間でシリアル通信をして信号をコンソールに表示させた。

部品

送信機

赤外線リモコンの送信データは一般的にパルス幅変調方式(PWM: pulse width modulation)で変調されている。これは、送信データのビットを赤外線の点灯・消灯時間の組み合わせによって表現する変調方式である。また、送信データ以外にも赤外線は自然界に存在する(ノイズという)ため、赤外線が点灯している時にも一定の周波数でPWM変調し、その周波数のみの赤外線を受信することで送信データとノイズを区別できるようにしている。

この変調方式を使った主な通信規格はNEC、家電製品協会、SONYフォーマットがある。今回はNECフォーマットでデータを送信する。

回路

f:id:szty1012:20180607200450j:plain

入力: PB0~PB3

出力: PD5

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

PD5にトランジスタのベースを接続し、信号を増幅している。

プログラム

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

送信する手順を以下に示す。

  1. 送信したいデータをdata配列にセットする。

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

  3. その搬送波をNECフォーマットに従って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;
}

送信している様子

スイッチを押すと紫色に光るので、信号は出力されていることが分かる。

受信機

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

回路

入力: PIN2

出力: USB

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

プログラム

受信する手順を以下に示す。

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

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

  3. 入力の状態が変化した時の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

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