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

# IPC Fundamentals

## Lab 1: IPC Fundamentals (Ping, Logging, Sensor Data)

### Part 4 - IPC & Event Bus

***

### 1. ภาพรวมของ Lab

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

* **Multi-Core Communication**: ระบบ Embedded สมัยใหม่ใช้หลาย Core ทำงานร่วมกัน ต้องมีกลไกสื่อสารระหว่าง Core ที่เชื่อถือได้
* **IPC (Inter-Processor Communication)**: เทคนิคพื้นฐานที่ใช้ในระบบ Industrial, Automotive, IoT ทุกประเภท
* **Remote Logging**: ไม่ใช่ทุก Core จะมี UART console ต้องส่ง log ผ่าน IPC เพื่อ debug และ diagnostics
* **Distributed Sensing**: ในระบบ Multi-Core sensor อยู่คนละ Core กับ UI ต้องส่งข้อมูลผ่าน IPC
* **Thread Safety**: เรียนรู้ pattern ที่ถูกต้องสำหรับการอัปเดต UI จาก IPC callback

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

1. **IPC Pipe Architecture**: โครงสร้างการสื่อสารระหว่าง CM33-NS กับ CM55
2. **Ping-Pong Protocol**: ส่ง PING → รับ PONG วัดเวลา Round-Trip
3. **Remote Logging**: CM55 ส่ง log ผ่าน IPC → CM33 พิมพ์ออก UART พร้อมระบบ Log Levels
4. **Sensor Request-Response**: CM55 ร้องขอ → CM33 อ่าน sensor → ส่งกลับผ่าน IPC
5. **Flag-Based Pattern**: วิธีอัปเดต LVGL UI อย่างปลอดภัยจาก IPC callback

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

Lab นี้แบ่งเป็น 3 ส่วน:

* **Part A**: สร้าง Ping-Pong ทดสอบ IPC พื้นฐาน วัด Round-Trip Time
* **Part B**: สร้างระบบ Logging ส่ง log จาก CM55 ไป CM33 console
* **Part C**: ส่งข้อมูล Sensor (IMU/ADC) ผ่าน IPC แสดงผลบน Chart

***

### 2. หลักการทางเทคนิค

#### 2.1 สถาปัตยกรรม Multi-Core ของ PSoC Edge E84

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         PSoC Edge E84                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌──────────────────────┐         ┌──────────────────────┐              │
│  │   Cortex-M33 (NS)    │         │   Cortex-M55         │              │
│  │   120 MHz            │         │   400 MHz            │              │
│  ├──────────────────────┤         ├──────────────────────┤              │
│  │  - WiFi/BLE drivers  │         │  - LVGL Display      │              │
│  │  - Sensor I2C (IMU)  │         │  - UI Rendering      │              │
│  │  - GPIO (LED/Button) │         │  - IPC Receiver      │              │
│  │  - ADC               │         │  - Event Bus         │              │
│  │  - UART (printf)     │         │  - DSP/ML Processing │              │
│  │  - IPC Sender        │         │                      │              │
│  └──────────┬───────────┘         └───────────┬──────────┘              │
│             │                                 │                         │
│             │        IPC Pipe (Hardware)      │                         │
│             └─────────────────────────────────┘                         │
│                                                                         │
│  Boot Order: CM33-S → CM33-NS → cm33_ipc_init() → Cy_SysEnableCM55()    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 2.2 ทำไม CM55 ถึง printf() ไม่ได้?

```
CM33-NS มี UART (retarget-io)  →  printf() ได้
CM55 ไม่มี UART                →  printf() ไม่ได้!

วิธีแก้: CM55 ส่ง Log ผ่าน IPC ไปยัง CM33-NS ให้ printf() แทน
         ใช้ macro: CM55_LOGI(), CM55_LOGE(), CM55_LOGW(), CM55_LOGD()
```

#### 2.3 IPC Message Structure

```c
/* จาก ipc_shared.h */
typedef struct {
    uint16_t client_id;             /* Destination client ID */
    uint16_t intr_mask;             /* Release mask (Pipe Driver ใช้) */
    uint32_t cmd;                   /* Command type (ipc_cmd_t) */
    uint32_t value;                 /* Numeric payload */
    char     data[IPC_DATA_MAX_LEN];/* String/binary payload (128 bytes) */
} ipc_msg_t;
```

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

```
┌─────────────────────────────────────────────────────────────────────────┐
│                  !! LVGL ไม่ใช่ Thread-Safe !!                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  LVGL ทำงานใน LVGL Task เท่านั้น                                           │
│  IPC Callback ทำงานใน IPC Task (คนละ Task กัน)                           │
│                                                                         │
│  ถ้าเรียก lv_label_set_text() จาก IPC callback โดยตรง:                    │
│  → CRASH! Race condition! Memory corruption!                            │
│                                                                         │
│  วิธีถูกต้อง: ใช้ Flag-Based Pattern                                         │
│                                                                         │
│  ┌─────────────┐  flag=true  ┌─────────────┐  อ่าน flag  ┌──────────┐    │
│  │ IPC Callback│ ──────────► │  volatile   │ ◄──────────│ LVGL     │    │
│  │ (IPC Task)  │  เก็บ data   │  flag+data  │  อัปเดต UI  │ Timer    │    │
│  └─────────────┘             └─────────────┘            └──────────┘    │
│                                                                         │
│  Key: ใช้ volatile keyword เพื่อป้องกัน compiler optimization                │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**ตัวอย่าง Pattern:**

```c
/* ===== Shared Variables (volatile เพราะใช้ข้าม Task) ===== */
static volatile bool     ping_received = false;   /* flag */
static volatile uint32_t pong_time_ms  = 0;       /* data */

/* ===== IPC Callback (ทำงานใน IPC Task) ===== */
void ipc_rx_callback(const ipc_msg_t *msg, void *user_data)
{
    if (msg->cmd == IPC_CMD_PONG) {
        /* เก็บข้อมูล + ตั้ง flag เท่านั้น */
        /* ห้ามเรียก lv_xxx() ที่นี่!! */
        pong_time_ms = lv_tick_get() - ping_start_time;
        ping_received = true;
    }
}

