# GPIO Dashboard

## Lab 9: Hardware GPIO Dashboard

### 1. โครงสร้างภาพรวมของ Lab

#### Why? - ทำไมต้องเรียนรู้เรื่องนี้

Dashboard ที่รวมทุก GPIO เป็นหัวใจของ Industrial HMI (Human-Machine Interface):

* **Complete Integration**: รวม LED + Button + ADC + PWM ไว้ในหน้าจอเดียว
* **Mixed I/O**: ควบคุม Output (LED) + อ่าน Input (Button, ADC) พร้อมกัน
* **Panel Layout**: จัดหน้าจอแบบ Panel สำหรับ Inputs/Outputs แยกกัน
* **Industrial Application**: คล้าย Control Panel ของเครื่องจักรในโรงงาน
* **Capstone Project**: รวมทุกสิ่งที่เรียนมาจาก Lab 6-8

#### What? - จะได้เรียนรู้อะไร

1. **Multi-device Control**: ควบคุม 3 LED + PWM + อ่าน Button + ADC
2. **Panel Layout**: จัด UI เป็น Input Panel (ซ้าย) + Output Panel (ขวา)
3. **Mixed Polling**: Timer callback อ่านทั้ง Button และ ADC พร้อมกัน
4. **Cross-control**: ADC ควบคุม PWM (analog input -> analog output)
5. **Status Indicators**: แสดงสถานะ Hardware ทั้งหมดแบบ Real-time

#### How? - ทำอย่างไร

1. Initialize ทุก Hardware: GPIO, PWM, Sensors
2. สร้าง Layout แบบ 2-panel: Input (ซ้าย) + Output (ขวา)
3. สร้าง Timer poll ทุก 50ms อ่าน Button + ADC
4. ใน timer callback อัพเดท UI + ควบคุม Hardware

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FkNIuXYxsPlWzrIGMiMGk%2Fweek30_ex9_hw_gpio_dashboard.gif?alt=media&#x26;token=352430e6-5f8b-482d-aef8-618070d5c9b7" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงานและ Flowchart

#### 2.1 Dashboard Layout

```ini
┌─────────────────────────────────────────────────────────────┐
│              Week 3 Ex9: HW GPIO Dashboard                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [Red LED]    Red    [Switch]    [All ON] [All OFF]         │
│  [Green LED]  Green  [Switch]                               │
│  [Blue LED]   Blue   (POT ctrl)                             │
│                                                             │
│ ┌─────────────────────┐     ┌─────────────────────┐         │
│ │   USER BTN2 (SW4)   │     │   POT -> Blue LED   │         │
│ │                     │     │                     │         │
│ │  ●    Released      │     │  ════════════════   │         │
│ │                     │     │        70%          │         │
│ └─────────────────────┘     └─────────────────────┘         │
│                     (C) 2026 AIC-EEC.com                    │
└─────────────────────────────────────────────────────────────┘

```

#### 2.2 Control Architecture

```ini
┌─────────────────────────────────────────────────────────────┐
│                  CONTROL ARCHITECTURE                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  User Controls (Switches)           Sensor Controls         │
│  ─────────────────────────         ─────────────────────    │
│  ┌────────────┐                    ┌────────────┐           │
│  │ Red Switch │──>aic_gpio_led_set │ Button SW4 │           │
│  │ Green Switch│  (toggle ON/OFF)  │ read state │           │
│  └────────────┘                    └────────────┘           │
│                                    ┌────────────┐           │
│  Batch Controls                    │ POT (ADC)  │           │
│  ─────────────────────────         │ read value │           │
│  ┌────────────┐                    └─────┬──────┘           │
│  │  All ON    │──>loop all LEDs         │                   │
│  │  All OFF   │                         ▼                   │
│  └────────────┘                    ┌────────────┐           │
│                                    │ Blue LED   │           │
│                                    │ PWM dimming│           │
│                                    └────────────┘           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

```

***

### 3. ฟังก์ชันสำคัญ (API Reference)

#### 3.1 Hardware API ทั้งหมดที่ใช้

