JavaScriptを有効にすると、ここにメニューを表示します。

Arduinoによる赤外線リモコンの記録・再生

ysflight.com

注意: ここのプログラム、回路図、情報を使ったことによって何か損害が発生したとしても作者は一切責任を負いません。この点に同意いただけない場合は利用しないようにお願いします。正しくないリモコンの信号を送信してしまった場合、機器が誤作動する危険があります。とくに熱を発する機器のリモコンには利用しないようにしてください。

ここでは、Arduinoを使った赤外線リモコンの記録・再生回路の回路図、ソースコード、Windows 10でのコントロールプログラム (GUI, CUI)を掲載している。こんなことをして何の役に立つのかというのは大きな問題だが、夏休みとか冬休みの自由研究のネタにはいいかもしれない。Arduinoを勉強してる人にはいい例題かもしれない。

これを始めたきっかけは1985年の富士通製PC、FM77AVのキーボードの赤外線信号を記録したいという目的だった。既にいろいろな資料を基に、FM77AVのキーボード赤外線信号を発生させることには成功していたのだが、それが果たして本来の正しい信号を出しているのか確証がなかった。一応、1985年に一世を風靡した8ビット機の技術資料として記録を残しておきたかった。

その後、いろいろ紆余曲折を経て、実は赤外線信号は単なるHigh/Lowではなく、例えば100usの間Highだとすると38KHzのPulse Width Modulationというのがかかって、約13usの間オン、次の13usがオフを4回繰り返していることがわかった。

当初IRToyという製品を使ってキャプチャを試みたのだが、まったく着いてこれなかった。ならば自作するまで。と思ってAmazonから適当な赤外線LED、フォトダイオードのペアを買って試してみたが、これも反応が遅すぎて取れない。半ばあきらめたのだが、真面目に仕様を調べて「高速」と主張している赤外線フォトダイオードを使ったところ、ついに成功した。なお、例によってApolloさんから多大なアドバイスをいただいた。

なお、受信・再生にそれぞれにひとつのArduinoを必要とする。再生の方はArduino UNOでも可能だが、受信中パルスを記録するバッファが多く必要なので、受信にはArduino Megaを使った方がいい。ソースコードはどちらでも動作するが、Arduino UNOだとパルスを受信しきれないかもしれない。ただ、UNOでも、送信機をLEDに1mmぐらいの距離まで近づけてキャプチャすると、明るすぎて38KHzの信号が一連の信号になってキャプチャされるので、テレビやエアコンのリモコンのキャプチャぐらいはできるかもしれない。

Arduinoと情報を送受信するために、PCのプログラムを使う必要がある。それは以下のURLからダウンロードできる。

[赤外線信号送受信GUI]

[赤外線送信コマンド]

[ソースコード (public/src/ir_transceiver)]

ただ、これを実行するときWindowsからサインされていない実行ファイルであるという警告が出ると思う。信用していただいて、警告を無視して実行していただくか、あるいは、ソースコードからビルドしていただければそのメッセージは出ない。今、プログラミングの自由が脅かされている。Apple, Google, Microsoftのような大企業は数多くの大事な仕事をしてコンピュータに貢献してきた。しかし、残念ながら正しいことばかりしたわけではない。大企業の中には、すべてのプログラマーを支配下に置こうという野心を持った人がいるらしい。そういう人たちは個人プログラマーが自由にコードを発表することを阻止しようとする。Windowsの警告を消すためには、コードサイン認証というのを買わなくてはならない。そもそも法外な値段を毎年払い続ける必要があったのだが、今ではいくら払おうが個人プログラマーにはコードサイン認証は発行されなくなってしまった。誰が書いたプログラムなのか確認する手段はセキュリティの対策にとって非常に有効であることは容易に理解できる。また、セキュリティ認証の管理をビジネスにするのも何も悪いことではない。しかし、公的機関でも無い私企業が恣意的に誰に対してセキュリティ認証を発行するか決める権限を持つことは許されるべきことではない。と、言い続けても、誰も大して聞く耳持たないだろうし、そのうちプログラミングの自由なんて無くなってしまうんだろうけど、何も抵抗せずにくたばるのは自分の性格に合わない。

 

GUIについて

