一人もくもく会 α verでサービス開始しました。
請求書作成システム α verでサービス開始しました。

PIC12F675で赤外線受信

ようやくPIC12F675でリモコンの赤外線受信に成功した。
環境は下記の通り。
SONYのテレビリモコン
・MPLAB X IDE
・XC8
・pickit3
・受信モジュールPL−IRM2161−XD1

sonyの赤外線フォーマットは書いてあるとおりだけど、
簡単に説明するとT = 600μsが全ての信号の単位長さになっており、
Frameというところに書かれている様に信号群がやってくる。
なので最初にLeaderが4THIGHできてあとは
1TLOW+(1or2)HIGHがひたすらくる。

8ピンPICでは詳細なデバッグが出来ないので、
信号の解析はarduinoのシリアル通信で行った。
スケッチは下記の通り。

const int PIN = 7;
const long TIMEOUT = 250000;
const int DATA_MAX = 400;

unsigned int lastInput = HIGH;
unsigned long startTime = micros();
int inputCount = 0;
int dataCount = 0;
unsigned int val;
unsigned long currentTime;
boolean data[100];
boolean highVal;
boolean isSony = false;
String str = "";

void setup() {
  Serial.begin(57600);
  pinMode(PIN, INPUT);
}

void loop() {
  val = digitalRead(PIN);
  unsigned long time;
 
  while (val == lastInput) {
    if (micros() - startTime > TIMEOUT) {
      if (inputCount > 2 && isSony) {
        String tmp = "";
        for (int i = 0; i < dataCount; i++) {
          tmp += String(data[i]) + " ";
        }
        Serial.println(tmp);
//        Serial.println(str);
      }
      isSony = false;
      inputCount = 0;
      dataCount = 0;
      str = "";
      break;
    }
    val = digitalRead(PIN);
  }
  
  currentTime = micros();
  time = currentTime - startTime;
  str += String(time) + " ";
  inputCount++;
  if (inputCount == 3) {
    if (1800 < time && time < 3000) {
      isSony = true;
    }
  } else if (inputCount > 3) {
    if (isSony && (inputCount % 2)) {
      if (time < 1000) {
        data[dataCount] = 0;
      } else {
        data[dataCount] = 1;
      }
      dataCount++;
    }
  }
  lastInput = val;
  startTime = currentTime;
}

これを実行するとリーダー以降の値の0と1の並びを確認できる。
実際の値も見たい場合はstr変数周りを使用してみると良い。

んで実行してみるとわかるんだけど
2チャンネルボタンは
1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1
3チャンネルボタンは
0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 1
のようになっていて、仕様書通り左の7bitを見てみると
1〜9チャンネルボタンは右詰めbitにすると普通に0〜8の値になっている。
とても簡単だ。
Addressという部分はよくわからなかったので今回は無視することにした。
charだけで判断できるようになるし。
BDのリモコンでBDとかTVとか切り替えができるので、多分そのあたりで変えているのだと思う。
(ただしその関係で、ボタンを長押しには対応できていないので
長押ししてると別のボタンと判断してしまう場合があるので
必要であれば残りは自分で解析して下さい)


上を元にPIC側のプログラムを作ってみた。
デバッグ方法がLEDしかないので、
想定通りの値が来ていたらLED点灯…等のようにして
ひたすら細かいデバッグを行っていった。大変…。
最初はシリアル通信かLCDかpickitでデバッグできるピン数の多いICで
作成した方が効率よさげ…。
delayとかのdefineが確認用なので実際は不要。
受信解析に成功するとチャンネル番号-1回LEDが光るようになっている。

#include <xc.h>

#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = ON, MCLRE = OFF

#define IR GPIO4
#define LED GPIO2
#define MOTOR GPIO0
#define TIMEOUT 27500
#define _XTAL_FREQ 4000000 // delay用に必要(クロック4MHzを指定)


#define __delay(x) _delay((unsigned long)((x)))
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000UL)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000UL)))

const char buttons[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; //0=1チャンネルボタン
const char buttonSize = sizeof(buttons) / sizeof(char);

char lastInput = 1;
char isSony = 0;
char data = 0;
unsigned long time = 0;
unsigned char inputCount = 0;
unsigned char dataCount = 0;

void setData(int no, char row) {
    //sonyは7bitなのでそれ以外は無視
    if (no >= 7) return;
    data |= row << no;
}

main()	{
    GPIO = 0;
    CMCON = 0x07;		// コンパレータ未使用
    TRISIO = 0b00010000;		// GP4:in
    //プリスケーラ8=8μS…カウント75で600μ(1T)…だけどなんか64くらいっぽい…
    OPTION_REG = 0b10000010;
    TMR0 = 0;
    ANSEL = 0b00110000;

    char val;

    while(1) {
        val = IR;
        while (val == lastInput) {
            if (TMR0 > 200) {
                time += TMR0;
                TMR0 = 0;
            }
            if (time + TMR0 > TIMEOUT) {
                if (inputCount > 2 && isSony) {
                    for (char i = 0; i < buttonSize; i++) {
                        if (buttons[i] == data) {
                            for (char j = 0; j < i; j++) {
                                LED = 1;
                                __delay_ms(200);
                                LED = 0;
                                __delay_ms(200);
                            }
                        }
                    }
                }
                isSony = 0;
                inputCount = 0;
                dataCount = 0;
                data = 0;
            }
            val = IR;
        }

        time += TMR0;
        inputCount++;
        if (inputCount == 160) LED = 1;
        if (inputCount == 2) {
            if (192 < time && time < 320) {
                isSony = 1;
            }
        } else if (inputCount > 2) {
            if (isSony && !(inputCount % 2)) {
                if (time < 100) {
                    setData(dataCount, 0);
                } else {
                    setData(dataCount, 1);
                }
                dataCount++;
            }
        }
        lastInput = val;
        time = 0;
        TMR0 = 0;
    }
}


はまったところのメモ。

前述のように信号は0と1の並びで来るのだが、
ボタンを離すまで信号が連続していることに気づかず
大量の0と1が必要と勘違いしていた。
そうすると入力内容を保存しているdata変数を配列にしてしまっていたのだが、
なんとこのICは64バイトくらいしかメモリを使用できないらしく、
大量に値を保存するプログラムにするとビルドに失敗する。
その辺りで四苦八苦していた名残でdataは1バイトのcharに
綺麗にまとまった作りになっている。
button2 = {1, 0, 0, 0, 0, 0, 0}
等とは間違ってもやらないように…。

あとarduinoのスケッチとほぼ同じように作ったつもりなのだが、
arduino側は信号群の先頭2つがゴミ、
PIC側は先頭一つだけがゴミ、
となっておりそれが原因で上手くマッチング出来ず困っていた。
いまだに原因はわからないのでとりあえず注意。


とりあえずおもちゃの電車を動かしたいと思っていただけなので
ボタン長押し不要のためこれで問題ない。
ラジコンなどを作りたいひとはaddressの部分やループの部分も作りこんで下さい。