> 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/capsense-via-ipc.md).

# CAPSENSE via IPC

## Lab 5: CAPSENSE via IPC

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

***

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

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

ในระบบ Embedded จริง อุปกรณ์ Input (sensor, touch, keypad) มักแชร์ bus กับอุปกรณ์อื่น:

* **HMI Touch Panel + External Sensor**: หน้าจอ touch กับ sensor ภายนอกใช้ I2C bus เดียวกัน ถ้าอ่านพร้อมกันจะ crash
* **Automotive Cluster**: CAPSENSE steering wheel controls อ่านจาก MCU ตัวนึง ส่งข้อมูลผ่าน CAN bus ไปแสดงบน instrument cluster
* **Industrial Keypad**: keypad module ต่อกับ controller ผ่าน I2C แล้วส่ง key event ผ่าน internal bus ไป HMI core
* **Medical Device**: capacitive touch interface ต้องแยก core อ่าน touch กับ core แสดงผลเพื่อความปลอดภัย (safety isolation)

ในบอร์ด PSoC Edge E84:

* **PSoC 4000T CAPSENSE** (2 ปุ่ม + 1 slider) ต่อผ่าน **I2C SCB0** (address 0x08)
* **Display Touch** ก็ใช้ **I2C SCB0** เหมือนกัน
* **Part 1 Ex11**: CM55 อ่าน CAPSENSE ตรง ต้อง disable touch screen --> ใช้ touch จากหน้าจอไม่ได้!
* **Part 4 Ex9**: CM33-NS อ่าน CAPSENSE --> ส่ง IPC ไป CM55 --> touch screen ยังทำงานปกติ!

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

1. **IPC-Based Peripheral Access**: CM33 อ่าน CAPSENSE ผ่าน I2C แล้วส่งข้อมูลผ่าน IPC ไป CM55
2. **Thread-Safe Flag Pattern**: IPC callback ตั้ง flag --> LVGL timer อ่าน flag --> update UI
3. **Edge Detection**: ส่ง IPC เฉพาะเมื่อสถานะเปลี่ยน (ลด traffic)
4. **IPC Protocol Design**: ออกแบบ command + payload สำหรับ CAPSENSE data
5. **Architecture Comparison**: เปรียบเทียบ Direct I2C vs IPC approach
6. **Bus Conflict Resolution**: แก้ปัญหา I2C bus sharing ด้วย multi-core architecture

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

CM33-NS poll PSoC 4000T CAPSENSE ทุก 100ms ผ่าน I2C --> เปรียบเทียบกับ state ก่อนหน้า (edge detection) --> ถ้าเปลี่ยน ส่ง `IPC_CMD_CAPSENSE_DATA` พร้อม payload \[BTN0, BTN1, Slider, SliderActive] ไป CM55 --> CM55 IPC callback เก็บข้อมูลใน volatile variables + set flag --> LVGL timer (50ms) ตรวจ flag --> update UI (slider, LED, panel backgrounds)

***

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

#### 2.1 เปรียบเทียบ: Direct I2C vs IPC Approach

```
+-----------------------------------------------------------+
|     DIRECT I2C (Part 1 Ex11) vs IPC (Part 4 Ex9)          |
+-----------------------------------------------------------+
|                                                           |
|  Part 1 Ex11: Direct I2C on CM55                          |
|  ====================================                     |
|                                                           |
|  CM55 Core:                                               |
|  +-------------------+    +-------------------+           |
|  | LVGL Display      |    | CAPSENSE I2C Read |           |
|  | (needs touch)     |    | (needs SCB0)      |           |
|  +---+---+-----------+    +---+---+-----------+           |
|      |   ^                    |   ^                       |
|      |   |   CONFLICT!        |   |                       |
|      v   |                    v   |                       |
|  +---+---+--------------------+---+-+                     |
|  |        I2C SCB0 (shared)         |                     |
|  +----------------------------------+                     |
|  Result: Must DISABLE touch screen!                       |
|                                                           |
|  Part 4 Ex9: IPC Approach                                 |
|  ====================================                     |
|                                                           |
|  CM33-NS Core:           CM55 Core:                       |
|  +------------------+    +------------------+             |
|  | CAPSENSE I2C     |    | LVGL Display     |             |
|  | Reader (SCB0)    |    | + Touch (SCB0)   |             |
|  +--------+---------+    +--------+---------+             |
|           |                       ^                       |
|           | IPC_CMD_CAPSENSE_DATA |                       |
|           +------------>----------+                       |
|  Result: Touch screen WORKS! Best of both worlds!         |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.2 Comparison Table: Direct I2C vs IPC

<table><thead><tr><th width="201.2691650390625">หัวข้อ</th><th>Part 1 Ex11 (Direct I2C)</th><th>Part 4 Ex9 (IPC)</th></tr></thead><tbody><tr><td><strong>CAPSENSE อ่านจาก</strong></td><td>CM55 (direct)</td><td>CM33-NS (via I2C)</td></tr><tr><td><strong>Touch Screen</strong></td><td>DISABLED (bus conflict)</td><td>WORKS normally</td></tr><tr><td><strong>I2C Bus</strong></td><td>CM55 ใช้ทั้ง touch + CAPSENSE</td><td>แยก: CM33=CAPSENSE, CM55=touch</td></tr><tr><td><strong>Latency</strong></td><td>~50ms (direct read)</td><td>~70ms (I2C + IPC + LVGL timer)</td></tr><tr><td><strong>Code Complexity</strong></td><td>ต่ำ (single core)</td><td>ปานกลาง (IPC callback + flags)</td></tr><tr><td><strong>Thread Safety</strong></td><td>ไม่มีปัญหา (single thread)</td><td>ต้องใช้ volatile + flag pattern</td></tr><tr><td><strong>Architecture</strong></td><td>ไม่ดี (bus conflict)</td><td>ดี (CM33=HW, CM55=UI)</td></tr><tr><td><strong>Scalability</strong></td><td>ยาก (bus เต็ม)</td><td>ดี (เพิ่ม sensor ได้)</td></tr><tr><td><strong>Production Ready</strong></td><td>ไม่ (touch ใช้ไม่ได้)</td><td>ใช่ (ทุกอย่างทำงาน)</td></tr><tr><td><strong>I2C Contention</strong></td><td>มี (must disable touch)</td><td>ไม่มี (แยก core)</td></tr></tbody></table>

#### 2.3 CAPSENSE IPC Architecture

```
+-----------------------------------------------------------+
|          CAPSENSE IPC ARCHITECTURE (Ex9)                  |
+-----------------------------------------------------------+
|                                                           |
|  CM33-NS Core (HW Service)       CM55 Core (LVGL UI)      |
|  ==========================      =====================    |
|                                                           |
|  +------------------+                                     |
|  | I2C SCB0         |  Poll every 100ms                   |
|  | Read PSoC 4000T  |                                     |
|  | (addr 0x08)      |                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+                                     |
|  | Parse 3 bytes:   |                                     |
|  | [0] BTN0 (ASCII) |                                     |
|  | [1] BTN1 (ASCII) |                                     |
|  | [2] Slider (raw) |                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+            +------------------+     |
|  | Edge Detection:  |  IPC TX   | IPC RX Callback   |     |
|  | Changed from     |---------->| (NOT LVGL-safe!)  |     |
|  | previous state?  |  0xB6     |                   |     |
|  |                  |           | Copy to volatile: |     |
|  | YES -> send IPC  |           | ex9_rx_btn0       |     |
|  | NO  -> skip      |           | ex9_rx_btn1       |     |
|  +------------------+           | ex9_rx_slider     |     |
|                                 | Set flag = true   |     |
|                                 +--------+----------+     |
|                                          |                |
|                                          v (LVGL timer)   |
|                                 +------------------+      |
|                                 | Check flag:      |      |
|                                 | if (flag) {      |      |
|                                 |   flag = false;  |      |
|                                 |   update_ui();   |      |
|                                 | }                |      |
|                                 +------------------+      |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.4 IPC CAPSENSE Data Format

