# Button Status

## Lab 7: Hardware Button Status

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

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

การอ่านสถานะปุ่มกดเป็นพื้นฐานสำคัญของ Embedded System:

* **Physical Input**: รับ input จากผู้ใช้ผ่านปุ่มจริงบนบอร์ด
* **Polling Pattern**: เรียนรู้ Timer-based polling สำหรับอ่าน Hardware
* **Debouncing**: เข้าใจปัญหา Mechanical Bounce และวิธีกรอง
* **Active Low Logic**: เข้าใจ Hardware logic ของปุ่มกดแบบ Pull-up
* **Industrial Use Case**: Emergency Stop, Mode Select, Start/Stop buttons

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

1. **Button API**: `aic_gpio_button_read_raw(AIC_BTN_USER)` อ่านปุ่มจริง
2. **Timer Polling**: ใช้ `lv_timer_create()` อ่านสถานะทุก 50ms
3. **Active Low Logic**: ปุ่มกด = return 0, ปล่อย = return 1
4. **Debounce Technique**: กรอง noise จาก mechanical switch
5. **UI Feedback**: แสดง LED widget + Label เปลี่ยนสีตามสถานะปุ่ม

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

1. Initialize GPIO ด้วย `aic_gpio_init()`
2. สร้าง LVGL Timer ที่ poll ทุก 50ms
3. ใน timer callback อ่านสถานะปุ่มด้วย `aic_gpio_button_read_raw()`
4. อัพเดท UI ตามสถานะ: LED widget + Label + สีพื้นหลัง

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FNdRAy5qXCIciC8xBBUBX%2Fweek30_ex7_hw_button_status.gif?alt=media&#x26;token=023e2208-bba3-49aa-9534-3a7dc0a74a30" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงาน (Technical Principles)

#### 2.1 Button Hardware Configuration

```
+-------------------------------------------------------------+
|             PSoC Edge E84 Button Configuration               |
+-------------------------------------------------------------+
|                                                              |
|   Button         Pin     Macro           Active Logic        |
|   ---------------------------------------------------------- |
|   USER Button    P8_3    AIC_BTN_USER    Active Low          |
|                                                              |
|   Active Low Circuit:                                        |
|                                                              |
|        3.3V                                                  |
|         |                                                    |
|        [R] Pull-up Resistor (10K)                            |
|         |                                                    |
|    +----+---- GPIO Pin (P8_3)                                |
|    |                                                         |
|   [SW] Button                                                |
|    |                                                         |
|   GND                                                        |
|                                                              |
|   Released: Pin reads HIGH (1) -- pulled up to 3.3V          |
|   Pressed:  Pin reads LOW  (0) -- shorted to GND             |
|                                                              |
|   aic_gpio_button_read_raw() returns:                        |
|     0 = Pressed (active LOW)                                 |
|     1 = Released                                             |
|                                                              |
+-------------------------------------------------------------+
```

#### 2.2 Timer Polling Architecture

```
+-------------------------------------------------------------+
|                  TIMER POLLING PATTERN                      |
+-------------------------------------------------------------+
|                                                             |
|   LVGL Main Loop                                            |
|   +--------------------------------------------------+      |
|   |  lv_timer_handler()                              |      |
|   |       |                                          |      |
|   |       +---> Check all registered timers          |      |
|   |       |                                          |      |
|   |       +---> [poll_timer] 50ms elapsed?           |      |
|   |                  |                               |      |
|   |             Yes  |                               |      |
|   |                  v                               |      |
|   |          poll_btn_cb()                           |      |
|   |          {                                       |      |
|   |            raw = aic_gpio_button_read_raw()      |      |
|   |            if (raw == 0) -> PRESSED              |      |
|   |            else          -> RELEASED             |      |
|   |            update_ui(state)                      |      |
|   |          }                                       |      |
|   +--------------------------------------------------+      |
|                                                             |
|   Why 50ms interval?                                        |
|   +-- Fast enough: user feels instant response              |
|   +-- Slow enough: does not overload CPU                    |
|   +-- Natural debounce: 50ms > bounce time (~5-20ms)        |
|                                                             |
+-------------------------------------------------------------+
```

#### 2.3 Mechanical Bounce Problem

```
+-------------------------------------------------------------+
|                  SWITCH BOUNCE PROBLEM                       |
+-------------------------------------------------------------+
|                                                              |
|   Ideal Switch Press:                                        |
|                                                              |
|   HIGH --------+                                             |
|                |                                             |
|   LOW          +-----------------------------                |
|                ^                                             |
|              Press                                           |
|                                                              |
|   Real Switch Press (with bounce):                           |
|                                                              |
|   HIGH ----+  +-+  +--+                                      |
|            |  | |  |  |                                      |
|   LOW      +--+ +--+  +-----------------------------         |
|            |<- bounce ->|                                    |
|            ~5-20ms      Stable LOW                           |
|                                                              |
|   Solution: Software Debounce                                |
|   +-- Read button every 50ms (larger than bounce time)       |
|   +-- Require same state for 2-3 consecutive reads           |
|   +-- Only then acknowledge state change                     |
|                                                              |
+-------------------------------------------------------------+
```