/* ===== LVGL Timer Callback (ทำงานใน LVGL Task - ปลอดภัย) ===== */
void ui_timer_cb(lv_timer_t *timer)
{
    if (ping_received) {
        ping_received = false;  /* Reset flag */
        /* ตอนนี้ปลอดภัยที่จะเรียก LVGL functions */
        lv_label_set_text_fmt(label_result,
            "Pong! RTT: %u ms", (unsigned int)pong_time_ms);
    }
}
```

#### 2.5 Sensor Data Flow Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                  Sensor Data Request-Response Flow                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  CM55 (Display)                          CM33-NS (Sensors)              │
│  ┌──────────────────┐                   ┌──────────────────────┐        │
│  │  LVGL Timer      │                   │  Sensor Hub          │        │
│  │  (ทุก 100ms)      │                   │                      │        │
│  │                  │  1. Request       │  ┌────────────────┐  │        │
│  │  cm55_ipc_       │ ────────────────► │  │ BMI270 IMU     │  │        │
│  │  request_sensor()│  IPC_CMD_SENSOR   │  │ - Accel X,Y,Z  │  │        │
│  │                  │  _REQ             │  │ - Gyro X,Y,Z   │  │        │
│  │                  │                   │  └────────────────┘  │        │
│  │                  │  2. Response      │                      │        │
│  │  ipc_callback()  │ ◄──────────────── │  ┌────────────────┐  │        │
│  │  ├── flag=true   │  IPC_CMD_SENSOR   │  │ ADC            │  │        │
│  │  └── store data  │  _DATA            │  │ - Potentiometer│  │        │
│  │                  │                   │  └────────────────┘  │        │
│  │  LVGL Timer      │                   │  3. CM33 อ่าน sensor  │        │
│  │  ├── check flag  │                   │     แปลงค่า           │        │
│  │  ├── update Chart│                   │     pack เป็น         │        │
│  │  └── update Label│                   │     ipc_imu_data_t   │        │
│  └──────────────────┘                   └──────────────────────┘        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

***

### Part A: IPC Ping-Pong (Ex1)

#### IPC Commands ที่ใช้

| Command        | Value | ทิศทาง      | คำอธิบาย              |
| -------------- | ----- | ----------- | --------------------- |
| `IPC_CMD_PING` | 0x42  | CM55 → CM33 | ส่งสัญญาณ Ping        |
| `IPC_CMD_PONG` | 0x43  | CM33 → CM55 | CM33 ตอบกลับอัตโนมัติ |

#### Ping-Pong Flow

```
  CM55 (LVGL)                              CM33-NS
  ──────────                               ────────
       │                                       │
       │  1. User กด Button "Ping"             │
       │  2. บันทึก start_time                   │
       │  3. cm55_ipc_send_cmd(PING, 0)        │
       │ ────────── IPC_CMD_PING ──────────►   │
       │                                       │  4. รับ PING
       │                                       │  5. ส่ง PONG กลับ (อัตโนมัติ)
       │   ◄────────── IPC_CMD_PONG ────────── │
       │  6. IPC callback ถูกเรียก               │
       │  7. คำนวณ round_trip_time             │
       │  8. ตั้ง flag = true                    │
       │                                       │
       │  9. LVGL timer ตรวจพบ flag            │
       │ 10. อัปเดต Label บน UI                 │
       │                                       │
```

#### Step-by-Step Implementation

**Step 1: ตั้งค่า Example Selector**

```c
/* proj_cm55/example_selector.h */
#define SELECTED_WEEK       6    /* Part 4 ใช้ Week 6 slot */
#define SELECTED_EXAMPLE    1    /* Example 1: IPC Ping */
```

**Step 2: Include Headers**

```c
/* part4_ex1_ipc_ping.c */
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"

#include <stdbool.h>
#include <stdint.h>
```

**Step 3: ประกาศ Global Variables**

```c
/*******************************************************************************
 * Global / Shared Variables
 ******************************************************************************/

/* LVGL Objects */
static lv_obj_t *label_title   = NULL;
static lv_obj_t *label_status  = NULL;
static lv_obj_t *label_rtt     = NULL;
static lv_obj_t *btn_ping      = NULL;
static lv_obj_t *led_indicator = NULL;

/* Flag-Based Pattern Variables (volatile!) */
static volatile bool     pong_received    = false;
static volatile uint32_t pong_rtt_ms      = 0;
static volatile uint32_t ping_start_tick  = 0;

/* Statistics */
static uint32_t ping_count  = 0;
static uint32_t pong_count  = 0;
static uint32_t total_rtt   = 0;
```

**Step 4: IPC Callback Function**

```c
/*******************************************************************************
 * IPC Receive Callback
 *
 * CRITICAL: ฟังก์ชันนี้ถูกเรียกจาก IPC Task ไม่ใช่ LVGL Task
 *           ห้ามเรียก lv_xxx() functions ที่นี่!!
 ******************************************************************************/
static void ipc_ping_callback(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    if (msg->cmd == IPC_CMD_PONG) {
        /* คำนวณ Round-Trip Time */
        uint32_t now = lv_tick_get();
        pong_rtt_ms = now - ping_start_tick;

        /* ตั้ง flag ให้ LVGL timer อัปเดต UI */
        pong_received = true;
    }
}
```

**Step 5: LVGL Timer Callback (UI Update)**

```c
/*******************************************************************************
 * LVGL Timer Callback - ตรวจสอบ flag ทุก 50ms
 *
 * ฟังก์ชันนี้ทำงานใน LVGL context จึงเรียก lv_xxx() ได้อย่างปลอดภัย
 ******************************************************************************/
static void ping_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (pong_received) {
        pong_received = false;  /* Reset flag ก่อน */
        pong_count++;

        /* อัปเดตผลลัพธ์ */
        lv_label_set_text_fmt(label_rtt,
            "RTT: %u ms", (unsigned int)pong_rtt_ms);

        /* อัปเดต LED indicator เป็นสีเขียว */
        lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_GREEN));
        lv_led_on(led_indicator);

        /* อัปเดตสถานะ */
        total_rtt += pong_rtt_ms;
        uint32_t avg = (pong_count > 0) ? (total_rtt / pong_count) : 0;
        lv_label_set_text_fmt(label_status,
            "Ping: %u  Pong: %u  Avg: %u ms",
            (unsigned int)ping_count,
            (unsigned int)pong_count,
            (unsigned int)avg);

        /* Log ผ่าน IPC */
        CM55_LOGI("PONG received! RTT=%u ms", (unsigned int)pong_rtt_ms);
    }
}
```

**Step 6: Button Event Callback**

```c
/*******************************************************************************
 * Button "Ping" Event Handler
 ******************************************************************************/
static void btn_ping_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);

    /* กรอง event - รับเฉพาะ CLICKED */
    if (code != LV_EVENT_CLICKED) return;

    /* ตรวจสอบ IPC พร้อมใช้งาน */
    if (!cm55_ipc_is_init()) {
        lv_label_set_text(label_status, "IPC not initialized!");
        lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_RED));
        return;
    }

    /* บันทึกเวลาเริ่มต้น */
    ping_start_tick = lv_tick_get();
    ping_count++;

    /* อัปเดต UI - กำลังรอ */
    lv_label_set_text(label_rtt, "Pinging...");
    lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_YELLOW));
    lv_led_on(led_indicator);

    /* ส่ง PING command ไปยัง CM33-NS */
    cy_en_ipc_pipe_status_t status = cm55_ipc_send_cmd(IPC_CMD_PING, ping_count);

    if (status != CY_IPC_PIPE_SUCCESS) {
        lv_label_set_text(label_rtt, "Send failed!");
        lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_RED));
        CM55_LOGE("PING send failed: %d", (int)status);
    }
}
```

**Step 7: สร้าง UI Layout (Main Entry Point)**

```c
/*******************************************************************************
 * Main Entry Point
 ******************************************************************************/
