2016.08.23
- 人×技術
真夏のArduino
はじめまして、山田です。
新居で迎えた初めての夏、備え付けのエアコンにタイマー機能がついていないことに絶望し、「エアコンはつけっぱなしの方が電気代が安い」という噂に惑わされながらも、室温に応じてエアコンをON/OFFするモジュールを、キャリアのほとんどをWebプログラマとして歩んできた私がArduinoで実装するエピソードです。
事前準備
使用するもの
- Arduino Uno
- ジャンパワイヤ
- ブレッドボード
- マイクロUSBケーブル(シリアル通信用)
- 高精度IC温度センサ LM35DZ
- 赤外線リモコン受信モジュール PL-IRM0101(38kHz)
- 5mm赤外線LED OSI5LA5113A
ちなみに電子工作素人の私は、オライリーの書籍「Arduinoをはじめようキット」と、Arduino単品ではなく「Arduinoをはじめようキット」を購入しました。 この書籍のサンプルに使われているパーツが一式セットになっており、Arduinoの学習がとても捗る商品です。(本エントリーで使用する温度センサや赤外線LED等は含まれていません)
書籍の方も、回路図が手書きである点を除けばとても読みやすい良書です。
設計
エアコンのタイマー機能の便利さに驚愕しながら、作成するモジュールの仕様を下記のように設計しました。
1. 温度センサで室温を定期的に計測する
2. 規定の室温(例.26℃)を超えたら、エアコンを点ける
3. エアコンを点けたら、1時間は切らない
4. 1時間後、規定の室温(例.25℃)を下回ったら、エアコンを消す
5. 1〜4を一晩中続ける
エアコンはつけっぱなしにしておいた方が安いのどうかはググらずに先に進みます。
実装
IC温度センサで室温の読み取り
まずは、ジャンパワイヤとブレッドボードを利用してIC温度センサをArduinoのアナログ入力に接続します。
そして、下記のようなプログラムで室温を読み取ります。
// アナログ入力からデータを取得
a = analogRead(0);
// 取得したデータを摂氏に変換
t = ((a * 5) / 1024) * 100;
摂氏変換の式ですが、Arduinoのアナログ入力最大電圧が5V、アナログ入力値が0〜1023の1024段階なので、
[入力電圧]:5V = [アナログ入力値]:1024
[入力電圧] = ([アナログ入力値]*5)/1024 (単位はV)
となり、LM35DZの仕様として1Vが0.01℃のため、
[摂氏] = (([アナログ入力値]*5)/1024)*100
となります。
こちらを実行すると、変数tに摂氏に変換された室温が格納されます。tをシリアルモニタに出力した結果がこちら。
赤外線リモコン受信モジュールでリモコン信号を受信する
次に、赤外線リモコン受信モジュールでリモコン信号を受信します。
なぜこんなことをするかというと、ここで読み取った赤外線信号値をそのまま出力(エアコンのON/OFF)に利用するためです。 したがって、ここで実装するモジュール・プログラムはこの作業だけで使用し、完成形には使用しません。
赤外線リモコン受信モジュールをArduinoのデジタル入力に接続し、赤外線信号を読み取るプログラムを実装します。
const int READ_PIN = 7; // 赤外線センサのピン設定
int state = 0; // 赤外線センサの入力値格納変数
unsigned long now = micros(); // 現在経過時間
unsigned long change = micros(); // 最終更新時間
void setup()
{
// シリアル通信設定
Serial.begin(57600);
// 赤外線センサのPIN設定
pinMode(READ_PIN, INPUT);
// 起動時の赤外線センサの値を取得
state = digitalRead(READ_PIN);
}
void loop()
{
// 赤外線センサの入力値が変化するまで待機
if (state != digitalRead(READ_PIN)) {
// 現在の経過時刻を取得
now = micros();
// 前回の変化から今回の変化までの経過時刻を10マイクロ秒単位で出力
Serial.print((now - change) / 10, DEC);
// セパレータを出力
Serial.print(",");
// 最終更新時刻を更新
change = now;
// 変化後の値を保管
state = !state;
}
}
プログラムを実行し、リモコンをセンサに向けてボタンを押すと信号が読み取れます。
この数字の羅列は、10μs単位の赤外線信号のON/OFF状態が続いた時間を交互に表しています。
※一番目の値は、プログラムを実行してからリモコンを操作する(赤外線信号が受信できる)までの経過時間なので使用しません。
つまり、3370μ秒ON、1720μ秒OFF、380μ秒ON、460μ秒OFF、...という信号になります。
ちなみに、シリアル通信の速度を57600bpsに上げていますが、9600bpsでは正確に信号値が取得できませんでした。
(57600bpsの場合と比較して取得できる数値の数が少ない)
おそらく、シリアル通信の速度が遅いと通信処理がボトルネックとなり、センサが受信している信号値を取りこぼしてしまうようです。
赤外線LEDでリモコン信号の送信
では、読み取った信号を赤外線LEDで送信してみます。 5mm赤外線LEDをArduinoに接続します。
下記プログラムで、5秒おきにエアコンをON/OFFしてみます。
const int LED = 13;
const int STATE_ON = 1;
const int STATE_OFF = 0;
// 赤外線リモコンのOFF・ONデータ
int data[2][275] = {
{338,171,40,44,39,44,40,44,37,47,40,44,39,44,39,129,39,45,39,44,37,47,39,44,40,44,40,44,40,44,39,44,39,44,37,47,40,44,40,128,40,44,40,128,40,44,39,44,37,47,37,47,39,44,39,45,39,45,36,47,40,44,39,47,36,130,38,128,40,44,40,44,39,44,39,45,39,44,39,44,38,131,39,129,39,45,40,44,40,46,37,45,39,44,39,45,40,128,43,41,40,44,40,129,39,129,37,49,37,128,40,44,40,44,40,44,40,44,39,45,39,44,39,129,39,129,40,129,37,131,40,128,38,131,40,44,40,47,34,47,37,49,38,44,40,44,39,129,40,129,40,44,39,128,40,129,37,48,36,131,40,44,40,128,39,129,40,46,36,131,40,128,40,46,38,128,40,47,36,47,35,132,37,47,39,129,40,46,36,131,39,45,37,132,39,128,40,128,38,48,36,47,40,46,35,131,40,128,40,47,37,128,40,44,40,47,37,47,36,48,34,51,33,47,38,48,37,45,37,49,37,44,39,44,40,129,37,47,40,44,39,44,37,48,38,44,39,44,40,129,39,47,37,44,39,45,39,45,40,46,37,129,39,47,37,47,37,45,39,44,40,46,37,130,39},
{338,169,42,42,41,42,42,42,42,42,41,43,41,42,41,127,41,43,41,43,41,43,41,42,42,43,41,43,40,43,41,43,41,43,41,42,42,42,41,127,42,42,42,127,42,42,42,42,41,43,40,43,41,43,41,42,42,42,41,43,41,42,42,43,41,127,41,128,40,42,41,43,41,43,41,43,41,43,40,43,41,127,42,42,41,127,41,43,41,43,41,43,41,42,42,42,42,126,42,42,41,44,40,127,41,127,41,43,41,127,41,42,41,42,42,43,40,43,41,43,40,42,42,127,41,128,40,127,41,127,42,127,41,127,42,42,41,42,42,43,41,127,41,42,42,42,41,127,42,127,41,42,41,127,41,127,41,42,42,127,41,42,41,127,41,127,41,44,40,127,41,127,41,42,41,127,42,43,41,42,41,127,41,42,41,127,42,42,41,127,41,44,40,127,41,127,41,127,41,42,42,42,41,42,41,127,42,127,41,42,41,127,41,44,40,43,41,42,42,42,42,42,41,43,41,42,42,42,41,43,41,43,41,42,41,127,41,42,42,42,42,42,42,42,42,41,42,43,41,127,41,42,41,43,41,42,41,43,41,127,41,43,41,127,42,42,41,43,41,42,42,42,42,128,40}
};
/**
* setup
* 起動時に一度だけ実行
*/
void setup()
{
// LED接続ピンを出力に設定
pinMode(LED, OUTPUT);
}
/**
* loop
* 繰り返し実行
*/
void loop()
{
// エアコンをON
sendSignal(STATE_ON);
// 5秒間スリープ
delay(5000);
// エアコンをOFF
sendSignal(STATE_OFF);
// 5秒間スリープ
delay(5000);
}
// リモコン信号を送信
void sendSignal(int mode) {
int dataSize = sizeof(data[mode]) / sizeof(data[mode][0]);
for (int cnt = 0; cnt < dataSize; cnt++) {
unsigned long len = data[mode][cnt]*10;
unsigned long us = micros();
do {
digitalWrite(LED, (cnt%2) ? LOW : HIGH);
delayMicroseconds(8);
digitalWrite(LED, LOW);
delayMicroseconds(7);
} while (long(us + len - micros()) > 0);
}
}
赤外線信号について調べ始めたところ、難しい話の連続でメンタルが複雑骨折しました。
ただ、エアコンの赤外線信号については「デューティ比1/3、周波数38kHzで信号を形成すればよい」ということがわかりました。
簡単に説明すると、
- 38kHzという周波数を周期(秒)に変換すると26μ秒。26μ秒の信号を1単位とする。
- デューティー比が1/3なので、ONの信号では26μ秒のうち1/3を1、2/3を0とする。OFFの場合は26μ秒間0。
この信号を一単位として、受信した信号を再現できればよいということのようです。
このON信号・OFF信号の形成を行っているのが下記の箇所になります。
do {
digitalWrite(LED, (cnt%2) ? LOW : HIGH);
delayMicroseconds(8);
digitalWrite(LED, LOW);
delayMicroseconds(7);
} while (long(us + len - micros()) > 0);
1. 偶数番目のデータの場合はLEDをONにし、奇数番目のデータの場合はLEDはOFFのまま
2. 8μ秒待機する
3. LEDをOFFにする
4. 7μ秒待機する
5. 信号の長さに達するまで繰り返し
なぜこのプログラムで「デューティー比1/3の26μ秒の信号」が形成できるかを説明したのが下の図です。
現在の経過時刻を取得して(micros())規定の信号の長さに達しているかどうかの判定処理に数μ秒かかってしまうため、 デューティー比が1/3で全体で約26μ秒となるように、待機時間(8μ秒、7μ秒)を調整しているということです。
※計測用のプログラムをはさんでこの信号の長さを測ったところ29〜30μ秒でしたが、おそらく計測用プログラムをはさむことで while判定と同様にその処理にさらに時間を使っている可能性があるため、正確なベンチマークとは言えません。 周波数を正確に計測するにはオシロスコープ等を利用するのが現実的かと思われます。 また、8μ秒待機・7μ秒待機の箇所を7μ秒待機・6μ秒待機にしてもエアコンをつけられたので、 μ秒レベルで厳密に信号形成できなくても大丈夫のようです。
そして、このON信号・OFF信号を1単位とし、
「ON信号を3380μ秒間出力、OFF信号を1710μ秒間出力(つまり1710μ秒間0)、ON信号を400μ秒間出力、...」
しているということになります。
プログラムとしては、エアコンをつける信号と消す信号をdataという配列にハードコーディングで格納しておき、 リモコン信号を送信する関数sendSignal()でそれらを出し分けています。
ちなみに、赤外線は肉眼では見えないのですが、デジカメを介してみると目視できます。iPhoneのカメラでは見えませんでした。
これでエアコンが5秒おきにON/OFFされます。が、家計と妻の機嫌に悪影響なので速やかにArduinoを取り外します。
すべての機能を設計通りに組み合わせる
それでは、ここまでに実装した各機能を組み合わせて設計通りのモジュール・プログラムを実装します。
const int LED = 13; // 赤外線LED
const int STATE_ON = 1; // エアコンON状態を表す数値
const int STATE_OFF = 0; // エアコンOFF状態を表す数値
const int THRESHOLD = 25; // 気温の閾値
int state = 0; // エアコンの状態
float temp = 0.0; // 計測気温格納変数
float temps[] = {0.0, 0.0, 0.0, 0.0, 0.0}; // 過去の計測気温格納配列
// 赤外線リモコンのOFF・ONデータ
int data[2][275] = {
{338,171,40,44,39,44,40,44,37,47,40,44,39,44,39,129,39,45,39,44,37,47,39,44,40,44,40,44,40,44,39,44,39,44,37,47,40,44,40,128,40,44,40,128,40,44,39,44,37,47,37,47,39,44,39,45,39,45,36,47,40,44,39,47,36,130,38,128,40,44,40,44,39,44,39,45,39,44,39,44,38,131,39,129,39,45,40,44,40,46,37,45,39,44,39,45,40,128,43,41,40,44,40,129,39,129,37,49,37,128,40,44,40,44,40,44,40,44,39,45,39,44,39,129,39,129,40,129,37,131,40,128,38,131,40,44,40,47,34,47,37,49,38,44,40,44,39,129,40,129,40,44,39,128,40,129,37,48,36,131,40,44,40,128,39,129,40,46,36,131,40,128,40,46,38,128,40,47,36,47,35,132,37,47,39,129,40,46,36,131,39,45,37,132,39,128,40,128,38,48,36,47,40,46,35,131,40,128,40,47,37,128,40,44,40,47,37,47,36,48,34,51,33,47,38,48,37,45,37,49,37,44,39,44,40,129,37,47,40,44,39,44,37,48,38,44,39,44,40,129,39,47,37,44,39,45,39,45,40,46,37,129,39,47,37,47,37,45,39,44,40,46,37,130,39},
{338,169,42,42,41,42,42,42,42,42,41,43,41,42,41,127,41,43,41,43,41,43,41,42,42,43,41,43,40,43,41,43,41,43,41,42,42,42,41,127,42,42,42,127,42,42,42,42,41,43,40,43,41,43,41,42,42,42,41,43,41,42,42,43,41,127,41,128,40,42,41,43,41,43,41,43,41,43,40,43,41,127,42,42,41,127,41,43,41,43,41,43,41,42,42,42,42,126,42,42,41,44,40,127,41,127,41,43,41,127,41,42,41,42,42,43,40,43,41,43,40,42,42,127,41,128,40,127,41,127,42,127,41,127,42,42,41,42,42,43,41,127,41,42,42,42,41,127,42,127,41,42,41,127,41,127,41,42,42,127,41,42,41,127,41,127,41,44,40,127,41,127,41,42,41,127,42,43,41,42,41,127,41,42,41,127,42,42,41,127,41,44,40,127,41,127,41,127,41,42,42,42,41,42,41,127,42,127,41,42,41,127,41,44,40,43,41,42,42,42,42,42,41,43,41,42,42,42,41,43,41,43,41,42,41,127,41,42,42,42,42,42,42,42,42,41,42,43,41,127,41,42,41,43,41,42,41,43,41,127,41,43,41,127,42,42,41,43,41,42,42,42,42,128,40}
};
/**
* setup
* 起動時に一度だけ実行
*/
void setup()
{
// LED接続ピンを出力に設定
pinMode(LED, OUTPUT);
}
/**
* loop
* 繰り返し実行
*/
void loop()
{
// 過去4回分の計測気温を保管
temps[4] = temps[3];
temps[3] = temps[2];
temps[2] = temps[1];
temps[1] = temps[0];
// 現在の気温を計測
temp = analogRead(0);
// 現在の気温を摂氏に変換
temps[0] = ((5 * temp) / 1024) * 100;
// エアコンがOFF状態 かつ 直近5回の計測気温がすべて閾値を超えている場合
if (state == STATE_OFF && temps[0] > THRESHOLD && temps[1] > THRESHOLD && temps[2] > THRESHOLD && temps[3] > THRESHOLD && temps[4] > THRESHOLD) {
// エアコンをON
sendSignal(STATE_ON);
state = STATE_ON;
// 1hスリープ
delay(1 * 1000 * 60 * 60);
// エアコンがON状態 かつ 直近5回の計測気温がすべて閾値未満の場合
} else if (state == STATE_ON && temps[0] < THRESHOLD && temps[1] < THRESHOLD && temps[2] < THRESHOLD && temps[3] < THRESHOLD && temps[4] < THRESHOLD) {
// エアコンをOFF
sendSignal(STATE_OFF);
state = STATE_OFF;
}
// 5秒間スリープ
delay(5000);
}
// リモコン信号を送信
void sendSignal(int mode) {
int dataSize = sizeof(data[mode]) / sizeof(data[mode][0]);
for (int cnt = 0; cnt < dataSize; cnt++) {
unsigned long len = data[mode][cnt]*10;
unsigned long us = micros();
do {
digitalWrite(LED, (cnt%2) ? LOW : HIGH);
delayMicroseconds(8);
digitalWrite(LED, LOW);
delayMicroseconds(7);
} while (long(us + len - micros()) > 0);
}
}
アルゴリズムとして、「5秒おきに室温を取得し、直近5回の計測値がすべて基準値を上回って(下回って)いたらON(OFF)にする」としました。 理由としては、アナログ入力装置である温度センサの計測値が安定していない可能性を考慮したためです。(見ている限りほぼ安定していましたが)
感想・課題
ブラウザ越しのWebの世界とは異なり目の前にある物体の実装・制御なので、Webの世界では感じられない楽しさがありました。 課題として、このモジュール自体のON/OFFスイッチや測定気温をデジタル表示器に表示する等のアレンジをしてみたいです。
また、赤外線に関しては、まだその仕組みを理解しきれておらず不完全燃焼感が隠せません。 というのも、OFFを表す信号は、単純に0だけで構成されるのではなくON信号と同様1と0で形成されるようなのです......が、それはまた別のお話。