```
+-----------------------------------------------------------+
|       IPC_CMD_CAPSENSE_DATA (0xB6) PAYLOAD                |
+-----------------------------------------------------------+
|                                                           |
|  ipc_msg_t.data[0..3]:                                    |
|  +--------+--------+-----------+---------------+          |
|  | btn0   | btn1   | slider    | slider_active |          |
|  | 1 byte | 1 byte | 1 byte    | 1 byte        |          |
|  +--------+--------+-----------+---------------+          |
|                                                           |
|  btn0:          0 = released, 1 = pressed (CSB1)          |
|  btn1:          0 = released, 1 = pressed (CSB2)          |
|  slider:        0-100 position (0 = no touch) (CSS1)      |
|  slider_active: 0 = not touching, 1 = touching            |
|                                                           |
|  Total: 4 bytes (minimal IPC payload)                     |
|                                                           |
|  Commands:                                                |
|  IPC_CMD_CAPSENSE_DATA (0xB6): CM33 -> CM55 (data)        |
|  IPC_CMD_CAPSENSE_REQ  (0xB7): CM55 -> CM33 (request)     |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.5 Thread-Safe Flag Pattern (CRITICAL)

```
+-----------------------------------------------------------+
|      WHY NEVER CALL LVGL FROM IPC CALLBACK                |
+-----------------------------------------------------------+
|                                                           |
|  IPC Callback runs in IPC task/ISR context:               |
|                                                           |
|  WRONG (will crash randomly):                             |
|  void ipc_cb(msg) {                                       |
|      lv_slider_set_value(slider, msg->data[2]);  // NO!   |
|      lv_label_set_text(label, "PRESSED");         // NO!  |
|  }                                                        |
|                                                           |
|  WHY? LVGL is NOT thread-safe.                            |
|  If LVGL is rendering a frame while IPC callback          |
|  modifies widget data, the internal state becomes         |
|  corrupted -> crash, visual glitches, or memory leak.     |
|                                                           |
|  CORRECT (flag-based pattern):                            |
|  volatile bool flag = false;                              |
|  volatile uint8_t rx_data = 0;                            |
|                                                           |
|  void ipc_cb(msg) {                                       |
|      rx_data = msg->data[2];    // OK: just copy data     |
|      flag = true;               // OK: just set flag      |
|  }                                                        |
|                                                           |
|  void lvgl_timer_cb(timer) {    // Runs in LVGL context   |
|      if (flag) {                                          |
|          flag = false;                                    |
|          lv_slider_set_value(slider, rx_data);  // SAFE!  |
|      }                                                    |
|  }                                                        |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.6 Program Flowchart

```
+-----------------------------------------------------------+
|              CAPSENSE IPC PROGRAM FLOW                    |
+-----------------------------------------------------------+
|                                                           |
|  CM55 Side:                                               |
|  ==========                                               |
|     +----------+                                          |
|     |  Start   |                                          |
|     +----+-----+                                          |
|          |                                                |
|          v                                                |
|  +--------------------+                                   |
|  | Register IPC cb    |                                   |
|  | ex9_ipc_callback() |                                   |
|  +--------+-----------+                                   |
|           |                                               |
|           v                                               |
|  +--------------------+                                   |
|  | Build UI:          |                                   |
|  | - Slider panel     |                                   |
|  | - Button panels x2 |                                   |
|  | - Status footer    |                                   |
|  +--------+-----------+                                   |
|           |                                               |
|           v                                               |
|  +--------------------+                                   |
|  | Create timer (50ms)|                                   |
|  +--------+-----------+                                   |
|           |                                               |
|           v                                               |
|  +--------------------+                                   |
|  | Send CAPSENSE_REQ  |  Request initial state            |
|  | to CM33            |                                   |
|  +--------+-----------+                                   |
|           |                                               |
|           v                                               |
|  +--------------------+         +--------------------+    |
|  | LVGL timer loop:   |<--flag--| IPC callback:      |    |
|  | check flag         |         | copy data to       |    |
|  | update_ui()        |         | volatile vars      |    |
|  | edge detection     |         | set flag = true    |    |
|  +--------------------+         +--------------------+    |
|                                                           |
|  CM33-NS Side:                                            |
|  =============                                            |
|  +--------------------+                                   |
|  | capsense_init()    |  Init I2C SCB0, addr 0x08         |
|  +--------+-----------+                                   |
|           |                                               |
|           v                                               |
|  +--------------------+                                   |
|  | capsense_poll()    |<------+ Loop every 100ms          |
|  | Read 3 bytes I2C   |       |                           |
|  | Parse buttons/slider|      |                           |
|  | Edge detection     |       |                           |
|  | Changed? send IPC  |-------+                           |
|  +--------------------+                                   |
|                                                           |
+-----------------------------------------------------------+
```

***

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

#### 3.1 CM55 IPC Functions

<table><thead><tr><th width="277.68603515625">Function</th><th>Description</th><th>Return</th></tr></thead><tbody><tr><td><code>cm55_ipc_register_callback(cb, data)</code></td><td>ลงทะเบียน IPC callback</td><td>void</td></tr><tr><td><code>cm55_ipc_send_cmd(IPC_CMD_CAPSENSE_REQ, 0)</code></td><td>ขอ CAPSENSE state จาก CM33</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_is_init()</code></td><td>ตรวจว่า IPC initialized หรือไม่</td><td><code>bool</code></td></tr><tr><td><code>cm55_ipc_get_stats(tx, rx, err)</code></td><td>ดึง IPC statistics</td><td>void</td></tr></tbody></table>

