01.T-Watch S3 plus + LGVL 9で簡易時計表示
01.T-Watch S3 plus + LGVL 9で簡易時計表示
T-Watch S3 が中国の空港でのX線検査で壊れました。朝は動作していて、保安検査前にかばんの中に入れて、出てきて着けようとしたら電源onしません。何も動作しなくなってしまったため、T-Watch S3 Plusを購入しました。427元(約9,600円)でした。バッテリー容量が2倍(470mAh→940mAh)になったのですが、厚みが13→20mmと1.5倍になってしまいました。ハードウェア構成は基本的に同じで、GPSが追加されています。
サンプルスケッチを見るとどれもLVGLを使用しているので、LVGLを使用して時計表示を作ってみました。ただし、時刻合わせはスケッチ内に設定した時刻を使用します。これでも±1分に合わせることができます。
T-Watch S3 Plus製品
https://lilygo.cc/products/t-watch-s3-plus
スケッチ例は、LVGL(Light and Versatile Graphics Library)を使用しています。
https://docs.lvgl.io/master/getting_started/
仕様
S3 Plus vs (S3)・GPS Position:あり (無し)
・BAT:940mAh (470mAh)
・RTC:PCF8563 (あり?)
S3 Plus と S3 が同じ仕様
・MCU:ESP32-S3・FLASH:16MB
・PS RAM:8MB
・Platform:Arduino-IDE, ESP-IDF, VS Code, Micropython
・Wi-Fi:802.11 b/g/n
・BLE:V5.0
・画面:1.54inch 240x240 touch
・Microphone:あり
・MAX98357A:スピーカアンプ
・BMA423:3軸加速度センサー
・DRV2605:振動モータ
・AXP2101:電源管理
・ST7789V:LCDコントローラ
・SX1262:LoRa Transceiver
電源のon/off
・電源on : ボタンを2秒間押す。(ボタンは電源ボタンのみ)・電源off : 6秒間押す
* BOOTボタンはダウンロードモードに入るための内蔵ボタンです。
BOOTボタン位置(写真)
https://github.com/Xinyuan-LilyGO/LilyGoLib/blob/master/docs/lilygo-t-watch-s3.md
ライブラリ
LilyGoLib (TーWatch S3以降の機種)https://github.com/Xinyuan-LilyGO/LilyGoLib
注意:以下は使用しません。
TTGO_TWatch_Library (T-Watch-2020以前の機種)
https://github.com/Xinyuan-LilyGO/TTGO_TWatch_Library/tree/t-watch-s3
開始方法など
https://wiki.lilygo.cc/get_started/en/Wearable/T-Watch-S3-PLUS/T-Watch-S3-PLUS.html6.15 消費電力(電流)
電流 タイマー 電源 起動 タッチ バックアップ(uA) ボタン ボタン パネル 電源
50 Shutdown ✖ ✖ ✖ ✖ 〇
460 Deep Sleep 〇 ✖ ✖ ✖ ✖
460 Deep Sleep ✖ 〇 〇 ✖ ✖
510 Deep Sleep 〇 ✖ ✖ ✖ 〇
530 Deep Sleep ✖ 〇 〇 ✖ 〇
1080 Deep Sleep ✖ ✖ ✖ 〇 ?
2380 Light Sleep ✖ 〇 〇 〇 ?
7.3.Arduinoのクイックスタート
1.Arduino-IDE をインストール (2.3.7 最新でした)2.(ボード) esp32 by Espressif Systems 3.0.7 → 3.3.5(2025/12 最新)
* 3.3.0-alpha1(2025/5)以上の事
3.(ライブラリ) LilyGoLib by LilyGo 無し → 0.1.0(最新)
5.(ライブラリ) LilyGoLib-ThirdParty をインストール
https://github.com/Xinyuan-LilyGO/LilyGoLib-ThirdParty
・ESP8266Audio by Earle F. 1.9.9→2.0.0にup(最新は2.4.1)
・TinyGPSPlus by Mikal Hart 1.1.0(済でした)
・lvgl by kisvegabor 9.3.0→9.2.2 (最新は9.4.0) セットVerは下記参照
・RadioLib by Jan Gromes 7.1.0→7.4.0(最新)
・XPowersLib by Lewis He 0.2.6→0.3.1(最新は0.3.2)
・SensorLib by Lewis He 0.2.1→0.3.3(最新)
・IRremoteESP8266 Fork(赤外受信 略)
・ESP32-BLE-Mouse-fork@0.3.1 (Bluetoothマウスとして動作 略)
LilyGoLib-ThirdParty のすべてのディレクトリを ArduinoIDE の libraries ディレクトリにコピー。
(注意: LilyGoLib-ThirdPartyディレクトリ自体をコピーするのではなく、LilyGoLib-ThirdPartyディレクトリ内のフォルダをlibrariesディレクトリにコピー)
最新Verにアップする前に、正常に動作することを確認。問題発生時は戻します。
6.(動作確認用スケッチ) Arduino-IDE > ファイル > スケッチ例 > (カスタムライブラリのスケッチ例) lvgl > Arduino > LVGL_Arduino.ino
7.(ボードの設定) Arduino-IDE > ツール にて
・ボード:esp32 > LilyGo T-Watch-S3
・ポート:使用ポート
以下、規定値です。
・USB CDC On Boot:"Enabled" ←シリアル表示を有効
・CPU Frequency:"240MHZ(WiFi)" ←最速
・Core Debug Level:"None" ←Debugなし
・USB DFU On Boot:"Disable" ←無効
・Erase All Flash Before Sketch Upload:"Disable" ←スケッチup前に全Flash消去を無効
・Events Run On:"Core 1" ←イベント実行コアは1
・JTAG Adapter:"Disable" ←JTAGアダプタ無効
・Arduino Runs On:"Core 1" ←Arduino 実行コアは1
・USB Firmware MSC On Boot:"Disable" ←無効
・Partition Scheme:"16M Flash(3M APP/9.9MB FATFS)" ←パーティション
・Board Revision:"Radio-SX1262" ←ボードリビジョン
・Upload Mode:"UART0/Hardware CDC" ←アップロードモード
・Upload Speed:"921600" ←アップロード速度 最速
・USB Mode:"Hardware CDC and JTAG" ←USBモード
LVGL
Ver
9.2.2とします。(LilyGoLibの動作確認より)C:\Users\.....\Documents\Arduino\libraries\LilyGoLib\library.json
の35-48行目は
・RadioLib 7.1.2
・lvgl 9.2.2
・XPowersLib 0.2.9
・SensorLib 0.3.1
となっています。よって、LilyGoLibの動作確認は、lvgl 9.2.2 なので、lvglは最新の 9.4 にせず、今のところ 9.2.2にします。
lv_conf.h の作成
C:\Users\.....\Documents\Arduino\libraries\lvgl\lv_conf_template.hを通常は、
C:\Users\.....\Documents\Arduino\libraries\lv_conf_template.h
にcopyしますが、LilyGoLibを使用している時は、
C:\Users\.....\Documents\Arduino\libraries\LilyGoLib\src\lv_conf_template.h
にcopyして、
lv_conf.h にファイル名を変更します。
ファイル内容の修正
lv_conf.hの15 #if 0 → 1
に変更(ファイルの内容を有効にする)
30 #define LV_COLOR_DEPTH 16
(パネルで使用されている色深度に合わせますが、よくわからないのでそのままにします)
484-504 (使用するサイズの英数フォントを0→1にします)
510 #define LV_FONT_SIMSUN_16_CJK 0→1
(日本語の一部が表示できる16px fontを使用できるようにします)
522 #define LV_FONT_DEFAULT &lv_font_simsun_16_cjk
(それをデフォルトfontとします)
logはエラーが出るので1にはしませんでした。
LovyanGFX
lovyanGFX(1.2.7 2025/5)では gpio_hal_iomux_func_selでエラーが出て止まってしまいます。Copilotによると
gpio_hal_iomux_func_sel() は、ESP32の低レベルGPIO設定に使われる関数で、特定のピンに特定の機能を割り当てるために使われていました。ESP-IDF v5.5.1以降では削除または非推奨になっていて、宣言されていない というエラーが出ます。
Espressif/Arduino ESP32のVer 一部略
https://github.com/espressif/arduino-esp32/tags より
・v3.3.5(2025/12) ESP-IDF v5.5.1+に基づく←LovyanGFXを使わずこれにする
・v3.3.3(2025/11) ESP-IDF v5.5.1+に基づく
・v3.3.1(2025/09) ESP-IDF v5.5.1に基づく
・v3.3.0(2025/07) ESP-IDF v5.5.0に基づく
・3.3.0-α1(2025/04) ESP-IDF v5.5、ESP32-C5 ECO1←製品はこれ以上が必要
・v3.2.1(2025/07) ESP-IDF v5.4.2に基づく←LovyanGFXはこれ以下
Arduino ESP32 3.3.0では、LovyanGFX(最新)でエラーが出ます。1つ下げて3.2.1にすると今度はLVGLでエラーが出ます。
開発中のLovyanGFX 1.2.9がESP-IDF v5.5に対応するらしいので、正式版が出た時に確認したいと思います。
動作
・年/月/日(曜日) 時:分を表示 (ただし、スケッチで時刻を設定)・充電マークを表示(充電中の時)
・バッテリー残量(%)を表示
* 画面で、目標時刻と残り分数は今のところ0に固定です。
スケッチ
// 時計表示 (ただし、設定する時刻はスケッチに記入)
// (ボード) esp32 by Espressif Systems 3.3.5(最新) 3.3.0以上
// lvgl by kisvegabor 9.2.2固定 (LilyGoLib 0.1.0より)
// LovyanGFX 1.2.7(最新)はエラーのため更新待ちで使用せず
// lv_conf.hでデフォルトfont:中日韓16pxの lv_font_simsun_16_cjk
// ツール > ボード > esp32 > LilyGo T-Watch-S3
// 電源部分は、PowerManageMonitor.ino を参照しました。
#include <LilyGoLib.h> // LilyGoLib 0.1.0(最新)を使用
#include <LV_Helper.h> // LVGL 9.2.2を使用
#include <time.h> // 時間を使用
long unsigned int BattInterval; // バッテリー測定間隔用
int BattPer; // バッテリー残量
bool BattCher; // 1=充電マーク or " "
lv_obj_t *label11, *label12, *label13, *label14, *label15, *label21, *label22, *label42; // setupとloopで使用
char youbi[7][4] = { "日", "月", "火", "水", "木", "金", "土" }; // 曜日配列
void setup() {
Serial.begin(115200); // シリアルモニタの通信速度
instance.begin(); // LilyGoLibを初期化
beginLvglHelper(instance); // LVGLを初期化
instance.pmu.enableBattDetection(); // 電源機能の有効化
//instance.rtc.setDateTime(2026, 1, 9, 15, 49, 55); // 1回目8分後の時刻にRTCセット 2回目1分後にセット その後注釈に
lv_display_t *disp = lv_display_create(240, 240); // 指定解像度で新しいディスプレイを作成
lv_style_t style_48; // 基本スタイル
lv_style_init(&style_48); // スタイル初期化
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_make(0, 0, 0), LV_PART_MAIN); // rgb 黒地
lv_obj_set_style_text_color(lv_screen_active(), lv_color_make(0, 255, 0), LV_PART_MAIN); // rgb 緑字
// 1行目1 年/月/日( 表示はloop内
label11 = lv_label_create(lv_screen_active()); // 現在の画面にラベル11を追加
lv_obj_set_style_text_font(label11,
&lv_font_montserrat_24, LV_PART_MAIN); // 英数字 24px
lv_obj_align(label11, LV_ALIGN_TOP_LEFT, 0, 0); // 左上からの位置
lv_obj_set_style_text_color(label11, lv_color_make(255, 255, 255), LV_PART_MAIN); // rgb 白字
lv_obj_set_style_transform_zoom(label11, 240, 0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384
// 1行目2 曜日 表示はloop内 デフォルトfont
label12 = lv_label_create(lv_screen_active()); // 現在の画面にラベル11を追加
lv_obj_align(label12, LV_ALIGN_TOP_LEFT, 130, 0); // 左上からの位置
lv_obj_set_style_text_color(label12, lv_color_make(255, 255, 255), LV_PART_MAIN); // rgb 白字
lv_obj_set_style_transform_zoom(label12, 352, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)320
// 1行目3 ")"
label13 = lv_label_create(lv_screen_active()); // 現在の画面にラベル11を追加
lv_obj_set_style_text_font(label13, &lv_font_montserrat_24, LV_PART_MAIN); // 英数字 24px
lv_obj_align(label13, LV_ALIGN_TOP_LEFT, 156, 0); // 左上からの位置
lv_obj_set_style_text_color(label13, lv_color_make(255, 255, 255), LV_PART_MAIN); // rgb 白字
lv_obj_set_style_transform_zoom(label13, 240, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)
lv_label_set_text(label13, ")"); // ラベルにセット
// 1行目4 充電マーク 表示はloop内
label14 = lv_label_create(lv_screen_active()); // 現在の画面にラベル12を追加
lv_obj_align(label14, LV_ALIGN_TOP_LEFT, 170, 5); // 左上からの位置
lv_obj_set_style_text_color(label14, lv_color_make(0, 255, 255), LV_PART_MAIN); // rgb 水色字
lv_obj_set_style_transform_zoom(label14, 240, 0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384
// 1行目5 Batt(%) 表示はloop内
label15 = lv_label_create(lv_screen_active()); // 現在の画面にラベル12を追加
lv_obj_set_style_text_font(label15, &lv_font_montserrat_24, LV_PART_MAIN); // 英数字 24px
lv_obj_align(label15, LV_ALIGN_TOP_LEFT, 182, 0); // 左上からの位置
lv_obj_set_style_text_color(label15, lv_color_make(0, 255, 255), LV_PART_MAIN); // rgb 水色字
lv_obj_set_style_transform_zoom(label15, 240, 0); // ズーム(=/256)倍 384(1.5) 320(1.25) 384
// 2行目1 "am" or "pm" 表示はloop内
label21 = lv_label_create(lv_screen_active()); // 現在の画面にラベル21を追加
lv_obj_set_style_text_font(label21,
&lv_font_montserrat_24, LV_PART_MAIN); // 英数字 24px
lv_obj_align(label21, LV_ALIGN_TOP_LEFT, 0, 70 * 0 + 40 + 25); // 左上からの位置
// 2行目2 現在時刻(12H) 表示はloop内
label22 = lv_label_create(lv_screen_active()); // 現在の画面にラベル22を追加
lv_obj_set_style_text_font(label22,
&lv_font_montserrat_48, LV_PART_MAIN); // 英数字 48px
lv_obj_align(label22, LV_ALIGN_TOP_LEFT, 60, 70 * 0 + 40); // 左上からの位置
lv_obj_set_style_transform_zoom(label22, 320, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)
// 3行目1 "設定" デフォルトfont
lv_obj_t *label31 = lv_label_create(lv_screen_active()); // 現在の画面にラベル31を追加
lv_obj_align(label31, LV_ALIGN_TOP_LEFT, 0, 70 * 1 + 40 + 25 - 10); // 左上からの位置
lv_obj_set_style_transform_zoom(label31, 448, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)320
lv_label_set_text(label31, "設定"); // テキスト
// 3行目2 設定時刻(24H)
lv_obj_t *label32 = lv_label_create(lv_screen_active()); // 現在の画面にラベル32を追加
lv_obj_set_style_text_font(label32,
&lv_font_montserrat_48, LV_PART_MAIN); // 英数字 48px
lv_obj_align(label32, LV_ALIGN_TOP_LEFT, 60, 70 * 1 + 40); // 左上からの位置
lv_obj_set_style_transform_zoom(label32, 320, 0); // ズーム(=/256)倍
lv_label_set_text(label32, "00:00"); // テキスト 目標時刻
// 4行目1 "あと" デフォルトfont
lv_obj_t *label41 = lv_label_create(lv_screen_active()); // 現在の画面にラベル41を追加
lv_obj_align(label41, LV_ALIGN_TOP_LEFT, 0, 70 * 2 + 40 + 25 - 10); // 左上からの位置
lv_obj_set_style_transform_zoom(label41, 448, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)320
lv_label_set_text(label41, "あと"); // テキスト
// 4行目2 残りの分数
label42 = lv_label_create(lv_screen_active()); // 現在の画面にラベル42を追加
lv_obj_set_style_text_font(label42,
&lv_font_montserrat_48, LV_PART_MAIN); // 英数字 48px
lv_obj_align(label42, LV_ALIGN_TOP_RIGHT, -60, 70 * 2 + 40); // 左上からの位置
lv_obj_set_style_transform_zoom(label42, 320, 0); // ズーム4(=/256)倍
lv_label_set_text(label42, "0"); // ラベルのテキスト 残り時間
// 4行目3 "分" デフォルトfont
lv_obj_t *label43 = lv_label_create(lv_screen_active()); // 現在の画面にラベル43を追加
lv_obj_align(label43, LV_ALIGN_TOP_LEFT, 200, 70 * 2 + 40); // 左上からの位置
lv_obj_set_style_transform_zoom(label43, 768, 0); // ズーム(=/256)倍 384(1.5) 320(1.25)
lv_label_set_text(label43, "分"); // ラベル1のテキスト 現在時刻
instance.setBrightness(100); // 明るさ off(0)-max(255)
}
void loop() {
char buf[64], buf2[64];
if (BattInterval < millis()) { // 時間を過ぎたら
struct tm timeinfo; // 時刻を取得するCライブラリ構造体
instance.rtc.getDateTime(&timeinfo); // RTCから読み込む
size_t written = strftime(buf, 64, "%Y/%m/%d(", &timeinfo); // 年/月/日(
lv_label_set_text(label11, buf); // ラベルにセット
written = strftime(buf2, 64, "%w", &timeinfo); // 曜日の数 日(0)-土(6)
sprintf(buf, "%s", youbi[atoi(buf2)]); // 曜日
lv_label_set_text(label12, buf); // ラベルにセット
written = strftime(buf, 64, "%P", &timeinfo); // am or pm
lv_label_set_text(label21, buf); // ラベルにセット
written = strftime(buf, 64, "%l:%M", &timeinfo); // 時:分
lv_label_set_text(label22, buf); // ラベルにセット
BattCher = instance.pmu.isCharging(); // 充電中か測定
lv_label_set_text_fmt(label14, "%s",
BattCher ? LV_SYMBOL_CHARGE : " "); // "充電マーク" or " "
BattPer = instance.pmu.getBatteryPercent(); // バッテリー残量(%)測定
if (BattPer < 0) BattPer = 0; // バッテリ残量最低値は0%
if (BattPer > 100) BattPer = 100; // バッテリ残量最高値は100%
String s = " " + String(BattPer); // 左に空白をつなげる
s = s.substring(s.length() - 3); // 右3文字をとる
lv_label_set_text_fmt(label15, "%s%%", s); // %
BattInterval = millis() + 2000; // 2秒ごとに測定
}
lv_timer_handler(); // lvglタスク処理は、ループ内に書く
delay(2); // 2mS待つ
}
* flash memory(3.1Mbyte)のうち、スケッチが34%使用。RAM(327kbyte)のうち、global変数が7%使用、local変数で301kbyte使用可能。(1000byte=1kbyteで計算)以下のVerを使用中
LilyGoLib 0.1.0
FFat 3.3.5
FS 3.3.5
Wire 3.3.5
SensorLib 0.3.3
SPI 3.3.5
RadioLib 7.1.0
TinyGPSPlus 1.1.0
ESP_I2S 3.3.5
XPowersLib 0.3.1
lvgl 9.2.2
esptool 5.1.0
