行き当たりばったり電子工作

昔の論理IC電子工作少年が、四苦八苦しながら現在の電子工作に挑戦する話。

Wi-Fi時計の改良

はじめに

前のエントリーに書いたESP32を用いたWi-Fi時計ですが、リビングに置いておく用に使おうとすると、電気を消したあとも点灯しているのが嫌なので、明るさに応じてON/OFFできるように改造しようと思いました。

要するに、暗くなると消灯させたいわけですが、ESP32なので暗くなったらディープスリープモードに入り、明るくなったら復帰するようにします。

ESP32を眠らせても、LCDディスプレイとRTCの電源が入ったままでは意味が無いので、こちらの電源制御も必要ということになります。

使ったもの

  • D1 mini ESP32 ESP-WROOM-32
  • adafruit PCF8523搭載 RTCモジュール
  • 3.5インチ TFT LCD ディスプレイ 480×320(ILI9488使用)
  • トランジスタ 2SC1815
  • フォトレジスタcds
  • 半固定抵抗
  • 抵抗×3

トランジスタ以降が、今回追加したものです。

配線図

改良版 Wi-Fi 時計 配線図

むちゃくちゃややこしくなっていますが、それは単にセンスが無いだけです。これだと分かりにくいので、LCDディスプレイとRTCの電源制御部分を取り出した回路図が下の図です。

電源制御部分回路図

元の回路のままLCDディスプレイとRTCでどのくらいの電流になっているかを計測したら、だいたい60~70mAだったのでIOピンからの出力だけではLCDとRTCを駆動できません。実際に直接つないでみたら息も絶え絶えに点灯している状態だったので、トランジスタを入れたスイッチング回路を構成しました。電流から、トランジスタは2SC1815で大丈夫かなと思いました。最大定格150mAなので。
スイッチング回路なので、かなり大雑把な計算でベース電流1mAとしてベース抵抗を(3.3v - 1.0v) / 0.001 ≒ 2.2KΩとして、ベース・エミッタ間抵抗を1KΩとしています。このあたり、あんまり自信ないですが、ちゃんと発熱もなく動いているので大丈夫でしょう(テキトー)。



一方、光を感知する部分はこちら。フォトレジスタcdsを使って分圧回路を作って、直接デジタル入力に入れています。半固定抵抗は感度調整のため。

光感知部 回路図

cdsは手元にあったものですが、実測したところ暗くしたときに2MΩくらい、部屋の明るさで2kΩくらいでした。半固定抵抗は手元にあったもので10kΩ。ですので、分圧のための抵抗は1kΩにしましたが、実際には半固定抵抗での調整が支配的です。IOピンへの入力は暗いとHIGH、明るいとLOWになります。

プログラム

#include <WiFi.h>
#include "time.h"
#include "sntp.h"
#include "RTClib.h"
#include "SPI.h"
#include "TFT_eSPI.h"

// NTP関連定義
const char* ssid       = "*******";               // SSID
const char* password   = "*******";            // パスワード
const char* ntpServer1 = "ntp.nict.jp";          // NTPサーバ1
const char* ntpServer2 = "ntp.jst.mfeed.ad.jp";  // NPTサーバ2
const char* time_zone  = "JST-9";                // タイムゾーン

// RTCオブジェクト
RTC_PCF8523 rtc;

// TFT LCDオブジェクト
TFT_eSPI tft = TFT_eSPI();

// グローバル変数
int old_sec = -1, old_day = -1;  // 前回の日と秒
int counter=0;                   // 暗くなった時のカウンター