void part4_ex1_ipc_ping(void)
{
    /* ===== ล้างหน้าจอ ===== */
    lv_obj_t *scr = lv_screen_active();
    lv_obj_clean(scr);
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a2e), 0);

    /* ===== Title ===== */
    label_title = lv_label_create(scr);
    lv_label_set_text(label_title, "IPC Ping-Pong");
    lv_obj_set_style_text_font(label_title, &lv_font_montserrat_24, 0);
    lv_obj_set_style_text_color(label_title, lv_color_hex(0x00ffff), 0);
    lv_obj_align(label_title, LV_ALIGN_TOP_MID, 0, 10);

    /* ===== LED Indicator ===== */
    led_indicator = lv_led_create(scr);
    lv_obj_set_size(led_indicator, 30, 30);
    lv_obj_align(led_indicator, LV_ALIGN_TOP_RIGHT, -20, 10);
    lv_led_set_color(led_indicator, lv_palette_main(LV_PALETTE_GREY));
    lv_led_off(led_indicator);

    /* ===== RTT Result Label ===== */
    label_rtt = lv_label_create(scr);
    lv_label_set_text(label_rtt, "RTT: -- ms");
    lv_obj_set_style_text_font(label_rtt, &lv_font_montserrat_20, 0);
    lv_obj_set_style_text_color(label_rtt, lv_color_white(), 0);
    lv_obj_align(label_rtt, LV_ALIGN_CENTER, 0, -30);

    /* ===== Status Label ===== */
    label_status = lv_label_create(scr);
    lv_label_set_text(label_status, "Ping: 0  Pong: 0  Avg: -- ms");
    lv_obj_set_style_text_font(label_status, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(label_status, lv_color_hex(0xaaaaaa), 0);
    lv_obj_align(label_status, LV_ALIGN_CENTER, 0, 10);

    /* ===== Ping Button ===== */
    btn_ping = lv_button_create(scr);
    lv_obj_set_size(btn_ping, 160, 50);
    lv_obj_align(btn_ping, LV_ALIGN_BOTTOM_MID, 0, -30);
    lv_obj_set_style_bg_color(btn_ping,
        lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_add_event_cb(btn_ping, btn_ping_event_cb,
        LV_EVENT_CLICKED, NULL);

    lv_obj_t *btn_label = lv_label_create(btn_ping);
    lv_label_set_text(btn_label, "PING");
    lv_obj_set_style_text_font(btn_label, &lv_font_montserrat_18, 0);
    lv_obj_center(btn_label);

    /* ===== Register IPC Callback ===== */
    cm55_ipc_register_callback(ipc_ping_callback, NULL);

    /* ===== สร้าง LVGL Timer สำหรับ poll flag ===== */
    lv_timer_create(ping_timer_cb, 50, NULL);  /* ทุก 50ms */

    CM55_LOGI("Part4 Ex1: IPC Ping-Pong initialized");
}
```

***

### Part B: IPC Logging System (Ex2)

#### Log Level System

```
┌─────────────────────────────────────────────────────────────────────────┐
│                      Log Level Hierarchy                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Level     IPC Command        Macro           สี        ใช้เมื่อ            │
│  ─────     ───────────        ─────           ──        ──────          │
│  ERROR     IPC_CMD_LOG_ERROR  CM55_LOGE()     แดง       ข้อผิดพลาดร้าย     │
│  WARN      IPC_CMD_LOG_WARN   CM55_LOGW()     เหลือง    เตือนภัย           │
│  INFO      IPC_CMD_LOG_INFO   CM55_LOGI()     เขียว     ข้อมูลปกติ          │
│  DEBUG     IPC_CMD_LOG_DEBUG  CM55_LOGD()     ขาว       debug เท่านั้น     │
│                                                                         │
│  ตัวกรอง: เมื่อตั้ง level = WARN                                             │
│           จะแสดง: ERROR + WARN เท่านั้น                                    │
│           ไม่แสดง: INFO + DEBUG                                          │
│                                                                         │
│  Production: ตั้ง level = ERROR หรือ WARN                                  │
│  Development: ตั้ง level = DEBUG (แสดงทั้งหมด)                              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### IPC Log Command Reference

<table><thead><tr><th>Command</th><th width="122.49005126953125">Value</th><th>ทิศทาง</th><th>คำอธิบาย</th></tr></thead><tbody><tr><td><code>IPC_CMD_LOG</code></td><td>0x90</td><td>CM55 → CM33</td><td>Log ทั่วไป (ไม่ระบุ level)</td></tr><tr><td><code>IPC_CMD_LOG_LEVEL</code></td><td>0x91</td><td>CM55 → CM33</td><td>ตั้ง log level filter</td></tr><tr><td><code>IPC_CMD_LOG_ERROR</code></td><td>0x92</td><td>CM55 → CM33</td><td>Error level log</td></tr><tr><td><code>IPC_CMD_LOG_WARN</code></td><td>0x93</td><td>CM55 → CM33</td><td>Warning level log</td></tr><tr><td><code>IPC_CMD_LOG_INFO</code></td><td>0x94</td><td>CM55 → CM33</td><td>Info level log</td></tr><tr><td><code>IPC_CMD_LOG_DEBUG</code></td><td>0x95</td><td>CM55 → CM33</td><td>Debug level log</td></tr></tbody></table>

#### Log Macros (จาก cm55\_ipc\_pipe.h)

```c
/* ใช้ได้เฉพาะใน CM55 */
CM55_LOGE("Sensor init failed: %d", error_code);
CM55_LOGW("Battery low: %d%%", battery_pct);
CM55_LOGI("System started, version %s", version);
CM55_LOGD("Register 0x%02X = 0x%04X", reg, val);

/* ภายในจะถูกแปลงเป็น */
cm55_ipc_log_level(IPC_CMD_LOG_ERROR, "Sensor init failed: %d", error_code);
/* → ส่ง ipc_msg_t { cmd=0x92, data="Sensor init failed: -1" } → CM33 printf */
```

#### Step-by-Step Implementation

**Step 1: ประกาศ Constants และ Variables**

```c
/* part4_ex2_ipc_log.c */
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

/*******************************************************************************
 * Constants
 ******************************************************************************/
#define LOG_HISTORY_MAX     20      /* จำนวน log สูงสุดที่เก็บ */
#define LOG_MSG_MAX_LEN     64      /* ความยาวข้อความสูงสุด */
#define LOG_DISPLAY_BUF     2048    /* Buffer สำหรับ TextArea */

/* Log Level Names */
static const char *log_level_names[] = {
    "ERROR", "WARN", "INFO", "DEBUG"
};

/* Log Level Colors (hex) */
static const uint32_t log_level_colors[] = {
    0xFF4444,   /* ERROR - แดง */
    0xFFAA00,   /* WARN  - เหลือง */
    0x44FF44,   /* INFO  - เขียว */
    0xCCCCCC    /* DEBUG - ขาว */
};

typedef enum {
    LOG_LEVEL_ERROR = 0,
    LOG_LEVEL_WARN,
    LOG_LEVEL_INFO,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_COUNT
} log_level_t;

/*******************************************************************************
 * Log Entry Structure
 ******************************************************************************/
typedef struct {
    log_level_t level;
    uint32_t    timestamp;
    char        message[LOG_MSG_MAX_LEN];
} log_entry_t;

/*******************************************************************************
 * Global Variables
 ******************************************************************************/

/* LVGL Objects */
static lv_obj_t *label_title     = NULL;
static lv_obj_t *dropdown_level  = NULL;
static lv_obj_t *btn_send        = NULL;
static lv_obj_t *textarea_log    = NULL;
static lv_obj_t *label_count     = NULL;

/* Log History */
static log_entry_t log_history[LOG_HISTORY_MAX];
static uint32_t    log_count        = 0;
static log_level_t current_filter   = LOG_LEVEL_DEBUG;  /* แสดงทั้งหมด */
static uint32_t    log_seq          = 0;  /* Sequence number */
static char        display_buf[LOG_DISPLAY_BUF];
```

**Step 2: Log Management Functions**

```c
/*******************************************************************************
 * Log Management
 ******************************************************************************/

/* เพิ่ม log entry ใหม่ */
static void log_add_entry(log_level_t level, const char *message)
{
    /* Circular buffer: เขียนทับรายการเก่าสุด */
    uint32_t idx = log_count % LOG_HISTORY_MAX;

    log_history[idx].level = level;
    log_history[idx].timestamp = lv_tick_get() / 1000;  /* วินาที */
    strncpy(log_history[idx].message, message, LOG_MSG_MAX_LEN - 1);
    log_history[idx].message[LOG_MSG_MAX_LEN - 1] = '\0';

    log_count++;
    log_seq++;
}

/* สร้าง display string จาก log history */
static void log_build_display(void)
{
    display_buf[0] = '\0';
    int pos = 0;
    uint32_t total = (log_count < LOG_HISTORY_MAX) ? log_count : LOG_HISTORY_MAX;
    uint32_t start = (log_count < LOG_HISTORY_MAX) ? 0 : (log_count % LOG_HISTORY_MAX);

    for (uint32_t i = 0; i < total; i++) {
        uint32_t idx = (start + i) % LOG_HISTORY_MAX;
        log_entry_t *entry = &log_history[idx];

        /* กรองตาม current_filter */
        if (entry->level > current_filter) continue;

        /* Format: [LEVEL] HH:MM:SS message */
        int written = snprintf(display_buf + pos,
            LOG_DISPLAY_BUF - pos,
            "[%s] %02u:%02u:%02u %s\n",
            log_level_names[entry->level],
            (unsigned int)((entry->timestamp / 3600) % 24),
            (unsigned int)((entry->timestamp / 60) % 60),
            (unsigned int)(entry->timestamp % 60),
            entry->message);

        if (written > 0) pos += written;
        if (pos >= LOG_DISPLAY_BUF - 1) break;
    }

    if (pos == 0) {
        strcpy(display_buf, "(no logs matching filter)");
    }
}
```

**Step 3: Send Log via IPC + Update Local Display**

```c
/*******************************************************************************
 * Send Log (via IPC to CM33 console + local display)
 ******************************************************************************/
static void send_log_message(log_level_t level, const char *message)
{
    /* 1. ส่งผ่าน IPC ไปยัง CM33 console */
    switch (level) {
        case LOG_LEVEL_ERROR:
            CM55_LOGE("%s", message);
            break;
        case LOG_LEVEL_WARN:
            CM55_LOGW("%s", message);
            break;
        case LOG_LEVEL_INFO:
            CM55_LOGI("%s", message);
            break;
        case LOG_LEVEL_DEBUG:
            CM55_LOGD("%s", message);
            break;
        default:
            break;
    }

    /* 2. เก็บ log ในหน่วยความจำ */
    log_add_entry(level, message);

    /* 3. อัปเดต TextArea */
    log_build_display();
    lv_textarea_set_text(textarea_log, display_buf);

    /* 4. Scroll ลงล่างสุดอัตโนมัติ */
    lv_textarea_set_cursor_pos(textarea_log, LV_TEXTAREA_CURSOR_LAST);

    /* 5. อัปเดต counter */
    lv_label_set_text_fmt(label_count,
        "Total: %u logs", (unsigned int)log_count);
}
```

**Step 4: Event Handlers**

```c
/*******************************************************************************
 * Button "Send" Event Handler
 ******************************************************************************/
static void btn_send_event_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;

    /* อ่าน level จาก dropdown */
    uint32_t selected = lv_dropdown_get_selected(dropdown_level);
    log_level_t level = (log_level_t)selected;

    /* สร้างข้อความตัวอย่างตาม level */
    char msg[LOG_MSG_MAX_LEN];
    switch (level) {
        case LOG_LEVEL_ERROR:
            snprintf(msg, sizeof(msg),
                "Sensor read failed (code=%d)", (int)(log_seq % 10));
            break;
        case LOG_LEVEL_WARN:
            snprintf(msg, sizeof(msg),
                "IPC buffer 80%% full (seq=%u)", (unsigned int)log_seq);
            break;
        case LOG_LEVEL_INFO:
            snprintf(msg, sizeof(msg),
                "System OK, uptime=%us", (unsigned int)(lv_tick_get() / 1000));
            break;
        case LOG_LEVEL_DEBUG:
            snprintf(msg, sizeof(msg),
                "Register dump: addr=0x%02X val=0x%04X",
                (unsigned int)(log_seq & 0xFF),
                (unsigned int)(log_seq * 7 & 0xFFFF));
            break;
        default:
            break;
    }

    send_log_message(level, msg);
}

/*******************************************************************************
 * Dropdown (Filter Level) Event Handler
 ******************************************************************************/
static void dropdown_filter_event_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;

    lv_obj_t *dd = lv_event_get_target(e);
    current_filter = (log_level_t)lv_dropdown_get_selected(dd);

    /* Rebuild display with new filter */
    log_build_display();
    lv_textarea_set_text(textarea_log, display_buf);

    CM55_LOGI("Log filter changed to: %s", log_level_names[current_filter]);
}
```

**Step 5: สร้าง UI Layout (Main Entry Point)**

```c
/*******************************************************************************
 * Main Entry Point
 ******************************************************************************/
void part4_ex2_ipc_log(void)
{
    /* ===== ล้างหน้าจอ ===== */
    lv_obj_t *scr = lv_screen_active();
    lv_obj_clean(scr);
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0d1117), 0);

    /* ===== Title ===== */
    label_title = lv_label_create(scr);
    lv_label_set_text(label_title, "IPC Logging System");
    lv_obj_set_style_text_font(label_title, &lv_font_montserrat_20, 0);
    lv_obj_set_style_text_color(label_title, lv_color_hex(0x58a6ff), 0);
    lv_obj_align(label_title, LV_ALIGN_TOP_MID, 0, 8);

    /* ===== Control Panel (Row: Dropdown + Button) ===== */
    lv_obj_t *panel = lv_obj_create(scr);
    lv_obj_set_size(panel, 300, 50);
    lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, 38);
    lv_obj_set_style_bg_color(panel, lv_color_hex(0x161b22), 0);
    lv_obj_set_style_border_color(panel, lv_color_hex(0x30363d), 0);
    lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(panel,
        LV_FLEX_ALIGN_SPACE_EVENLY,
        LV_FLEX_ALIGN_CENTER,
        LV_FLEX_ALIGN_CENTER);
    lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE);

    /* Dropdown - Log Level */
    dropdown_level = lv_dropdown_create(panel);
    lv_dropdown_set_options(dropdown_level,
        "ERROR\nWARN\nINFO\nDEBUG");
    lv_dropdown_set_selected(dropdown_level, LOG_LEVEL_INFO);
    lv_obj_set_width(dropdown_level, 120);

    /* Button - Send */
    btn_send = lv_button_create(panel);
    lv_obj_set_size(btn_send, 80, 35);
    lv_obj_set_style_bg_color(btn_send,
        lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_add_event_cb(btn_send, btn_send_event_cb,
        LV_EVENT_CLICKED, NULL);

    lv_obj_t *btn_label = lv_label_create(btn_send);
    lv_label_set_text(btn_label, "Send");
    lv_obj_center(btn_label);

    /* ===== Filter Dropdown ===== */
    lv_obj_t *filter_panel = lv_obj_create(scr);
    lv_obj_set_size(filter_panel, 300, 40);
    lv_obj_align(filter_panel, LV_ALIGN_TOP_MID, 0, 92);
    lv_obj_set_style_bg_color(filter_panel, lv_color_hex(0x161b22), 0);
    lv_obj_set_style_border_width(filter_panel, 0, 0);
    lv_obj_set_flex_flow(filter_panel, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(filter_panel,
        LV_FLEX_ALIGN_START,
        LV_FLEX_ALIGN_CENTER,
        LV_FLEX_ALIGN_CENTER);
    lv_obj_remove_flag(filter_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *filter_label = lv_label_create(filter_panel);
    lv_label_set_text(filter_label, "Filter:");
    lv_obj_set_style_text_color(filter_label, lv_color_hex(0x8b949e), 0);

    lv_obj_t *dd_filter = lv_dropdown_create(filter_panel);
    lv_dropdown_set_options(dd_filter,
        "ERROR only\nWARN+\nINFO+\nDEBUG (all)");
    lv_dropdown_set_selected(dd_filter, LOG_LEVEL_DEBUG);
    lv_obj_set_width(dd_filter, 140);
    lv_obj_add_event_cb(dd_filter, dropdown_filter_event_cb,
        LV_EVENT_VALUE_CHANGED, NULL);

    /* ===== Log TextArea ===== */
    textarea_log = lv_textarea_create(scr);
    lv_obj_set_size(textarea_log, 300, 130);
    lv_obj_align(textarea_log, LV_ALIGN_BOTTOM_MID, 0, -30);
    lv_textarea_set_text(textarea_log, "(no logs yet)");
    lv_textarea_set_cursor_click_pos(textarea_log, false);
    lv_obj_set_style_bg_color(textarea_log, lv_color_hex(0x010409), 0);
    lv_obj_set_style_text_color(textarea_log, lv_color_hex(0xc9d1d9), 0);
    lv_obj_set_style_text_font(textarea_log, &lv_font_montserrat_12, 0);
    lv_obj_set_style_border_color(textarea_log, lv_color_hex(0x30363d), 0);

    /* ===== Log Counter ===== */
    label_count = lv_label_create(scr);
    lv_label_set_text(label_count, "Total: 0 logs");
    lv_obj_set_style_text_font(label_count, &lv_font_montserrat_12, 0);
    lv_obj_set_style_text_color(label_count, lv_color_hex(0x484f58), 0);
    lv_obj_align(label_count, LV_ALIGN_BOTTOM_MID, 0, -10);

    /* ===== Initial log messages ===== */
    send_log_message(LOG_LEVEL_INFO, "Logging system initialized");
    send_log_message(LOG_LEVEL_DEBUG, "IPC Pipe ready");

    CM55_LOGI("Part4 Ex2: IPC Logging System initialized");
}
```

***

### Part C: IPC Sensor Data (Ex3)

#### IPC Sensor Data Structures

**IMU Data (จาก ipc\_shared.h)**

```c
typedef struct __attribute__((packed)) {
    int16_t accel_x;        /* Accelerometer X (raw or scaled) */
    int16_t accel_y;        /* Accelerometer Y */
    int16_t accel_z;        /* Accelerometer Z */
    int16_t gyro_x;         /* Gyroscope X */
    int16_t gyro_y;         /* Gyroscope Y */
    int16_t gyro_z;         /* Gyroscope Z */
    uint32_t timestamp;     /* Timestamp (ms) */
} ipc_imu_data_t;           /* Total: 16 bytes */
```

**ADC Data (จาก ipc\_shared.h)**

```c
typedef struct __attribute__((packed)) {
    uint16_t adc_ch0;       /* ADC Channel 0 (potentiometer) */
    uint16_t adc_ch1;       /* ADC Channel 1 */
    uint16_t adc_ch2;       /* ADC Channel 2 */
    uint16_t adc_ch3;       /* ADC Channel 3 */
    uint32_t timestamp;     /* Timestamp (ms) */
} ipc_adc_data_t;           /* Total: 12 bytes */
```

**การ Pack ข้อมูลใน IPC Message**

```
ipc_msg_t structure (total ~144 bytes):
┌──────────┬──────────┬──────────┬────────────────────────────────────┐
│client_id │intr_mask │   cmd    │              data[128]             │
│ (2 bytes)│ (2 bytes)│ (4 bytes)│    ← ipc_imu_data_t อยู่ตรงนี้         │
│          │          │ SENSOR   │    (16 bytes ของ 128 bytes)        │
│          │          │ _DATA    │                                    │
└──────────┴──────────┴──────────┴────────────────────────────────────┘

วิธีอ่าน:
    ipc_imu_data_t *imu = (ipc_imu_data_t *)msg->data;
    int16_t ax = imu->accel_x;
```

#### IPC Sensor Commands

<table><thead><tr><th>Command</th><th width="127.17047119140625">Value</th><th>ทิศทาง</th><th>คำอธิบาย</th></tr></thead><tbody><tr><td><code>IPC_CMD_SENSOR_REQ</code></td><td>0xA0</td><td>CM55 → CM33</td><td>ร้องขอข้อมูล sensor</td></tr><tr><td><code>IPC_CMD_SENSOR_DATA</code></td><td>0xA1</td><td>CM33 → CM55</td><td>ส่งข้อมูล sensor กลับ</td></tr><tr><td><code>IPC_CMD_IMU_DATA</code></td><td>0xA2</td><td>CM33 → CM55</td><td>ข้อมูล IMU โดยเฉพาะ</td></tr><tr><td><code>IPC_CMD_ADC_DATA</code></td><td>0xA3</td><td>CM33 → CM55</td><td>ข้อมูล ADC โดยเฉพาะ</td></tr></tbody></table>

#### Request-Response Sequence

```
  CM55                                     CM33-NS
  ────                                     ───────
   │                                          │
   │ cm55_ipc_request_sensor(IPC_CMD_IMU_DATA)│
   │ ──── IPC_CMD_SENSOR_REQ ──────────────►  │
   │      (value = IPC_CMD_IMU_DATA)          │
   │                                          │  mtb_bmi270_read()
   │                                          │  pack ipc_imu_data_t
   │   ◄───── IPC_CMD_IMU_DATA ─────────────  │
   │          data = ipc_imu_data_t           │
   │                                          │
   │ ipc_callback():                          │
   │   memcpy(&imu_buf, msg->data, sizeof)    │
   │   imu_data_ready = true;                 │
   │                                          │
   │ LVGL timer:                              │
   │   if (imu_data_ready) → update chart     │
   │                                          │
```

#### IPC Buffer Warning (CRITICAL)

> PSoC Edge IPC Pipe ใช้ **rx\_buffer เดียว** ถ้าส่ง request ติดกันเร็วเกินไป ข้อมูลจะถูก overwrite **วิธีแก้:** 1) ใช้ timer คนละตัวที่ offset กัน (ใช้ใน Lab นี้) 2) ให้ CM33 ส่ง IMU+ADC รวมกัน 3) ใช้ FreeRTOS Queue