#### 3.2 CM33-NS CAPSENSE Functions

| Function                         | Description                      | Return |
| -------------------------------- | -------------------------------- | ------ |
| `capsense_module_init(hw, ctx)`  | Initialize CAPSENSE module + I2C | void   |
| `capsense_module_poll()`         | อ่าน I2C + edge detect + ส่ง IPC | void   |
| `capsense_module_send_current()` | ส่งสถานะปัจจุบัน (for REQ)       | void   |

#### 3.3 LVGL Widget Functions

| Function                                         | Description                   | Return       |
| ------------------------------------------------ | ----------------------------- | ------------ |
| `lv_slider_create(parent)`                       | สร้าง slider (read-only mode) | `lv_obj_t *` |
| `lv_slider_set_value(slider, val, anim)`         | ตั้งค่า slider                | void         |
| `lv_led_create(parent)`                          | สร้าง LED widget              | `lv_obj_t *` |
| `lv_led_on(led)` / `lv_led_off(led)`             | เปิด/ปิด LED widget           | void         |
| `lv_led_set_brightness(led, bright)`             | ตั้ง brightness (0-255)       | void         |
| `lv_obj_set_style_bg_color(obj, color, sel)`     | เปลี่ยนสีพื้นหลัง panel       | void         |
| `lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE)` | ปิด click บน slider           | void         |

#### 3.4 IPC CAPSENSE Data

| Field           | Type      | Byte     | Description                  |
| --------------- | --------- | -------- | ---------------------------- |
| `btn0`          | `uint8_t` | data\[0] | 0=released, 1=pressed (CSB1) |
| `btn1`          | `uint8_t` | data\[1] | 0=released, 1=pressed (CSB2) |
| `slider_pos`    | `uint8_t` | data\[2] | 0-100 (0=no touch)           |
| `slider_active` | `uint8_t` | data\[3] | 0=not touching, 1=touching   |

***

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

#### 4.1 CM55 Code - CAPSENSE IPC Display