#### 2.4 UI Layout

```
+--------------------------------------------------------------+
|           Part 1 Ex7: HW Button Status                       |
+--------------------------------------------------------------+
|                                                              |
|              [ LED Indicator ]                               |
|                                                              |
|              USER BUTTON (SW2)                               |
|                                                              |
|                Released                                      |
|              (text color: green)                             |
|                                                              |
|         Press Count: 0                                       |
|                                                              |
|   [Part II - HW] Press the USER button on the board          |
+--------------------------------------------------------------+
```

***

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

#### 3.1 Button Hardware API (aic-eec.h)

<table><thead><tr><th width="275.77911376953125">Function</th><th width="98.6739501953125">Returns</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_gpio_init()</code></td><td>void</td><td>Initialize GPIO (เรียกครั้งเดียว)</td></tr><tr><td><code>aic_gpio_button_read_raw(btn)</code></td><td>int</td><td>อ่านค่าดิบ: 0=Pressed, 1=Released</td></tr></tbody></table>

#### 3.2 Button Constants

```c
AIC_BTN_USER       // ปุ่ม USER Button (SW2) บน P8_3
```

#### 3.3 LVGL Timer API

| Function                                    | Description          |
| ------------------------------------------- | -------------------- |
| `lv_timer_create(cb, period_ms, user_data)` | สร้าง periodic timer |
| `lv_timer_delete(timer)`                    | ลบ timer             |
| `lv_timer_set_period(timer, new_ms)`        | เปลี่ยน period       |
| `lv_timer_reset(timer)`                     | Reset countdown      |

#### 3.4 LVGL Functions ที่ใช้ในบทนี้

<table><thead><tr><th width="383.06610107421875">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_led_create(parent)</code></td><td>สร้าง LED indicator widget</td></tr><tr><td><code>lv_led_on(led)</code> / <code>lv_led_off(led)</code></td><td>เปิด/ปิด LED indicator</td></tr><tr><td><code>lv_led_set_color(led, color)</code></td><td>ตั้งสี LED</td></tr><tr><td><code>lv_label_set_text(label, text)</code></td><td>ตั้งข้อความ Label</td></tr><tr><td><code>lv_label_set_text_fmt(label, fmt, ...)</code></td><td>ตั้งข้อความแบบ format</td></tr><tr><td><code>lv_obj_set_style_text_color(obj, color, 0)</code></td><td>เปลี่ยนสีข้อความ</td></tr></tbody></table>

***

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

#### 4.1 Minimal Example: Read Button and Update Label

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

static lv_obj_t * status_label;

/* Timer callback: poll button every 50ms */
static void poll_btn_cb(lv_timer_t * t)
{
    int raw = aic_gpio_button_read_raw(AIC_BTN_USER);

    if (raw == 0) {
        /* Pressed (Active Low) */
        lv_label_set_text(status_label, "PRESSED");
        lv_obj_set_style_text_color(status_label,
            lv_palette_main(LV_PALETTE_RED), 0);
    } else {
        /* Released */
        lv_label_set_text(status_label, "Released");
        lv_obj_set_style_text_color(status_label,
            lv_palette_main(LV_PALETTE_GREEN), 0);
    }
}

