部屋の蛍光灯を使った光目覚ましをArduinoで自作した

※記事内に商品プロモーションを含む場合があります

例年10月ぐらいになると目覚まし、iPhoneのアラームで起きれない事があります。

それは何故なのか。
どうも日の出の時間が影響している様です。

私の部屋には小さな窓があり、夏など日照時間の長い時期は
窓から日光が入ってきます。

しかし、秋になるにつれ日の出が遅くなり
遅い時間にならないと日光が窓から入りません。

実際、強い光で目覚ましを行う目覚まし、
光目覚ましが販売されています。

色々な種類がある様で、inti(インティ)と言う商品が有名な様です。
ベッド脇に置くスペースが無いのと、お高いので自作する事にしました。

自作したArduninoベースの光目覚まし、リモコン信号を送信する事で部屋の蛍光灯を点ける

自作した光目覚ましの効果

市販品を買って無いので、自作した光目覚ましの効果です。

何処かのサイトで、起きる時間の15分前に光目覚ましをセットすると
良いと書かれていたので15分前にセットしています。

起きる時間には、iPhoneの目覚ましが鳴ります。

光目覚まし導入後は、iPhoneのアラームが鳴る前に起きる事が出来ます。
ほぼ、二度寝無しです。

12月に近くなり、寒くなると光目覚ましだけでは起きれない場合も出てきました。
やっぱり、暖房も連動させるとより効果的かもしれません。

光目覚まし無しだと、起きれなかった

先日、不規則な出勤だったので光目覚ましをOFFにしていたら、
2日も予定時間に起きる事が出来ず、家族に起こされました。

1日はアラームの音で起きた記憶があるのですが、
二度寝してしまった様です。

私の自作品はLEDでは無く、部屋の蛍光灯(シーリングライト)を
光源として使用していますが十分に効果があります。

部屋の蛍光灯を使った光目覚ましの自作

ベッドサイドなどにモノは置きたく無いので光源は部屋の蛍光灯を使います。

蛍光灯のON/OFFはリモコン信号を赤外線LEDで送信する事で行います。
よって、リモコンでON/OFF出来ない蛍光灯にはこの光目覚ましは使えません。

もちろん、部屋のスイッチを切った場合はリモコン信号を受け付けないので
寝る時はリモコンでOFFにする必要があります。

仕様

  • RTCを内蔵して、時間が分かる様にする
  • CDSを内蔵して、部屋の明るさを取得出来る様にする
  • 目覚まし指定時間になる、かつ部屋が暗い場合はリモコン信号送信
  • リモコン信号を送信しても部屋が明るくならない場合は、もう一度信号送信
  • 電源はシリアル変換モジュール経由、USB5V

実際には、部屋の温度が分かる様に温度計も内蔵しています。
まだマイコンのメモリには空きがあるのでゆくゆくは
エアコンのコントロールも出来る様にするため温度計も内蔵しました。

回路

全体の回路図はありません。

RTCには秋月で売っているDS1307 I2Cリアルタイムクロックモジュール(RTC)。
他のRTCより値が張りますが充電池内蔵なので場所も取らずお得です。

5V系では便利ですが、電源電圧が4.6V以上となっているので
3.3V系では使えません。

このRTCには。温度センサーDS18B20を搭載出来るパターンがあるので
私はここにDS18B20を搭載させました。

DS18B20では通常、プルアップ抵抗が必要になります。
RTCのパターンに搭載した場合はRTC内にネットワーク抵抗があるので
プルアップ抵抗は不要です。

CDSは適当に買ったら大きかったので、適当に買っても良いと思います。
今回買ったのはCDSセル 20mm MI20528。

大きさを考えずに買ったので部品の配置に悩み
RTC基板が外付けになりました。

CDSによる明るさの取得は分圧によって行うのでもう一つ抵抗が必要です。

今回は手持ちにあった3KΩの抵抗を使いました。

リモコン信号の送信はマイコン直でも動作しますが
光が弱いのか反応しない事も多いです。

そこで、トランジスタで増幅し4個の赤外線LEDを使い
リモコン信号を送信します。

赤外線LEDはOSI5FU5111C-40
トランジスタには手持ちにあった2SC3422
ベース抵抗には1KΩの抵抗を使用しました。

リモコン信号の送信は数ms以内に終わるのと、パルスなら1A流せるLEDなので
LEDの電流制限抵抗は無しにしました。

よくあるベース・エミッタ間の抵抗も無しにしました。