<table><thead><tr><th width="311.3721923828125">Function</th><th width="266.21728515625">Description</th><th>Category</th></tr></thead><tbody><tr><td><code>aic_gpio_init()</code></td><td>Initialize GPIO</td><td>Setup</td></tr><tr><td><code>aic_gpio_pwm_init(AIC_LED_BLUE)</code></td><td>Initialize PWM</td><td>Setup</td></tr><tr><td><code>aic_sensors_init()</code></td><td>Initialize sensors/ADC</td><td>Setup</td></tr><tr><td><code>aic_gpio_led_set(led, state)</code></td><td>Control LED ON/OFF</td><td>Output</td></tr><tr><td><code>aic_gpio_pwm_set_duty(led, duty)</code></td><td>Set PWM duty 0-100</td><td>Output</td></tr><tr><td><code>aic_gpio_button_read_raw(btn)</code></td><td>Read button (0=pressed)</td><td>Input</td></tr><tr><td><code>aic_adc_read()</code></td><td>Read ADC 0-4095</td><td>Input</td></tr></tbody></table>

#### 3.2 LVGL Layout Functions

<table><thead><tr><th width="439.95172119140625">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW)</code></td><td>จัดเรียงแนวนอน</td></tr><tr><td><code>lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN)</code></td><td>จัดเรียงแนวตั้ง</td></tr><tr><td><code>lv_obj_set_flex_align(obj, main, cross, track)</code></td><td>จัดตำแหน่ง Flex</td></tr><tr><td><code>lv_obj_set_size(obj, w, h)</code></td><td>กำหนดขนาด</td></tr><tr><td><code>LV_PCT(50)</code></td><td>ขนาด 50% ของ parent</td></tr></tbody></table>

#### 3.3 LVGL Widget Functions

| Function                                    | Description           |
| ------------------------------------------- | --------------------- |
| `lv_switch_create(parent)`                  | สร้าง Switch (toggle) |
| `lv_slider_create(parent)`                  | สร้าง Slider          |
| `lv_bar_create(parent)`                     | สร้าง Bar (progress)  |
| `lv_led_create(parent)`                     | สร้าง LED indicator   |
| `lv_button_create(parent)`                  | สร้าง Button          |
| `lv_label_create(parent)`                   | สร้าง Label           |
| `lv_obj_add_state(sw, LV_STATE_CHECKED)`    | ตั้ง Switch เป็น ON   |
| `lv_obj_remove_state(sw, LV_STATE_CHECKED)` | ตั้ง Switch เป็น OFF  |

***

### 4. โค้ดตัวอย่าง (Code Examples)

#### 4.1 Full Dashboard Code

