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


---

# 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/capsense-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.