```c
/*******************************************************************************
 * Part 4 - Example 9: CAPSENSE via IPC (CM55 Side)
 *
 * CM33-NS reads PSoC 4000T CAPSENSE via I2C (slave 0x08) and sends
 * state changes to CM55 via IPC_CMD_CAPSENSE_DATA.
 *
 * Advantages over Part 1 Ex11 (direct I2C on CM55):
 * - Touch screen remains functional (no lv_port_indev_disable_touch)
 * - Follows CM33=HW service, CM55=UI pattern (IPC architecture)
 * - Edge detection on CM33 reduces IPC traffic
 *
 * Data format (ipc_msg_t.data[0..3]):
 *   [0] btn0:          0=released, 1=pressed
 *   [1] btn1:          0=released, 1=pressed
 *   [2] slider_pos:    0-100 (0=no touch)
 *   [3] slider_active: 0=not touching, 1=touching
 *
 * CRITICAL: LVGL is NOT thread-safe!
 *   IPC callback -> volatile flags -> LVGL timer -> UI update
 ******************************************************************************/

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

#define EX9_NUM_BUTTONS 2

/* ===== UI Elements ===== */
static lv_obj_t *ex9_btn_panels[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_btn_leds[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_btn_status[EX9_NUM_BUTTONS];
static lv_obj_t *ex9_slider;
static lv_obj_t *ex9_slider_value;
static lv_obj_t *ex9_output_led;
static lv_obj_t *ex9_ipc_count_label;
static lv_timer_t *ex9_timer;

/* ===== IPC Flag-Based Pattern (volatile for thread safety) ===== */
static volatile bool    ex9_capsense_updated  = false;
static volatile uint8_t ex9_rx_btn0           = 0;
static volatile uint8_t ex9_rx_btn1           = 0;
static volatile uint8_t ex9_rx_slider         = 0;
static volatile uint8_t ex9_rx_slider_active  = 0;
static uint32_t         ex9_ipc_event_count   = 0;

/* Previous state for UI edge detection (reduces unnecessary redraws) */
static uint8_t ex9_prev_btn0   = 0xFF;
static uint8_t ex9_prev_btn1   = 0xFF;
static uint8_t ex9_prev_slider = 0xFF;

/*******************************************************************************
 * IPC Receive Callback (NOT LVGL-safe context!)
 *
 * CRITICAL: This function runs in IPC task context.
 * NEVER call any lv_* function here!
 * Only copy data to volatile variables and set the flag.
 ******************************************************************************/
static void ex9_ipc_callback(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;
    if (msg->cmd == IPC_CMD_CAPSENSE_DATA) {
        /* Copy payload to volatile variables */
        ex9_rx_btn0          = (uint8_t)msg->data[0];
        ex9_rx_btn1          = (uint8_t)msg->data[1];
        ex9_rx_slider        = (uint8_t)msg->data[2];
        ex9_rx_slider_active = (uint8_t)msg->data[3];

        /* Set flag -- LVGL timer will read and update UI */
        ex9_capsense_updated = true;
    }
}

/*******************************************************************************
 * Update UI with CAPSENSE data (called from LVGL timer - SAFE context)
 *
 * Uses edge detection: only update widgets when state actually changes.
 * This reduces unnecessary LVGL redraws and improves performance.
 ******************************************************************************/
static void ex9_update_ui(uint8_t btn0, uint8_t btn1, uint8_t slider_pos)
{
    /* ===== Button 0 (CSB1) ===== */
    if (btn0 != ex9_prev_btn0) {
        if (btn0) {
            lv_obj_set_style_bg_color(ex9_btn_panels[0],
                lv_color_hex(0x00AA00), 0);
            lv_led_on(ex9_btn_leds[0]);
            lv_label_set_text(ex9_btn_status[0], "TOUCHED");
            lv_obj_set_style_text_color(ex9_btn_status[0],
                AIC_COLOR_SUCCESS, 0);
        } else {
            lv_obj_set_style_bg_color(ex9_btn_panels[0],
                lv_color_hex(0x333355), 0);
            lv_led_off(ex9_btn_leds[0]);
            lv_label_set_text(ex9_btn_status[0], "Ready");
            lv_obj_set_style_text_color(ex9_btn_status[0],
                AIC_COLOR_TEXT_DIM, 0);
        }
        ex9_prev_btn0 = btn0;
    }

    /* ===== Button 1 (CSB2) ===== */
    if (btn1 != ex9_prev_btn1) {
        if (btn1) {
            lv_obj_set_style_bg_color(ex9_btn_panels[1],
                lv_color_hex(0x00AA00), 0);
            lv_led_on(ex9_btn_leds[1]);
            lv_label_set_text(ex9_btn_status[1], "TOUCHED");
            lv_obj_set_style_text_color(ex9_btn_status[1],
                AIC_COLOR_SUCCESS, 0);
        } else {
            lv_obj_set_style_bg_color(ex9_btn_panels[1],
                lv_color_hex(0x333355), 0);
            lv_led_off(ex9_btn_leds[1]);
            lv_label_set_text(ex9_btn_status[1], "Ready");
            lv_obj_set_style_text_color(ex9_btn_status[1],
                AIC_COLOR_TEXT_DIM, 0);
        }
        ex9_prev_btn1 = btn1;
    }

    /* ===== Slider (CSS1) ===== */
    if (slider_pos != ex9_prev_slider) {
        lv_slider_set_value(ex9_slider, slider_pos, LV_ANIM_ON);
        lv_label_set_text_fmt(ex9_slider_value, "%d%%", slider_pos);

        /* LED brightness follows slider position */
        if (slider_pos > 0) {
            uint8_t brightness = (uint8_t)(slider_pos * 255 / 100);
            lv_led_on(ex9_output_led);
            lv_led_set_brightness(ex9_output_led, brightness);
        } else {
            lv_led_off(ex9_output_led);
        }

        ex9_prev_slider = slider_pos;
    }
}

/*******************************************************************************
 * LVGL Timer Callback (50ms) - Check IPC flag and update UI
 *
 * This runs in LVGL context - SAFE to call lv_* functions here.
 ******************************************************************************/
static void ex9_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (ex9_capsense_updated) {
        ex9_capsense_updated = false;  /* Clear flag FIRST */
        ex9_ipc_event_count++;

        /* Update UI widgets (with edge detection) */
        ex9_update_ui(ex9_rx_btn0, ex9_rx_btn1, ex9_rx_slider);

        /* Update IPC event counter in footer */
        lv_label_set_text_fmt(ex9_ipc_count_label,
            "Ex9: CAPSENSE (IPC)    IPC events: %u",
            (unsigned)ex9_ipc_event_count);
    }
}

/*******************************************************************************
 * Main Function: Build CAPSENSE IPC UI
 ******************************************************************************/
void part4_ex9_capsense_ipc(void)
{
    /* Register IPC callback for CAPSENSE_DATA */
    cm55_ipc_register_callback(ex9_ipc_callback, NULL);

    /* Reset UI state */
    ex9_prev_btn0   = 0xFF;
    ex9_prev_btn1   = 0xFF;
    ex9_prev_slider = 0xFF;
    ex9_ipc_event_count  = 0;
    ex9_capsense_updated = false;

    lv_obj_t *scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a2e), LV_PART_MAIN);

    /* ===== TITLE ===== */
    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "Part 4 Ex9: CAPSENSE (IPC)");
    lv_obj_set_style_text_color(title, AIC_COLOR_TEXT, 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);

    /* ===== MODE LABEL ===== */
    lv_obj_t *mode_label = lv_label_create(scr);
    if (cm55_ipc_is_init()) {
        lv_label_set_text(mode_label,
            "Mode: IPC (CM33 reads I2C -> CM55 display)");
        lv_obj_set_style_text_color(mode_label, AIC_COLOR_SUCCESS, 0);
    } else {
        lv_label_set_text(mode_label, "Mode: IPC NOT initialized");
        lv_obj_set_style_text_color(mode_label, AIC_COLOR_ERROR, 0);
    }
    lv_obj_align(mode_label, LV_ALIGN_TOP_MID, 0, 32);

    /* ===== CAPSENSE SLIDER PANEL (420x80) ===== */
    lv_obj_t *slider_panel = lv_obj_create(scr);
    lv_obj_set_size(slider_panel, 420, 80);
    lv_obj_align(slider_panel, LV_ALIGN_TOP_MID, 0, 55);
    lv_obj_set_style_bg_color(slider_panel, lv_color_hex(0x0f0f23), 0);
    lv_obj_set_style_pad_all(slider_panel, 8, 0);
    lv_obj_clear_flag(slider_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *slider_title = lv_label_create(slider_panel);
    lv_label_set_text(slider_title, "SLIDER (CSS1)");
    lv_obj_set_style_text_color(slider_title, AIC_COLOR_TEXT, 0);
    lv_obj_align(slider_title, LV_ALIGN_TOP_LEFT, 10, 0);

    ex9_slider_value = lv_label_create(slider_panel);
    lv_label_set_text(ex9_slider_value, "0%");
    lv_obj_set_style_text_color(ex9_slider_value, AIC_COLOR_PRIMARY, 0);
    lv_obj_set_style_text_font(ex9_slider_value, &lv_font_montserrat_16, 0);
    lv_obj_align(ex9_slider_value, LV_ALIGN_TOP_RIGHT, -10, 0);

    /* Slider widget (read-only: controlled by IPC data, not touch) */
    ex9_slider = lv_slider_create(slider_panel);
    lv_obj_set_width(ex9_slider, 340);
    lv_obj_set_height(ex9_slider, 25);
    lv_obj_align(ex9_slider, LV_ALIGN_BOTTOM_MID, 0, -8);
    lv_slider_set_range(ex9_slider, 0, 100);
    lv_slider_set_value(ex9_slider, 0, LV_ANIM_OFF);
    lv_obj_set_style_bg_color(ex9_slider, lv_color_hex(0x333355),
        LV_PART_MAIN);
    lv_obj_set_style_bg_color(ex9_slider, AIC_COLOR_PRIMARY,
        LV_PART_INDICATOR);
    lv_obj_remove_flag(ex9_slider, LV_OBJ_FLAG_CLICKABLE);

    /* Output LED next to slider */
    ex9_output_led = lv_led_create(slider_panel);
    lv_obj_set_size(ex9_output_led, 25, 25);
    lv_obj_align(ex9_output_led, LV_ALIGN_BOTTOM_RIGHT, -5, -8);
    lv_led_set_color(ex9_output_led,
        lv_palette_main(LV_PALETTE_LIGHT_BLUE));
    lv_led_off(ex9_output_led);

    /* ===== CAPSENSE BUTTON PANELS (140x150 each) ===== */
    const char *btn_names[] = {"BTN0", "BTN1"};
    const char *btn_ids[]   = {"(CSB1)", "(CSB2)"};
    uint32_t led_colors[]   = {0xff0000, 0x00ff00};
    int btn_x_pos[]         = {-110, 110};

    for (int i = 0; i < EX9_NUM_BUTTONS; i++) {
        ex9_btn_panels[i] = lv_obj_create(scr);
        lv_obj_set_size(ex9_btn_panels[i], 140, 150);
        lv_obj_align(ex9_btn_panels[i], LV_ALIGN_BOTTOM_MID,
            btn_x_pos[i], -55);
        lv_obj_set_style_bg_color(ex9_btn_panels[i],
            lv_color_hex(0x333355), 0);
        lv_obj_set_style_border_width(ex9_btn_panels[i], 3, 0);
        lv_obj_set_style_border_color(ex9_btn_panels[i],
            lv_color_hex(0x666699), 0);
        lv_obj_set_style_radius(ex9_btn_panels[i], 10, 0);
        lv_obj_set_style_pad_all(ex9_btn_panels[i], 5, 0);
        lv_obj_clear_flag(ex9_btn_panels[i], LV_OBJ_FLAG_SCROLLABLE);

        /* Button name */
        lv_obj_t *name = lv_label_create(ex9_btn_panels[i]);
        lv_label_set_text(name, btn_names[i]);
        lv_obj_set_style_text_color(name, AIC_COLOR_TEXT, 0);
        lv_obj_set_style_text_font(name, &lv_font_montserrat_16, 0);
        lv_obj_align(name, LV_ALIGN_TOP_MID, 0, 2);

        /* Hardware ID */
        lv_obj_t *id_lbl = lv_label_create(ex9_btn_panels[i]);
        lv_label_set_text(id_lbl, btn_ids[i]);
        lv_obj_set_style_text_color(id_lbl, AIC_COLOR_TEXT_DIM, 0);
        lv_obj_align(id_lbl, LV_ALIGN_TOP_MID, 0, 22);

        /* LED indicator */
        ex9_btn_leds[i] = lv_led_create(ex9_btn_panels[i]);
        lv_obj_set_size(ex9_btn_leds[i], 50, 50);
        lv_obj_align(ex9_btn_leds[i], LV_ALIGN_CENTER, 0, 8);
        lv_led_set_color(ex9_btn_leds[i], lv_color_hex(led_colors[i]));
        lv_led_off(ex9_btn_leds[i]);

        /* Status label */
        ex9_btn_status[i] = lv_label_create(ex9_btn_panels[i]);
        lv_label_set_text(ex9_btn_status[i], "Ready");
        lv_obj_set_style_text_color(ex9_btn_status[i],
            AIC_COLOR_TEXT_DIM, 0);
        lv_obj_align(ex9_btn_status[i], LV_ALIGN_BOTTOM_MID, 0, -2);
    }

    /* ===== FOOTER STATUS ===== */
    ex9_ipc_count_label = lv_label_create(scr);
    lv_label_set_text(ex9_ipc_count_label,
        "Ex9: CAPSENSE (IPC)    IPC events: 0");
    lv_obj_set_style_text_color(ex9_ipc_count_label,
        AIC_COLOR_TEXT_DIM, 0);
    lv_obj_set_style_text_align(ex9_ipc_count_label,
        LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_align(ex9_ipc_count_label, LV_ALIGN_BOTTOM_MID, 0, -25);

    /* Copyright footer */
    aic_create_footer(scr);

    /* Timer to check IPC flags and update UI (50ms interval) */
    ex9_timer = lv_timer_create(ex9_timer_cb, 50, NULL);

    /* Request initial CAPSENSE state from CM33 */
    if (cm55_ipc_is_init()) {
        cm55_ipc_send_cmd(IPC_CMD_CAPSENSE_REQ, 0);
    }

    printf("[Part4] Ex9: CAPSENSE via IPC started\n");
    printf("  - CM33-NS reads PSoC 4000T (I2C 0x08)\n");
    printf("  - State changes sent via IPC_CMD_CAPSENSE_DATA\n");
    printf("  - Touch screen remains functional!\n");
}
```