#### Step-by-Step Implementation

**Step 1: ประกาศ Variables และ Structures**

```c
/* part4_ex3_ipc_sensor.c */
#include "lvgl.h"
#include "ipc/cm55_ipc_pipe.h"

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

/*******************************************************************************
 * Constants
 ******************************************************************************/
#define CHART_POINT_COUNT   50      /* จำนวนจุดใน Chart */
#define SENSOR_POLL_MS      100     /* อ่าน sensor ทุก 100ms */
#define ACCEL_SCALE         100     /* Scale factor สำหรับ chart (x100) */

/*******************************************************************************
 * LVGL Objects
 ******************************************************************************/
static lv_obj_t *label_title     = NULL;
static lv_obj_t *chart_accel     = NULL;
static lv_chart_series_t *ser_x  = NULL;
static lv_chart_series_t *ser_y  = NULL;
static lv_chart_series_t *ser_z  = NULL;
static lv_obj_t *label_accel     = NULL;
static lv_obj_t *label_adc      = NULL;
static lv_obj_t *label_rate     = NULL;

/*******************************************************************************
 * Flag-Based Pattern Variables (volatile!)
 ******************************************************************************/
static volatile bool imu_data_ready       = false;
static volatile bool adc_data_ready       = false;

/* Buffers สำหรับเก็บข้อมูลจาก IPC callback */
static volatile ipc_imu_data_t imu_buffer;
static volatile ipc_adc_data_t adc_buffer;

/*******************************************************************************
 * Statistics
 ******************************************************************************/
static uint32_t sample_count      = 0;
static uint32_t last_rate_tick    = 0;
static uint32_t last_rate_count   = 0;
static uint32_t samples_per_sec   = 0;
```