送受信GUIを使うと、赤外線信号を受信して、ファイルに記録、またArduinoの送信回路を接続すればそれを送信することができる。起動時オプションにとしてデータファイル名を指定すると、起動時にファイルを読み込む。また、-recv 1のように受信ポート、-trans 2のように送信ポートを指定することも可能。

普通に起動したら、まず受信回路をプログラムに接続する。RECEIVER横のCONNECTボタンをクリックしてポート番号を指定する。ポート番号はDevice ManagerなどでArduinoが何番ポートに割り当てられているか確認する。ここは番号だけで良くて、ポートがCOM1だったら単に数字の1を指定すればいい。

ひとつのファイルに複数のボタンのデータを記録できる。また、それぞれのボタンについて複数回サンプルを取ることができる。ひとつのボタンに何個のサンプルを取るか制限はないが、画面上にはウィンドウサイズ分しか表示しない。多くサンプルしすぎた場合は↑↓でサンプルを選んでDELETEボタンまたはDELキーでサンプルを削除できる。

ボタンのサンプルを取るには、RECORDボタンをクリックして記録モードにする。次に、赤外線リモコンを赤外線フォトダイオード (PD204-6B)の正面5cm~10cm程度の距離でボタンを押す。押し続けると何度もサンプルを取ってしまうが、サンプルが増えすぎる以外とくに害は無い。なお、PWM信号を検出するためにフォトダイオードの感度はかなり低くなるように抵抗を選んだので、遠くから照射してもサンプルは取れない。

サンプルはそれぞれのボタンで何度か取って、同じ波形が取れていることを確認した方がいいと思う。

ひとつのボタンが終わったら、NAME:の左のテキストボックスで名前を指定できる。超簡易GUIで作ったので英数字しか入力できない。

次のボタンのサンプルを開始するにはNEW DATAボタンを押す。これをボタン回数分繰り返すとひとつのリモコンのサンプルが撮り終わる。

なお、プログラムがなるべくクラッシュしないように気を付けたつもりだが(一応プログラミングの先生だし)人間のすることに100%はありえないので、ときどきSAVEボタンでファイルにサンプルをセーブした方がいい。

既に記録したサンプル間を行き来するには、Ctrl+TabまたはCtrl+Shift+Tabを使う。PREVとNEXTボタンをクリックしてもいいけど。

サンプルしたデータを送信するには、TRANSMITTERをCONNECTして、TRANSMITボタンをクリックする。なお、TRANSMITボタンはTRANSMITTERを接続すると出てくる。各ボタンの最上位のサンプルを元に送信するので、なるべく良く取れた波形を最上位にしておくといい。

 

CUI (送信コマンド)について

例えばテレビのリモコンのボタンを記録したら、コマンドからボタン信号を送信することができる。コマンドプロンプトで、

.\irsend datafile.txt port_name "button_name1" "button_name2" "button_name3" -wait 500

のように指定する。port_nameはArduino送信回路がつながっているCOMポート番号、button_name1~button_name3は記録したときにつけたボタンの名前、-wait 500はそれぞれのボタンの間に500msの感覚を開けるという意味。

 

回路図とArduinoソースコード

以下は回路図とArduinoのソースコード

受信回路

受信回路+Arduino写真

Arduino受信プログラム