#### 4.2 CM33-NS Code - CAPSENSE I2C Reader + IPC Sender

```c
/*******************************************************************************
 * File: capsense_task.c
 * Description: CAPSENSE I2C reader for CM33-NS
 *
 * Reads PSoC 4000T CAPSENSE chip via I2C and sends state changes
 * to CM55 via IPC_CMD_CAPSENSE_DATA. Uses edge detection to only
 * send data when button/slider state actually changes.
 *
 * I2C Protocol:
 *   Slave address: 0x08
 *   Read 3 bytes:
 *     [0] Button 0 (CSB1) - ASCII '0' or '1' (subtract 0x30)
 *     [1] Button 1 (CSB2) - ASCII '0'/'1'/'2' (subtract 0x30)
 *     [2] Slider (CSS1) - raw value 0-100
 *
 * IMPORTANT: CM33-NS has NO retarget-io -- NEVER use printf()!
 ******************************************************************************/

#include "capsense_task.h"
#include "../../shared/ipc_shared.h"
#include "../ipc/cm33_ipc_pipe.h"
#include <string.h>

/* ===== I2C Configuration ===== */
#define CAPSENSE_I2C_SLAVE_ADDR     (0x08U)
#define CAPSENSE_I2C_READ_SIZE      (3U)
#define CAPSENSE_I2C_TIMEOUT_MS     (0U)    /* Blocking */
#define CAPSENSE_ASCII_OFFSET       (0x30U)

/* ===== Private State ===== */
static CySCB_Type *cs_hw = NULL;
static cy_stc_scb_i2c_context_t *cs_ctx = NULL;
static bool cs_initialized = false;

/* Previous state for edge detection */
static uint8_t prev_btn0 = 0;
static uint8_t prev_btn1 = 0;
static uint8_t prev_slider = 0;
static uint8_t prev_slider_active = 0;

/* Current state */
static uint8_t cur_btn0 = 0;
static uint8_t cur_btn1 = 0;
static uint8_t cur_slider = 0;
static uint8_t cur_slider_active = 0;

/*******************************************************************************
 * Private: Read 3 bytes from CAPSENSE via I2C
 ******************************************************************************/
static bool capsense_i2c_read(uint8_t *btn0, uint8_t *btn1,
                               uint8_t *slider_pos, uint8_t *slider_active)
{
    if (!cs_initialized || !cs_hw || !cs_ctx) return false;

    uint8_t buffer[CAPSENSE_I2C_READ_SIZE] = {0};
    uint8_t *pData = &buffer[0];
    uint8_t remaining = CAPSENSE_I2C_READ_SIZE;
    cy_en_scb_i2c_command_t ack = CY_SCB_I2C_ACK;
    cy_en_scb_i2c_status_t status;

    /* Start I2C read transaction */
    status = (cs_ctx->state == CY_SCB_I2C_IDLE) ?
        Cy_SCB_I2C_MasterSendStart(cs_hw, CAPSENSE_I2C_SLAVE_ADDR,
                                    CY_SCB_I2C_READ_XFER,
                                    CAPSENSE_I2C_TIMEOUT_MS, cs_ctx) :
        Cy_SCB_I2C_MasterSendReStart(cs_hw, CAPSENSE_I2C_SLAVE_ADDR,
                                      CY_SCB_I2C_READ_XFER,
                                      CAPSENSE_I2C_TIMEOUT_MS, cs_ctx);

    if (CY_SCB_I2C_SUCCESS == status) {
        while (remaining > 0) {
            if (remaining == 1) {
                ack = CY_SCB_I2C_NAK;  /* NAK on last byte */
            }
            status = Cy_SCB_I2C_MasterReadByte(cs_hw, ack, pData,
                                                CAPSENSE_I2C_TIMEOUT_MS,
                                                cs_ctx);
            if (status != CY_SCB_I2C_SUCCESS) break;
            ++pData;
            --remaining;
        }
    }

    /* Always send STOP */
    Cy_SCB_I2C_MasterSendStop(cs_hw, CAPSENSE_I2C_TIMEOUT_MS, cs_ctx);

    if (status != CY_SCB_I2C_SUCCESS) return false;

    /* Parse: subtract ASCII offset for buttons */
    buffer[0] -= CAPSENSE_ASCII_OFFSET;
    buffer[1] -= CAPSENSE_ASCII_OFFSET;

    /* Button 0: 0=not pressed, non-zero=pressed */
    *btn0 = (buffer[0] != 0) ? 1 : 0;

    /* Button 1: 1=not pressed, 2=pressed (inverted logic) */
    *btn1 = (buffer[1] != 1) ? 1 : 0;

    /* Slider: 0=no touch, 1-100=position */
    *slider_pos = buffer[2];
    *slider_active = (buffer[2] != 0) ? 1 : 0;

    return true;
}

/*******************************************************************************
 * Private: Send current state via IPC
 ******************************************************************************/
static void capsense_send_ipc(void)
{
    ipc_msg_t msg;
    IPC_MSG_INIT(&msg, IPC_CMD_CAPSENSE_DATA);
    msg.data[0] = cur_btn0;
    msg.data[1] = cur_btn1;
    msg.data[2] = cur_slider;
    msg.data[3] = cur_slider_active;
    cm33_ipc_send_retry(&msg, 0);
}

/*******************************************************************************
 * Public: Initialize CAPSENSE module
 ******************************************************************************/
void capsense_module_init(CySCB_Type *hw,
                           cy_stc_scb_i2c_context_t *context)
{
    cs_hw  = hw;
    cs_ctx = context;
    cs_initialized = (hw != NULL && context != NULL);

    prev_btn0 = 0;
    prev_btn1 = 0;
    prev_slider = 0;
    prev_slider_active = 0;
}

/*******************************************************************************
 * Public: Poll CAPSENSE and send IPC on state change
 ******************************************************************************/
void capsense_module_poll(void)
{
    uint8_t b0, b1, sp, sa;

    if (!capsense_i2c_read(&b0, &b1, &sp, &sa)) return;

    cur_btn0 = b0;
    cur_btn1 = b1;
    cur_slider = sp;
    cur_slider_active = sa;

    /* Edge detection: only send IPC when state changes */
    if (b0 != prev_btn0 || b1 != prev_btn1 ||
        sp != prev_slider || sa != prev_slider_active) {

        capsense_send_ipc();

        prev_btn0 = b0;
        prev_btn1 = b1;
        prev_slider = sp;
        prev_slider_active = sa;
    }
}

/*******************************************************************************
 * Public: Send current state (for CAPSENSE_REQ from CM55)
 ******************************************************************************/
void capsense_module_send_current(void)
{
    capsense_send_ipc();
}
```