// 時刻合わせ後のコールバック関数
void timeavailable(struct timeval *t)
{
  char buf[255];
  struct tm timeinfo;

  // RTC時刻合わせ前の時刻表示(デバッグ用)
  DateTime rtctime=rtc.now();
  tft.setCursor(10, 260);
  tft.setTextFont(1);
  tft.setTextSize(3);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  sprintf(buf, "RTC=%02d:%02d:%02d", rtctime.hour(), rtctime.minute(),rtctime.second());
  tft.print(buf);

  // RTCの時刻合わせ 
  getLocalTime(&timeinfo);
  rtc.adjust(DateTime(timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec));

  // ローカルタイムの表示(デバッグ用)
  tft.setCursor(240, 260);
  tft.setTextFont(1);
  tft.setTextSize(3);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  sprintf(buf, "NTP=%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
  tft.print(buf);

  // 時刻合わせをした時刻の表示
  DateTime now=rtc.now();
  tft.fillRect (445,140,10,10,TFT_BLACK);
  tft.fillTriangle (435,150,465,150,450,170,TFT_BLACK);
  tft.fillCircle (450,150,10,TFT_WHITE);
  tft.setCursor(437, 170);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextFont(1);
  tft.setTextSize(1);
  sprintf(buf, "%02d:%02d", now.hour(), now.minute());
  tft.print(buf);
}

// NTPサーバを用いた時刻合わせ関数
void call_ntp() {
  tft.fillCircle (450,150,10,TFT_BLACK);
  tft.fillRect (445,140,10,10,TFT_WHITE);
  tft.fillTriangle (435,150,465,150,450,170,TFT_WHITE);
  configTzTime(time_zone, ntpServer1, ntpServer2);
}

void setup () {
  // 初期設定
  Serial.begin(57600);
  delay(5000);
  while (!Serial); // wait for serial port to connect. Needed for native USB

  // ピンモードの指定
  pinMode (17, OUTPUT);          // LCD、RTC駆動スイッチ用
  pinMode (26, INPUT_PULLDOWN);  // 明暗検知用

  // LCD、RTC 駆動
  digitalWrite(17, HIGH);
  delay(500);

  // RTC初期設定
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }
  if (! rtc.initialized() || rtc.lostPower()) {
    Serial.println("RTC is NOT initialized, let's set the time!");
  }
  rtc.start();

  // RTCの更正値計算(42時間で16秒進んでいた場合)
  float drift = 16; // 観察期間での誤差(単位は秒で+が進んだ場合)
  float period_sec = (42 * 3600);  // 観察期間(秒)
  float deviation_ppm = (drift / period_sec * 1000000); // 偏差(μ秒)
  float drift_unit = 4.069; // 毎分修正する場合の単位(2時間毎の場合は4.34)
  int offset = round(deviation_ppm / drift_unit); // 最終的なオフセット値

  // RTCキャリブレーション設定(1分毎に修正する場合)
  rtc.calibrate(PCF8523_OneMinute, offset); 

  // TFT LCD 初期設定
  tft.init();
  tft.fillScreen(TFT_BLACK);
  tft.setRotation(3);
  tft.setTextFont(7);
  tft.fillRect (10,148,400,4,TFT_WHITE);

  // NTP時刻合わせ後のコールバック関数の設定
  sntp_set_time_sync_notification_cb( timeavailable );

  // Wi-Fi接続
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(" CONNECTED");

  // NTPを用いた時刻合わせ
  call_ntp();
  
}

void loop () {
  char buf[255];

  // RTCから時刻を取得    
  DateTime now = rtc.now();

  // 前回取得から秒が変わっていたら時刻表示を変更する
  if (now.second() != old_sec) {
    tft.setCursor(10, 20);
    tft.setTextFont(7);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setTextSize(2);
    sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
    tft.print(buf);
    old_sec=now.second();
  }

  // 前回取得から日が変わっていたら年月日表示を変更する
  if (now.day() != old_day) {
    tft.setCursor(80, 180);
    tft.setTextFont(7);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setTextSize(1);
    sprintf(buf, "%04d-%02d-%02d", now.year(), now.month(), now.day());
    tft.print(buf);
    old_day = now.day();
  }

  // 0時、4時、8時、12時、16時、20時にNTPによる時刻合わせを行う
  if (now.minute()==0 && now.second()==0) {
    int hour = now.hour();
    if (hour == 0 || hour == 4 || hour == 8 || hour == 12 || hour == 16 || hour == 20) {
      call_ntp();
    }
  }

  // 明暗判定(ピン26が1なら暗い)
  int inp = digitalRead(26);
  if (inp==1) {
    // 暗い場合にはカウンターインクリメント
    counter++;
  }
  else {
    // 明るい場合はカウンターリセット
    counter = 0;
  }

  // 半固定抵抗調整用デバッグ出力
  Serial.print ("input = ");
  Serial.print (inp);
  Serial.print (",");
  Serial.println (counter);

  // カウンター100でディープスリープモードに入る
  if (counter >= 100) {
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_26, LOW);
    esp_deep_sleep_start();
    delay(100);
  }

  // 0.1秒の待機
  delay(100);
}

前回記事から追加したのは、LCDディスプレイとRTCの電源管理部分と、明暗によるディープスリープへの移行です。暗くなった時にすぐに消えるのではなく、カウンターで100数えてからディープスリープするようにしています。

まず81、82行目でピンモードを指定し、85行目でピン17をHIGHにすることでLCDディスプレイとRTCの電源をONにしています。

明暗判定とディープスリープモードへの移行は、169~191行目。途中でデバッグ用にシリアル出力にピン26の値を出力しています。これを見つつ明るさを変えながら半固定抵抗を調整することができます。ディープスリープのWakeUpモードはext0にして、ピン26が再びLOWになる、すなわち明るくなったら目覚めるようにしています。

課題

とりあえずこれで、「部屋が暗くなると消灯するWi-Fi時計」はできましたが、実際に動かしていると、たまにエラーを起こしてリブートがかかることがあります。なぜかよく分からないのですよね。どこかでメモリの不正アクセスがあるのか...少し様子見しながら原因を探ろうと思います。