main.cpp
Arduino/CCIT4080_CL05_PKPD_Group2/src/main.cpp
/*
* CCIT4080 Health Monitor — Main UI
*
* Uses LVGL 8.x on ESP32-S3 with:
* - TFT_eSPI (320×240 ILI9341 / ST7789)
* - FT6336U capacitive touch panel
*
* Screens:
* 1. Main — digital clock + Settings gear button
* 2. Settings — 24-hour toggle, brightness slider
*/
#include <Arduino.h>
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <SPI.h>
#include "RAK14014_FT6336U.h"
/* ── Hardware Pin Definitions ─────────────────────── */
#define TOUCH_RST_PIN 1
#define TOUCH_INT_PIN 2
#define TOUCH_SDA_PIN 6
#define TOUCH_SCL_PIN 7
#define BACKLIGHT_PIN 15
/* ── Display Geometry ─────────────────────────────── */
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define LVGL_BUF_LINES 20
/* ── Hardware Instances ───────────────────────────── */
static TFT_eSPI tft = TFT_eSPI();
static FT6336U ft6336u;
/* ── LVGL Draw Buffer ─────────────────────────────── */
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[SCREEN_WIDTH * LVGL_BUF_LINES];
/* ── Clock State ──────────────────────────────────── */
static uint8_t hh, mm_clock, ss_clock;
static bool use24h = true;
static uint32_t lastClockTick = 0;
/* ── UI Handles ───────────────────────────────────── */
static lv_obj_t *scr_main = NULL;
static lv_obj_t *scr_settings = NULL;
static lv_obj_t *lbl_time = NULL;
static lv_obj_t *lbl_ampm = NULL;
static lv_obj_t *lbl_date = NULL;
/* ── Forward Declarations ─────────────────────────── */
static uint8_t conv2d(const char *p);
static void create_main_screen(void);
static void create_settings_screen(void);
static void update_clock_display(void);
/* ═══════════════════════════════════════════════════
* LVGL Display Flush
* ═══════════════════════════════════════════════════ */
static void disp_flush_cb(lv_disp_drv_t *disp,
const lv_area_t *area,
lv_color_t *color_p)
{
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors((uint16_t *)&color_p->full, w * h, true);
tft.endWrite();
lv_disp_flush_ready(disp);
}
/* ═══════════════════════════════════════════════════
* LVGL Touchpad Read
* Adjust the x/y mapping if touch orientation
* doesn't match your display rotation.
* ═══════════════════════════════════════════════════ */
static void touchpad_read_cb(lv_indev_drv_t * /*drv*/,
lv_indev_data_t *data)
{
if (ft6336u.read_td_status() > 0) {
uint16_t raw_x = ft6336u.read_touch1_x();
uint16_t raw_y = ft6336u.read_touch1_y();
/* Mapping for display rotation 1 (landscape).
* Swap / invert as needed for your panel. */
data->point.x = raw_y;
data->point.y = SCREEN_HEIGHT - 1 - raw_x;
data->state = LV_INDEV_STATE_PR;
} else {
data->state = LV_INDEV_STATE_REL;
}
}
/* ═══════════════════════════════════════════════════
* Event Callbacks
* ═══════════════════════════════════════════════════ */
static void settings_btn_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED)
lv_scr_load_anim(scr_settings, LV_SCR_LOAD_ANIM_MOVE_LEFT,
300, 0, false);
}
static void back_btn_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED)
lv_scr_load_anim(scr_main, LV_SCR_LOAD_ANIM_MOVE_RIGHT,
300, 0, false);
}
static void toggle_24h_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
lv_obj_t *sw = lv_event_get_target(e);
use24h = lv_obj_has_state(sw, LV_STATE_CHECKED);
update_clock_display();
}
}
static void brightness_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
lv_obj_t *slider = lv_event_get_target(e);
int32_t pct = lv_slider_get_value(slider);
analogWrite(BACKLIGHT_PIN, map(pct, 0, 100, 0, 255));
}
}
/* ═══════════════════════════════════════════════════
* Main Screen — Clock + Settings Gear
* ═══════════════════════════════════════════════════ */
static void create_main_screen(void)
{
scr_main = lv_obj_create(NULL);
lv_obj_set_style_bg_color(scr_main, lv_color_hex(0x0D1117), 0);
/* ── Settings gear button (top-right) ── */
lv_obj_t *btn = lv_btn_create(scr_main);
lv_obj_set_size(btn, 50, 50);
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -5, 5);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x21262D), 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x30363D), LV_STATE_PRESSED);
lv_obj_set_style_radius(btn, LV_RADIUS_CIRCLE, 0);
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_set_style_border_width(btn, 1, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0x30363D), 0);
lv_obj_add_event_cb(btn, settings_btn_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *icon = lv_label_create(btn);
lv_label_set_text(icon, LV_SYMBOL_SETTINGS);
lv_obj_set_style_text_font(icon, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(icon, lv_color_hex(0x58A6FF), 0);
lv_obj_center(icon);
/* ── Digital Clock ── */
lbl_time = lv_label_create(scr_main);
lv_obj_set_style_text_font(lbl_time, &lv_font_montserrat_48, 0);
lv_obj_set_style_text_color(lbl_time, lv_color_hex(0xF0F6FC), 0);
lv_label_set_text(lbl_time, "00:00:00");
lv_obj_align(lbl_time, LV_ALIGN_CENTER, 0, -15);
/* AM/PM indicator (hidden when 24-hour mode) */
lbl_ampm = lv_label_create(scr_main);
lv_obj_set_style_text_font(lbl_ampm, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(lbl_ampm, lv_color_hex(0x58A6FF), 0);
lv_label_set_text(lbl_ampm, "");
lv_obj_align_to(lbl_ampm, lbl_time,
LV_ALIGN_OUT_RIGHT_BOTTOM, 5, -5);
/* ── Date ── */
lbl_date = lv_label_create(scr_main);
lv_obj_set_style_text_font(lbl_date, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(lbl_date, lv_color_hex(0x8B949E), 0);
lv_label_set_text(lbl_date, __DATE__);
lv_obj_align(lbl_date, LV_ALIGN_CENTER, 0, 35);
/* ── Decorative accent line ── */
lv_obj_t *accent = lv_obj_create(scr_main);
lv_obj_set_size(accent, 200, 2);
lv_obj_align(accent, LV_ALIGN_CENTER, 0, 60);
lv_obj_set_style_bg_color(accent, lv_color_hex(0x58A6FF), 0);
lv_obj_set_style_border_width(accent, 0, 0);
lv_obj_set_style_radius(accent, 1, 0);
lv_obj_clear_flag(accent, LV_OBJ_FLAG_SCROLLABLE);
}
/* ═══════════════════════════════════════════════════
* Settings Screen
* ═══════════════════════════════════════════════════ */
static void create_settings_screen(void)
{
scr_settings = lv_obj_create(NULL);
lv_obj_set_style_bg_color(scr_settings, lv_color_hex(0x0D1117), 0);
/* ── Back button ── */
lv_obj_t *btn_back = lv_btn_create(scr_settings);
lv_obj_set_size(btn_back, 45, 40);
lv_obj_align(btn_back, LV_ALIGN_TOP_LEFT, 5, 8);
lv_obj_set_style_bg_color(btn_back, lv_color_hex(0x21262D), 0);
lv_obj_set_style_bg_color(btn_back, lv_color_hex(0x30363D),
LV_STATE_PRESSED);
lv_obj_set_style_shadow_width(btn_back, 0, 0);
lv_obj_set_style_border_width(btn_back, 1, 0);
lv_obj_set_style_border_color(btn_back, lv_color_hex(0x30363D), 0);
lv_obj_set_style_radius(btn_back, 8, 0);
lv_obj_add_event_cb(btn_back, back_btn_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *lbl_back = lv_label_create(btn_back);
lv_label_set_text(lbl_back, LV_SYMBOL_LEFT);
lv_obj_set_style_text_color(lbl_back, lv_color_hex(0x58A6FF), 0);
lv_obj_center(lbl_back);
/* ── Title ── */
lv_obj_t *title = lv_label_create(scr_settings);
lv_label_set_text(title, LV_SYMBOL_SETTINGS " Settings");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xF0F6FC), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
/* ── Row 1: 24-Hour Format ── */
lv_obj_t *row1 = lv_obj_create(scr_settings);
lv_obj_set_size(row1, 290, 50);
lv_obj_align(row1, LV_ALIGN_TOP_MID, 0, 60);
lv_obj_set_style_bg_color(row1, lv_color_hex(0x161B22), 0);
lv_obj_set_style_border_width(row1, 1, 0);
lv_obj_set_style_border_color(row1, lv_color_hex(0x30363D), 0);
lv_obj_set_style_radius(row1, 8, 0);
lv_obj_set_style_pad_hor(row1, 15, 0);
lv_obj_set_flex_flow(row1, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(row1, LV_FLEX_ALIGN_SPACE_BETWEEN,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_clear_flag(row1, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *lbl_24h = lv_label_create(row1);
lv_label_set_text(lbl_24h, "24-hour format");
lv_obj_set_style_text_color(lbl_24h, lv_color_hex(0xC9D1D9), 0);
lv_obj_t *sw = lv_switch_create(row1);
if (use24h) lv_obj_add_state(sw, LV_STATE_CHECKED);
lv_obj_set_style_bg_color(sw, lv_color_hex(0x30363D), LV_PART_MAIN);
lv_obj_set_style_bg_color(sw, lv_color_hex(0x238636),
LV_PART_INDICATOR | LV_STATE_CHECKED);
lv_obj_add_event_cb(sw, toggle_24h_cb, LV_EVENT_VALUE_CHANGED, NULL);
/* ── Row 2: Brightness ── */
lv_obj_t *row2 = lv_obj_create(scr_settings);
lv_obj_set_size(row2, 290, 75);
lv_obj_align(row2, LV_ALIGN_TOP_MID, 0, 120);
lv_obj_set_style_bg_color(row2, lv_color_hex(0x161B22), 0);
lv_obj_set_style_border_width(row2, 1, 0);
lv_obj_set_style_border_color(row2, lv_color_hex(0x30363D), 0);
lv_obj_set_style_radius(row2, 8, 0);
lv_obj_set_style_pad_all(row2, 12, 0);
lv_obj_clear_flag(row2, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_t *lbl_bright = lv_label_create(row2);
lv_label_set_text(lbl_bright, LV_SYMBOL_EYE_OPEN " Brightness");
lv_obj_set_style_text_color(lbl_bright, lv_color_hex(0xC9D1D9), 0);
lv_obj_align(lbl_bright, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_t *slider = lv_slider_create(row2);
lv_obj_set_width(slider, 240);
lv_slider_set_range(slider, 10, 100);
lv_slider_set_value(slider, 80, LV_ANIM_OFF);
lv_obj_align(slider, LV_ALIGN_BOTTOM_MID, 0, -5);
lv_obj_set_style_bg_color(slider, lv_color_hex(0x30363D), LV_PART_MAIN);
lv_obj_set_style_bg_color(slider, lv_color_hex(0x58A6FF),
LV_PART_INDICATOR);
lv_obj_set_style_bg_color(slider, lv_color_hex(0xF0F6FC),
LV_PART_KNOB);
lv_obj_add_event_cb(slider, brightness_cb, LV_EVENT_VALUE_CHANGED, NULL);
}
/* ═══════════════════════════════════════════════════
* Clock Helpers
* ═══════════════════════════════════════════════════ */
static void update_clock_display(void)
{
char buf[16];
if (use24h) {
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hh, mm_clock, ss_clock);
lv_label_set_text(lbl_time, buf);
lv_label_set_text(lbl_ampm, "");
} else {
uint8_t h12 = hh % 12;
if (h12 == 0) h12 = 12;
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", h12, mm_clock, ss_clock);
lv_label_set_text(lbl_time, buf);
lv_label_set_text(lbl_ampm, hh < 12 ? "AM" : "PM");
}
}
static uint8_t conv2d(const char *p)
{
uint8_t v = 0;
if ('0' <= *p && *p <= '9') v = *p - '0';
return 10 * v + *++p - '0';
}
/* ═══════════════════════════════════════════════════
* Arduino setup / loop
* ═══════════════════════════════════════════════════ */
void setup(void)
{
Serial.begin(115200);
/* Seed clock from compile time */
hh = conv2d(__TIME__);
mm_clock = conv2d(__TIME__ + 3);
ss_clock = conv2d(__TIME__ + 6);
/* ── TFT ── */
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setSwapBytes(true);
/* ── Backlight ── */
pinMode(BACKLIGHT_PIN, OUTPUT);
analogWrite(BACKLIGHT_PIN, 204); /* ~80 % */
/* ── Touch panel (FT6336U over I2C) ── */
Wire.begin(TOUCH_SDA_PIN, TOUCH_SCL_PIN);
delay(100);
pinMode(TOUCH_RST_PIN, OUTPUT);
digitalWrite(TOUCH_RST_PIN, LOW);
delay(50);
digitalWrite(TOUCH_RST_PIN, HIGH);
delay(100);
ft6336u.begin();
/* ── LVGL core ── */
lv_init();
lv_disp_draw_buf_init(&draw_buf, buf1, NULL,
SCREEN_WIDTH * LVGL_BUF_LINES);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_WIDTH;
disp_drv.ver_res = SCREEN_HEIGHT;
disp_drv.flush_cb = disp_flush_cb;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read_cb;
lv_indev_drv_register(&indev_drv);
/* ── Build UI ── */
create_main_screen();
create_settings_screen();
lv_scr_load(scr_main);
update_clock_display();
lastClockTick = millis();
Serial.println("[UI] LVGL ready — main screen loaded");
}
void loop(void)
{
lv_timer_handler();
/* Tick the clock every second */
if (millis() - lastClockTick >= 1000) {
lastClockTick += 1000;
if (++ss_clock >= 60) { ss_clock = 0; mm_clock++; }
if (mm_clock >= 60) { mm_clock = 0; hh++; }
if (hh >= 24) hh = 0;
update_clock_display();
}
delay(5);
}
関連記事
lv_conf.h
lv_conf.h — c source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/lv_conf.h).
記事を読む →2.4_max30102.cpp
2.4_max30102.cpp — cpp source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/src/Combination/2.4_and_max30102_test/2.4_max30102.cpp).
記事を読む →bluetooth.cpp
bluetooth.cpp — cpp source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/src/Test_components/bluetooth_test/bluetooth.cpp).
記事を読む →ec11.cpp
ec11.cpp — cpp source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/src/Test_components/button_test/ec11.cpp).
記事を読む →1.69_inch_display.cpp
1.69_inch_display.cpp — cpp source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/src/Test_components/display_test/1.69_inch_display/1.69_inch_display.cpp).
記事を読む →2.4_inch_display.cpp
2.4_inch_display.cpp — cpp source code from the Arduino learning materials (Arduino/CCIT4080_CL05_PKPD_Group2/src/Test_components/display_test/2.4_inch_display/2.4_inch_display.cpp).
記事を読む →