/*

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

enum
{
  STATE_STANDBY,
  STATE_READY_TX,
};


// Timer 1 is used for recording pulse timings.
// Don't care about overflow.  Just record 16-bit values.
// Later overflow will be taken into account.

// Timer 2 is used for detecting long IR off.
// Using 1024x scaling and if it goes up to 255, it is taken as IR signal is done.


#if defined(__AVR_ATmega2560__)    // Arduino Mega 2560
  #define PULSE_BUF_LEN 3072

  #define Pin3State (PINE&_BV(5))

  #define SetPin0High  PORTE|=_BV(0);
  #define SetPin0Low   PORTE&=~_BV(0);
  #define SetPin1High  PORTE|=_BV(1);
  #define SetPin1Low   PORTE&=~_BV(1);
  #define SetPin2High  PORTE|=_BV(4);
  #define SetPin2Low   PORTE&=~_BV4();
  #define SetPin3High  PORTE|=_BV(5);
  #define SetPin3Low   PORTE&=~_BV(5);
  #define SetPin4High  PORTG|=_BV(5);
  #define SetPin4Low   PORTG&=~_BV(5);
  #define SetPin5High  PORTE|=_BV(3);
  #define SetPin5Low   PORTE&=~_BV(3);
  #define SetPin6High  PORTH|=_BV(3);
  #define SetPin6Low   PORTH&=~_BV(3);
  #define SetPin7High  PORTH|=_BV(4);
  #define SetPin7Low   PORTH&=~_BV(4);

  #define SetPin8High  PORTH|=_BV(5);
  #define SetPin8Low   PORTH&=~_BV(5);
  #define SetPin9High  PORTH|=_BV(6);
  #define SetPin9Low   PORTH&=~_BV(6);
  #define SetPin10High PORTB|=_BV(4);
  #define SetPin10Low  PORTB&=~_BV(4);
  #define SetPin11High PORTB|=_BV(5);
  #define SetPin11Low  PORTB&=~_BV(5);
  #define SetPin12High PORTB|=_BV(6);
  #define SetPin12Low  PORTB&=~_BV(6);
  #define SetPin13High PORTB|=_BV(7);
  #define SetPin13Low  PORTB&=~_BV(7);
#elif defined(__AVR_ATmega328P__)          // Arduino Uno (Can be Nano)
  #define PULSE_BUF_LEN 768
  #define Pin3State (PIND&_BV(3))

  #define SetPin0High  PORTD|=_BV(0);
  #define SetPin0Low   PORTD&=~_BV(0);
  #define SetPin1High  PORTD|=_BV(1);
  #define SetPin1Low   PORTD&=~_BV(1);
  #define SetPin2High  PORTD|=_BV(2);
  #define SetPin2Low   PORTD&=~_BV(2);
  #define SetPin3High  PORTD|=_BV(3);
  #define SetPin3Low   PORTD&=~_BV(3);
  #define SetPin4High  PORTD|=_BV(4);
  #define SetPin4Low   PORTD&=~_BV(4);
  #define SetPin5High  PORTD|=_BV(5);
  #define SetPin5Low   PORTD&=~_BV(5);
  #define SetPin6High  PORTD|=_BV(6);
  #define SetPin6Low   PORTD&=~_BV(6);
  #define SetPin7High  PORTD|=_BV(7);
  #define SetPin7Low   PORTD&=~_BV(7);

  #define SetPin8High  PORTB|=_BV(0);
  #define SetPin8Low   PORTB&=~_BV(0);
  #define SetPin9High  PORTB|=_BV(1);
  #define SetPin9Low   PORTB&=~_BV(1);
  #define SetPin10High PORTB|=_BV(2);
  #define SetPin10Low  PORTB&=~_BV(2);
  #define SetPin11High PORTB|=_BV(3);
  #define SetPin11Low  PORTB&=~_BV(3);
  #define SetPin12High PORTB|=_BV(4);
  #define SetPin12Low  PORTB&=~_BV(4);
  #define SetPin13High PORTB|=_BV(5);
  #define SetPin13Low  PORTB&=~_BV(5);

  #define SetPin9and10Low PORTB&=~(_BV(1)|_BV(2));

//#elif defined(__AVR_ATMega4809__)          // Arduino Nano Every
//#elif defined(__AVR_ATmega328__)           // Arduino Nano?
//#elif defined(__AVR_ATmega32U4__)          // Arduino Leonardo
//  #elif defined(__AVR_ATmega__)
//  #elif defined(__AVR_ATmega__)
//  #elif defined(__AVR_ATmega__)
#else
  #error Unable to identify Board Type
#endif


volatile int state=STATE_STANDBY;
volatile unsigned int nBufFilled=0;
volatile unsigned int pulseBuf[PULSE_BUF_LEN];


void PinChange(void)
{
  TCNT1=0;

  if(STATE_STANDBY!=state)
  {
    return;
  }
  
  SetPin13High;

  while(nBufFilled<PULSE_BUF_LEN)
  {
    while(0==Pin3State)
    {
    }

    pulseBuf[nBufFilled]=TCNT1;
    ++nBufFilled;

    // Infra-Red OFF (Pin3=Active Low)
    TCNT2=0;
    while(0!=Pin3State)
    {
      if(255==TCNT2)
      {
        goto END_OF_SIGNAL;
      }
    }

    pulseBuf[nBufFilled]=TCNT1;
    ++nBufFilled;
  }
END_OF_SIGNAL:

  SetPin13Low;
  state=STATE_READY_TX;
}


void ClearBuffer(void)
{
  nBufFilled=0;
  for(int i=0; i<PULSE_BUF_LEN; ++i)
  {
    pulseBuf[i]=0;  
  }
}

void setup()
{
  state=STATE_STANDBY;
  ClearBuffer();


  // Reset timer 1
  TCCR1A=0;
  // TCCR1B=bit(CS10); // No Pre-Scaling
  TCCR1B=bit(CS11); // 8x Pre-Scaling
  OCR1A=0;
  OCR1B=0;
  TCNT1=0;

  TCCR2A=0;
  // TCCR2B=bit(CS20); // No Pre-Scaling
  // TCCR2B=bit(CS21); // 8x Pre-Scaling
  TCCR2B=bit(CS20)|bit(CS21)|bit(CS22); // 1024x Pre-Scaling
  OCR2A=0;
  OCR2B=0;
  TCNT2=0;


  pinMode(3,INPUT);
  pinMode(13,OUTPUT);
  attachInterrupt(digitalPinToInterrupt(3),PinChange,FALLING);

  Serial.begin(115200);
}

void loop()
{
  if(STATE_READY_TX==state)
  {
    Serial.begin(115200);
    Serial.println("Begin");
    long int total=0;
    unsigned long int prevPtr=0;
    unsigned long int basePtr=0;
    int balance=0;
    for(int i=0; i<nBufFilled; ++i)
    {
      if(0<i && pulseBuf[i]<pulseBuf[i-1])
      {
        basePtr+=0x10000;
      }
      unsigned long int curPtr=basePtr+pulseBuf[i];
      if(0!=(curPtr&1)) // 1 tick = 0.5us.  Need to make everything even, and send tick/2.
      {
        if(0<balance && prevPtr+2<curPtr)
        {
          --curPtr;
          --balance;
        }
        else
        {
          ++curPtr;
          ++balance;
        }
      }
      unsigned long int dt=curPtr-prevPtr;
      prevPtr=curPtr;

      Serial.print(dt/2);
      Serial.print(" ");

      total+=dt;
    }

    Serial.println("");
    Serial.println("End");
    Serial.print("Total=");
    Serial.print((long int)total);
    Serial.println("");
    Serial.print("nSample=");
    Serial.print(nBufFilled);
    Serial.println("");
    Serial.end();

    ClearBuffer();
    state=STATE_STANDBY;
  }

  TCCR0A=0;
  TCCR0B=0;
}

送信回路

送信回路+Arduino

送信回路+Arduino (PCB版)

Arduino送信プログラム

/*
38KHz 50% duty cycle Infra-Red Signal Emitter
Copyright 2019 CaptainYS (http://www.ysflight.com)  All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#define SERIAL_BPS 115200
// Confirmed to work up to 440000bps.
// Stopped working at 460000bps.
// Not tested between 440000 and 460000bps.


#define RECVBUF_SIZE 384
int nRecvBuf=0;
unsigned char recvBuf[RECVBUF_SIZE];
unsigned int cycle[RECVBUF_SIZE/2];
bool transmitMode=false;
unsigned long lastDataReceivedTime=0;
unsigned char processingCmd=0;


unsigned char pulseWidthTable[30];
const unsigned char pulseWidthSource[3]={100,125,175};


#define CMD_RESET 0
#define CMD_IRMAN_COMPATIBLE_MODE 'R'
#define CMD_VERSION 'V'
#define CMD_SELFTEST 't'
#define CMD_SAMPLERMODE 'S'
#define CMD_ENABLE_BYTECOUNT 0x24
#define CMD_ENABLE_TRANSMISSION_NOTIFY 0x25
#define CMD_ENABLE_HANDSHAKE 0x26
#define CMD_TRANSMIT_MICROSEC 0x80
#define CMD_TRANSMIT_30BIT_100US 0x81

#define NOTIFY_READY 'C'
#define NOTIFY_FAIL 'F'

// 50% duty cycle: On and off within 1/38000 sec.  Must togle every 1/76000 sec.
#define TIMER_THRESHOLD_38K_WITH_1xPRESCALE ((F_CPU/38000)/2)
// Experiment with FM77AV40 IR LED receiver:  19000Hz No response 34000-36000 drops key.  38000 perfect.  40000 drops keys.

#define PWM_TIMER_THRESHOLD TIMER_THRESHOLD_38K_WITH_1xPRESCALE

// Succeeded Xfinity remote

#define PIN_OC1A 9
#define PIN_OC1B 10
#define PIN_OC2A 11
#define PIN_OC2B 3

#define PIN_POWER 13
#define PIN_STATUS 8

// PIN9=PB1 (PortB bit 1)
#define SetPin9High  PORTB|=_BV(1);
#define SetPin9Low   PORTB&=~_BV(1);
// PIN10=PB2 (PortB bit 2)
#define SetPin10High PORTB|=_BV(2);
#define SetPin10Low  PORTB&=~_BV(2);
// PIN11=PB3 (PortB bit 3)
#define SetPin11High PORTB|=_BV(3);
#define SetPin11Low  PORTB&=~_BV(3);
// PIN3=PD3 (PortD bit 3)
#define SetPin3High  PORTD|=_BV(3);
#define SetPin3Low   PORTD&=~_BV(3);

// PIN13=PB5 PortB bit 5
#define SetPin13High PORTB|=_BV(5);
#define SetPin13Low PORTB&=~_BV(5);
// PIN8=PB0 Port B bit 0
#define SetPin8High PORTB|=_BV(0);
#define SetPin8Low PORTB&=~_BV(0);

#define SetPin9and10Low PORTB&=~(_BV(1)|_BV(2));


#define SET_CTC_MODE {TCCR1B=bit(CS10)|bit(WGM12);}
// TCCR1B  WGM10=0,WGM11=0,WGM12=1 means CTC mode
//         CS10=1,CS11=0,CS12=0 means 1x pre-scale (no scaling).
//         CS10=0,CS11=0,CS12=0 means timer stop.

#define SET_OC1A_OC1B_TOGGLE {TCCR1A=bit(COM1A0)|bit(COM1B0);}
// ATmega 328 datasheet pp.134
// TCCR1A  COM1A0=1, COM1B0=1 means toggle OC1A and OC1B on compare match

#define SET_OC1A_OC1B_LOW {TCCR1A=(bit(COM1A1)|bit(COM1B1));TCCR1C=(bit(FOC1A)|bit(FOC1B));TCCR1C=0;}
// TCCR1A=(bit(COM1A1)|bit(COM1B1));  // Clear OC1A low on compare match
// TCCR1C=(bit(FOC1A)|bit(FOC1B));    // Force match
// TCCR1C=0;                          // Do I need to clear?


// Failed Attmept:
// For each HIGH pulse, force OC1A to be HIGH, and then start timer.  This approach didn't work.
// Probable reason is that it makes the first duty cycle slightly shorter.
// This method FM77AV40 misses one in every 80 to 100 key strokes.



void setup() {
  for(int i=0; i<30; ++i)
  {
    pulseWidthTable[i]=pulseWidthSource[i%3];
  }
  
  Serial.begin(SERIAL_BPS);

  pinMode(PIN_POWER,OUTPUT);
  pinMode(PIN_STATUS,OUTPUT);

  pinMode(PIN_OC1A,OUTPUT);
  pinMode(PIN_OC1B,OUTPUT);
  pinMode(PIN_OC2A,OUTPUT);
  pinMode(PIN_OC2B,OUTPUT);

  // Reset timers 1 and 2
  TCCR1A=0;
  TCCR1B=0;
  TCCR2A=0;
  TCCR2B=0;
  TCNT1=0;
  TCNT2=0;

  // Timer 1 (8-bit) for 38K PWM IR LED output
  // ATmega 328 datasheet pp.134
  OCR1A=PWM_TIMER_THRESHOLD;
  OCR1B=0;

  SET_OC1A_OC1B_LOW;

  // Timer 2 for measuring 1us tick.
  TCCR2A=0;
  TCCR2B=bit(CS21);
  // CS20=0,CS21=1,CS22=0 means 8x pre-scaling.  0.5us per tick.  ATmega328 datasheet pp.162
  OCR2A=0;
  OCR2B=0;

  SetPin13Low;
  SetPin8Low;
  SetPin9and10Low;
}

void SendCycleHWPWM(unsigned int cycle[])
{
  noInterrupts();

  TCNT1=0;
  SET_CTC_MODE;
  SET_OC1A_OC1B_LOW;
  // Now pins 9 and 10 are under control of Timer 1, OC1A, OC1B both low.
  // Pin9=OC1A
  // Pin10=OC1B

  for(int i=0; cycle[i]!=0xffff; i+=2)
  {
    TCNT2=0;

    TCNT1=PWM_TIMER_THRESHOLD-8;
    // Timer 1 pre-scalar is 1x.
    // Need to start toggling within 8 cycles.  0.5us error.
    SET_OC1A_OC1B_TOGGLE;

    auto w=cycle[i]<<1;
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }

    TCNT2=0;
    SET_OC1A_OC1B_LOW;
    w=cycle[i+1]<<1;
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }
  }

  SET_OC1A_OC1B_LOW;
  SetPin9and10Low;

  interrupts();
}

void MakeCycle(unsigned int cycle[],int nSample,unsigned char sample[])
{
  unsigned char k=0;
  switch(processingCmd)
  {
  case CMD_TRANSMIT_MICROSEC:
    {
      for(int i=0; i+1<nSample && (sample[i]!=0xff || sample[i+1]!=0xff); i+=2)
      {
        cycle[k]=(sample[i]<<8);
        cycle[k]+=sample[i+1];
        ++k;
      }
    }
    break;
  case CMD_TRANSMIT_30BIT_100US:
    {
      unsigned char samplePtr=0,sampleBit=1;
      cycle[k]=0;
      for(unsigned char i=0; i<30; ++i)
      {
        bool currentBit=((~k)&1);
        bool nextBit=(sample[samplePtr]&sampleBit);
        if(currentBit!=nextBit)
        {
          ++k;
          cycle[k]=0;
        }
        cycle[k]+=pulseWidthTable[i];

        if(128==sampleBit)
        {
          ++samplePtr;
          sampleBit=1;
        }
        else
        {
          sampleBit<<=1;
        }
      }
      ++k;
    }
    break;
  }
  cycle[k]=0;
  k+=(k&1); // Force it to be even.
  cycle[k  ]=0xffff;
  cycle[k+1]=0xffff;
}

void Transmit()
{
  SetPin8High;
  MakeCycle(cycle,nRecvBuf,recvBuf);
  SendCycleHWPWM(cycle);

  while(0==Serial.availableForWrite());
  Serial.write(NOTIFY_READY);

  transmitMode=false;

  SetPin8Low;
}

void loop() {
  bool received=false;
  while(0<Serial.available())
  {
    auto recvByte=Serial.read();
    if(true!=transmitMode)
    {
      processingCmd=recvByte;
      if(CMD_TRANSMIT_MICROSEC==recvByte ||
         CMD_TRANSMIT_30BIT_100US==recvByte)
      {
        transmitMode=true;
        nRecvBuf=0;
      }
      else if(CMD_IRMAN_COMPATIBLE_MODE==recvByte)
      {
        Serial.println("OK");
      }
      else if(CMD_VERSION==recvByte)
      {
        Serial.println("A277");
      }
      else if(CMD_SAMPLERMODE==recvByte)
      {
        Serial.println("S77");
      }
    }
    else
    {
      recvBuf[nRecvBuf++]=recvByte;
      if(CMD_TRANSMIT_30BIT_100US==processingCmd && 4<=nRecvBuf)
      {
        Transmit();
      }
      else if(RECVBUF_SIZE<=nRecvBuf)
      {
        Transmit();
      }
      else if(2<=nRecvBuf && 0xff==recvBuf[nRecvBuf-1] && 0xff==recvBuf[nRecvBuf-2])
      {
        Transmit();
      }
    }
    received=true;
  }

  unsigned long t=millis();
  if(true==received)
  {
    lastDataReceivedTime=t;
  }
  else
  {
    // Second mode of failure.
    // In transmit mode, the FIFO buffer is overwhelmed and starts losing bytes.
    // The terminator 0xffff won't be caught, however, 0x03 included in the
    // pulse-width data put it into the transmitMode again, and never recover.
    // To get out of this mode, the user needs to release the key for 100ms.
    // If no byte is received for 100ms, the program goes back to command mode.
    if(true==transmitMode)
    {
      if(t<lastDataReceivedTime ||   // Timer overflow
         100<t-lastDataReceivedTime) // 100ms no transmittion from host
      {
        transmitMode=false;
        Serial.write(NOTIFY_FAIL);
        Serial.write(NOTIFY_READY);
      }
    }
  }
}
Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com