#### 4.3 Code Breakdown

**Part A: Volatile Flag Pattern (CRITICAL)**

```c
/* ===== ทำไมต้อง volatile? ===== */

/*
 * IPC callback ทำงานใน IPC task context (อีก thread/interrupt)
 * LVGL timer ทำงานใน LVGL task context
 *
 * ถ้าไม่ใช้ volatile:
 * - Compiler อาจ optimize ออก (cache ค่าใน register)
 * - LVGL timer อาจไม่เห็นค่าที่ IPC callback เขียน
 * - Bug ที่หาไม่เจอ: ทำงานบาง build, ไม่ทำงานบาง build
 */

static volatile bool    flag = false;    /* volatile = ห้าม cache! */
static volatile uint8_t data = 0;       /* volatile = อ่านจาก RAM เสมอ */

/* IPC callback (thread A) */
void ipc_cb(const ipc_msg_t *msg, void *user) {
    data = msg->data[0];    /* เขียน volatile */
    flag = true;            /* เขียน volatile - ต้องเป็นคำสั่งสุดท้าย! */
}

/* LVGL timer (thread B) */
void timer_cb(lv_timer_t *t) {
    if (flag) {             /* อ่าน volatile */
        flag = false;       /* Clear ก่อนใช้ data */
        use_data(data);     /* อ่าน volatile */
    }
}

/*
 * ทำไม flag = false ต้องมาก่อน use_data()?
 * - ถ้า clear หลัง: IPC อาจ set flag ใหม่ระหว่าง use_data()
 *   แล้ว clear ทำให้ event ใหม่หาย
 * - ถ้า clear ก่อน: IPC set flag ใหม่จะถูกจับในรอบถัดไป
 */
```

**Part B: Edge Detection (CM33 Side)**

```c
/* ===== Edge detection ลด IPC traffic อย่างมาก ===== */

/*
 * WITHOUT edge detection:
 * - Poll 100ms = 10 IPC/sec = 600 IPC/min
 * - ส่งทุกครั้งแม้ไม่มีอะไรเปลี่ยน
 * - IPC traffic สูง -> เสี่ยง buffer overwrite
 *
 * WITH edge detection:
 * - ส่งเฉพาะตอนเปลี่ยน = ~1-5 IPC/event
 * - ผู้ใช้แตะ slider 2 วินาที = ~20 IPC (slider position changes)
 * - ผู้ใช้ไม่แตะ = 0 IPC
 * - ลด traffic 90%+
 */

if (b0 != prev_btn0 || b1 != prev_btn1 ||
    sp != prev_slider || sa != prev_slider_active) {
    /* State changed! Send IPC */
    capsense_send_ipc();
    prev_btn0 = b0;      /* Update previous state */
    prev_btn1 = b1;
    prev_slider = sp;
    prev_slider_active = sa;
}
/* else: no change, skip IPC -> save bandwidth */
```

**Part C: Edge Detection (CM55 Side)**

```c
/* ===== CM55 ก็ทำ edge detection ลด LVGL redraws ===== */

/*
 * แม้ CM33 ส่งมาเฉพาะเมื่อเปลี่ยน
 * CM55 ก็ควรเช็คอีกชั้น:
 * - กัน race condition (IPC ส่งซ้ำ retry)
 * - กัน initial state request ซ้ำซ้อน
 * - ลด LVGL redraw ที่ไม่จำเป็น
 */

/* ใช้ 0xFF เป็น initial "unknown" state */
static uint8_t prev = 0xFF;

/* อัพเดท UI เฉพาะเมื่อค่าเปลี่ยนจริง */
if (btn0 != prev) {
    if (btn0) {
        lv_led_on(led);          /* 1 redraw */
    } else {
        lv_led_off(led);         /* 1 redraw */
    }
    prev = btn0;
}
/* ถ้า btn0 == prev: ไม่มี lv_* call = 0 redraws */
```