ベースは抵抗経由でマイコンに繋がっている事と
マイコンでは信号送信時以外OFFになっている事が理由です。

リモコン送信部も回路的にはこんな感じです。

リモコン送信部の簡単な回路図

リモコン出力はデジタル13番。
温度計1wireはデジタル5番。
明るさ(CDS)はアナログ0番。

RTCはI2C接続です。

欠点

電源電圧低下

最初、作った基板上でスケッチを書き込もうとすると失敗するので
シリアルモジュール上のLEDが点滅するので電圧不足だと判断。

シリアルモジュール上のポリスイッチを外し、外付けで300mA程の
ポリスイッチを追加しました。

電圧の安定化の為、すこし大きめですが16V1000μFのOSコンを
電源ラインに追加しました。

これでも、スケッチ書き込み時やリモコン送信時は電源LEDが一時的に暗くなりますが
正常動作するのでそのままにしています。

1ヶ月で3分ぐらい時間がズレる

DS1307の精度が悪いのか、1ヶ月に3分ほど時計がズレます。

1年放っておくと30分ぐらいズレるので、
スタンドアロンで使うのは難しいかもしれません。

現在はパソコンに繋いでいるのですが、
コマンドで時間を修正しています。

スケッチ

現在の所のスケッチです。
コマンドで時刻設定、アラーム時間設定、などが出来る様になっています。

//ひかり目覚まし by @eaxjp (C)https://b.eax.jp start:2016/10/19
/*
Hikari-Mezamasi コマンド一覧(小文字)
取得・他
ca アラーム時間取得 hh:mmで返す
ct RTC時刻確認 日付と時刻を返す
cb CDS明るさ取得 0~1024の間の数値で返す
cc 気温取得 xx.xの値で返す、85.0になる場合はエラー
r1 シーリングライト順送り赤外線送信 R1 SENDと返す
al 生存確認 OKと返す

設定 設定値が異常な場合は、ERROR DATAと返す
sa アラーム時刻セット
sa hh:mm —-> sa 05:15[CR/LF]

st RTC時刻セット
sa yymmddw-hhnnss —>1610242-212800[CR/LF]
年月日週-時分秒
wは曜日番号、1=日曜日~7=土曜日
yyは年、2016年の場合は16とする

*/

#include <avr/pgmspace.h>
#include <Wire.h>
#include <DS1307N.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>

int ans; //明るさ取得用
int ir_out = 13; //リモコン出力先(D13)
int maza_done = 0; //目覚ましフラグ
int akasiki = 300; //明るさの閾値
int meza_h; //目覚まし時間
int meza_m; //目覚まし分
// DS1307ライブラリ初期化
#define SQtimer_PIN 15 // RTC SQ(1S)タイマー(A1)
#define RTC1307 0x68 // 0xd0 << 1
DS1307 RTC( RTC1307 ) ;
// SQmode = true でRTCのSQに同期させることができる
const boolean SQmode = false ;

unsigned long us = micros();

#define ONE_WIRE_BUS 5 // DS19B20で使用するポート番号
#define SENSER_BIT 9 // 精度の設定bit
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

char us_buff[30]; //シリアル受信データ保管用バッファ
int us_cnt = 0; //シリアル受信データ保管用インデックス