**Step 2: IPC Callback (รับข้อมูล Sensor)**

```c
/*******************************************************************************
 * IPC Receive Callback
 *
 * CRITICAL: ทำงานใน IPC Task - ห้ามเรียก lv_xxx()!
 * เก็บข้อมูลลง buffer + ตั้ง flag เท่านั้น
 ******************************************************************************/
static void ipc_sensor_callback(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    switch (msg->cmd) {
        case IPC_CMD_IMU_DATA: {
            /* Copy IMU data จาก IPC message */
            const ipc_imu_data_t *imu = (const ipc_imu_data_t *)msg->data;
            memcpy((void *)&imu_buffer, imu, sizeof(ipc_imu_data_t));
            imu_data_ready = true;
            break;
        }

        case IPC_CMD_ADC_DATA: {
            /* Copy ADC data จาก IPC message */
            const ipc_adc_data_t *adc = (const ipc_adc_data_t *)msg->data;
            memcpy((void *)&adc_buffer, adc, sizeof(ipc_adc_data_t));
            adc_data_ready = true;
            break;
        }

        case IPC_CMD_PONG:
            /* Ignore PONG จาก Lab 1 */
            break;

        default:
            CM55_LOGW("Unknown sensor cmd: 0x%X", (unsigned int)msg->cmd);
            break;
    }
}
```