```c
#include "aic-eec.h"

/* ===== Global Variables: Input Panel ===== */
static lv_obj_t * btn_led_ind;       /* Button status LED */
static lv_obj_t * btn_status_label;  /* Button status text */
static lv_obj_t * adc_bar;           /* ADC progress bar */
static lv_obj_t * adc_percent_label; /* ADC percentage */
static lv_obj_t * adc_voltage_label; /* ADC voltage */

/* ===== Global Variables: Output Panel ===== */
static lv_obj_t * led_inds[3];       /* LED indicators R,G,B */
static lv_obj_t * led_switches[2];   /* Switches for Red, Green */
static lv_obj_t * blue_slider;       /* PWM slider for Blue */
static lv_obj_t * blue_duty_label;   /* Blue duty % */

/* ===== Global Variables: Status Bar ===== */
static lv_obj_t * status_label;

/* ===== Forward Declarations ===== */
static void poll_timer_cb(lv_timer_t * t);
static void red_switch_cb(lv_event_t * e);
static void green_switch_cb(lv_event_t * e);
static void blue_slider_cb(lv_event_t * e);
static void all_on_cb(lv_event_t * e);
static void all_off_cb(lv_event_t * e);

/* ============================================================ */
/*                     CALLBACK FUNCTIONS                       */
/* ============================================================ */

/* Timer callback: poll Button + ADC every 50ms */
static void poll_timer_cb(lv_timer_t * t)
{
    /* === [1] Read Button === */
    int btn_raw = aic_gpio_button_read_raw(AIC_BTN_USER);
    bool btn_pressed = (btn_raw == 0);

    if (btn_pressed) {
        lv_led_on(btn_led_ind);
        lv_label_set_text(btn_status_label, "PRESSED");
        lv_obj_set_style_text_color(btn_status_label,
            lv_palette_main(LV_PALETTE_RED), 0);
    } else {
        lv_led_off(btn_led_ind);
        lv_label_set_text(btn_status_label, "Released");
        lv_obj_set_style_text_color(btn_status_label,
            lv_palette_main(LV_PALETTE_GREEN), 0);
    }

    /* === [2] Read ADC === */
    uint16_t adc_raw = aic_adc_read();
    int percent = (int)((uint32_t)adc_raw * 100 / 4095);
    float voltage = (float)adc_raw * 3.3f / 4095.0f;

    lv_bar_set_value(adc_bar, percent, LV_ANIM_ON);
    lv_label_set_text_fmt(adc_percent_label, "ADC: %d (%d%%)",
                          (int)adc_raw, percent);
    lv_label_set_text_fmt(adc_voltage_label, "%.2f V", voltage);

    /* === [3] Update Status Bar === */
    lv_label_set_text_fmt(status_label,
        "BTN=%s | ADC=%d | Blue=%d%%",
        btn_pressed ? "PRESS" : "REL",
        (int)adc_raw,
        lv_slider_get_value(blue_slider));
}

/* Red LED Switch callback */
static void red_switch_cb(lv_event_t * e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t * sw = lv_event_get_target(e);
    bool on = lv_obj_has_state(sw, LV_STATE_CHECKED);
    aic_gpio_led_set(AIC_LED_RED, on);
    on ? lv_led_on(led_inds[0]) : lv_led_off(led_inds[0]);
}

/* Green LED Switch callback */
static void green_switch_cb(lv_event_t * e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t * sw = lv_event_get_target(e);
    bool on = lv_obj_has_state(sw, LV_STATE_CHECKED);
    aic_gpio_led_set(AIC_LED_GREEN, on);
    on ? lv_led_on(led_inds[1]) : lv_led_off(led_inds[1]);
}

/* Blue LED PWM Slider callback */
static void blue_slider_cb(lv_event_t * e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t * slider = lv_event_get_target(e);
    int duty = lv_slider_get_value(slider);

    aic_gpio_pwm_set_duty(AIC_LED_BLUE, duty);
    lv_led_set_brightness(led_inds[2], (uint8_t)(duty * 255 / 100));
    lv_label_set_text_fmt(blue_duty_label, "%d%%", duty);
}

/* All ON callback */
static void all_on_cb(lv_event_t * e)
{
    if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;

    /* Hardware */
    aic_gpio_led_set(AIC_LED_RED, true);
    aic_gpio_led_set(AIC_LED_GREEN, true);
    aic_gpio_pwm_set_duty(AIC_LED_BLUE, 100);

    /* UI: LEDs */
    lv_led_on(led_inds[0]);
    lv_led_on(led_inds[1]);
    lv_led_set_brightness(led_inds[2], 255);

    /* UI: Sync switches */
    lv_obj_add_state(led_switches[0], LV_STATE_CHECKED);
    lv_obj_add_state(led_switches[1], LV_STATE_CHECKED);
    lv_slider_set_value(blue_slider, 100, LV_ANIM_ON);
    lv_label_set_text(blue_duty_label, "100%");
}

/* All OFF callback */
static void all_off_cb(lv_event_t * e)
{
    if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;

    /* Hardware */
    aic_gpio_led_set(AIC_LED_RED, false);
    aic_gpio_led_set(AIC_LED_GREEN, false);
    aic_gpio_pwm_set_duty(AIC_LED_BLUE, 0);

    /* UI: LEDs */
    lv_led_off(led_inds[0]);
    lv_led_off(led_inds[1]);
    lv_led_set_brightness(led_inds[2], 0);

    /* UI: Sync switches */
    lv_obj_remove_state(led_switches[0], LV_STATE_CHECKED);
    lv_obj_remove_state(led_switches[1], LV_STATE_CHECKED);
    lv_slider_set_value(blue_slider, 0, LV_ANIM_ON);
    lv_label_set_text(blue_duty_label, "0%");
}

/* ============================================================ */
/*                  HELPER: Create Input Panel                  */
/* ============================================================ */
static lv_obj_t * create_input_panel(lv_obj_t * parent)
{
    lv_obj_t * panel = lv_obj_create(parent);
    lv_obj_set_size(panel, LV_PCT(48), LV_PCT(100));
    lv_obj_set_style_bg_color(panel, lv_color_hex(0x0f3460), 0);
    lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(panel, 8, 0);
    lv_obj_set_style_pad_row(panel, 6, 0);
    lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE);

    /* Panel Title */
    lv_obj_t * lbl = lv_label_create(panel);
    lv_label_set_text(lbl, "-- INPUT --");
    lv_obj_set_style_text_color(lbl, lv_color_hex(0xFFAA00), 0);

    /* Button Section */
    lv_obj_t * btn_lbl = lv_label_create(panel);
    lv_label_set_text(btn_lbl, "USER Button (SW2)");
    lv_obj_set_style_text_color(btn_lbl, lv_color_hex(0xCCCCCC), 0);

    lv_obj_t * btn_row = lv_obj_create(panel);
    lv_obj_set_size(btn_row, LV_PCT(100), 35);
    lv_obj_set_flex_flow(btn_row, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(btn_row, LV_FLEX_ALIGN_START,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_bg_opa(btn_row, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(btn_row, 0, 0);
    lv_obj_set_style_pad_all(btn_row, 0, 0);
    lv_obj_set_style_pad_column(btn_row, 10, 0);
    lv_obj_remove_flag(btn_row, LV_OBJ_FLAG_SCROLLABLE);

    btn_led_ind = lv_led_create(btn_row);
    lv_led_set_color(btn_led_ind, lv_palette_main(LV_PALETTE_CYAN));
    lv_obj_set_size(btn_led_ind, 25, 25);
    lv_led_off(btn_led_ind);

    btn_status_label = lv_label_create(btn_row);
    lv_label_set_text(btn_status_label, "Released");
    lv_obj_set_style_text_color(btn_status_label,
        lv_palette_main(LV_PALETTE_GREEN), 0);

    /* ADC Section */
    lv_obj_t * adc_lbl = lv_label_create(panel);
    lv_label_set_text(adc_lbl, "Potentiometer");
    lv_obj_set_style_text_color(adc_lbl, lv_color_hex(0xCCCCCC), 0);

    adc_bar = lv_bar_create(panel);
    lv_obj_set_size(adc_bar, LV_PCT(95), 15);
    lv_bar_set_range(adc_bar, 0, 100);
    lv_bar_set_value(adc_bar, 0, LV_ANIM_OFF);
    lv_obj_set_style_bg_color(adc_bar, lv_color_hex(0x333333), 0);
    lv_obj_set_style_bg_color(adc_bar,
        lv_palette_main(LV_PALETTE_CYAN), LV_PART_INDICATOR);

    adc_percent_label = lv_label_create(panel);
    lv_label_set_text(adc_percent_label, "ADC: 0 (0%)");
    lv_obj_set_style_text_color(adc_percent_label,
        lv_color_hex(0x00BFFF), 0);

    adc_voltage_label = lv_label_create(panel);
    lv_label_set_text(adc_voltage_label, "0.00 V");
    lv_obj_set_style_text_color(adc_voltage_label,
        lv_color_hex(0x00FF88), 0);

    return panel;
}

/* ============================================================ */
/*                  HELPER: Create Output Panel                 */
/* ============================================================ */
static lv_obj_t * create_output_panel(lv_obj_t * parent)
{
    lv_obj_t * panel = lv_obj_create(parent);
    lv_obj_set_size(panel, LV_PCT(48), LV_PCT(100));
    lv_obj_set_style_bg_color(panel, lv_color_hex(0x1a1a40), 0);
    lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(panel, 8, 0);
    lv_obj_set_style_pad_row(panel, 4, 0);
    lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE);

    /* Panel Title */
    lv_obj_t * lbl = lv_label_create(panel);
    lv_label_set_text(lbl, "-- OUTPUT --");
    lv_obj_set_style_text_color(lbl, lv_color_hex(0xFF6600), 0);

    /* LED colors and names */
    lv_color_t colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };
    const char * names[] = {"Red", "Green", "Blue"};
    lv_event_cb_t cbs[] = {red_switch_cb, green_switch_cb, NULL};

    /* Red + Green rows (Switch control) */
    for (int i = 0; i < 2; i++) {
        lv_obj_t * row = lv_obj_create(panel);
        lv_obj_set_size(row, LV_PCT(100), 32);
        lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
        lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START,
                              LV_FLEX_ALIGN_CENTER,
                              LV_FLEX_ALIGN_CENTER);
        lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
        lv_obj_set_style_border_width(row, 0, 0);
        lv_obj_set_style_pad_all(row, 0, 0);
        lv_obj_set_style_pad_column(row, 8, 0);
        lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);

        led_inds[i] = lv_led_create(row);
        lv_led_set_color(led_inds[i], colors[i]);
        lv_obj_set_size(led_inds[i], 20, 20);
        lv_led_off(led_inds[i]);

        lv_obj_t * name = lv_label_create(row);
        lv_label_set_text(name, names[i]);
        lv_obj_set_style_text_color(name, lv_color_hex(0xCCCCCC), 0);

        led_switches[i] = lv_switch_create(row);
        lv_obj_add_event_cb(led_switches[i], cbs[i],
                            LV_EVENT_VALUE_CHANGED, NULL);
    }

    /* Blue row (Slider control) */
    lv_obj_t * blue_row = lv_obj_create(panel);
    lv_obj_set_size(blue_row, LV_PCT(100), 32);
    lv_obj_set_flex_flow(blue_row, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(blue_row, LV_FLEX_ALIGN_START,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_bg_opa(blue_row, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(blue_row, 0, 0);
    lv_obj_set_style_pad_all(blue_row, 0, 0);
    lv_obj_set_style_pad_column(blue_row, 6, 0);
    lv_obj_remove_flag(blue_row, LV_OBJ_FLAG_SCROLLABLE);

    led_inds[2] = lv_led_create(blue_row);
    lv_led_set_color(led_inds[2], colors[2]);
    lv_obj_set_size(led_inds[2], 20, 20);
    lv_led_off(led_inds[2]);

    blue_slider = lv_slider_create(blue_row);
    lv_obj_set_width(blue_slider, 100);
    lv_slider_set_range(blue_slider, 0, 100);
    lv_slider_set_value(blue_slider, 0, LV_ANIM_OFF);
    lv_obj_add_event_cb(blue_slider, blue_slider_cb,
                        LV_EVENT_VALUE_CHANGED, NULL);

    blue_duty_label = lv_label_create(blue_row);
    lv_label_set_text(blue_duty_label, "0%");
    lv_obj_set_style_text_color(blue_duty_label,
        lv_color_hex(0x00BFFF), 0);

    /* Batch Buttons */
    lv_obj_t * batch_row = lv_obj_create(panel);
    lv_obj_set_size(batch_row, LV_PCT(100), 35);
    lv_obj_set_flex_flow(batch_row, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(batch_row, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_bg_opa(batch_row, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(batch_row, 0, 0);
    lv_obj_set_style_pad_column(batch_row, 8, 0);
    lv_obj_remove_flag(batch_row, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t * btn_on = lv_button_create(batch_row);
    lv_obj_set_size(btn_on, 70, 28);
    lv_obj_set_style_bg_color(btn_on,
        lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_add_event_cb(btn_on, all_on_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_t * lo = lv_label_create(btn_on);
    lv_label_set_text(lo, "All ON");
    lv_obj_center(lo);

    lv_obj_t * btn_off = lv_button_create(batch_row);
    lv_obj_set_size(btn_off, 70, 28);
    lv_obj_set_style_bg_color(btn_off,
        lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_add_event_cb(btn_off, all_off_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_t * lf = lv_label_create(btn_off);
    lv_label_set_text(lf, "All OFF");
    lv_obj_center(lf);

    return panel;
}

/* ============================================================ */
/*                      MAIN FUNCTION                           */
/* ============================================================ */
void part1_ex9_hw_gpio_dashboard(void)
{
    /* ============================== */
    /*    HARDWARE INITIALIZATION     */
    /* ============================== */
    aic_gpio_init();                       /* [1] GPIO */
    aic_gpio_pwm_init(AIC_LED_BLUE);       /* [2] PWM */
    aic_sensors_init();                    /* [3] ADC */

    /* ============================== */
    /*         LVGL UI SETUP          */
    /* ============================== */
    lv_obj_t * scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0a0a1a), 0);

    /* --- Title --- */
    lv_obj_t * title = lv_label_create(scr);
    lv_label_set_text(title, "Lab 9: HW GPIO Dashboard");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* --- Main Container (2 panels side by side) --- */
    lv_obj_t * main_cont = lv_obj_create(scr);
    lv_obj_set_size(main_cont, 470, 195);
    lv_obj_align(main_cont, LV_ALIGN_CENTER, 0, 5);
    lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(main_cont, LV_FLEX_ALIGN_SPACE_BETWEEN,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_bg_opa(main_cont, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(main_cont, 0, 0);
    lv_obj_set_style_pad_all(main_cont, 0, 0);
    lv_obj_set_style_pad_column(main_cont, 6, 0);
    lv_obj_remove_flag(main_cont, LV_OBJ_FLAG_SCROLLABLE);

    /* Create Panels */
    create_input_panel(main_cont);
    create_output_panel(main_cont);

    /* --- Status Bar --- */
    status_label = lv_label_create(scr);
    lv_label_set_text(status_label, "BTN=REL | ADC=0 | Blue=0%");
    lv_obj_set_style_text_color(status_label, lv_color_hex(0x888888), 0);
    lv_obj_align(status_label, LV_ALIGN_BOTTOM_MID, 0, -5);

    /* ============================== */
    /*    START POLLING TIMER         */
    /* ============================== */
    lv_timer_create(poll_timer_cb, 50, NULL);
}
```