**Part D: Read-Only Slider Widget**

```c
/* ===== ปิด click บน slider เพราะควบคุมจาก IPC เท่านั้น ===== */

ex9_slider = lv_slider_create(slider_panel);
lv_slider_set_range(ex9_slider, 0, 100);

/* CRITICAL: ปิด clickable flag */
lv_obj_remove_flag(ex9_slider, LV_OBJ_FLAG_CLICKABLE);

/*
 * ถ้าไม่ปิด:
 * - ผู้ใช้แตะ slider บน touch screen
 * - LVGL เปลี่ยนค่า slider
 * - แต่ CAPSENSE ไม่ได้เปลี่ยน
 * - IPC ส่งค่าเดิมมา -> slider กระโดดกลับ
 * - UX ไม่ดี + confusing
 *
 * การปิด clickable ทำให้ slider เป็น "display-only"
 * ค่าเปลี่ยนได้จาก lv_slider_set_value() เท่านั้น
 */
```

***

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

#### 5.1 I2C Bus Sharing Problem

```c
/* ===== ปัญหา I2C Bus Sharing บน PSoC Edge E84 ===== */

/*
 * SCB0 I2C Bus:
 * +-------+     +-------------------+
 * | SCB0  |---->| Display Touch     |  (built-in to display)
 * | I2C   |---->| PSoC 4000T CAPS.  |  (on-board CAPSENSE)
 * +-------+
 *
 * ปัญหา: ถ้า CM55 อ่านทั้ง touch + CAPSENSE:
 * 1. Touch driver อ่าน I2C (60Hz)
 * 2. CAPSENSE poll อ่าน I2C (20Hz)
 * 3. I2C bus contention -> NAK, timeout, crash
 *
 * วิธีแก้ใน Part 1 Ex11:
 * lv_port_indev_disable_touch();  // ปิด touch!
 * // ใช้ CAPSENSE แทน touch -> จอแตะไม่ได้
 *
 * วิธีแก้ใน Part 4 Ex9 (IPC):
 * CM33-NS อ่าน CAPSENSE (I2C bus แยก)
 * CM55 ใช้ I2C สำหรับ touch เท่านั้น
 * ข้อมูล CAPSENSE มาผ่าน IPC pipe -> ไม่ conflict!
 */
```

#### 5.2 IPC Message Protocol Design

```c
/* ===== ออกแบบ IPC Protocol สำหรับ CAPSENSE ===== */

/*
 * Design decisions:
 *
 * 1. ใช้ data[] แทน struct ใน payload
 *    - CAPSENSE data แค่ 4 bytes
 *    - ไม่ต้องสร้าง struct แยก
 *    - msg.data[0..3] = btn0, btn1, slider, slider_active
 *
 * 2. ใช้ uint8_t (0/1) แทน bool
 *    - IPC ส่ง raw bytes
 *    - bool ขนาดอาจต่างกันระหว่าง cores
 *    - uint8_t = 1 byte แน่นอน
 *
 * 3. แยก CAPSENSE_DATA (0xB6) กับ CAPSENSE_REQ (0xB7)
 *    - DATA: CM33 -> CM55 (push data)
 *    - REQ: CM55 -> CM33 (pull request)
 *    - ใช้ REQ ตอน startup เพื่อ sync initial state
 *
 * 4. Edge detection บน CM33
 *    - CM33 เปรียบเทียบกับ state ก่อนหน้า
 *    - ส่งเฉพาะเมื่อเปลี่ยน
 *    - ลด IPC traffic 90%+
 */

/* Command allocation ใน GPIO range (0xB0-0xBF) */
IPC_CMD_CAPSENSE_DATA = 0xB6,  /* อยู่หลัง BUTTON_EVENT (0xB5) */
IPC_CMD_CAPSENSE_REQ  = 0xB7,  /* อยู่ถัดจาก DATA */
```

#### 5.3 PSoC 4000T I2C Protocol Details

```c
/* ===== PSoC 4000T CAPSENSE I2C Protocol ===== */

/*
 * Slave address: 0x08 (7-bit)
 * Transaction: Master Read, 3 bytes
 *
 * Byte 0: Button 0 (CSB1)
 *   - ASCII encoded: '0' (0x30) = not pressed
 *                    '1' (0x31) = pressed
 *   - Subtract 0x30: 0 = not pressed, 1 = pressed
 *
 * Byte 1: Button 1 (CSB2)
 *   - ASCII encoded: '1' (0x31) = not pressed (!)
 *                    '2' (0x32) = pressed
 *   - Subtract 0x30: 1 = not pressed, 2 = pressed
 *   - INVERTED LOGIC: check (value != 1) for pressed
 *
 * Byte 2: Slider (CSS1)
 *   - Raw value: 0 = not touching
 *                1-100 = position (left to right)
 *   - No ASCII encoding (direct value)
 *
 * I2C Read sequence:
 *   START -> Addr+R -> ACK -> Byte0 -> ACK ->
 *   Byte1 -> ACK -> Byte2 -> NAK -> STOP
 *
 * Last byte gets NAK per I2C protocol.
 */

/* Parse example */
buffer[0] -= 0x30;           /* ASCII to number */
buffer[1] -= 0x30;           /* ASCII to number */
*btn0 = (buffer[0] != 0);   /* Standard logic */
*btn1 = (buffer[1] != 1);   /* INVERTED logic! */
*slider = buffer[2];         /* Direct value, no offset */
```

#### 5.4 CAPSENSE\_REQ: Request-Response Pattern

```c
/* ===== Initial State Sync via CAPSENSE_REQ ===== */

/*
 * ปัญหา: CM55 เริ่มทำงานหลัง CM33
 * CM33 อาจส่ง CAPSENSE_DATA ก่อน CM55 register callback
 * --> CM55 พลาด initial state
 *
 * แก้ไข: CM55 ส่ง CAPSENSE_REQ หลัง setup เสร็จ
 */

void part4_ex9_capsense_ipc(void)
{
    /* 1. Register callback ก่อน */
    cm55_ipc_register_callback(ex9_ipc_callback, NULL);

    /* 2. สร้าง UI */
    /* ... build UI ... */

    /* 3. สร้าง timer */
    lv_timer_create(ex9_timer_cb, 50, NULL);

    /* 4. ขอ initial state (หลังจากทุกอย่าง ready) */
    if (cm55_ipc_is_init()) {
        cm55_ipc_send_cmd(IPC_CMD_CAPSENSE_REQ, 0);
    }
}

/* CM33 side: handle CAPSENSE_REQ */
case IPC_CMD_CAPSENSE_REQ:
    capsense_module_send_current();  /* ส่งสถานะปัจจุบัน */
    break;
```