**Step 3: Sensor Request Timers**

```c
/*******************************************************************************
 * Sensor Request Timer
 * ส่ง request ไปยัง CM33 ทุก SENSOR_POLL_MS
 ******************************************************************************/
static void sensor_request_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (!cm55_ipc_is_init()) return;

    /* ร้องขอ IMU data */
    cm55_ipc_request_sensor(IPC_CMD_IMU_DATA);

    /*
     * IMPORTANT: ต้องเว้นช่วงระหว่าง IPC sends
     * เพราะ IPC ใช้ single rx_buffer
     * ถ้าส่งติดกันเร็วเกินไป จะ overwrite กัน
     *
     * วิธีแก้: ร้องขอ ADC ใน timer แยก หรือ
     *          ให้ CM33 ส่ง IMU+ADC รวมกัน
     */
}

/* Timer แยกสำหรับ ADC (offset 50ms จาก IMU) */
static void adc_request_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    if (!cm55_ipc_is_init()) return;

    /* ร้องขอ ADC data */
    cm55_ipc_request_sensor(IPC_CMD_ADC_DATA);
}
```

**Step 4: UI Update Timer (อ่าน flag + อัปเดต Chart)**

```c
/*******************************************************************************
 * UI Update Timer - ตรวจสอบ flag ทุก 50ms
 * ทำงานใน LVGL context จึงเรียก lv_xxx() ได้อย่างปลอดภัย
 ******************************************************************************/
static void ui_update_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* ===== IMU Data Update ===== */
    if (imu_data_ready) {
        imu_data_ready = false;
        sample_count++;

        /* อ่านข้อมูลจาก buffer (copy เพื่อความปลอดภัย) */
        ipc_imu_data_t imu;
        memcpy(&imu, (void *)&imu_buffer, sizeof(ipc_imu_data_t));

        /* อัปเดต Chart */
        lv_chart_set_next_value(chart_accel, ser_x, imu.accel_x / ACCEL_SCALE);
        lv_chart_set_next_value(chart_accel, ser_y, imu.accel_y / ACCEL_SCALE);
        lv_chart_set_next_value(chart_accel, ser_z, imu.accel_z / ACCEL_SCALE);

        /* อัปเดต Labels */
        lv_label_set_text_fmt(label_accel,
            "Accel  X:%d  Y:%d  Z:%d",
            (int)imu.accel_x, (int)imu.accel_y, (int)imu.accel_z);
    }

    /* ===== ADC Data Update ===== */
    if (adc_data_ready) {
        adc_data_ready = false;

        ipc_adc_data_t adc;
        memcpy(&adc, (void *)&adc_buffer, sizeof(ipc_adc_data_t));

        lv_label_set_text_fmt(label_adc,
            "ADC  CH0:%u  CH1:%u",
            (unsigned int)adc.adc_ch0,
            (unsigned int)adc.adc_ch1);
    }

    /* ===== Sample Rate Calculation ===== */
    uint32_t now = lv_tick_get();
    if (now - last_rate_tick >= 1000) {
        samples_per_sec = sample_count - last_rate_count;
        last_rate_count = sample_count;
        last_rate_tick = now;

        lv_label_set_text_fmt(label_rate,
            "Rate: %u sps | Total: %u",
            (unsigned int)samples_per_sec,
            (unsigned int)sample_count);
    }
}
```