#### 4.2 อธิบายโครงสร้างโค้ด

| ส่วน                    | หน้าที่                      | จำนวนบรรทัด |
| ----------------------- | ---------------------------- | ----------- |
| Global Variables        | เก็บ widget pointers ทั้งหมด | \~15        |
| `poll_timer_cb()`       | อ่าน Button + ADC ทุก 50ms   | \~25        |
| `red/green_switch_cb()` | ควบคุม Red/Green LED         | \~10 each   |
| `blue_slider_cb()`      | ควบคุม Blue LED PWM          | \~8         |
| `all_on/off_cb()`       | Batch control ทุก LED        | \~15 each   |
| `create_input_panel()`  | สร้าง UI ฝั่ง Input          | \~40        |
| `create_output_panel()` | สร้าง UI ฝั่ง Output         | \~60        |
| `part1_ex9_...()`       | Main function init + layout  | \~30        |

***

### 5. องค์ความรู้และเทคนิค

#### 5.1 Pattern: Panel-based Layout

```c
/* แบ่ง Dashboard เป็น panels */
lv_obj_t * main = lv_obj_create(scr);
lv_obj_set_flex_flow(main, LV_FLEX_FLOW_ROW);

/* Panel ซ้าย: Input (48% width) */
lv_obj_t * input = lv_obj_create(main);
lv_obj_set_size(input, LV_PCT(48), LV_PCT(100));

/* Panel ขวา: Output (48% width) */
lv_obj_t * output = lv_obj_create(main);
lv_obj_set_size(output, LV_PCT(48), LV_PCT(100));
```

