2018-09-12に更新

PIC12F675で赤外線受信

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

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

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

|c|
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が光るようになっている。

|c|

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 &lt; buttonSize; i++) {
                    if (buttons[i] == data) {
                        for (char j = 0; j &lt; 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 &lt; time && time &lt; 320) {
            isSony = 1;
        }
    } else if (inputCount > 2) {
        if (isSony && !(inputCount % 2)) {
            if (time &lt; 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の部分やループの部分も作りこんで下さい。

ツイッターでシェア
みんなに共有、忘れないようにメモ

だら@Crieit開発者

Crieitの開発者です。 Webエンジニアです(在宅)。大体10年ちょい。 記事でわかりにくいところがあればDMで質問していただくか、案件発注してください。 業務依頼、同業種の方からのコンタクトなどお気軽にご連絡ください。 業務経験有:PHP, MySQL, Laravel, React, Flutter, Vue.js, Node, RoR 趣味:Elixir, Phoenix, Nuxt, Express, GCP, AWS等色々 PHPフレームワークちいたんの作者

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント