ESP32-DevKitC ESP-WROOM-32開発ボード(以下、ESP32-DevKitC)を使ってインターネット上のNTP(Network Time Protocol)サーバと時刻同期してRTC(リアルタイムクロック)モジュールの時刻合わせを行うLCD時計を作った際の作業メモです。時刻の精度を高めた上で温度・湿度・気圧・CO2濃度を測る環境センサを加えていきます。
集めたパーツ
ネット通販(No.1、4、11は秋月電子通商、他はAmazon)でパーツを集めてブレッドボード上で結線しました。ESP32-DevKitCの基板は横幅が広いのでブレッドボードは6穴タイプです。
ESP32-DevKitCはRTCを内蔵していますが標準実装はRC発振。精度を上げるために水晶発振子をチップ内蔵したDS3231SNを使ったRTCモジュールをI2Cで外部接続しました。
LCDモジュールは今後環境モニタとして温湿度、気圧などを追加表示するので、20文字 x 4行の5V系の2004タイプを使っています。
# | パーツ | 個数 |
1 | ESP32-DevKitC ESP-WROOM-32開発ボード | 1 |
2 | DS3231SN I2C RTCモジュール | 1 |
3 | 2004 LCDモジュール 20×4キャラクタ 青 I2C I/F モジュール、バックライト付き、 バックライト調整用半固定ボリューム付き | 1 |
4 | I2Cバス用双方向電圧レベル変換モジュール(PCA9306) プルアップ抵抗付き(1kΩx4個) | 1 |
5 | XL4015 可変DC-DCステップダウンコンバータ (3個入り) | 1 (手持ち) |
6 | DCジャック DIP化モジュール | 1 |
7 | サンハヤト ニューブレッドボード SAD-01 | 1 |
8 | ジャンパーワイヤ | 適量 |
9 | USBケーブル(USB A オス to microB オス) | 1 (手持ち) |
10 | コイン形リチウムイオン2次電池「LIR2032」(4個入り) | 1 |
11 | スイッチングACアダプター9V1.3A 100~240V | 1 |
モジュール間の結線図とLCD時計の組み立て(2022/4/21訂正)
結線図
各モジュール間をジャンパーワイヤで接続した際の結線図です。網掛け部分は温湿度センサやCO2センサ、microSDカードスロットなど拡張予定箇所です。
組立て
ブレッドボード上に ESP32-DevKitC、DS3231SN RTC、20行 4桁の2004 LCDモジュールを実装してジャンパーワイヤで結線します。I2C(SCL、SDA)の2線と給電(5V or 3.3V、GND)の2線、計4本の配線ワイヤでモジュール間をつないでいくので組み立ても容易です。
スケッチ書き換え時などESP32-DevKitC とPCのシリアルポート接続はUSBケーブル(USB A オス to microB オス、データ通信用)を使います。
電源は、9V出力のACアダプタからXL4015 可変DC-DCステップダウンコンバータで降圧した5Vを使っています。
1602 LCDモジュール利用の場合
手持ちの16行、2桁表示の1602 LCDモジュールに交換してみました。表示文字数が16文字内に収まるように曜日表示を3文字+括弧にします。
スケッチ(3)の修正箇所は下記2箇所です。
LiquidCrystal_I2C lcd(0x27, 16, 2); // 1602LCD利用時
const char* weekStr[7] = {"(Sun)","(Mon)","(Tue)","(Wed)","(Thu)","(Fri)","(Sat)"}; // 1602LCD利用時
バッテリーバックアップ
DS3231SNモジュールの背面にはバッテリーバックアップ用のコイン電池ホルダが付いているので、充電できるコイン形リチウムイオン2次電池「LIR2032」を入れておきます。
参考:
・ESP32-DevKitC ESP-WROOM-32開発ボード回路図 | akizukidenshi.com
・ESP32-DevKitC-1 pin-layout | espressif.com
各モジュールの電源電圧とI2C信号レベルの確認とI2C信号波形の観察(2021/12/6追記)
ESP32-DevKitCモジュールにはLDO(Low Dropout、NCP1117)が実装されているので、PCからのUSBバスパワー給電や5Vピンで5V外部電源にも直結できますが、ESP32チップ(GPIO)の動作電圧は3.3V動作です。
ESP32-DevKitCの5Vピンから給電する場合には3.3VはLDOが作り出すので、消費電力が大きいLCDモジュールやセンサ等の外付けモジュールは5Vの外部電源(DC-DCステップダウンコンバータ)からの給電で動作するものを選んでいます。
ESP32-DevKitCモジュールのI2C信号レベルは3.3Vなので、5V系のモジュールとの接続は「I2Cバス用双方向電圧レベル変換モジュール(PCA9306)、プルアップ抵抗付き(1kΩx4個)」でレベル変換しました。
# | モジュール名 | 給電電圧 | モジュールへの信号レベル | I2CSCL | I2CSDA | I2C
1 | ESP32-DevKitC | 5V ※5V0ピンから給電、LDOで5V → 3.3V。内部は3.3V動作。 | 5V ※双方向電圧 レベル変換モジュールで 5V <—> 3.3V変換。内部は3.3V動作。 | GPIO22 | GPIO21 |
2 | DS3231SN RTC | 5V (仕様:3.3~5.5V) | 5V (仕様:3.3~5.5V) | 印字 | 印字 |
3 | 2004 LCD I2C I/F | 5V | 5V | 印字 | 印字 |
「I2Cバス用双方向電圧レベル変換モジュール(PCA9306)、プルアップ抵抗付き(1kΩx4個)」のI2C波形をデジタルオシロスコープでモニタすると、ESP32-DevKitC側の3.3V信号が5Vに変換されていることが確認できました。
開発ツールarduino-esp32をインストール(2021/12/21更新)
arduino-esp32のインストール手順を別ページに纏めました。
ESP32-DevKitC上でArduino IDE(arduino-esp32)のスケッチを実行
スケッチ1:I2c Scannerで各モジュールのI2Cアドレスを確認
ArduinoサイトにあるI2CアドレスをスキャンするスケッチArduino Playground – I2cScannerを実行すると3つのI2Cアドレスが検出されます。
0x27はLCDモジュール(I2C I/F モジュール)のアドレス。0x68がRTCモジュールDS3231SNのアドレス。0x57はRTCモジュールのAtmel社のAT24C32 EEPROMのアドレスでした。
各モジュールのI2Cアドレスと、モジュール上のコネクタに割り当てられたSCL信号とSDA信号のピン番号のメモです。
# | モジュール | I2C アドレス | I2C SCL | I2C SDA |
1 | ESP32-DevKitC | マスタ | 22番 | 21番 |
2 | 2004 LCD +I2C I/Fモジュール | 0x27 | 印字 | 印字 |
3 | DS3231SN RTC AT24C32 EEPROM | 0x68 0x57 | 印字 | 印字 |
スケッチ2:WiFi接続してNTPサーバから時刻を取得してシリアルモニタに表示
ESP32開発元 Espressif Systems提供の「SimpleTime.ino」をArduino IDE(arduino-esp32)で動作確認しました。このスケッチをGitHub「arduino-esp32/SimpleTime.ino at master」からダウンロードします。
Time.h、TimeLib.hライブラリは、GitHub「PaulStoffregen/Time」サイトの「Code」プルダウンから「Download ZIP」でPCに「Time-master.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」で「Time-master.zip」を指定します。
ntpServerにはNTPサーバ(ntp.nict.jp)を、gmtOffset_secはローカル時刻とGMTとの差:9時間を秒(32400)で、daylightOffset_secは日本ではサマータイムは無いので(0)を設定します。
シリアルモニタへの日時の出力形式を
“%Y/%m/%d(%a) %H:%M:%S”
に変更しています。
スケッチ3:WiFi接続してNTPサーバから時刻を取得、DS3231SNと時刻同期してLCD表示
RTCモジュールに必要なライブラリ「DS3232RTC.h」を取り込みます。名称はDS3232ですがライブラリには「Arduino Library for Maxim Integrated DS3232 and DS3231 Real-Time Clocks」の記載があり、DS3231SNでも使えます。
GitHub「JChristensen/DS3232RTC」サイトの「Code」プルダウンから「Download ZIP」で、PCに「DS3232RTC-master.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」でダウンロードした「DS3232RTC-master.zip」を指定します。
同様に、今回使った2004 LCDモジュール表示に必要なライブラリ「LiquidCrystal_I2C.h」は、GitHub「johnrickman/LiquidCrystal_I2C」サイトからダウンロードします。
「esp_sntp.h」は arduino-esp32同梱モジュールです。詳細は Official develooment framework for ESP32 (ESP-IDF) に記述があります。mulong.meサイトを参考にESP32内蔵RTCの時刻が NTPで取得した時刻に一致しているかの判定に使っています。
下記のスケッチでは起動時にNTPとの時刻合わせを行いますが、定期的なNTPサーバとの時刻合わせ機能は未実装です。NTPとの時刻合わせは、ESP32-DevKitC基板上の「ENボタン(Enableボタン)」を押して再起動することで行っています。
電源を入れて時計表示までの間に少し待ち時間ある(その間LCDには表示なしで寂しい)ので無線LAN接続やJSTとの時刻合わせ状況のメッセージ表示を加えています。
ESP32_wifi_NTP_DS3232RTC_LCD.ino
※ここをクリックするとコード表示を開閉できます。
#include <WiFi.h>
#include <time.h> // Timeライブラリ
#include <LiquidCrystal_I2C.h> // LCD用ライブラリ
#include <DS3232RTC.h> // DS3232、DS3231用ライブラリ
#include <esp_sntp.h>
DS3232RTC myRTC(false);
// LiquidCrystal_I2C lcd(0x27, 16, 2); // 1602LCD利用時
LiquidCrystal_I2C lcd(0x27, 20, 4);
// const char* weekStr[7] = {"(Sun)","(Mon)","(Tue)","(Wed)","(Thu)","(Fri)","(Sat)"}; // 1602LCD利用時
// 2022/03/27:曜日の文字数を9文字固定に変更
const char* weekStr[7] = {"Sunday ","Monday ","Tuesday ","Wednesday","Thursday ","Friday ","Saturday "};
const char* ssid = "your ssid";
const char* password = "your password";
const char* ntpServer = "ntp.nict.jp";
const long gmtOffset_sec = 32400;
const int daylightOffset_sec = 0;
void setup() {
struct tm timeInfo;
Serial.begin(115200);
myRTC.begin();
lcd.init();
lcd.backlight();
//WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
lcd.print("."); // 進捗表示
delay(500);
}
// WiFi接続の表示
lcd.clear();
lcd.print("WiFi connected");
delay(2000);
lcd.clear();
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
lcd.clear();
lcd.print("JST synchro.");
delay(2000);
lcd.clear();
// 内蔵RTCの時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
lcd.print(">"); // 進捗表示
delay(1000);
}
//内蔵RTC時刻 = NTP時刻の表示
lcd.clear();
lcd.print("Time matched");
delay(2000);
lcd.clear();
// 内蔵RTCの時刻の取得
getLocalTime(&timeInfo);
// 内蔵RTCの時刻をDS3231に時刻設定
// setTime(12, 40, 0, 14, 11, 2021); // 手動設定・動作確認用(時、分、秒、日、月、年)
setTime(timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec, timeInfo.tm_mday, timeInfo.tm_mon + 1, timeInfo.tm_year + 1900);
myRTC.set(now());
//WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void loop(void) {
dispLCD();
delay(1000);
}
void dispLCD(void) {
// RTCから時刻取得
tmElements_t tm;
myRTC.read(tm);
// LCD表示
lcd.setCursor(0,0);
lcd.print(tm.Year + 1970);
lcd.print("/");
lcdzeroSup(tm.Month);
lcd.print("/");
lcdzeroSup(tm.Day);
lcd.print(" ");
lcd.setCursor(11,0);
lcd.print(weekStr[tm.Wday - 1]);
lcd.setCursor(0,1);
lcdzeroSup(tm.Hour);
lcd.print(":");
lcdzeroSup(tm.Minute);
lcd.print(":");
lcdzeroSup(tm.Second);
}
// 先頭のゼロ(0)を空白に置換
void lcdzeroSup(int digit) {
if(digit < 10)
lcd.print(' ');
lcd.print(digit);
}