#### 5.2 Pattern: Batch Control with UI Sync

```c
/* เมื่อกด All ON ต้อง sync ทั้ง Hardware + UI */
static void all_on_cb(lv_event_t * e)
{
    /* Hardware */
    aic_gpio_led_set(AIC_LED_RED, true);
    aic_gpio_led_set(AIC_LED_GREEN, true);

    /* UI: LED indicators */
    lv_led_on(led_red);
    lv_led_on(led_green);

    /* UI: Sync switches (important!) */
    lv_obj_add_state(sw_red, LV_STATE_CHECKED);
    lv_obj_add_state(sw_green, LV_STATE_CHECKED);
}
```

#### 5.3 Pattern: Mixed Poll + Event

```c
/* INPUT devices: ใช้ Timer Polling */
lv_timer_create(poll_cb, 50, NULL);  /* Button + ADC */

/* OUTPUT devices: ใช้ Event Callback */
lv_obj_add_event_cb(switch, cb, LV_EVENT_VALUE_CHANGED, NULL);

/* RULE: อ่าน Input = poll, เขียน Output = event */
```

#### 5.4 สิ่งที่ต้องระวัง

<table><thead><tr><th width="197.1605224609375">หัวข้อ</th><th>รายละเอียด</th></tr></thead><tbody><tr><td><strong>Init Order</strong></td><td><code>aic_gpio_init()</code> -> <code>aic_gpio_pwm_init()</code> -> <code>aic_sensors_init()</code></td></tr><tr><td><strong>UI Sync</strong></td><td>เมื่อ All ON/OFF ต้อง sync ทั้ง Hardware + Switch states + LED indicators</td></tr><tr><td><strong>Single Timer</strong></td><td>ใช้ timer เดียวอ่านทั้ง Button + ADC (ไม่ต้องแยก timer)</td></tr><tr><td><strong>LVGL Thread</strong></td><td>ทุก UI update ต้องอยู่ใน LVGL callback context</td></tr><tr><td><strong>PWM Blue Only</strong></td><td>Red/Green ใช้ Switch (ON/OFF), Blue ใช้ Slider (0-100%)</td></tr></tbody></table>