**Step 5: สร้าง UI Layout (Main Entry Point)**

```c
/*******************************************************************************
 * Main Entry Point
 ******************************************************************************/
void part4_ex3_ipc_sensor(void)
{
    /* ===== ล้างหน้าจอ ===== */
    lv_obj_t *scr = lv_screen_active();
    lv_obj_clean(scr);
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0d1117), 0);

    /* ===== Title ===== */
    label_title = lv_label_create(scr);
    lv_label_set_text(label_title, "IPC Sensor Monitor");
    lv_obj_set_style_text_font(label_title, &lv_font_montserrat_20, 0);
    lv_obj_set_style_text_color(label_title, lv_color_hex(0x58a6ff), 0);
    lv_obj_align(label_title, LV_ALIGN_TOP_MID, 0, 5);

    /* ===== Accelerometer Chart ===== */
    chart_accel = lv_chart_create(scr);
    lv_obj_set_size(chart_accel, 290, 110);
    lv_obj_align(chart_accel, LV_ALIGN_TOP_MID, 0, 32);
    lv_chart_set_type(chart_accel, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(chart_accel, CHART_POINT_COUNT);
    lv_chart_set_range(chart_accel, LV_CHART_AXIS_PRIMARY_Y, -200, 200);
    lv_obj_set_style_bg_color(chart_accel, lv_color_hex(0x010409), 0);
    lv_obj_set_style_border_color(chart_accel, lv_color_hex(0x30363d), 0);
    lv_obj_set_style_line_width(chart_accel, 2, LV_PART_ITEMS);
    lv_obj_set_style_size(chart_accel, 0, 0, LV_PART_INDICATOR);

    /* Series: X(Red), Y(Green), Z(Blue) */
    ser_x = lv_chart_add_series(chart_accel,
        lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
    ser_y = lv_chart_add_series(chart_accel,
        lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y);
    ser_z = lv_chart_add_series(chart_accel,
        lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y);

    /* ===== Legend ===== */
    lv_obj_t *legend_panel = lv_obj_create(scr);
    lv_obj_set_size(legend_panel, 290, 22);
    lv_obj_align(legend_panel, LV_ALIGN_TOP_MID, 0, 145);
    lv_obj_set_style_bg_opa(legend_panel, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(legend_panel, 0, 0);
    lv_obj_set_style_pad_all(legend_panel, 0, 0);
    lv_obj_set_flex_flow(legend_panel, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(legend_panel,
        LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_remove_flag(legend_panel, LV_OBJ_FLAG_SCROLLABLE);

    /* X legend */
    lv_obj_t *lx = lv_label_create(legend_panel);
    lv_label_set_text(lx, "X");
    lv_obj_set_style_text_color(lx, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_text_font(lx, &lv_font_montserrat_14, 0);

    lv_obj_t *sep1 = lv_label_create(legend_panel);
    lv_label_set_text(sep1, "   ");

    /* Y legend */
    lv_obj_t *ly = lv_label_create(legend_panel);
    lv_label_set_text(ly, "Y");
    lv_obj_set_style_text_color(ly, lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_set_style_text_font(ly, &lv_font_montserrat_14, 0);

    lv_obj_t *sep2 = lv_label_create(legend_panel);
    lv_label_set_text(sep2, "   ");

    /* Z legend */
    lv_obj_t *lz = lv_label_create(legend_panel);
    lv_label_set_text(lz, "Z");
    lv_obj_set_style_text_color(lz, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_set_style_text_font(lz, &lv_font_montserrat_14, 0);

    /* ===== Accel Values Label ===== */
    label_accel = lv_label_create(scr);
    lv_label_set_text(label_accel, "Accel  X:--  Y:--  Z:--");
    lv_obj_set_style_text_font(label_accel, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(label_accel, lv_color_hex(0xc9d1d9), 0);
    lv_obj_align(label_accel, LV_ALIGN_TOP_MID, 0, 170);

    /* ===== ADC Values Label ===== */
    label_adc = lv_label_create(scr);
    lv_label_set_text(label_adc, "ADC  CH0:--  CH1:--");
    lv_obj_set_style_text_font(label_adc, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(label_adc, lv_color_hex(0x8b949e), 0);
    lv_obj_align(label_adc, LV_ALIGN_TOP_MID, 0, 192);

    /* ===== Sample Rate Label ===== */
    label_rate = lv_label_create(scr);
    lv_label_set_text(label_rate, "Rate: -- sps | Total: 0");
    lv_obj_set_style_text_font(label_rate, &lv_font_montserrat_12, 0);
    lv_obj_set_style_text_color(label_rate, lv_color_hex(0x484f58), 0);
    lv_obj_align(label_rate, LV_ALIGN_BOTTOM_MID, 0, -8);

    /* ===== Register IPC Callback ===== */
    cm55_ipc_register_callback(ipc_sensor_callback, NULL);

    /* ===== Create Timers ===== */
    /* Request IMU ทุก 100ms */
    lv_timer_create(sensor_request_timer_cb, SENSOR_POLL_MS, NULL);

    /* Request ADC ทุก 100ms (offset 50ms จาก IMU) */
    lv_timer_t *adc_timer = lv_timer_create(adc_request_timer_cb,
        SENSOR_POLL_MS, NULL);
    /* หน่วงเวลาเริ่มต้น 50ms เพื่อไม่ให้ชนกับ IMU request */
    lv_timer_set_repeat_count(adc_timer, -1);

    /* UI update ทุก 50ms */
    lv_timer_create(ui_update_timer_cb, 50, NULL);

    /* Initialize timing */
    last_rate_tick = lv_tick_get();

    CM55_LOGI("Part4 Ex3: IPC Sensor Monitor initialized");
}
```

***

### 3. แบบฝึกหัด

#### Exercise 1: Ping Statistics (Min/Max/Avg RTT)

เพิ่มการวิเคราะห์สถิติ Ping-Pong:

* RTT: min / max / average
* Packet loss rate (%)
* แสดงผลบน Label เพิ่มเติม

**Hints:**

```c
static uint32_t rtt_min = UINT32_MAX;
static uint32_t rtt_max = 0;
static uint32_t rtt_sum = 0;

/* ใน ping_timer_cb() */
if (pong_rtt_ms < rtt_min) rtt_min = pong_rtt_ms;
if (pong_rtt_ms > rtt_max) rtt_max = pong_rtt_ms;
rtt_sum += pong_rtt_ms;
uint32_t rtt_avg = rtt_sum / pong_count;
uint32_t loss = ((ping_count - pong_count) * 100) / ping_count;
```

#### Exercise 2: Data Rate Monitor (Sensor)

เพิ่มการวัดอัตราข้อมูล sensor:

* แยก IMU sps กับ ADC sps
* เปรียบเทียบ actual rate กับ target rate (10 Hz)
* แสดงเป็น % ของ target
* เปลี่ยนสีเตือนเมื่อ rate ต่ำกว่า 80%

**Hints:**

