> For the complete documentation index, see [llms.txt](https://docs.aic-eec.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/multi-core-communication/gpio-via-ipc.md).

# GPIO via IPC

## Lab 3: Hardware IPC GPIO (LED + Button Control)

### Part 4 - IPC & Event Bus (Hardware Labs)

***

### 1. โครงสร้างภาพรวม (Overview)

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

ในระบบ Multi-Core จริง การควบคุม Hardware ไม่ได้ทำจาก core เดียว -- LED เป็น **output** (ส่งคำสั่งจาก UI ไปควบคุม GPIO) และ Button เป็น **input** (อ่าน GPIO แล้วส่ง event กลับมาแสดงบน UI) ทั้งสองเป็นคู่ธรรมชาติของ GPIO control ผ่าน IPC:

**Output (LED):**

* **Industrial Automation**: หน้าจอ HMI (CM55) ส่งคำสั่งไปยัง PLC Controller (CM33) เพื่อเปิด/ปิดมอเตอร์ วาล์ว ไฟสัญญาณ
* **Smart Factory**: Operator กด UI บน Dashboard --> คำสั่งเดินทางผ่าน IPC --> CM33 ควบคุม Actuator จริง
* **Safety Isolation**: แยก UI core กับ Control core เพื่อความปลอดภัย ถ้า UI crash, control ยังทำงาน

**Input (Button):**

* **Industrial Safety**: ปุ่ม E-STOP (Emergency Stop) อยู่ที่ PLC controller --> ส่ง event ไป HMI แสดงสถานะ
* **Remote Monitoring**: ปุ่มกดที่ field device --> event ถูกส่งผ่าน bus --> แสดงที่ control room
* **Safety Interlock**: ปุ่ม confirm ต้องถูกกดก่อน actuator จะทำงาน (Two-hand safety control)

ในบอร์ด PSoC Edge E84:

* **CM55** รัน LVGL display แต่ไม่มี GPIO access โดยตรง
* **CM33-NS** ควบคุม GPIO, LED, PWM และอ่าน button ได้
* การควบคุม LED จาก UI ต้องส่งคำสั่ง IPC จาก CM55 ไปยัง CM33-NS
* การอ่าน Button ต้องให้ CM33-NS poll แล้วส่ง event กลับมา CM55

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

**Part A (Ex5 - IPC LED Control):**

1. **IPC LED Commands**: ส่ง `IPC_CMD_LED_SET` และ `IPC_CMD_LED_BRIGHTNESS` จาก CM55
2. **Multi-LED Control**: ควบคุม LED 3 ดวง (Red, Green, Blue) ผ่าน IPC
3. **PWM Brightness**: ควบคุมความสว่างผ่าน Slider --> IPC --> CM33 PWM
4. **Status Feedback**: CM33 ยืนยันสถานะ LED กลับมาแสดงบน UI
5. **Flag-Based Pattern**: อัพเดท LVGL อย่างปลอดภัยจาก IPC callback
6. **IPC Timing**: จัดการ 20ms delay ระหว่าง IPC sends

**Part B (Ex6 - IPC Button Events):**

1. **Button Polling + Debounce**: CM33-NS อ่าน button ด้วย debounce algorithm
2. **IPC Button Events**: ส่ง `IPC_CMD_BUTTON_EVENT` จาก CM33 ไป CM55
3. **Long-Press Detection**: ตรวจจับการกดค้าง (>1 วินาที) แยกจาก short press
4. **Flag-Based UI Update**: CM55 รับ event แล้ว update UI ผ่าน LVGL timer
5. **Event Counter**: นับจำนวนครั้งที่กด + แสดง timestamp
6. **Event Logger**: บันทึก button events พร้อม timestamp

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

**Part A:** สร้าง LED Control Panel บน CM55 ที่มี Switch 3 ตัว + Brightness Slider --> เมื่อ user สั่งจาก UI --> CM55 ส่ง IPC command --> CM33-NS ควบคุม GPIO LED จริง --> CM33-NS ตอบกลับสถานะ --> CM55 อัพเดท UI

**Part B:** CM33-NS poll USER button ทุก 10ms พร้อม debounce --> เมื่อตรวจจับ press/release/long-press ส่ง IPC event ไป CM55 --> CM55 IPC callback ตั้ง flag --> LVGL timer อ่าน flag แล้ว update UI

***

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

#### 2.1 Data Flow Direction Comparison

```
+-----------------------------------------------------------+
|         DATA FLOW DIRECTION COMPARISON                    |
+-----------------------------------------------------------+
|                                                           |
|  Part A (LED Control):                                    |
|  CM55 (UI command) ---------> CM33 (GPIO action)          |
|  CM55 <-------- (ACK/status) CM33                         |
|  Direction: CM55 --> CM33 (command)                       |
|                                                           |
|  Part B (Button Events):                                  |
|  CM33 (GPIO read) ----------> CM55 (UI display)           |
|  Direction: CM33 --> CM55 (event)                         |
|                                                           |
|  Lab 4 (Dashboard): ทั้งสองทิศทางพร้อมกัน!                     |
|  CM55 <--> CM33 (bidirectional)                           |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.2 IPC LED Control Architecture (Part A)

```
+-----------------------------------------------------------+
|            IPC LED CONTROL ARCHITECTURE                   |
+-----------------------------------------------------------+
|                                                           |
|  CM55 Core (LVGL Display)        CM33-NS Core (GPIO)      |
|  ========================        =====================    |
|                                                           |
|  +------------------+            +------------------+     |
|  | LVGL Switch/     |  IPC TX   | IPC RX Callback   |     |
|  | Slider Event     |---------->| Parse LED cmd     |     |
|  | - LED ON/OFF     |  0xB2     | - Set GPIO        |     |
|  | - Brightness     |  0xB3     | - Set PWM duty    |     |
|  +------------------+           +-------------------+     |
|                                          |                |
|  +------------------+            +-------v----------+     |
|  | IPC RX Callback  |  IPC TX   | LED GPIO Driver   |     |
|  | - Set flag       |<----------| - aic_gpio_led    |     |
|  | - Store state    |  ACK/NAK  | - PWM control     |     |
|  +--------+---------+           +-------------------+     |
|           |                                               |
|           v (LVGL timer reads flag)                       |
|  +------------------+                                     |
|  | Update UI        |                                     |
|  | - LED widget     |                                     |
|  | - Status label   |                                     |
|  +------------------+                                     |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.3 IPC LED Command Protocol

```
+-----------------------------------------------------------+
|              IPC LED COMMAND PROTOCOL                     |
+-----------------------------------------------------------+
|                                                           |
|  Command              Code   Direction    Payload         |
|  ---------------------------------------------------------|
|  IPC_CMD_LED_SET      0xB2   CM55->CM33   ipc_led_data_t  |
|  IPC_CMD_LED_BRIGHTNESS 0xB3 CM55->CM33   ipc_led_data_t  |
|  IPC_CMD_ACK          0x44   CM33->CM55   led_id + state  |
|                                                           |
|  ipc_led_data_t structure:                                |
|  +--------+--------+------------+----------+              |
|  | led_id | state  | brightness | reserved |              |
|  | 1 byte | 1 byte | 1 byte     | 1 byte   |              |
|  +--------+--------+------------+----------+              |
|                                                           |
|  LED IDs:                                                 |
|  +-- 0: Red LED    (AIC_LED_RED / CYBSP_USER_LED)         |
|  +-- 1: Green LED  (AIC_LED_GREEN)                        |
|  +-- 2: Blue LED   (AIC_LED_BLUE)                         |
|                                                           |
|  Brightness: 0-100 (percent, mapped to PWM duty cycle)    |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.4 Flag-Based Pattern (Thread Safety)

```
+-----------------------------------------------------------+
|         CRITICAL: LVGL THREAD SAFETY PATTERN              |
+-----------------------------------------------------------+
|                                                           |
|  IPC Callback (ISR context)     LVGL Timer (UI context)   |
|  ==========================     ======================    |
|                                                           |
|  void ipc_rx_cb(msg) {          void led_timer_cb() {     |
|    if(msg->cmd == ACK) {          if(led_flag_updated) {  |
|      led_state[id] = state;         led_flag_updated=0;   |
|      led_flag_updated = 1;          // Safe: update UI    |
|      // NEVER call LVGL here!       update_led_ui();      |
|    }                              }                       |
|  }                              }                         |
|                                                           |
|  WRONG (will crash):            RIGHT (thread-safe):      |
|  void ipc_rx_cb(msg) {          1. IPC sets flag + data   |
|    lv_label_set_text(..);       2. LVGL timer checks flag |
|    // CRASH! LVGL not safe!     3. Timer updates UI       |
|  }                                                        |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.5 IPC Button Event Architecture (Part B)

```
+-----------------------------------------------------------+
|            IPC BUTTON EVENT ARCHITECTURE                  |
+-----------------------------------------------------------+
|                                                           |
|  CM33-NS Core (GPIO Reader)      CM55 Core (LVGL UI)      |
|  ==========================      =====================    |
|                                                           |
|  +------------------+                                     |
|  | GPIO Button Poll |  Every 10ms                         |
|  | (USER_BTN pin)   |                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+                                     |
|  | Debounce Filter  |  30ms stable required               |
|  | (software)       |                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+            +------------------+     |
|  | Detect Event:    |  IPC TX   | IPC RX Callback   |     |
|  | - PRESS          |---------->| Set flags:        |     |
|  | - RELEASE        |  0xB5     | - btn_pressed     |     |
|  | - LONG_PRESS     |           | - btn_long_press  |     |
|  +------------------+           +--------+----------+     |
|                                          |                |
|                                          v (LVGL timer)   |
|                                 +------------------+      |
|                                 | Update UI:       |      |
|                                 | - Status label   |      |
|                                 | - LED widget     |      |
|                                 | - Press counter  |      |
|                                 | - Event log      |      |
|                                 +------------------+      |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.6 Button Debounce Algorithm

```
+-----------------------------------------------------------+
|              BUTTON DEBOUNCE STATE MACHINE                |
+-----------------------------------------------------------+
|                                                           |
|  Physical Button Signal (noisy):                          |
|  _____|~|~|__________|~|~|~|___________                   |
|                                                           |
|  After Debounce (clean):                                  |
|  _____|-------------|________________                     |
|       ^             ^                                     |
|       PRESS         RELEASE                               |
|                                                           |
|  States:                                                  |
|  +-- IDLE:       รอกด (GPIO = HIGH = ไม่กด)                |
|  +-- DEBOUNCING: ตรวจพบการกด รอ 30ms ยืนยัน                 |
|  +-- PRESSED:    กดแล้ว เริ่มนับเวลา long-press               |
|  +-- LONG_PRESS: กดค้างเกิน 1000ms                          |
|  +-- RELEASING:  ตรวจพบปล่อย รอ 30ms ยืนยัน                  |
|                                                           |
|  Timing:                                                  |
|  +-- Debounce time:   30ms                                |
|  +-- Long-press time: 1000ms                              |
|  +-- Poll interval:   10ms                                |
|                                                           |
|         Press detected     30ms stable    1000ms hold     |
|  IDLE ------> DEBOUNCING ------> PRESSED ------> LONG     |
|   ^                 |                |         PRESS      |
|   |          bounce |                |            |       |
|   |          <------+        Release |            |       |
|   |                          detected|            |       |
|   |                 RELEASING <------+            |       |
|   |                     |                         |       |
|   |            30ms     |                 Release |       |
|   +--------- stable ----+                         |       |
|   |                                               |       |
|   +-----------------------------------------------+       |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.7 IPC Button Data Structure

```
+-----------------------------------------------------------+
|           IPC_CMD_BUTTON_EVENT (0xB5) PAYLOAD             |
+-----------------------------------------------------------+
|                                                           |
|  ipc_button_data_t:                                       |
|  +-----------+---------+------------+----------+          |
|  | button_id | pressed | long_press | reserved |          |
|  | 1 byte    | 1 byte  | 1 byte     | 1 byte   |          |
|  +-----------+---------+------------+----------+          |
|  | timestamp (uint32_t, 4 bytes)               |          |
|  +---------------------------------------------+          |
|                                                           |
|  Events:                                                  |
|  +-- Short Press:  pressed=1, long_press=0                |
|  +-- Long Press:   pressed=1, long_press=1                |
|  +-- Release:      pressed=0, long_press=0                |
|                                                           |
|  button_id:                                               |
|  +-- 0: USER Button (CYBSP_USER_BTN)                      |
|                                                           |
+-----------------------------------------------------------+
```

***

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

#### 3.1 CM55 IPC Send Functions (LED Control)

<table><thead><tr><th width="332.6363525390625">Function</th><th>Description</th><th>Return</th></tr></thead><tbody><tr><td><code>cm55_ipc_set_led(led_id, state)</code></td><td>ส่งคำสั่ง LED ON/OFF ไป CM33</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_set_led_brightness(led_id, brightness)</code></td><td>ส่งคำสั่ง brightness (0-100)</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_send_retry(msg, retries)</code></td><td>ส่ง IPC message พร้อม retry</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_register_callback(cb, data)</code></td><td>ลงทะเบียน IPC callback</td><td>void</td></tr></tbody></table>

#### 3.2 CM33-NS GPIO Functions

<table><thead><tr><th width="351.1875">Function</th><th width="245.492919921875">Description</th><th>Return</th></tr></thead><tbody><tr><td><code>aic_gpio_init()</code></td><td>Initialize GPIO subsystem</td><td>void</td></tr><tr><td><code>aic_gpio_led_set(led, state)</code></td><td>ควบคุม LED GPIO จริง</td><td>void</td></tr><tr><td><code>aic_gpio_button_read_raw(AIC_BTN_USER)</code></td><td>อ่าน button state (0=pressed)</td><td><code>uint8_t</code></td></tr><tr><td><code>Cy_SysLib_Delay(ms)</code></td><td>Delay (CM33, ไม่ใช่ FreeRTOS)</td><td>void</td></tr></tbody></table>

#### 3.3 CM33-NS IPC Functions

<table><thead><tr><th width="245.00714111328125">Function</th><th width="277.757080078125">Description</th><th>Return</th></tr></thead><tbody><tr><td><code>cm33_ipc_send_button_event(id, pressed)</code></td><td>ส่ง button event ไป CM55</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm33_ipc_send_retry(msg, retries)</code></td><td>ส่ง IPC message พร้อม retry</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm33_ipc_send_led_state(led_id, state)</code></td><td>ส่งยืนยันสถานะ LED กลับ</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr></tbody></table>

#### 3.4 IPC Data Structures

<table><thead><tr><th width="186.7769775390625">Structure</th><th width="363.5545654296875">Fields</th><th>Usage</th></tr></thead><tbody><tr><td><code>ipc_led_data_t</code></td><td><code>led_id</code>, <code>state</code>, <code>brightness</code>, <code>reserved</code></td><td>LED control payload</td></tr><tr><td><code>ipc_button_data_t</code></td><td><code>button_id</code>, <code>pressed</code>, <code>long_press</code>, <code>timestamp</code></td><td>Button event payload</td></tr><tr><td><code>ipc_msg_t</code></td><td><code>client_id</code>, <code>intr_mask</code>, <code>cmd</code>, <code>value</code>, <code>data[]</code></td><td>IPC message wrapper</td></tr></tbody></table>

#### 3.5 LVGL Timer

<table><thead><tr><th width="365.7535400390625">Function</th><th width="233.1939697265625">Description</th><th>Return</th></tr></thead><tbody><tr><td><code>lv_timer_create(cb, period_ms, user_data)</code></td><td>สร้าง timer สำหรับ poll flags</td><td><code>lv_timer_t *</code></td></tr><tr><td><code>lv_led_set_brightness(led, bright)</code></td><td>ตั้ง LED widget brightness (0-255)</td><td>void</td></tr></tbody></table>

***

### 4. Part A: IPC LED Control (Ex5)

Part A สอน output direction -- CM55 ส่งคำสั่งไปควบคุม LED จริงบน CM33-NS ผ่าน IPC พร้อม ACK feedback

#### 4.1 Program Flowchart

```
+-----------------------------------------------------------+
|                  PROGRAM FLOW                             |
+-----------------------------------------------------------+
|                                                           |
|                   +----------+                            |
|                   |  Start   |                            |
|                   +----+-----+                            |
|                        |                                  |
|                        v                                  |
|             +---------------------+                       |
|             | cm55_ipc_init()     |                       |
|             | Register callback   |                       |
|             +----------+----------+                       |
|                        |                                  |
|                        v                                  |
|             +---------------------+                       |
|             | Create LVGL UI      |                       |
|             | - 3x Switch (RGB)   |                       |
|             | - Brightness Slider |                       |
|             | - Status labels     |                       |
|             +----------+----------+                       |
|                        |                                  |
|                        v                                  |
|             +---------------------+                       |
|             | Create LVGL Timer   |                       |
|             | (check IPC flags)   |                       |
|             +----------+----------+                       |
|                        |                                  |
|                        v                                  |
|             +---------------------+                       |
|             | User toggles switch |<-----------+          |
|             +----------+----------+            |          |
|                        |                       |          |
|                        v                       |          |
|             +---------------------+            |          |
|             | Send IPC_CMD_LED_SET|            |          |
|             | to CM33-NS          |            |          |
|             +----------+----------+            |          |
|                        |                       |          |
|                        v                       |          |
|             +---------------------+            |          |
|             | CM33 receives       |            |          |
|             | Controls GPIO LED   |            |          |
|             | Sends ACK back      |            |          |
|             +----------+----------+            |          |
|                        |                       |          |
|                        v                       |          |
|             +---------------------+            |          |
|             | CM55 IPC callback   |            |          |
|             | Sets flag + state   |            |          |
|             +----------+----------+            |          |
|                        |                       |          |
|                        v                       |          |
|             +---------------------+            |          |
|             | LVGL timer fires    |            |          |
|             | Updates LED widget  |            |          |
|             | Updates status label|            |          |
|             +----------+----------+            |          |
|                        |                       |          |
|                        +-----------------------+          |
|                                                           |
+-----------------------------------------------------------+
```

#### 4.2 CM55 Code - LED Control UI + IPC Send

```c
/*******************************************************************************
 * Part 4 - Example 5: Hardware IPC LED Control (CM55 Side)
 *
 * Learning: IPC LED commands, flag-based pattern, multi-LED control
 * Platform: PSoC Edge E84 - CM55 Core + LVGL v9.2.0
 *
 * CRITICAL: LVGL is NOT thread-safe!
 * - IPC callback sets flags only
 * - LVGL timer reads flags and updates UI
 ******************************************************************************/

#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"
#include "../../shared/ipc_shared.h"

/* ===== LED Definitions ===== */
#define NUM_LEDS  3

typedef struct {
    uint8_t     id;
    const char *name;
    lv_color_t  color;
    bool        state;
    uint8_t     brightness;
} led_info_t;

static led_info_t leds[NUM_LEDS] = {
    { 0, "Red",   {0}, false, 100 },
    { 1, "Green", {0}, false, 100 },
    { 2, "Blue",  {0}, false, 100 },
};

/* ===== LVGL UI Objects ===== */
static lv_obj_t * led_widgets[NUM_LEDS];    /* LED indicator widgets */
static lv_obj_t * led_switches[NUM_LEDS];   /* ON/OFF switches */
static lv_obj_t * led_status[NUM_LEDS];     /* Status labels */
static lv_obj_t * brightness_slider;
static lv_obj_t * brightness_label;
static lv_obj_t * ipc_status_label;

/* ===== IPC Flag Variables (volatile for thread safety) ===== */
static volatile bool     ipc_led_ack_flag = false;
static volatile uint8_t  ipc_ack_led_id   = 0;
static volatile bool     ipc_ack_state    = false;
static volatile uint8_t  ipc_ack_brightness = 0;
static volatile uint32_t ipc_error_count  = 0;

/*******************************************************************************
 * IPC Receive Callback (ISR context - NEVER call LVGL here!)
 ******************************************************************************/
static void ex5_ipc_rx_callback(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    switch (msg->cmd) {
        case IPC_CMD_ACK: {
            /* CM33 confirmed LED state change */
            ipc_led_data_t *led_data = (ipc_led_data_t *)msg->data;
            ipc_ack_led_id    = led_data->led_id;
            ipc_ack_state     = (led_data->state != 0);
            ipc_ack_brightness = led_data->brightness;
            ipc_led_ack_flag  = true;  /* Set flag - LVGL timer will handle */
            break;
        }
        case IPC_CMD_NACK:
            ipc_error_count++;
            break;
        default:
            break;
    }
}

/*******************************************************************************
 * LVGL Timer: Check IPC flags and update UI (safe context)
 ******************************************************************************/
static void ex5_ipc_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (ipc_led_ack_flag) {
        ipc_led_ack_flag = false;

        uint8_t id = ipc_ack_led_id;
        if (id < NUM_LEDS) {
            /* Update internal state */
            leds[id].state      = ipc_ack_state;
            leds[id].brightness = ipc_ack_brightness;

            /* Update LED widget */
            if (leds[id].state) {
                lv_led_on(led_widgets[id]);
                lv_led_set_brightness(led_widgets[id],
                    (uint8_t)(leds[id].brightness * 255 / 100));
            } else {
                lv_led_off(led_widgets[id]);
            }

            /* Update status label */
            if (leds[id].state) {
                lv_label_set_text_fmt(led_status[id],
                    "%s: ON (%d%%)", leds[id].name, (int)leds[id].brightness);
                lv_obj_set_style_text_color(led_status[id],
                    lv_palette_main(LV_PALETTE_GREEN), 0);
            } else {
                lv_label_set_text_fmt(led_status[id], "%s: OFF", leds[id].name);
                lv_obj_set_style_text_color(led_status[id],
                    lv_palette_main(LV_PALETTE_RED), 0);
            }
        }

        /* Update IPC status */
        lv_label_set_text_fmt(ipc_status_label,
            "IPC: ACK received (LED%d = %s) | Errors: %u",
            (int)id, ipc_ack_state ? "ON" : "OFF",
            (unsigned int)ipc_error_count);
    }
}

/*******************************************************************************
 * Switch Event Callback - ส่ง IPC LED ON/OFF
 ******************************************************************************/
static void ex5_switch_cb(lv_event_t *e)
{
    lv_obj_t *sw = lv_event_get_target(e);
    int *led_idx = (int *)lv_event_get_user_data(e);
    int idx = *led_idx;

    if (idx < 0 || idx >= NUM_LEDS) return;

    bool is_on = lv_obj_has_state(sw, LV_STATE_CHECKED);

    /* ส่ง IPC command ไป CM33-NS */
    cy_en_ipc_pipe_status_t status;
    status = cm55_ipc_set_led(leds[idx].id, is_on);

    if (status == CY_IPC_PIPE_SUCCESS) {
        lv_label_set_text_fmt(ipc_status_label,
            "IPC: Sent LED_%s %s (waiting ACK...)",
            leds[idx].name, is_on ? "ON" : "OFF");
        CM55_LOGI("LED %s -> %s", leds[idx].name, is_on ? "ON" : "OFF");
    } else {
        lv_label_set_text_fmt(ipc_status_label,
            "IPC: SEND FAILED (LED_%s) err=%d",
            leds[idx].name, (int)status);
        CM55_LOGE("IPC send failed for LED%d, err=%d", idx, (int)status);
        ipc_error_count++;
    }
}

/*******************************************************************************
 * Slider Event Callback - ส่ง IPC LED Brightness
 ******************************************************************************/
static void ex5_brightness_cb(lv_event_t *e)
{
    lv_obj_t *slider = lv_event_get_target(e);
    int32_t value = lv_slider_get_value(slider);

    /* Update label */
    lv_label_set_text_fmt(brightness_label, "Brightness: %d%%", (int)value);

    /* ส่ง brightness ให้ LED ทุกดวงที่เปิดอยู่ */
    for (int i = 0; i < NUM_LEDS; i++) {
        if (leds[i].state) {
            cm55_ipc_set_led_brightness(leds[i].id, (uint8_t)value);

            /* CRITICAL: 20ms delay ระหว่าง IPC sends
             * ป้องกัน CM55 rx_buffer overwrite */
            vTaskDelay(pdMS_TO_TICKS(20));
        }
    }

    CM55_LOGD("Brightness set to %d%%", (int)value);
}

/*******************************************************************************
 * Main Function: สร้าง LED Control Panel
 ******************************************************************************/

/* Static index storage for user_data (ต้องเป็น static เพื่อให้ pointer valid) */
static int led_indices[NUM_LEDS] = {0, 1, 2};

void part4_ex5_hw_ipc_led(void)
{
    /* [1] Initialize LED colors */
    leds[0].color = lv_palette_main(LV_PALETTE_RED);
    leds[1].color = lv_palette_main(LV_PALETTE_GREEN);
    leds[2].color = lv_palette_main(LV_PALETTE_BLUE);

    /* [2] Register IPC callback */
    cm55_ipc_register_callback(ex5_ipc_rx_callback, NULL);

    /* [3] Background */
    lv_obj_set_style_bg_color(lv_screen_active(),
                              lv_color_hex(0x0a0e27), LV_PART_MAIN);
    lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

    /* [4] Title */
    lv_obj_t *title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Part 4 - Ex5: IPC LED Control");
    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, 8);

    lv_obj_t *subtitle = lv_label_create(lv_screen_active());
    lv_label_set_text(subtitle, "CM55 UI --> IPC --> CM33 GPIO");
    lv_obj_set_style_text_color(subtitle, lv_color_hex(0x888888), 0);
    lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 30);

    /* [5] LED Control Panel (Container) */
    lv_obj_t *panel = lv_obj_create(lv_screen_active());
    lv_obj_set_size(panel, 440, 160);
    lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, 50);
    lv_obj_set_style_bg_color(panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(panel, 1, 0);
    lv_obj_set_style_radius(panel, 8, 0);
    lv_obj_clear_flag(panel, LV_OBJ_FLAG_SCROLLABLE);

    /* [6] สร้าง LED rows (LED widget + Switch + Status) */
    for (int i = 0; i < NUM_LEDS; i++) {
        int y_pos = 5 + i * 48;

        /* LED indicator widget */
        led_widgets[i] = lv_led_create(panel);
        lv_obj_set_size(led_widgets[i], 30, 30);
        lv_obj_set_pos(led_widgets[i], 10, y_pos);
        lv_led_set_color(led_widgets[i], leds[i].color);
        lv_led_off(led_widgets[i]);

        /* LED name label */
        lv_obj_t *name_lbl = lv_label_create(panel);
        lv_label_set_text(name_lbl, leds[i].name);
        lv_obj_set_style_text_color(name_lbl, lv_color_hex(0xCCCCCC), 0);
        lv_obj_set_pos(name_lbl, 50, y_pos + 6);

        /* ON/OFF Switch */
        led_switches[i] = lv_switch_create(panel);
        lv_obj_set_size(led_switches[i], 60, 28);
        lv_obj_set_pos(led_switches[i], 130, y_pos + 2);
        lv_obj_set_style_bg_color(led_switches[i],
            leds[i].color, LV_PART_INDICATOR | LV_STATE_CHECKED);
        lv_obj_add_event_cb(led_switches[i], ex5_switch_cb,
            LV_EVENT_VALUE_CHANGED, &led_indices[i]);

        /* Status label */
        led_status[i] = lv_label_create(panel);
        lv_label_set_text_fmt(led_status[i], "%s: OFF", leds[i].name);
        lv_obj_set_style_text_color(led_status[i],
            lv_palette_main(LV_PALETTE_RED), 0);
        lv_obj_set_pos(led_status[i], 210, y_pos + 6);
    }

    /* [7] Brightness Control */
    lv_obj_t *bright_panel = lv_obj_create(lv_screen_active());
    lv_obj_set_size(bright_panel, 440, 50);
    lv_obj_align(bright_panel, LV_ALIGN_TOP_MID, 0, 218);
    lv_obj_set_style_bg_color(bright_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(bright_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(bright_panel, 1, 0);
    lv_obj_set_style_radius(bright_panel, 8, 0);
    lv_obj_clear_flag(bright_panel, LV_OBJ_FLAG_SCROLLABLE);

    brightness_label = lv_label_create(bright_panel);
    lv_label_set_text(brightness_label, "Brightness: 100%");
    lv_obj_set_style_text_color(brightness_label, lv_color_hex(0xCCCCCC), 0);
    lv_obj_set_pos(brightness_label, 10, 8);

    brightness_slider = lv_slider_create(bright_panel);
    lv_obj_set_size(brightness_slider, 200, 15);
    lv_obj_set_pos(brightness_slider, 200, 12);
    lv_slider_set_range(brightness_slider, 0, 100);
    lv_slider_set_value(brightness_slider, 100, LV_ANIM_OFF);
    lv_obj_set_style_bg_color(brightness_slider,
        lv_palette_main(LV_PALETTE_AMBER),
        LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(brightness_slider,
        lv_palette_main(LV_PALETTE_AMBER),
        LV_PART_KNOB);
    lv_obj_add_event_cb(brightness_slider, ex5_brightness_cb,
        LV_EVENT_VALUE_CHANGED, NULL);

    /* [8] IPC Status bar */
    ipc_status_label = lv_label_create(lv_screen_active());
    lv_label_set_text(ipc_status_label, "IPC: Ready (waiting for command...)");
    lv_obj_set_style_text_color(ipc_status_label,
        lv_palette_main(LV_PALETTE_CYAN), 0);
    lv_obj_set_style_text_font(ipc_status_label, &lv_font_montserrat_12, 0);
    lv_obj_align(ipc_status_label, LV_ALIGN_BOTTOM_MID, 0, -8);

    /* [9] Create LVGL Timer to check IPC flags (50ms interval) */
    lv_timer_create(ex5_ipc_timer_cb, 50, NULL);

    CM55_LOGI("Part4 Ex5: IPC LED Control initialized");
}
```

#### 4.3 CM33-NS Code - LED IPC Handler

```c
/*******************************************************************************
 * Part 4 - Example 5: CM33-NS Side - LED GPIO Handler
 *
 * Receives IPC LED commands from CM55 and controls real GPIO LEDs.
 * Sends ACK back to CM55 with confirmed state.
 *
 * IMPORTANT: CM33-NS has NO retarget-io -- NEVER use printf()!
 ******************************************************************************/

#include "ipc/cm33_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
#include "aic-eec/aic-eec.h"    /* GPIO API */

/* LED ID to hardware pin mapping */
#define LED_ID_RED    0
#define LED_ID_GREEN  1
#define LED_ID_BLUE   2

/* Local LED state tracking */
static bool     led_states[3]      = {false, false, false};
static uint8_t  led_brightness[3]  = {100, 100, 100};

/*******************************************************************************
 * Handle LED commands from CM55
 ******************************************************************************/
static void cm33_handle_led_cmd(const ipc_msg_t *msg)
{
    ipc_led_data_t *led_data = (ipc_led_data_t *)msg->data;
    uint8_t led_id = led_data->led_id;

    if (led_id > LED_ID_BLUE) return;  /* Invalid LED */

    switch (msg->cmd) {
        case IPC_CMD_LED_SET: {
            led_states[led_id] = (led_data->state != 0);

            /* Control actual GPIO LED */
            switch (led_id) {
                case LED_ID_RED:
                    aic_gpio_led_set(AIC_LED1, led_states[led_id]);
                    break;
                case LED_ID_GREEN:
                    aic_gpio_led_set(AIC_LED2, led_states[led_id]);
                    break;
                case LED_ID_BLUE:
                    aic_gpio_led_set(AIC_LED3, led_states[led_id]);
                    break;
            }

            /* Send ACK back to CM55 with confirmed state */
            ipc_msg_t ack_msg;
            IPC_MSG_INIT(&ack_msg, IPC_CMD_ACK);
            ipc_led_data_t *ack_data = (ipc_led_data_t *)ack_msg.data;
            ack_data->led_id     = led_id;
            ack_data->state      = led_states[led_id] ? 1 : 0;
            ack_data->brightness = led_brightness[led_id];
            cm33_ipc_send_retry(&ack_msg, 5);
            break;
        }

        case IPC_CMD_LED_BRIGHTNESS: {
            led_brightness[led_id] = led_data->brightness;

            /* TODO: Set PWM duty cycle for actual brightness control
             * cyhal_pwm_set_duty_cycle(&pwm_led, led_brightness[led_id], 1000);
             */

            /* Send ACK with updated brightness */
            ipc_msg_t ack_msg;
            IPC_MSG_INIT(&ack_msg, IPC_CMD_ACK);
            ipc_led_data_t *ack_data = (ipc_led_data_t *)ack_msg.data;
            ack_data->led_id     = led_id;
            ack_data->state      = led_states[led_id] ? 1 : 0;
            ack_data->brightness = led_brightness[led_id];
            cm33_ipc_send_retry(&ack_msg, 5);
            break;
        }

        default:
            break;
    }
}

/*******************************************************************************
 * CM33 IPC Callback - route commands to handlers
 ******************************************************************************/
static void cm33_ipc_rx_handler(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    switch (msg->cmd) {
        case IPC_CMD_LED_SET:
        case IPC_CMD_LED_BRIGHTNESS:
            cm33_handle_led_cmd(msg);
            break;

        case IPC_CMD_PING:
            cm33_ipc_send_cmd(IPC_CMD_PONG, 0);
            break;

        case IPC_CMD_LOG:
        case IPC_CMD_LOG_INFO:
        case IPC_CMD_LOG_WARN:
        case IPC_CMD_LOG_ERROR:
        case IPC_CMD_LOG_DEBUG:
            cm33_ipc_handle_log(msg);
            break;

        default:
            break;
    }
}

/*******************************************************************************
 * CM33-NS Main Initialization (relevant portion)
 ******************************************************************************/
void cm33_app_init(void)
{
    /* Initialize GPIO */
    aic_gpio_init();

    /* Initialize IPC */
    cm33_ipc_init();
    cm33_ipc_register_callback(cm33_ipc_rx_handler, NULL);

    /* Enable CM55 core */
    Cy_SysEnableCM55(CY_CORTEX_M55_APPL_ADDR);

    /* Main loop: process IPC messages */
    for (;;) {
        cm33_ipc_process();
        Cy_SysLib_Delay(5);  /* 5ms polling interval */
    }
}
```

***

### 5. Part B: IPC Button Read (Ex6)

Part B สอน input direction -- CM33-NS อ่าน button ด้วย debounce state machine แล้วส่ง event ไป CM55 แสดงผล

#### 5.1 CM33-NS Code - Button Polling with Debounce

```c
/*******************************************************************************
 * Part 4 - Example 6: CM33-NS Button Handler
 *
 * Polls USER button with software debounce.
 * Sends IPC_CMD_BUTTON_EVENT to CM55 on press/release/long-press.
 *
 * IMPORTANT: CM33-NS has NO retarget-io -- NEVER use printf()!
 ******************************************************************************/

#include "ipc/cm33_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
#include "aic-eec/aic-eec.h"

/* ===== Button Configuration ===== */
#define BTN_POLL_INTERVAL_MS    10      /* Poll ทุก 10ms */
#define BTN_DEBOUNCE_MS         30      /* Debounce 30ms (3 samples) */
#define BTN_LONG_PRESS_MS       1000    /* Long press = 1 วินาที */
#define BTN_ACTIVE_LOW          1       /* 0 = pressed on this board */

/* ===== Button State Machine ===== */
typedef enum {
    BTN_STATE_IDLE,
    BTN_STATE_DEBOUNCE_PRESS,
    BTN_STATE_PRESSED,
    BTN_STATE_LONG_PRESSED,
    BTN_STATE_DEBOUNCE_RELEASE
} btn_state_t;

static btn_state_t  btn_state          = BTN_STATE_IDLE;
static uint32_t     btn_debounce_start = 0;
static uint32_t     btn_press_start    = 0;
static bool         btn_long_sent      = false;

/*******************************************************************************
 * Send Button Event via IPC
 ******************************************************************************/
static void send_button_event(bool pressed, bool long_press)
{
    ipc_msg_t msg;
    IPC_MSG_INIT(&msg, IPC_CMD_BUTTON_EVENT);

    ipc_button_data_t *btn_data = (ipc_button_data_t *)msg.data;
    btn_data->button_id  = 0;   /* USER button */
    btn_data->pressed    = pressed ? 1 : 0;
    btn_data->long_press = long_press ? 1 : 0;
    btn_data->timestamp  = Cy_SysLib_GetTicks();

    cm33_ipc_send_retry(&msg, 5);
}

/*******************************************************************************
 * Button Polling with Debounce State Machine
 * เรียกจาก main loop ทุก 10ms
 ******************************************************************************/
static void button_poll(void)
{
    /* อ่าน button (active low: 0 = pressed) */
    uint8_t raw = aic_gpio_button_read_raw(AIC_BTN_USER);
    bool is_pressed = (raw == 0);  /* Active low */
    uint32_t now = Cy_SysLib_GetTicks();

    switch (btn_state) {
        case BTN_STATE_IDLE:
            if (is_pressed) {
                /* เริ่ม debounce สำหรับ press */
                btn_state = BTN_STATE_DEBOUNCE_PRESS;
                btn_debounce_start = now;
            }
            break;

        case BTN_STATE_DEBOUNCE_PRESS:
            if (!is_pressed) {
                /* Bounce - กลับ IDLE */
                btn_state = BTN_STATE_IDLE;
            } else if ((now - btn_debounce_start) >= BTN_DEBOUNCE_MS) {
                /* Debounce passed - confirmed press */
                btn_state = BTN_STATE_PRESSED;
                btn_press_start = now;
                btn_long_sent = false;

                /* ส่ง press event */
                send_button_event(true, false);
            }
            break;

        case BTN_STATE_PRESSED:
            if (!is_pressed) {
                /* เริ่ม debounce สำหรับ release */
                btn_state = BTN_STATE_DEBOUNCE_RELEASE;
                btn_debounce_start = now;
            } else if (!btn_long_sent &&
                       (now - btn_press_start) >= BTN_LONG_PRESS_MS) {
                /* Long press detected */
                btn_long_sent = true;
                btn_state = BTN_STATE_LONG_PRESSED;

                /* ส่ง long-press event */
                send_button_event(true, true);
            }
            break;

        case BTN_STATE_LONG_PRESSED:
            if (!is_pressed) {
                /* เริ่ม debounce สำหรับ release */
                btn_state = BTN_STATE_DEBOUNCE_RELEASE;
                btn_debounce_start = now;
            }
            break;

        case BTN_STATE_DEBOUNCE_RELEASE:
            if (is_pressed) {
                /* Bounce - กลับสถานะก่อนหน้า */
                btn_state = btn_long_sent ?
                    BTN_STATE_LONG_PRESSED : BTN_STATE_PRESSED;
            } else if ((now - btn_debounce_start) >= BTN_DEBOUNCE_MS) {
                /* Debounce passed - confirmed release */
                btn_state = BTN_STATE_IDLE;

                /* ส่ง release event */
                send_button_event(false, false);
            }
            break;
    }
}

/*******************************************************************************
 * CM33-NS Main Loop (relevant portion)
 ******************************************************************************/
void cm33_button_app(void)
{
    aic_gpio_init();
    cm33_ipc_init();
    cm33_ipc_register_callback(cm33_ipc_handler, NULL);

    Cy_SysEnableCM55(CY_CORTEX_M55_APPL_ADDR);

    /* Main loop: poll button + process IPC */
    for (;;) {
        button_poll();
        cm33_ipc_process();
        Cy_SysLib_Delay(BTN_POLL_INTERVAL_MS);
    }
}
```

#### 5.2 CM55 Code - Button Event UI Display

```c
/*******************************************************************************
 * Part 4 - Example 6: Hardware IPC Button Events (CM55 Side)
 *
 * Receives button events from CM33-NS via IPC.
 * Updates LVGL UI using flag-based pattern (thread-safe).
 *
 * CRITICAL: LVGL is NOT thread-safe!
 ******************************************************************************/

#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"
#include "../../shared/ipc_shared.h"

/* ===== IPC Flags (volatile for thread safety) ===== */
static volatile bool     btn_event_flag    = false;
static volatile bool     btn_pressed       = false;
static volatile bool     btn_long_press    = false;
static volatile uint32_t btn_timestamp     = 0;

/* ===== Button Statistics ===== */
static uint32_t press_count      = 0;
static uint32_t long_press_count = 0;
static uint32_t release_count    = 0;

/* ===== LVGL UI Objects ===== */
static lv_obj_t * btn_status_label;
static lv_obj_t * btn_led_widget;
static lv_obj_t * counter_label;
static lv_obj_t * long_press_label;
static lv_obj_t * event_log_label;
static lv_obj_t * timestamp_label;

/* Event log buffer (circular) */
#define EVENT_LOG_SIZE  8
static char event_log[EVENT_LOG_SIZE][48];
static int  event_log_idx = 0;

/*******************************************************************************
 * IPC Receive Callback (ISR context - NEVER call LVGL!)
 ******************************************************************************/
static void ex6_ipc_rx_callback(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    if (msg->cmd == IPC_CMD_BUTTON_EVENT) {
        ipc_button_data_t *btn_data = (ipc_button_data_t *)msg->data;

        /* Store data in volatile variables */
        btn_pressed    = (btn_data->pressed != 0);
        btn_long_press = (btn_data->long_press != 0);
        btn_timestamp  = btn_data->timestamp;

        /* Set flag for LVGL timer to process */
        btn_event_flag = true;
    }
}

/*******************************************************************************
 * Add entry to event log
 ******************************************************************************/
static void add_log_entry(const char *event, uint32_t ts)
{
    /* Format: "[12345] PRESS" */
    lv_snprintf(event_log[event_log_idx], sizeof(event_log[0]),
                "[%u] %s", (unsigned int)ts, event);
    event_log_idx = (event_log_idx + 1) % EVENT_LOG_SIZE;
}

/*******************************************************************************
 * Build log display string from circular buffer
 ******************************************************************************/
static void update_log_display(void)
{
    static char log_buf[512];
    log_buf[0] = '\0';

    /* แสดง log ล่าสุด EVENT_LOG_SIZE entries */
    for (int i = 0; i < EVENT_LOG_SIZE; i++) {
        int idx = (event_log_idx - EVENT_LOG_SIZE + i + EVENT_LOG_SIZE)
                  % EVENT_LOG_SIZE;
        if (event_log[idx][0] != '\0') {
            strcat(log_buf, event_log[idx]);
            strcat(log_buf, "\n");
        }
    }

    lv_label_set_text(event_log_label, log_buf);
}

/*******************************************************************************
 * LVGL Timer: Check IPC flags and update UI (safe context)
 ******************************************************************************/
static void ex6_ipc_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (!btn_event_flag) return;
    btn_event_flag = false;

    /* ===== Update status label ===== */
    if (btn_pressed && btn_long_press) {
        lv_label_set_text(btn_status_label, "LONG PRESS!");
        lv_obj_set_style_text_color(btn_status_label,
            lv_palette_main(LV_PALETTE_ORANGE), 0);
        long_press_count++;
        add_log_entry("LONG_PRESS", btn_timestamp);
    } else if (btn_pressed) {
        lv_label_set_text(btn_status_label, "PRESSED");
        lv_obj_set_style_text_color(btn_status_label,
            lv_palette_main(LV_PALETTE_GREEN), 0);
        press_count++;
        add_log_entry("PRESS", btn_timestamp);
    } else {
        lv_label_set_text(btn_status_label, "RELEASED");
        lv_obj_set_style_text_color(btn_status_label,
            lv_palette_main(LV_PALETTE_RED), 0);
        release_count++;
        add_log_entry("RELEASE", btn_timestamp);
    }

    /* ===== Update LED widget ===== */
    if (btn_pressed) {
        lv_led_on(btn_led_widget);
        if (btn_long_press) {
            lv_led_set_color(btn_led_widget,
                lv_palette_main(LV_PALETTE_ORANGE));
        } else {
            lv_led_set_color(btn_led_widget,
                lv_palette_main(LV_PALETTE_GREEN));
        }
    } else {
        lv_led_off(btn_led_widget);
    }

    /* ===== Update counters ===== */
    lv_label_set_text_fmt(counter_label,
        "Press: %u | Release: %u",
        (unsigned int)press_count, (unsigned int)release_count);
    lv_label_set_text_fmt(long_press_label,
        "Long Press: %u", (unsigned int)long_press_count);

    /* ===== Update timestamp ===== */
    lv_label_set_text_fmt(timestamp_label,
        "Last Event: tick=%u", (unsigned int)btn_timestamp);

    /* ===== Update event log ===== */
    update_log_display();
}

/*******************************************************************************
 * Main Function: สร้าง Button Event UI
 ******************************************************************************/
void part4_ex6_hw_ipc_button(void)
{
    /* Initialize event log */
    for (int i = 0; i < EVENT_LOG_SIZE; i++) {
        event_log[i][0] = '\0';
    }

    /* Register IPC callback */
    cm55_ipc_register_callback(ex6_ipc_rx_callback, NULL);

    /* Background */
    lv_obj_set_style_bg_color(lv_screen_active(),
                              lv_color_hex(0x0a0e27), LV_PART_MAIN);
    lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

    /* Title */
    lv_obj_t *title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Part 4 - Ex6: IPC Button Events");
    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, 8);

    lv_obj_t *subtitle = lv_label_create(lv_screen_active());
    lv_label_set_text(subtitle, "CM33 Button --> IPC --> CM55 Display");
    lv_obj_set_style_text_color(subtitle, lv_color_hex(0x888888), 0);
    lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 30);

    /* ===== Status Panel (Left) ===== */
    lv_obj_t *status_panel = lv_obj_create(lv_screen_active());
    lv_obj_set_size(status_panel, 200, 180);
    lv_obj_set_pos(status_panel, 10, 50);
    lv_obj_set_style_bg_color(status_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(status_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(status_panel, 1, 0);
    lv_obj_set_style_radius(status_panel, 8, 0);
    lv_obj_clear_flag(status_panel, LV_OBJ_FLAG_SCROLLABLE);

    /* Panel title */
    lv_obj_t *panel_title = lv_label_create(status_panel);
    lv_label_set_text(panel_title, "Button Status");
    lv_obj_set_style_text_color(panel_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(panel_title, 10, 5);

    /* LED indicator */
    btn_led_widget = lv_led_create(status_panel);
    lv_obj_set_size(btn_led_widget, 50, 50);
    lv_obj_align(btn_led_widget, LV_ALIGN_CENTER, 0, -15);
    lv_led_set_color(btn_led_widget, lv_palette_main(LV_PALETTE_GREEN));
    lv_led_off(btn_led_widget);

    /* Status label */
    btn_status_label = lv_label_create(status_panel);
    lv_label_set_text(btn_status_label, "RELEASED");
    lv_obj_set_style_text_color(btn_status_label,
        lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_text_font(btn_status_label, &lv_font_montserrat_16, 0);
    lv_obj_align(btn_status_label, LV_ALIGN_CENTER, 0, 30);

    /* Counters */
    counter_label = lv_label_create(status_panel);
    lv_label_set_text(counter_label, "Press: 0 | Release: 0");
    lv_obj_set_style_text_color(counter_label, lv_color_hex(0xCCCCCC), 0);
    lv_obj_set_style_text_font(counter_label, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(counter_label, 10, 130);

    long_press_label = lv_label_create(status_panel);
    lv_label_set_text(long_press_label, "Long Press: 0");
    lv_obj_set_style_text_color(long_press_label,
        lv_palette_main(LV_PALETTE_ORANGE), 0);
    lv_obj_set_style_text_font(long_press_label, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(long_press_label, 10, 148);

    /* ===== Event Log Panel (Right) ===== */
    lv_obj_t *log_panel = lv_obj_create(lv_screen_active());
    lv_obj_set_size(log_panel, 260, 180);
    lv_obj_set_pos(log_panel, 220, 50);
    lv_obj_set_style_bg_color(log_panel, lv_color_hex(0x0d1117), 0);
    lv_obj_set_style_border_color(log_panel, lv_color_hex(0x30363d), 0);
    lv_obj_set_style_border_width(log_panel, 1, 0);
    lv_obj_set_style_radius(log_panel, 8, 0);
    lv_obj_clear_flag(log_panel, LV_OBJ_FLAG_SCROLLABLE);

    /* Log title */
    lv_obj_t *log_title = lv_label_create(log_panel);
    lv_label_set_text(log_title, "Event Log");
    lv_obj_set_style_text_color(log_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(log_title, 10, 5);

    /* Log content */
    event_log_label = lv_label_create(log_panel);
    lv_label_set_text(event_log_label, "(waiting for events...)");
    lv_obj_set_style_text_color(event_log_label,
        lv_palette_main(LV_PALETTE_LIME), 0);
    lv_obj_set_style_text_font(event_log_label, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(event_log_label, 10, 25);

    /* ===== Timestamp bar ===== */
    timestamp_label = lv_label_create(lv_screen_active());
    lv_label_set_text(timestamp_label, "Last Event: (none)");
    lv_obj_set_style_text_color(timestamp_label,
        lv_palette_main(LV_PALETTE_CYAN), 0);
    lv_obj_set_style_text_font(timestamp_label, &lv_font_montserrat_12, 0);
    lv_obj_align(timestamp_label, LV_ALIGN_BOTTOM_MID, 0, -30);

    /* Instruction */
    lv_obj_t *instr = lv_label_create(lv_screen_active());
    lv_label_set_text(instr,
        "Press USER button on board | Hold >1s for Long Press");
    lv_obj_set_style_text_color(instr, lv_color_hex(0x666666), 0);
    lv_obj_set_style_text_font(instr, &lv_font_montserrat_12, 0);
    lv_obj_align(instr, LV_ALIGN_BOTTOM_MID, 0, -10);

    /* Create LVGL timer to check IPC flags (30ms interval) */
    lv_timer_create(ex6_ipc_timer_cb, 30, NULL);

    CM55_LOGI("Part4 Ex6: IPC Button Events initialized");
}
```

***

### 6. องค์ความรู้และเทคนิค (Patterns & Tips)

#### 6.1 IPC Message Packing Pattern

```c
/* วิธีส่ง struct ผ่าน IPC data field */

/* ===== ฝั่งส่ง (CM55) ===== */
ipc_msg_t msg;
IPC_MSG_INIT(&msg, IPC_CMD_LED_SET);

/* Cast data[] เป็น struct pointer แล้วเขียนค่า */
ipc_led_data_t *payload = (ipc_led_data_t *)msg.data;
payload->led_id     = 0;      /* Red LED */
payload->state      = 1;      /* ON */
payload->brightness = 75;     /* 75% */

cm55_ipc_send_retry(&msg, 0); /* 0 = use default retries */

/* ===== ฝั่งรับ (CM33) ===== */
void rx_handler(const ipc_msg_t *msg, void *data)
{
    /* Cast data[] กลับเป็น struct pointer */
    ipc_led_data_t *led = (ipc_led_data_t *)msg->data;
    uint8_t id    = led->led_id;     /* 0 = Red */
    bool    on    = (led->state != 0);
    uint8_t bright = led->brightness; /* 75 */
}
```

#### 6.2 Convenience API vs Raw Message

```c
/* วิธีที่ 1: ใช้ convenience API (แนะนำ) */
cm55_ipc_set_led(0, true);             /* LED 0 ON */
cm55_ipc_set_led_brightness(0, 50);    /* LED 0, 50% */

/* วิธีที่ 2: สร้าง message เอง (flexible กว่า) */
ipc_msg_t msg;
IPC_MSG_INIT(&msg, IPC_CMD_LED_SET);
ipc_led_data_t *d = (ipc_led_data_t *)msg.data;
d->led_id = 0;
d->state  = 1;
d->brightness = 50;
cm55_ipc_send_retry(&msg, 10);

/* ใช้วิธีที่ 2 เมื่อ:
 * - ต้องการ custom retry count
 * - ต้องการส่ง data field เพิ่มเติม
 * - Debug: ดู return status แต่ละ step
 */
```

#### 6.3 Multiple LED Animation with Delay

```c
/* WRONG - จะ overwrite buffer! */
cm55_ipc_set_led(0, true);   /* Red ON */
cm55_ipc_set_led(1, true);   /* Green ON -- อาจ overwrite Red command! */
cm55_ipc_set_led(2, true);   /* Blue ON  -- อาจ overwrite Green command! */

/* RIGHT - 20ms delay ระหว่าง sends */
cm55_ipc_set_led(0, true);
vTaskDelay(pdMS_TO_TICKS(20));
cm55_ipc_set_led(1, true);
vTaskDelay(pdMS_TO_TICKS(20));
cm55_ipc_set_led(2, true);
```

#### 6.4 Debounce Timing Selection

```c
/* ===== เลือก debounce time ตามประเภทปุ่ม ===== */

/* Tactile switch (ปุ่มจิ๋ว บนบอร์ด) - bounce 5-20ms */
#define DEBOUNCE_TACTILE    30   /* ใช้ 30ms เผื่อปลอดภัย */

/* Mechanical switch (toggle switch) - bounce 10-50ms */
#define DEBOUNCE_MECHANICAL 50

/* Reed switch (magnetic) - bounce น้อย */
#define DEBOUNCE_REED       15

/* Rule of thumb: debounce = 2x maximum bounce time */
```

#### 6.5 Long-Press vs Short-Press Pattern

```c
/*
 * Pattern: แยก short-press กับ long-press
 *
 * ใช้ในงานจริง:
 * - Short press: Toggle ON/OFF
 * - Long press:  Reset / Factory reset / Enter config mode
 */

case BTN_STATE_DEBOUNCE_RELEASE:
    if ((now - debounce_start) >= DEBOUNCE_MS) {
        btn_state = BTN_STATE_IDLE;

        if (was_long_press) {
            send_event("LONG_PRESS_RELEASE");
            /* Execute long-press action here */
        } else {
            send_event("SHORT_PRESS_RELEASE");
            /* Execute short-press action here */
        }
    }
    break;
```

#### 6.6 Circular Event Log Buffer

```c
/* Circular buffer สำหรับเก็บ log entries */
#define LOG_SIZE 8
static char log_buf[LOG_SIZE][48];
static int  log_idx = 0;

/* เพิ่ม entry (overwrite เก่าสุดเมื่อเต็ม) */
static void add_log(const char *event, uint32_t ts)
{
    lv_snprintf(log_buf[log_idx], 48, "[%u] %s", (unsigned int)ts, event);
    log_idx = (log_idx + 1) % LOG_SIZE;
}

/* สร้าง string แสดง log (เรียงจากเก่าไปใหม่) */
static void build_log_str(char *out, int out_size)
{
    out[0] = '\0';
    for (int i = 0; i < LOG_SIZE; i++) {
        int idx = (log_idx - LOG_SIZE + i + LOG_SIZE) % LOG_SIZE;
        if (log_buf[idx][0] != '\0') {
            strcat(out, log_buf[idx]);
            strcat(out, "\n");
        }
    }
}
```

***

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

#### Exercise 1: LED Animation Controller (Part A - Intermediate)

สร้าง LED Sequence Pattern Controller ที่ส่ง pattern ผ่าน IPC:

**Requirements:**

* 4 pattern buttons: "Chase", "Blink All", "Alternate", "Fade"
* Chase: LED แดง -> เขียว -> น้ำเงิน วนไป
* Blink All: กะพริบพร้อมกัน (ON 500ms -> OFF 500ms)
* Alternate: สลับ (Red+Blue ON, Green OFF -> Red+Blue OFF, Green ON)
* Fade: ไล่ brightness จาก 0-100-0 แบบ smooth
* ปุ่ม STOP หยุด animation ทันที
* แสดง pattern ปัจจุบันบน UI

**Hints:**

* สร้าง FreeRTOS task แยกสำหรับ animation
* ใช้ global flag `animation_running` + `animation_type`
* STOP button ตั้ง flag false --> task หยุดวน
* ระหว่าง IPC sends ใน animation ต้อง delay 20ms
* Fade: ใช้ loop step ละ 5% ส่ง brightness ทุก 50ms

#### Exercise 2: Long-Press Detection with Different Actions (Part B - Intermediate)

เพิ่ม long-press detection ที่มี action แตกต่างจาก short press:

**Requirements:**

* Short press (<1s): Toggle LED widget (ON/OFF) + increment counter
* Long press (>1s): Reset counter เป็น 0 + flash LED widget 3 ครั้ง
* Very long press (>3s): แสดง "FACTORY RESET" warning label (แค่แสดง ไม่ reset จริง)
* LED widget เปลี่ยนสีตาม action (Green/Orange/Red)
* Progress bar แสดง hold duration (0-3s) ขณะกดค้าง

#### Exercise 3: LED Brightness Sync (Part A - Advanced)

สร้างระบบ Sync ที่ Slider บน CM55 ควบคุม PWM brightness บน CM33 พร้อม feedback:

**Requirements:**

* 3 Sliders แยกสำหรับ Red, Green, Blue (range 0-100)
* แต่ละ slider ส่ง `IPC_CMD_LED_BRIGHTNESS` ไป CM33
* CM33 set PWM duty cycle --> ส่ง ACK + actual brightness กลับ
* แสดง "Requested: 75%" vs "Actual: 73%"
* Color Preview: สี่เหลี่ยมแสดง mixed color จาก R+G+B values
* มีปุ่ม "All 100%", "All 0%", "Random" presets
* IPC round-trip time display

#### Exercise 4: Button Event Logger with Statistics (Part B - Advanced)

สร้าง full event logger ที่บันทึกทุก button event พร้อม timestamp และ statistics:

**Requirements:**

* Event Log แสดง 12 entries ล่าสุด (circular buffer)
* แต่ละ entry แสดง: \[timestamp] event\_type (duration)
* Statistics panel: Total events, Average press duration, Longest/shortest press, Events per minute rate
* Export button: รวม log เป็น string ส่งไป CM33 console ผ่าน IPC LOG
* Clear button: ล้าง log + reset statistics

***

### 8. สรุป (Summary)

#### สิ่งที่เรียนรู้ใน Lab นี้

**Part A (LED):**

1. **IPC LED Control**: ส่งคำสั่ง `IPC_CMD_LED_SET` จาก CM55 ไปควบคุม GPIO บน CM33-NS
2. **Flag-Based Pattern**: วิธีเดียวที่ปลอดภัยในการ update LVGL จาก IPC callback
3. **Status Feedback**: CM33 ส่ง ACK กลับเพื่อยืนยันว่า LED ทำงานจริง
4. **20ms IPC Delay**: จำเป็นเมื่อส่ง IPC หลายครั้งติดกัน ป้องกัน buffer overwrite
5. **Multi-LED Management**: จัดการ LED หลายดวงด้วย struct array + user\_data pattern

**Part B (Button):**

1. **Button Debounce**: State machine pattern สำหรับกรอง contact bounce
2. **Long-Press Detection**: ใช้ timer ตรวจ hold duration ในขณะ pressed state
3. **IPC Button Events**: ส่ง `ipc_button_data_t` จาก CM33 ไป CM55 พร้อม timestamp
4. **Event Logging**: Circular buffer pattern สำหรับเก็บ event history
5. **Flag-Based Update**: Volatile flags + LVGL timer = thread-safe UI update

#### กฎสำคัญที่ต้องจำ

```
+-----------------------------------------------------------+
|            MULTI-CORE IPC GOLDEN RULES                    |
+-----------------------------------------------------------+
|                                                           |
|  1. NEVER call LVGL from IPC callback                     |
|     --> ใช้ flag + LVGL timer เท่านั้น                        |
|                                                           |
|  2. 20ms delay ระหว่าง IPC sends                           |
|     --> Single rx_buffer ที่ CM55                           |
|                                                           |
|  3. NEVER use printf() in CM33-NS                         |
|     --> ไม่มี retarget-io, ใช้ IPC log แทน                   |
|                                                           |
|  4. Always use volatile for IPC flags                     |
|     --> ISR เขียน, main thread อ่าน                         |
|                                                           |
|  5. Use cm55_ipc_send_retry() not cm55_ipc_send()         |
|     --> Pipe อาจ busy, retry ช่วยป้องกัน                     |
|                                                           |
+-----------------------------------------------------------+
```

#### Application ในงานจริง

<table><thead><tr><th width="212.03265380859375">ด้าน</th><th>ตัวอย่าง</th></tr></thead><tbody><tr><td><strong>Industrial Automation</strong></td><td>HMI panel ควบคุม motor, valve, indicator light ผ่าน fieldbus</td></tr><tr><td><strong>Smart Building</strong></td><td>Touch panel ควบคุมไฟ, HVAC, ม่าน ผ่าน KNX/BACnet</td></tr><tr><td><strong>Medical Device</strong></td><td>UI แสดงผล + ควบคุม actuator (pump, valve) แยก core เพื่อ safety</td></tr><tr><td><strong>Automotive</strong></td><td>Instrument cluster ส่งคำสั่งไป body control module</td></tr><tr><td><strong>Safety Systems</strong></td><td>E-STOP button detection, safety interlock monitoring</td></tr><tr><td><strong>Access Control</strong></td><td>Keypad/card reader events --> central monitoring</td></tr><tr><td><strong>IoT Gateway</strong></td><td>Physical reset button --> cloud notification</td></tr></tbody></table>