#### 5.5 Application ในอุตสาหกรรม

* **Factory Control Panel**: ควบคุมมอเตอร์ + อ่าน sensor + แสดงสถานะ
* **Building Management**: ควบคุมไฟ + อ่าน occupancy sensor + แสดง energy
* **Process Control**: ปรับ valve (slider) + อ่าน pressure (ADC) + alarm (LED)
* **Medical Device**: ตั้งค่า parameter + monitor patient vitals

***

### 6. แบบฝึกหัด (Exercises)

#### แบบฝึกหัดที่ 1: ADC-controlled LED Brightness (Auto Mode)

**โจทย์**: เพิ่มโหมด "Auto" ที่ ADC Potentiometer ควบคุมความสว่าง Blue LED โดยอัตโนมัติ

**ข้อกำหนด**:

* เพิ่ม Switch "Auto Mode" บน Output Panel
* เมื่อ Auto Mode = ON:
  * Blue Slider ถูก disable (สีจาง)
  * ค่า ADC (0-4095) ถูก map เป็น PWM duty (0-100)
  * Blue LED เปลี่ยนความสว่างตาม Potentiometer แบบ real-time
  * Slider เคลื่อนตามค่า ADC (read-only visual)
* เมื่อ Auto Mode = OFF:
  * Blue Slider กลับมาใช้งานได้ปกติ (Manual control)