```c
static uint32_t imu_sample_count = 0;
static uint32_t adc_sample_count = 0;

/* คำนวณ rate ทุก 1 วินาที */
if (now - last_rate_tick >= 1000) {
    imu_sps = imu_sample_count - last_imu_count;
    uint32_t target = 1000 / SENSOR_POLL_MS;  /* 10 Hz */
    uint32_t pct = (imu_sps * 100) / target;

    lv_label_set_text_fmt(label_rate,
        "IMU: %u/%u sps (%u%%)  ADC: %u sps",
        imu_sps, target, pct, adc_sps);
}
```

#### Exercise 3: Color-Coded Log Viewer

สร้าง log viewer ที่แสดงแต่ละ level ด้วยสีต่างกัน:

* ERROR: สีแดง, WARN: สีเหลือง, INFO: สีเขียว, DEBUG: สีเทา
* ปุ่ม "Clear" ล้าง log ทั้งหมด
* จำนวน log แต่ละ level (E:3 W:5 I:10 D:2)

**Hints:**

LVGL TextArea ไม่รองรับหลายสี ต้องใช้ approach อื่น:

```c
/* ใช้หลาย Label ใน Scrollable Container */
lv_obj_t *log_container = lv_obj_create(scr);
lv_obj_set_flex_flow(log_container, LV_FLEX_FLOW_COLUMN);

void add_log_label(log_level_t level, const char *text) {
    lv_obj_t *lbl = lv_label_create(log_container);
    lv_label_set_text(lbl, text);
    lv_obj_set_style_text_color(lbl,
        lv_color_hex(log_level_colors[level]), 0);
    lv_obj_scroll_to_view(lbl, LV_ANIM_ON);
}
```

***

### 4. Quick Reference / Cheat Sheet

```c
/*******************************************************************************
 * IPC API (CM55 Side)
 ******************************************************************************/
cm55_ipc_init();                              /* Initialize IPC */
cm55_ipc_is_init();                           /* Check if ready */
cm55_ipc_send_cmd(IPC_CMD_PING, value);       /* Send command */
cm55_ipc_send_retry(&msg, 10);                /* Send with retry */
cm55_ipc_register_callback(cb_func, NULL);    /* Register RX callback */
cm55_ipc_request_sensor(IPC_CMD_IMU_DATA);    /* Request IMU */
cm55_ipc_request_sensor(IPC_CMD_ADC_DATA);    /* Request ADC */

/*******************************************************************************
 * IPC Message Structure
 ******************************************************************************/
typedef struct {
    uint16_t client_id;             /* Destination */
    uint16_t intr_mask;             /* Pipe driver */
    uint32_t cmd;                   /* IPC_CMD_xxx */
    uint32_t value;                 /* Numeric data */
    char     data[128];             /* String data */
} ipc_msg_t;

/* Initialize and send */
ipc_msg_t msg;
IPC_MSG_INIT(&msg, IPC_CMD_LOG_INFO);
snprintf(msg.data, IPC_DATA_MAX_LEN, "Temperature: %d C", temp);
cm55_ipc_send_retry(&msg, 5);

/*******************************************************************************
 * Logging Macros (CM55 → CM33 console)
 ******************************************************************************/
CM55_LOGI("info: %d", val);                   /* Info level (0x94) */
CM55_LOGE("error: %s", msg);                  /* Error level (0x92) */
CM55_LOGW("warning");                          /* Warning level (0x93) */
CM55_LOGD("debug data: 0x%X", data);          /* Debug level (0x95) */

/* Low-level API */
cm55_ipc_log("general log message");
cm55_ipc_log_level(IPC_CMD_LOG_ERROR, "formatted %d", val);
cm55_ipc_send_data(IPC_CMD_LOG, "raw string data");

/*******************************************************************************
 * Sensor Data Structures
 ******************************************************************************/
ipc_imu_data_t imu;                             /* 16 bytes packed */
  .accel_x, .accel_y, .accel_z                  /* int16_t */
  .gyro_x,  .gyro_y,  .gyro_z                   /* int16_t */
  .timestamp                                     /* uint32_t */

ipc_adc_data_t adc;                             /* 12 bytes packed */
  .adc_ch0 ~ .adc_ch3                           /* uint16_t */
  .timestamp                                     /* uint32_t */

/* Cast จาก IPC message */
ipc_imu_data_t *imu = (ipc_imu_data_t *)msg->data;

/*******************************************************************************
 * Flag-Based Pattern (CRITICAL!)
 ******************************************************************************/
static volatile bool flag = false;            /* Shared flag */
static volatile int32_t shared_data = 0;      /* Shared data */

/* IPC callback: ตั้ง flag + เก็บ data */
/* LVGL timer:   อ่าน flag + อัปเดต UI */

/*******************************************************************************
 * Timing
 ******************************************************************************/
uint32_t start = lv_tick_get();               /* Get current tick (ms) */
uint32_t elapsed = lv_tick_get() - start;     /* Elapsed time */

/*******************************************************************************
 * LVGL Chart API (v9.2)
 ******************************************************************************/
lv_chart_create(parent);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_set_point_count(chart, 50);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -200, 200);
lv_chart_add_series(chart, color, LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_next_value(chart, series, value);

/*******************************************************************************
 * LVGL TextArea API (v9.2)
 ******************************************************************************/
lv_textarea_create(parent);
lv_textarea_set_text(ta, "content");
lv_textarea_add_text(ta, "append");
lv_textarea_set_cursor_pos(ta, LV_TEXTAREA_CURSOR_LAST);

/*******************************************************************************
 * LVGL Dropdown API (v9.2)
 ******************************************************************************/
lv_dropdown_create(parent);
lv_dropdown_set_options(dd, "A\nB\nC");
lv_dropdown_get_selected(dd);                  /* Get index (0-based) */
```

#### Application ในงาน Industrial

| Use Case                   | คำอธิบาย                                                 |
| -------------------------- | -------------------------------------------------------- |
| **System Health Monitor**  | วัด latency ระหว่าง Core ตรวจจับ hang                    |
| **Watchdog Ping**          | Core A ping Core B ทุก 1 วินาที ถ้าไม่ตอบ → reset        |
| **Production Diagnostics** | บันทึก error log สำหรับวิเคราะห์ปัญหาในโรงงาน            |
| **Field Service**          | ช่างซ่อมอ่าน log จากหน้าจอเครื่องโดยไม่ต้องต่อ PC        |
| **Condition Monitoring**   | วัดการสั่นสะเทือนของเครื่องจักรเพื่อพยากรณ์การบำรุงรักษา |
| **Process Control**        | แสดงค่า sensor ของกระบวนการผลิตแบบ real-time             |
| **Remote Monitoring**      | ส่ง log ผ่าน WiFi/BLE ไปยัง cloud dashboard              |
| **Quality Inspection**     | ตรวจจับค่าผิดปกติในสายการผลิต                            |

***

### 5. References

* LVGL Docs: <https://docs.lvgl.io/master/>
* PSoC Edge E84: Infineon documentation
* ModusToolbox: <https://www.infineon.com/modustoolbox>
* LVGL Chart Widget: <https://docs.lvgl.io/master/widgets/chart.html>
* LVGL TextArea Widget: <https://docs.lvgl.io/master/widgets/textarea.html>
