S SmartDocs
系列: Arduino cpp 376 行 · 更新于 2026-03-08

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);
}

相关文章