皆さん、楽しい赤外線ライフを満喫していますか? 前回の記事ではシリアル接続のモジュールを作りましたが、シリアルが無かったり空いてなかったりする場合もあります。またシリアルは一対一接続なので、あまりたくさんのデバイスをつなぐことができません。そこで今回はI2C slave版を作ってみました。
例によってまず今回作ったもの。ATtiny85を I2C slave device として使い、受け取ったコマンドを赤外線で送出します。
動機
前回のモジュールを ESP8266 + MicroPython で使おうとしたら、UARTが空いてなくて悲しい気持ちになったから。よろしい、ならばI2Cだ。
やりたいこと
- 言語やフレームワーク・ランタイムを問わない赤外線送信の抽象化
- ハードウエアを問わず接続できるモジュール化
- NEC フォーマットとその派生フォーマットへの対応
- UARTがない/空いてないマイコン向けのI2C接続 ← NEW!
ハードウエア編
用意したもの
前回の記事とまったく同じです。と言いつつPololu USB AVR Programmer v2.1を導入しました。In-System Programming は鼻血が出そうなぐらい便利です。
仕様と回路図
- 電源: 3.3V
- 内蔵プルアップなし
ソフトウエア編
ArduinoでI2C slaveを作るとき、受け取ったデータを「割り込みで処理する」方法と「loop()関数で処理する」方法があります。「割り込みで処理する」方法ではバッファが小さく(16バイト)データを取りこぼすため、loop()関数で処理する方法を採ります。
ここでもATtiny85で赤外線リモコン用の波形を作る話 | 東京お気楽カメラを参考にリモコンの信号を作っています。基本的な動きは前回の記事と同じです。
Arduino で ATtiny85 に書き込む際、GitHub - damellis/attiny: ATtiny microcontroller support for the Arduino IDEではなくGitHub - SpenceKonde/ATTinyCore: Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8 を使う必要があります。前者ではWire.hを扱えません。
#include <avr/wdt.h>
#include <Wire.h>
#define COMMAND_BUFFER_SIZE 150
#define OUTPUT_PIN _BV(1)
unsigned int commandBuffer[COMMAND_BUFFER_SIZE];
char c;
int index = 0;
int signalSize = 0;
unsigned int t;
boolean ready = false;
void setup() {
// I2C slave
Wire.begin(0x40);
}
void mark() {
// 38kHz, 1/3 duty
OCR0A = OCR1C = 25; // TOP value : 38kHz --> 26.3us.
OCR0B = OCR1B = 6; // H period : 6.4us.
TCCR0A = B00100011; // COM0A: 00, COM0B: 10, WGM01-00: 11
TCCR0B = B00001000; // FOC:00, WGM02:1, CS02-01: 000 (stop clock)
GTCCR = B01100000; // OC1B cleared on compare match. Set when TCNT1 = $00.
TCCR1 = B10000100; // CTC=1. clear TCNT1 on OCR1C==TCNT1. CS13-10 = 0100B (/8)
TCNT1 = 0;
TCCR0B |= B010; // enable clock (/8).
TCNT0 = OCR0A ;
}
void space() {
TCCR0A = 0;
TCCR0B = 0;
TCCR1 = 0;
GTCCR = 0;
}
void loop() {
while(Wire.available()) {
c = Wire.read();
switch (c) {
case '0': t = t * 10; break;
case '1': t = t * 10 + 1; break;
case '2': t = t * 10 + 2; break;
case '3': t = t * 10 + 3; break;
case '4': t = t * 10 + 4; break;
case '5': t = t * 10 + 5; break;
case '6': t = t * 10 + 6; break;
case '7': t = t * 10 + 7; break;
case '8': t = t * 10 + 8; break;
case '9': t = t * 10 + 9; break;
case ',':
commandBuffer[index] = t;
index++;
t = 0;
break;
case '.':
commandBuffer[index] = t;
signalSize = index+2;
t = 0;
ready = true;
break;
}
if(COMMAND_BUFFER_SIZE <= index) {
index = 0;
}
}
if(ready) {
// IR LED
// http://okiraku-camera.tokyo/blog/?p=7480
noInterrupts();
DDRB = OUTPUT_PIN; // inputは0
PORTB = 0; // 他は0
ACSR |= 0x80; // アナログコンパレータ禁止
ADCSRA &= 0x7f; // disable ADC
wdt_disable();
TIMSK = 0; // disable Timer0/1 overflow interrupt.
for(index = 0; index < signalSize; index++) {
if(0 == index % 2) {
mark();
} else {
space();
}
delayMicroseconds(commandBuffer[index]);
}
space();
ready = false;
index = 0;
signalSize = 0;
// restore I2C slave
Wire.begin(0x40);
interrupts();
}
}
使用例
前回同様、東芝のシーリングライト用リモコン FRC-194T(W) の「全灯」ボタン チャネル2を送ってみます。
ESP8266(ESP-WROOM-02)のMicroPythonで送信
スイッチサイエンスのESPr Developer (ESP-WROOM-02開発ボード)に接続した図。
from machine import I2C, Pin
i2c = I2C(scl=Pin(14), sda=Pin(4))
i2c.writeto(0x40, "10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
謝辞
台所でハンダ付け工作をしてもいいよと言ってくれる妻に感謝します。ちゃんと鉛フリーハンダを使って後片付けもしてるからね。