// セットアップ //*****************************************************
void setup() {

pinMode(ir_out, OUTPUT);

Wire.begin();
RTC.SQset(); // RTC初期化、SQ(1秒)タイマー起動

sensors.setResolution(SENSER_BIT); //温度センサー初期化

//アラーム時間読み込み
meza_h = EEPROM.read(0);
if( meza_h < 0 or meza_h > 24){
meza_h = 6;
}
meza_m = EEPROM.read(1);
if( meza_m < 0 or meza_m > 59){
meza_m = 0;
}

delay(100);
Serial.begin(9600);
Serial.println(“Hikari-mezamasi by @eaxjp (C)https://b.eax.jp/”);
Serial.println(“Version:2016-10-24”);

}

// dataからリモコン信号を送信
void sendSignal_r1() {
// シーリングライト ON (順送り)
unsigned int data_r1[] = {
910,443,61,49,61,164,61,161,61,51,62,49,61,51,80,30,61,165,61,48,62,50,61,49,60,51,62,48,62,51,82,141,62,51,61,49,60,164,61,48,62,164,62,161,61,51,62,49,61,51,61,162,61,51,61,161,61,52,61,49,77,148,62,161,62,164,62,
};
int dataSize = sizeof(data_r1) / sizeof(data_r1[0]);
for (int cnt = 0; cnt < dataSize; cnt++) {
unsigned long len = data_r1[cnt]*10; // dataは10us単位でON/OFF時間を記録している
unsigned long us = micros();
do {
digitalWrite(ir_out, 1 – (cnt&1)); // cntが偶数なら赤外線ON、奇数ならOFFのまま
delayMicroseconds(8); // キャリア周波数38kHzでON/OFFするよう時間調整
digitalWrite(ir_out, 0);
delayMicroseconds(7);
}
while (long(us + len – micros()) > 0); // 送信時間に達するまでループ
}
}

void loop() {
if (Serial.available()>0){
char us_rx = Serial.read(); //シリアルから1バイト受信
us_buff[us_cnt] = us_rx; //シリアルデータをバッファーに保存

if (us_rx == 0x0A or us_rx == 0x0D){
//シリアルデータが改行(0x0A or 0x0D)だった場合はコマンド解析
//▼コマンド処理//*****************************************************************//

//▼ ca アラーム時間確認 ////////////////////
if(us_buff[0] == ‘c’ and us_buff[1] == ‘a’){
Serial.print(meza_h);
Serial.print(“:”);
Serial.println(meza_m);
}
//▲ ca アラーム時間確認 ////////////////////

//▼ r1 シーリングライト順送り送信 //////////
if(us_buff[0] == ‘r’ and us_buff[1] == ‘1’){
Serial.println(“R1 SEND”);
sendSignal_r1();
}
//▲ r1 シーリングライト順送り送信 //////////

//▼ cb CDS明るさ取得 ///////////////////////
if(us_buff[0] == ‘c’ and us_buff[1] == ‘b’){
int ans ;
ans = analogRead(0) ; // アナログ0番ピンから可変抵抗の値を読み込む
Serial.println(ans) ; // 読み取った値をパソコン(IDE)に送る
}
//▲ cb CDS明るさ取得 ///////////////////////

//▼ cc 温度取得 ////////////////////////////
if(us_buff[0] == ‘c’ and us_buff[1] == ‘c’){
sensors.requestTemperatures(); // 温度取得要求
Serial.println(sensors.getTempCByIndex(0)); //温度の取得&シリアル送信
}
//▲ cc 温度取得 ////////////////////////////

//▼ ct RTC時間確認 ////////////////////////
if(us_buff[0] == ‘c’ and us_buff[1] == ‘t’){
//
clockDef L ;
byte lastSQ ;
char buffer[5] ;
RTC.RTCread( &L ) ;
Serial.print ( L.year ) ;
Serial.print ( “/”) ;
Serial.print ( L.month ) ;
Serial.print ( “/”) ;
Serial.print ( L.day ) ;
Serial.print (” “) ;
Serial.print ( L.hour ) ;
Serial.print ( “:”) ;
Serial.print ( L.minute ) ;
Serial.print ( “:”) ;
Serial.println ( L.second) ;
}
//▲ ct RTC時間確認 ////////////////////////

//▼ sa アラーム時間設定 ////////////////////////////
if(us_buff[0] == ‘s’ and us_buff[1] == ‘a’){
int al_set_h = ((int(us_buff[3])-48)*10)+(int(us_buff[4])-48);
int al_set_m = ((int(us_buff[6])-48)*10)+(int(us_buff[7])-48);
int al_set_f =1;
if( al_set_h < 0 or al_set_h > 24){
al_set_f =0;
}
if( al_set_m < 0 or al_set_m > 59){
al_set_f =0;
}
if(al_set_f == 1){
EEPROM.write(0, al_set_h);
EEPROM.write(1, al_set_m);
meza_h = al_set_h;
meza_m = al_set_m;
Serial.print(“ALARM UPDATED at “);
Serial.print(meza_h);
Serial.print(“:”);
Serial.print(meza_m);
}else{
Serial.println(“ERROR DATA”);
}
}
//▲ sa アラーム時間設定 ////////////////////////////

//▼ st RTC時間設定 ////////////////////////
if(us_buff[0] == ‘s’ and us_buff[1] == ‘t’){

int rtc_set_y = ((int(us_buff[3])-48)*10)+(int(us_buff[4])-48);
int rtc_set_m = ((int(us_buff[5])-48)*10)+(int(us_buff[6])-48);
int rtc_set_d = ((int(us_buff[7])-48)*10)+(int(us_buff[8])-48);
int rtc_set_w = (int(us_buff[9])-48);
int rtc_set_h = ((int(us_buff[11])-48)*10)+(int(us_buff[12])-48);
int rtc_set_n = ((int(us_buff[13])-48)*10)+(int(us_buff[14])-48);
int rtc_set_s = ((int(us_buff[15])-48)*10)+(int(us_buff[16])-48);
int rtc_set_f =1;

if( rtc_set_y < 0 or rtc_set_y > 100){
rtc_set_f =0;
}
if( rtc_set_m < 0 or rtc_set_m > 12){
rtc_set_f =0;
}
if( rtc_set_d < 1 or rtc_set_d > 31){
rtc_set_f =0;
}
if( rtc_set_w < 1 or rtc_set_w > 7){
rtc_set_f =0;
}
if( rtc_set_h < 1 or rtc_set_h > 24){
rtc_set_f =0;
}
if( rtc_set_n < 0 or rtc_set_n > 60){
rtc_set_f =0;
}
if( rtc_set_s < 0 or rtc_set_s > 60){
rtc_set_f =0;
}

if(rtc_set_f == 1){
clockDef L;
L.year = rtc_set_y ; // 2015年の下2桁
L.month = rtc_set_m ;
L.day = rtc_set_d ;
L.weekday = rtc_set_w ; // 月曜 日曜日が1、土曜日が7
RTC.setDate( L ) ; // 年月日、曜日の設定
// (注) 日付設定でもSQが設定されている
L.hour = rtc_set_h ; // 24時間で設定する
L.minute = rtc_set_n;
L.second = rtc_set_s ;
RTC.setTime( L ) ; // 時分を設定
RTC.clockAjust( L.second ) ; // 秒を設定して時計スタート

Serial.print(“RTC UPDATED at “);

byte lastSQ ;
char buffer[5] ;
RTC.RTCread( &L ) ;
Serial.print ( L.year ) ;
Serial.print ( “/”) ;
Serial.print ( L.month ) ;
Serial.print ( “/”) ;
Serial.print ( L.day ) ;
Serial.print (” “) ;
Serial.print ( L.hour ) ;
Serial.print ( “:”) ;
Serial.print ( L.minute ) ;
Serial.print ( “:”) ;
Serial.println ( L.second) ;
}else{
Serial.println(“ERROR DATA”);
}

}
//▲ st RTC時間設定 ////////////////////////

//▼ al 生存時間確認 OKと返す////////////////////
if(us_buff[0] == ‘a’ and us_buff[1] == ‘l’){
Serial.println(“OK”);
}
//▲ al 生存時間確認 OKと返す////////////////////

//▼ ca アラーム時間確認 ////////////////////
if(us_buff[0] == ‘x’ and us_buff[1] == ‘x’){
//
}
//▲ ca アラーム時間確認 ////////////////////

//▲コマンド処理//*****************************************************************//
us_cnt = 0; //シリアル受信用インデックスのリセット
}else{
us_cnt++; //受信データが改行以外の場合、インデックスをインクリメント
}

}else{
//シリアルから受信が無い場合の処理は時間を確認の上、目覚ましの処理
clockDef L;
byte lastSQ;
char buffer[5];
RTC.RTCread( &L ); //RTCより現時刻を取得
if(L.hour == meza_h and L.minute == meza_m) { //目覚ましが設定時刻、まだ実行して無い場合は目覚ましを実行
if(maza_done == 0){
ans = analogRead(0) ; // アナログ0番ピンから明るさを取得
if(akasiki > ans){ //明るさが暗く閾値以下場合は目覚ましを実行
Serial.println (“Alarm Start”); //シリアルにアラーム開始を送信
sendSignal_r1(); //リモコンを送信
delay(1000); //1秒待つ
ans = analogRead(0) ; //明るさが暗く閾値以下場合は再度目覚ましを実行
if(ans < akasiki){
sendSignal_r1(); //リモコンを送信
}
delay(1000);
ans = analogRead(0) ; //明るさが暗く閾値以下場合は再度目覚ましを実行
if(ans < akasiki){
sendSignal_r1(); //リモコンを送信
}
delay(1000);
ans = analogRead(0) ; //2度リモコン送信しても明るさが変わらない場合はエラー
if(ans < akasiki){
Serial.println (“Alarm Error”); //シリアルにエラーを送信
}
}
}
maza_done = 1; //目覚ましフラグON(目覚まし実行済)
}else{
maza_done = 0; //目覚まし時間以外は、目覚ましフラグOFF
}
}
}