***

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

#### Exercise 1: CAPSENSE Event Counter + Statistics (Intermediate)

เพิ่มระบบนับ event และแสดง statistics ของ CAPSENSE:

**Requirements:**

* นับจำนวนครั้งที่กด BTN0, BTN1, และ slider touch
* แสดง total events, events per minute
* Last event timestamp (tick count)
* Longest slider touch duration (ms)
* Average slider position (เฉพาะขณะ touch)
* Statistics panel เพิ่มระหว่าง button panels

**Expected Output:**

```
+-------------------------------------------+
|  CAPSENSE Statistics                      |
|                                           |
|  BTN0 touches: 12    BTN1 touches: 8      |
|  Slider touches: 15                       |
|  Total events: 35    Rate: 7/min          |
|  Avg slider pos: 62%                      |
|  Longest touch: 2400ms                    |
|  Last event: tick 145230                  |
+-------------------------------------------+
```

**Hints:**

* เก็บ `touch_start_tick` เมื่อ `slider_active` เปลี่ยนจาก 0 -> 1
* คำนวณ duration เมื่อ `slider_active` เปลี่ยนจาก 1 -> 0
* Average = `total_position_sum / total_position_count`
* Rate = `total_events * 60 / (uptime_seconds)`
* ใช้ panel ขนาด 300x80 อยู่กลางระหว่าง slider กับ buttons

***

#### Exercise 2: CAPSENSE LED Control via IPC (Advanced)

ใช้ CAPSENSE ควบคุม physical LED ผ่าน IPC chain:

```
CAPSENSE -> I2C -> CM33 -> IPC(CAPSENSE_DATA) -> CM55 -> IPC(LED_SET) -> CM33 -> GPIO LED
```

**Requirements:**

* BTN0 (CSB1): Toggle Red LED (กด = toggle ON/OFF)
* BTN1 (CSB2): Toggle Green LED (กด = toggle ON/OFF)
* Slider (CSS1): Blue LED brightness (0-100% via IPC\_CMD\_LED\_BRIGHTNESS)
* UI แสดง LED state + slider brightness
* IPC flow: CAPSENSE data มาถึง CM55 -> CM55 ส่ง LED command กลับ CM33
* 20ms delay ระหว่าง IPC sends (CRITICAL)
* LED toggle ต้องมี edge detection (กดทีเดียว toggle ครั้งเดียว)

**Expected Output:**

```
+-------------------------------------------+
| CAPSENSE -> LED Control (IPC Chain)       |
|                                           |
| Slider: [========|-----] 62% -> Blue LED  |
|                                           |
| BTN0 -> Red LED: [ON]    Toggle count: 5  |
| BTN1 -> Green LED: [OFF] Toggle count: 3  |
|                                           |
| IPC Chain: CAPS->CM33->CM55->CM33->LED    |
| Total IPC: TX=42  RX=38                   |
+-------------------------------------------+
```

**Hints:**

* Toggle pattern: track `prev_btn0` state, toggle only on 0->1 transition
* Slider brightness: ส่ง `cm55_ipc_set_led_brightness(2, slider_pos)` เฉพาะเมื่อค่าเปลี่ยน
* 20ms delay ระหว่าง LED commands:

```c
/* ถ้าต้องส่ง LED + brightness พร้อมกัน */
cm55_ipc_set_led(0, true);
vTaskDelay(pdMS_TO_TICKS(20));   /* delay 20ms! */
cm55_ipc_set_led_brightness(2, slider_val);
```

* Full IPC chain creates round-trip latency: \~100-150ms end-to-end
* ใช้ `cm55_ipc_get_stats()` แสดง TX/RX statistics

***

### 7. สรุปและขั้นตอนถัดไป (Summary & Next Steps)

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

1. **IPC-Based Peripheral Access**: ย้ายการอ่าน CAPSENSE จาก CM55 ไป CM33 ผ่าน IPC เพื่อแก้ปัญหา I2C bus conflict
2. **Thread-Safe Flag Pattern**: ใช้ volatile flags + LVGL timer เพื่อ update UI อย่างปลอดภัยจาก IPC callback
3. **Edge Detection (Dual-Layer)**: ทั้ง CM33 (ลด IPC traffic) และ CM55 (ลด LVGL redraws)
4. **IPC Protocol Design**: ออกแบบ command + payload สำหรับ CAPSENSE data (4 bytes)
5. **Architecture Decision**: เลือก IPC approach แทน direct I2C เพื่อให้ touch screen ทำงานได้

#### Architecture Lesson

```
+-----------------------------------------------------------+
|      KEY LESSON: PROPER MULTI-CORE ARCHITECTURE           |
+-----------------------------------------------------------+
|                                                           |
|  WRONG (Part 1 Ex11):                                     |
|  CM55 does EVERYTHING:                                    |
|  - Read CAPSENSE (I2C)                                    |
|  - Read Touch (I2C)        <-- BUS CONFLICT!              |
|  - Display LVGL                                           |
|  Solution: disable touch   <-- BAD TRADEOFF               |
|                                                           |
|  RIGHT (Part 4 Ex9):                                      |
|  CM33-NS = Hardware Service:                              |
|  - Read CAPSENSE (I2C)                                    |
|  - Send data via IPC                                      |
|                                                           |
|  CM55 = UI Controller:                                    |
|  - Read Touch (I2C)        <-- NO CONFLICT                |
|  - Receive IPC data                                       |
|  - Display LVGL                                           |
|                                                           |
|  RULE: CM33 owns HW peripherals, CM55 owns display.       |
|  IPC bridges the gap between them.                        |
|                                                           |
+-----------------------------------------------------------+
```

#### ขั้นตอนถัดไป

* **Previous**: Lab 4: HW IPC Dashboard
* **Next**: Mini Project Part 4 -- Multi-Core Sensor Fusion System

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

<table><thead><tr><th width="214.55755615234375">ด้าน</th><th>ตัวอย่าง</th></tr></thead><tbody><tr><td><strong>Automotive</strong></td><td>Touch controls on steering wheel -> CAN -> instrument cluster display</td></tr><tr><td><strong>Industrial HMI</strong></td><td>Capacitive keypad on machine -> fieldbus -> SCADA display</td></tr><tr><td><strong>Consumer Electronics</strong></td><td>Touch slider (volume/brightness) -> MCU -> display controller</td></tr><tr><td><strong>Medical Devices</strong></td><td>Capacitive buttons (sealed enclosure) -> MCU -> UI processor</td></tr><tr><td><strong>Smart Home</strong></td><td>Touch panel -> Zigbee/BLE -> central hub display</td></tr></tbody></table>