void minimal_button_read(void)
{
    aic_gpio_init();

    status_label = lv_label_create(lv_screen_active());
    lv_label_set_text(status_label, "Released");
    lv_obj_center(status_label);

    /* สร้าง timer poll ทุก 50ms */
    lv_timer_create(poll_btn_cb, 50, NULL);
}
```

#### 4.2 Full Example: Button Status with Debounce + Press Counter

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

/* ===== Global Variables ===== */
static lv_obj_t * led_indicator;
static lv_obj_t * status_label;
static lv_obj_t * count_label;

static int press_count = 0;
static bool prev_pressed = false;    /* สถานะรอบก่อน สำหรับ edge detection */
static int debounce_count = 0;       /* นับจำนวนครั้งที่อ่านค่าเดิมติดกัน */

#define DEBOUNCE_THRESHOLD  3  /* ต้องอ่านค่าเดิม 3 ครั้งติด (3x50ms = 150ms) */

/* ===== Timer Callback: Poll Button ===== */
static void poll_btn_cb(lv_timer_t * t)
{
    int raw = aic_gpio_button_read_raw(AIC_BTN_USER);
    bool is_pressed = (raw == 0);  /* Active Low: 0 = pressed */

    /* ===== Software Debounce ===== */
    static bool stable_state = false;
    static bool last_raw = false;

    if (is_pressed == last_raw) {
        debounce_count++;
    } else {
        debounce_count = 0;
    }
    last_raw = is_pressed;

    /* ยังไม่เสถียร -> ข้ามไป */
    if (debounce_count < DEBOUNCE_THRESHOLD) return;

    /* สถานะเสถียรแล้ว */
    stable_state = is_pressed;

    /* ===== Update UI ===== */
    if (stable_state) {
        /* PRESSED */
        lv_led_on(led_indicator);
        lv_label_set_text(status_label, "PRESSED");
        lv_obj_set_style_text_color(status_label,
            lv_palette_main(LV_PALETTE_RED), 0);
    } else {
        /* RELEASED */
        lv_led_off(led_indicator);
        lv_label_set_text(status_label, "Released");
        lv_obj_set_style_text_color(status_label,
            lv_palette_main(LV_PALETTE_GREEN), 0);
    }

    /* ===== Edge Detection: นับ Press ===== */
    if (stable_state && !prev_pressed) {
        /* Rising edge: just pressed */
        press_count++;
        lv_label_set_text_fmt(count_label, "Press Count: %d", press_count);
    }
    prev_pressed = stable_state;
}

/* ===== Main Function ===== */
void part1_ex7_hw_button_status(void)
{
    /* ============================== */
    /*    HARDWARE INITIALIZATION     */
    /* ============================== */
    aic_gpio_init();

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

    /* --- Title --- */
    lv_obj_t * title = lv_label_create(scr);
    lv_label_set_text(title, "Lab 7: HW Button Status");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /* --- Container --- */
    lv_obj_t * cont = lv_obj_create(scr);
    lv_obj_set_size(cont, 350, 180);
    lv_obj_align(cont, LV_ALIGN_CENTER, 0, 10);
    lv_obj_set_style_bg_color(cont, lv_color_hex(0x16213e), 0);
    lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_row(cont, 12, 0);
    lv_obj_set_style_pad_all(cont, 15, 0);

    /* --- LED Indicator --- */
    led_indicator = lv_led_create(cont);
    lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_CYAN));
    lv_obj_set_size(led_indicator, 50, 50);
    lv_led_off(led_indicator);

    /* --- Button Name --- */
    lv_obj_t * btn_name = lv_label_create(cont);
    lv_label_set_text(btn_name, "USER BUTTON (SW2)");
    lv_obj_set_style_text_color(btn_name, lv_color_hex(0xCCCCCC), 0);

    /* --- Status Label --- */
    status_label = lv_label_create(cont);
    lv_label_set_text(status_label, "Released");
    lv_obj_set_style_text_color(status_label,
        lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_set_style_text_font(status_label, &lv_font_montserrat_24, 0);

    /* --- Press Counter --- */
    count_label = lv_label_create(cont);
    lv_label_set_text(count_label, "Press Count: 0");
    lv_obj_set_style_text_color(count_label, lv_color_hex(0x00BFFF), 0);

    /* --- Footer --- */
    lv_obj_t * footer = lv_label_create(scr);
    lv_label_set_text(footer, "[Part II - HW] Press the USER button on the board");
    lv_obj_set_style_text_color(footer, lv_color_hex(0x888888), 0);
    lv_obj_align(footer, LV_ALIGN_BOTTOM_MID, 0, -10);

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

#### 4.3 อธิบายโค้ดทีละขั้นตอน

<table><thead><tr><th width="92.6328125">ขั้นตอน</th><th width="357.593017578125">โค้ด</th><th>คำอธิบาย</th></tr></thead><tbody><tr><td>1</td><td><code>aic_gpio_init()</code></td><td>เริ่มต้น GPIO subsystem</td></tr><tr><td>2</td><td><code>lv_timer_create(poll_btn_cb, 50, NULL)</code></td><td>สร้าง timer poll ทุก 50ms</td></tr><tr><td>3</td><td><code>aic_gpio_button_read_raw(AIC_BTN_USER)</code></td><td>อ่านค่าดิบ (0=pressed)</td></tr><tr><td>4</td><td>Debounce check</td><td>นับค่าเดิมติดกัน 3 ครั้ง = เสถียร</td></tr><tr><td>5</td><td>Edge detection</td><td><code>stable &#x26;&#x26; !prev</code> = just pressed, นับ press</td></tr><tr><td>6</td><td><code>lv_led_on/off()</code> + <code>lv_label_set_text()</code></td><td>อัพเดท UI ตามสถานะ</td></tr></tbody></table>

***

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

#### 5.1 Pattern: Hardware Polling with LVGL Timer

```c
/* Template สำหรับ poll hardware ใดก็ได้ */
static void hw_poll_cb(lv_timer_t * t)
{
    /* [1] อ่าน hardware */
    int value = read_hardware_sensor();

    /* [2] อัพเดท UI (safe - เราอยู่ใน LVGL context) */
    lv_label_set_text_fmt(my_label, "Value: %d", value);
}

