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


---

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