> 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>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/multi-core-communication/gpio-via-ipc.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