void setup(void) {
    init_hardware();
    /* สร้าง timer สำหรับ polling */
    lv_timer_create(hw_poll_cb, 50, NULL);  /* 50ms interval */
}
```

#### 5.2 Pattern: Edge Detection (ตรวจจับจังหวะกด)

```c
static bool prev_state = false;

static void poll_cb(lv_timer_t * t)
{
    bool current = (aic_gpio_button_read_raw(AIC_BTN_USER) == 0);

    if (current && !prev_state) {
        /* FALLING EDGE: just pressed */
        on_button_press();
    }
    if (!current && prev_state) {
        /* RISING EDGE: just released */
        on_button_release();
    }
    prev_state = current;
}
```

#### 5.3 Pattern: Simple Debounce

```c
#define DEBOUNCE_MS  3  /* consecutive same-reads needed */
static int same_count = 0;
static bool last_raw = false;
static bool stable = false;

static void debounce_poll(lv_timer_t * t)
{
    bool raw = (aic_gpio_button_read_raw(AIC_BTN_USER) == 0);

    if (raw == last_raw) {
        if (++same_count >= DEBOUNCE_MS) {
            stable = raw;  /* สถานะเสถียร */
        }
    } else {
        same_count = 0;    /* เปลี่ยนแปลง -> reset */
    }
    last_raw = raw;

    /* ใช้ stable เท่านั้นสำหรับ logic */
    update_ui(stable);
}
```

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

| หัวข้อ                 | รายละเอียด                                                         |
| ---------------------- | ------------------------------------------------------------------ |
| **Active Low**         | `read_raw()` คืน 0 เมื่อกด, 1 เมื่อปล่อย -- กลับจากสัญชาตญาณ       |
| **LVGL Thread Safety** | อัพเดท UI ได้เฉพาะใน LVGL context (timer callback, event callback) |
| **Timer Period**       | 50ms เป็นค่าที่ดี: เร็วพอสำหรับ UI, ช้าพอไม่กิน CPU                |
| **Debounce**           | ไม่ debounce = นับ press ผิด, ค่า flicker                          |
| **Static Variables**   | ตัวแปร state ต้องเป็น `static` เพราะ callback ถูกเรียกซ้ำหลายครั้ง |

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

* **Emergency Stop**: ปุ่มหยุดฉุกเฉินในโรงงาน ต้อง debounce + edge detect
* **Mode Selection**: ปุ่มเลือกโหมดทำงาน (Manual/Auto/Service)
* **Counting**: นับจำนวนชิ้นงานที่ผ่าน sensor (press = detect)

***

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

#### แบบฝึกหัดที่ 1: Press Counter with Long-Press Detection

**โจทย์**: สร้างระบบนับการกดปุ่มที่แยก Short Press กับ Long Press

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

* Short Press (กดน้อยกว่า 1 วินาที): เพิ่ม counter +1
* Long Press (กดค้าง >= 1 วินาที): Reset counter เป็น 0
* แสดงสถานะ: "Short Press!" (สีเขียว) / "Long Press - Reset!" (สีแดง)
* แสดง Press Duration ขณะกดค้างอยู่ (เช่น "Holding: 0.8s")
* LED indicator เปลี่ยนสีตามระยะเวลากด (เขียว -> เหลือง -> แดง)

**Hints**:

* ใช้ตัวแปร `static uint32_t press_start_tick = 0;` เก็บเวลาเริ่มกด
* `lv_tick_get()` ได้ค่า milliseconds ปัจจุบัน
* Long press threshold = 1000ms (20 timer ticks x 50ms)

#### แบบฝึกหัดที่ 2: Button-Triggered LED Toggle with Visual Feedback

**โจทย์**: กดปุ่ม USER แต่ละครั้ง ให้สลับสถานะ LED ตามลำดับ

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

* กดครั้งที่ 1: Red LED ON
* กดครั้งที่ 2: Red OFF + Green LED ON
* กดครั้งที่ 3: Green OFF + Blue LED ON
* กดครั้งที่ 4: ปิดหมด แล้วกลับไปครั้งที่ 1
* แสดงสถานะบนจอ: LED ตัวไหนติด + หมายเลขครั้งที่กด
* LVGL LED indicators 3 ตัว แสดงสถานะตรงกับ Hardware LED

**Hints**:

* ใช้ `static int led_mode = 0;` และ `led_mode = (led_mode + 1) % 4;`
* ใช้ Edge Detection เพื่อนับเฉพาะจังหวะกดลง