* แสดงโหมดปัจจุบัน: "Auto: POT->Blue" หรือ "Manual"

**Hints**:

* ใช้ `static bool auto_mode = false;`
* `lv_obj_add_state(slider, LV_STATE_DISABLED)` / `lv_obj_remove_state()`
* ใน `poll_timer_cb`: ถ้า auto\_mode ให้ set PWM จาก ADC

#### แบบฝึกหัดที่ 2: Mini Alarm System

**โจทย์**: สร้างระบบ Alarm ย่อที่ใช้ Button arm/disarm และ ADC trigger

**ข้อกำหนด**:

* **สถานะ Disarmed** (เริ่มต้น):
  * แสดง "DISARMED" สีเขียว
  * LED ดับหมด
  * กดปุ่ม USER -> เปลี่ยนเป็น Armed
* **สถานะ Armed**:
  * แสดง "ARMED" สีเหลือง
  * Green LED ติด (indicator ว่า armed)
  * ถ้า ADC > 80% -> เปลี่ยนเป็น Alarm Triggered
  * กดปุ่ม USER -> กลับเป็น Disarmed
* **สถานะ Alarm Triggered**:
  * แสดง "!! ALARM !!" สีแดง กะพริบ
  * Red LED กะพริบ (toggle ทุก 200ms ใน timer)
  * Blue LED สว่าง 100%
  * กดปุ่ม USER -> กลับเป็น Disarmed
* แสดง ADC value เป็น Bar graph ตลอดเวลา
* แสดง Threshold line บน Bar (เส้นที่ 80%)

**Hints**:

* ใช้ `enum { DISARMED, ARMED, TRIGGERED }` สำหรับ state machine
* Edge detection สำหรับ button press (เปลี่ยนสถานะเฉพาะจังหวะกด)
* Timer 50ms สำหรับ poll; ใช้ counter นับ tick สำหรับกะพริบ (4 ticks = 200ms)
