> 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-dashboard.md).

# IPC Dashboard

## Lab 4: Hardware IPC Dashboard

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

***

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

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

ในระบบ Industrial จริง Dashboard ต้องแสดงข้อมูลหลายอย่างพร้อมกัน และ ควบคุมอุปกรณ์หลายตัวผ่าน Multi-Core IPC:

* **SCADA System**: หน้าจอ HMI แสดง sensor data, alarm status, control panels ทั้งหมดในหน้าเดียว
* **Factory Floor Dashboard**: แสดง production line status, machine health, operator controls
* **Smart Building BMS**: HVAC sensor data + lighting control + access events ใน TabView
* **IoT Gateway**: Sensor fusion จากหลาย source แสดงรวมพร้อม control interface

Lab นี้รวมทุกอย่างจาก Lab 5 (LED) + Lab 6 (Button) เข้ากับ Sensor data (IMU, ADC) ที่ส่งจาก CM33-NS ผ่าน IPC --> แสดงใน TabView Dashboard บน CM55

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

1. **Multi-Channel IPC**: จัดการ IPC หลาย command type พร้อมกัน (LED + Button + IMU + ADC)
2. **TabView Dashboard**: แยก UI เป็น 3 Tabs: Controls | Sensors | Status
3. **Real-Time Charts**: แสดง IMU data แบบ real-time chart ผ่าน IPC
4. **Bidirectional IPC**: CM55 ส่งคำสั่ง + CM33 ส่ง data/events กลับพร้อมกัน
5. **System Health**: Monitor IPC latency, error count, uptime
6. **Concurrent Flag Management**: จัดการ flags หลายตัวสำหรับ IPC commands ต่าง ๆ

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

สร้าง TabView Dashboard บน CM55 ที่แบ่งเป็น 3 tabs:

* **Controls**: LED switches + brightness slider + button status
* **Sensors**: IMU 3-axis chart + ADC gauge (data จาก CM33 ผ่าน IPC)
* **Status**: IPC statistics + event log + system health

CM33-NS ทำหน้าที่: อ่าน sensor data ส่งมาทุก 100ms + รับคำสั่ง LED + ส่ง button events

***

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

#### 2.1 Full IPC Dashboard Architecture

```
+-----------------------------------------------------------+
|         FULL IPC DASHBOARD ARCHITECTURE                   |
+-----------------------------------------------------------+
|                                                           |
|  CM55 Core (LVGL Dashboard)                               |
|  ============================                             |
|  +--------------------------------------------------+     |
|  | TabView                                          |     |
|  | +----------+ +----------+ +----------+           |     |
|  | | Controls | | Sensors  | | Status   |           |     |
|  | +----------+ +----------+ +----------+           |     |
|  | | LED SW x3| | IMU Chart| | IPC Stats|           |     |
|  | | Bright.  | | ADC Gauge| | Event Log|           |     |
|  | | Btn Stat | |          | | Health   |           |     |
|  | +----------+ +----------+ +----------+           |     |
|  +--------------------------------------------------+     |
|      |               ^                                    |
|      | IPC TX        | IPC RX                             |
|      v               |                                    |
|  +----------------------------------------------+         |
|  |              IPC PIPE (bidirectional)        |         |
|  +----------------------------------------------+         |
|      |               ^                                    |
|      | IPC RX        | IPC TX                             |
|      v               |                                    |
|  CM33-NS Core (Hardware Controller)                       |
|  ================================                         |
|  +--------------------------------------------------+     |
|  | +----------+ +----------+ +----------+           |     |
|  | | LED GPIO | | Sensors  | | Button   |           |     |
|  | | Handler  | | Reader   | | Poller   |           |     |
|  | | (PWM)    | | (IMU+ADC)| | (Debounce|           |     |
|  | +----------+ +----------+ +----------+           |     |
|  +--------------------------------------------------+     |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.2 IPC Command Map (All Channels)

```
+-----------------------------------------------------------+
|           ACTIVE IPC CHANNELS IN DASHBOARD                |
+-----------------------------------------------------------+
|                                                           |
|  Direction: CM55 --> CM33                                 |
|  ------------------------------------------------         |
|  IPC_CMD_LED_SET       (0xB2)  LED ON/OFF control         |
|  IPC_CMD_LED_BRIGHTNESS(0xB3)  LED PWM brightness         |
|  IPC_CMD_SENSOR_REQ    (0xA0)  Request sensor data        |
|  IPC_CMD_BUTTON        (0xB4)  Request button state       |
|  IPC_CMD_PING          (0x42)  Health check               |
|                                                           |
|  Direction: CM33 --> CM55                                 |
|  ------------------------------------------------         |
|  IPC_CMD_ACK           (0x44)  LED command confirmed      |
|  IPC_CMD_BUTTON_EVENT  (0xB5)  Button press/release       |
|  IPC_CMD_IMU_DATA      (0xA2)  IMU 6-axis data            |
|  IPC_CMD_ADC_DATA      (0xA3)  ADC channel data           |
|  IPC_CMD_PONG          (0x43)  Health check response      |
|                                                           |
|  Total Active Channels: 10                                |
|  Max Concurrent Traffic: ~20 msg/sec                      |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.3 Multi-Flag Management Pattern

```
+-----------------------------------------------------------+
|        MANAGING MULTIPLE IPC FLAGS                        |
+-----------------------------------------------------------+
|                                                           |
|  /* แต่ละ IPC command type มี flag set แยก */               |
|                                                           |
|  volatile struct {                                        |
|      bool led_ack;         /* LED ACK received */         |
|      bool btn_event;       /* Button event received */    |
|      bool imu_data;        /* IMU data received */        |
|      bool adc_data;        /* ADC data received */        |
|      bool pong;            /* PONG received */            |
|  } ipc_flags;                                             |
|                                                           |
|  volatile struct {                                        |
|      ipc_led_data_t    led;                               |
|      ipc_button_data_t button;                            |
|      ipc_imu_data_t    imu;                               |
|      ipc_adc_data_t    adc;                               |
|  } ipc_data;                                              |
|                                                           |
|  /* LVGL timer ตรวจทุก flag ทุก cycle */                    |
|  void timer_cb() {                                        |
|   if (flags.led_ack)   { flags.led_ack=0; update_led(); } |
|   if (flags.btn_event){ flags.btn_event=0; update_btn(); }|
|   if (flags.imu_data) { flags.imu_data=0; update_imu(); } |
|   if (flags.adc_data)  { flags.adc_data=0; update_adc(); }|
|   if (flags.pong)      { flags.pong=0; update_health(); } |
|  }                                                        |
|                                                           |
+-----------------------------------------------------------+
```

#### 2.4 Program Flowchart

```
+-----------------------------------------------------------+
|              DASHBOARD INITIALIZATION FLOW                |
+-----------------------------------------------------------+
|                                                           |
|     +----------+                                          |
|     |  Start   |                                          |
|     +----+-----+                                          |
|          |                                                |
|          v                                                |
|  +------------------+                                     |
|  | IPC Init         |                                     |
|  | Register callback|                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+                                     |
|  | Create TabView   |                                     |
|  | - Tab 1: Controls|                                     |
|  | - Tab 2: Sensors |                                     |
|  | - Tab 3: Status  |                                     |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+                                     |
|  | Create Timers:   |                                     |
|  | - UI flag check  | 50ms                                |
|  | - Sensor request | 100ms                               |
|  | - Health check   | 5000ms                              |
|  +--------+---------+                                     |
|           |                                               |
|           v                                               |
|  +------------------+         +------------------+        |
|  | LVGL Main Loop   |-------->| IPC RX Callback  |        |
|  | Process timers   |         | Set flags + data |        |
|  | Check flags      |         +------------------+        |
|  | Update UI        |                                     |
|  +------------------+                                     |
|                                                           |
+-----------------------------------------------------------+
```

***

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

#### 3.1 CM55 IPC Functions

<table><thead><tr><th width="289.52557373046875">Function</th><th>Description</th><th>Return</th></tr></thead><tbody><tr><td><code>cm55_ipc_set_led(id, state)</code></td><td>ส่ง LED ON/OFF</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_set_led_brightness(id, bright)</code></td><td>ส่ง LED brightness</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_request_sensor(type)</code></td><td>ขอ sensor data จาก CM33</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_request_button(id)</code></td><td>ขอ button state</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_send_cmd(IPC_CMD_PING, tick)</code></td><td>ส่ง PING + timestamp</td><td><code>cy_en_ipc_pipe_status_t</code></td></tr><tr><td><code>cm55_ipc_get_stats(tx, rx, err)</code></td><td>ดึง IPC statistics</td><td>void</td></tr></tbody></table>

#### 3.2 LVGL TabView Functions

<table><thead><tr><th width="318.6129150390625">Function</th><th>Description</th><th>Return</th></tr></thead><tbody><tr><td><code>lv_tabview_create(parent)</code></td><td>สร้าง TabView container</td><td><code>lv_obj_t *</code></td></tr><tr><td><code>lv_tabview_add_tab(tv, title)</code></td><td>เพิ่ม tab ใหม่</td><td><code>lv_obj_t *</code></td></tr><tr><td><code>lv_tabview_set_active(tv, idx, anim)</code></td><td>เลือก tab ที่ active</td><td>void</td></tr><tr><td><code>lv_tabview_get_tab_bar(tv)</code></td><td>ดึง tab bar สำหรับ styling</td><td><code>lv_obj_t *</code></td></tr></tbody></table>

#### 3.3 LVGL Chart Functions

| Function                                    | Description        | Return                |
| ------------------------------------------- | ------------------ | --------------------- |
| `lv_chart_create(parent)`                   | สร้าง chart widget | `lv_obj_t *`          |
| `lv_chart_add_series(chart, color, axis)`   | เพิ่ม data series  | `lv_chart_series_t *` |
| `lv_chart_set_next_value(chart, ser, val)`  | เพิ่มค่าถัดไป      | void                  |
| `lv_chart_set_range(chart, axis, min, max)` | ตั้งช่วงค่า        | void                  |
| `lv_chart_set_point_count(chart, count)`    | จำนวน data points  | void                  |

#### 3.4 CM33-NS Sensor Functions

| Function                               | Description                 | Return                    |
| -------------------------------------- | --------------------------- | ------------------------- |
| `aic_sensors_init()`                   | Initialize sensor subsystem | void                      |
| `imu_shared_read_accel(&ax, &ay, &az)` | อ่าน accelerometer          | void                      |
| `aic_adc_read(channel)`                | อ่าน ADC channel            | `uint16_t`                |
| `cm33_ipc_send_imu(data)`              | ส่ง IMU data ไป CM55        | `cy_en_ipc_pipe_status_t` |
| `cm33_ipc_send_adc(data)`              | ส่ง ADC data ไป CM55        | `cy_en_ipc_pipe_status_t` |

***

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

#### 4.1 CM55 Code - Full IPC Dashboard

```c
/*******************************************************************************
 * Part 4 - Example 7: Hardware IPC Dashboard (CM55 Side)
 *
 * Full dashboard with TabView:
 * - Tab 1: LED Controls + Button Status
 * - Tab 2: IMU Chart + ADC Gauge (data via IPC from CM33)
 * - Tab 3: IPC Statistics + System Health
 *
 * CRITICAL: All UI updates through flag-based pattern only!
 ******************************************************************************/

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

/* ===== IPC Flag Structure ===== */
typedef struct {
    volatile bool led_ack;
    volatile bool btn_event;
    volatile bool imu_data;
    volatile bool adc_data;
    volatile bool pong;
} ipc_flags_t;

static ipc_flags_t flags = {0};

/* ===== IPC Data Buffers (volatile) ===== */
static volatile ipc_led_data_t    rx_led    = {0};
static volatile ipc_button_data_t rx_button = {0};
static volatile ipc_imu_data_t    rx_imu    = {0};
static volatile ipc_adc_data_t    rx_adc    = {0};
static volatile uint32_t          rx_pong_tick = 0;

/* ===== IPC Statistics ===== */
static volatile uint32_t total_rx_count = 0;
static volatile uint32_t total_tx_count = 0;
static volatile uint32_t ipc_error_count = 0;
static volatile uint32_t ping_send_tick = 0;
static volatile uint32_t ipc_rtt_ms = 0;

/* ===== LED State ===== */
#define NUM_LEDS 3
static bool led_states[NUM_LEDS] = {false, false, false};
static int led_indices[NUM_LEDS] = {0, 1, 2};

/* ===== UI Objects: Tab 1 (Controls) ===== */
static lv_obj_t * led_switches[NUM_LEDS];
static lv_obj_t * led_indicators[NUM_LEDS];
static lv_obj_t * led_status_labels[NUM_LEDS];
static lv_obj_t * brightness_slider;
static lv_obj_t * brightness_label;
static lv_obj_t * btn_status_label;
static lv_obj_t * btn_led_widget;
static lv_obj_t * btn_counter_label;

/* ===== UI Objects: Tab 2 (Sensors) ===== */
static lv_obj_t * imu_chart;
static lv_chart_series_t * imu_ser_x;
static lv_chart_series_t * imu_ser_y;
static lv_chart_series_t * imu_ser_z;
static lv_obj_t * imu_value_label;
static lv_obj_t * adc_arc;
static lv_obj_t * adc_value_label;

/* ===== UI Objects: Tab 3 (Status) ===== */
static lv_obj_t * stat_tx_label;
static lv_obj_t * stat_rx_label;
static lv_obj_t * stat_err_label;
static lv_obj_t * stat_rtt_label;
static lv_obj_t * stat_uptime_label;
static lv_obj_t * event_log_label;

/* Button counts */
static uint32_t press_count = 0;

/* Event log */
#define EVENT_LOG_MAX 6
static char event_log_buf[EVENT_LOG_MAX][40];
static int event_log_idx = 0;

static uint32_t start_tick = 0;

/*******************************************************************************
 * IPC Receive Callback (ISR context)
 ******************************************************************************/
static void dashboard_ipc_rx_cb(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;
    total_rx_count++;

    switch (msg->cmd) {
        case IPC_CMD_ACK: {
            ipc_led_data_t *d = (ipc_led_data_t *)msg->data;
            rx_led = *d;
            flags.led_ack = true;
            break;
        }
        case IPC_CMD_BUTTON_EVENT: {
            ipc_button_data_t *d = (ipc_button_data_t *)msg->data;
            rx_button = *d;
            flags.btn_event = true;
            break;
        }
        case IPC_CMD_IMU_DATA: {
            ipc_imu_data_t *d = (ipc_imu_data_t *)msg->data;
            rx_imu = *d;
            flags.imu_data = true;
            break;
        }
        case IPC_CMD_ADC_DATA: {
            ipc_adc_data_t *d = (ipc_adc_data_t *)msg->data;
            rx_adc = *d;
            flags.adc_data = true;
            break;
        }
        case IPC_CMD_PONG:
            rx_pong_tick = msg->value;
            ipc_rtt_ms = xTaskGetTickCount() - ping_send_tick;
            flags.pong = true;
            break;
        case IPC_CMD_NACK:
            ipc_error_count++;
            break;
        default:
            break;
    }
}

/*******************************************************************************
 * Event Log Helper
 ******************************************************************************/
static void log_event(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vsnprintf(event_log_buf[event_log_idx],
              sizeof(event_log_buf[0]), fmt, args);
    va_end(args);
    event_log_idx = (event_log_idx + 1) % EVENT_LOG_MAX;
}

static void refresh_event_log(void)
{
    static char display_buf[320];
    display_buf[0] = '\0';
    for (int i = 0; i < EVENT_LOG_MAX; i++) {
        int idx = (event_log_idx - EVENT_LOG_MAX + i + EVENT_LOG_MAX)
                  % EVENT_LOG_MAX;
        if (event_log_buf[idx][0] != '\0') {
            strcat(display_buf, event_log_buf[idx]);
            strcat(display_buf, "\n");
        }
    }
    lv_label_set_text(event_log_label, display_buf);
}

/*******************************************************************************
 * LVGL Timer: Check ALL IPC flags and update UI
 ******************************************************************************/
static void dashboard_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* --- LED ACK --- */
    if (flags.led_ack) {
        flags.led_ack = false;
        uint8_t id = rx_led.led_id;
        if (id < NUM_LEDS) {
            led_states[id] = (rx_led.state != 0);
            if (led_states[id]) {
                lv_led_on(led_indicators[id]);
            } else {
                lv_led_off(led_indicators[id]);
            }
            lv_label_set_text_fmt(led_status_labels[id], "%s",
                led_states[id] ? "ON" : "OFF");
            lv_obj_set_style_text_color(led_status_labels[id],
                led_states[id] ? lv_palette_main(LV_PALETTE_GREEN) :
                                 lv_palette_main(LV_PALETTE_RED), 0);
            log_event("LED%d -> %s", (int)id, led_states[id] ? "ON" : "OFF");
        }
    }

    /* --- Button Event --- */
    if (flags.btn_event) {
        flags.btn_event = false;
        bool pressed = (rx_button.pressed != 0);
        bool long_p  = (rx_button.long_press != 0);

        if (pressed) {
            lv_led_on(btn_led_widget);
            if (long_p) {
                lv_label_set_text(btn_status_label, "LONG PRESS");
                lv_obj_set_style_text_color(btn_status_label,
                    lv_palette_main(LV_PALETTE_ORANGE), 0);
                lv_led_set_color(btn_led_widget,
                    lv_palette_main(LV_PALETTE_ORANGE));
                log_event("BTN: LONG_PRESS");
            } else {
                press_count++;
                lv_label_set_text(btn_status_label, "PRESSED");
                lv_obj_set_style_text_color(btn_status_label,
                    lv_palette_main(LV_PALETTE_GREEN), 0);
                lv_led_set_color(btn_led_widget,
                    lv_palette_main(LV_PALETTE_GREEN));
                log_event("BTN: PRESS #%u", (unsigned int)press_count);
            }
        } else {
            lv_led_off(btn_led_widget);
            lv_label_set_text(btn_status_label, "RELEASED");
            lv_obj_set_style_text_color(btn_status_label,
                lv_palette_main(LV_PALETTE_RED), 0);
        }
        lv_label_set_text_fmt(btn_counter_label,
            "Count: %u", (unsigned int)press_count);
    }

    /* --- IMU Data --- */
    if (flags.imu_data) {
        flags.imu_data = false;
        /* Scale: raw int16 -> chart range (e.g., -2000 to +2000) */
        lv_chart_set_next_value(imu_chart, imu_ser_x, rx_imu.accel_x);
        lv_chart_set_next_value(imu_chart, imu_ser_y, rx_imu.accel_y);
        lv_chart_set_next_value(imu_chart, imu_ser_z, rx_imu.accel_z);
        lv_chart_refresh(imu_chart);

        lv_label_set_text_fmt(imu_value_label,
            "X:%d  Y:%d  Z:%d",
            (int)rx_imu.accel_x, (int)rx_imu.accel_y, (int)rx_imu.accel_z);
    }

    /* --- ADC Data --- */
    if (flags.adc_data) {
        flags.adc_data = false;
        /* Scale ADC to 0-100 for arc display */
        int32_t adc_pct = (int32_t)rx_adc.adc_ch0 * 100 / 4095;
        lv_arc_set_value(adc_arc, adc_pct);
        lv_label_set_text_fmt(adc_value_label,
            "%d%%\n(%u)", (int)adc_pct, (unsigned int)rx_adc.adc_ch0);
    }

    /* --- PONG (Health) --- */
    if (flags.pong) {
        flags.pong = false;
        lv_label_set_text_fmt(stat_rtt_label,
            "RTT: %u ms", (unsigned int)ipc_rtt_ms);
        log_event("PONG: RTT=%ums", (unsigned int)ipc_rtt_ms);
    }

    /* --- Update Status Tab stats (always) --- */
    uint32_t uptime = (xTaskGetTickCount() - start_tick) / 1000;
    lv_label_set_text_fmt(stat_tx_label,
        "TX: %u", (unsigned int)total_tx_count);
    lv_label_set_text_fmt(stat_rx_label,
        "RX: %u", (unsigned int)total_rx_count);
    lv_label_set_text_fmt(stat_err_label,
        "Errors: %u", (unsigned int)ipc_error_count);
    lv_label_set_text_fmt(stat_uptime_label,
        "Uptime: %us", (unsigned int)uptime);

    refresh_event_log();
}

/*******************************************************************************
 * Sensor Request Timer (100ms) - ขอ data จาก CM33
 ******************************************************************************/
static void sensor_request_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* Request IMU data */
    cm55_ipc_request_sensor(IPC_CMD_IMU_DATA);
    total_tx_count++;

    /* 20ms delay ก่อนส่ง request ถัดไป */
    vTaskDelay(pdMS_TO_TICKS(20));

    /* Request ADC data */
    cm55_ipc_request_sensor(IPC_CMD_ADC_DATA);
    total_tx_count++;
}

/*******************************************************************************
 * Health Check Timer (5000ms) - ส่ง PING
 ******************************************************************************/
static void health_timer_cb(lv_timer_t *timer)
{
    (void)timer;
    ping_send_tick = xTaskGetTickCount();
    cm55_ipc_send_cmd(IPC_CMD_PING, ping_send_tick);
    total_tx_count++;
}

/*******************************************************************************
 * LED Switch Callback
 ******************************************************************************/
static void led_switch_cb(lv_event_t *e)
{
    int *idx = (int *)lv_event_get_user_data(e);
    lv_obj_t *sw = lv_event_get_target(e);
    bool on = lv_obj_has_state(sw, LV_STATE_CHECKED);

    cm55_ipc_set_led(*idx, on);
    total_tx_count++;
    CM55_LOGD("LED%d -> %s", *idx, on ? "ON" : "OFF");
}

/*******************************************************************************
 * Brightness Slider Callback
 ******************************************************************************/
static void brightness_cb(lv_event_t *e)
{
    lv_obj_t *slider = lv_event_get_target(e);
    int32_t val = lv_slider_get_value(slider);
    lv_label_set_text_fmt(brightness_label, "%d%%", (int)val);

    /* ส่ง brightness ให้ LED ที่เปิดอยู่ (ทีละดวง + delay) */
    for (int i = 0; i < NUM_LEDS; i++) {
        if (led_states[i]) {
            cm55_ipc_set_led_brightness(i, (uint8_t)val);
            total_tx_count++;
            vTaskDelay(pdMS_TO_TICKS(20));
        }
    }
}

/*******************************************************************************
 * Build Tab 1: Controls
 ******************************************************************************/
static void build_controls_tab(lv_obj_t *tab)
{
    lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
    lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);

    /* --- LED Control Section --- */
    lv_obj_t *led_panel = lv_obj_create(tab);
    lv_obj_set_size(led_panel, 230, 160);
    lv_obj_set_pos(led_panel, 5, 5);
    lv_obj_set_style_bg_color(led_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(led_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(led_panel, 1, 0);
    lv_obj_set_style_radius(led_panel, 6, 0);
    lv_obj_clear_flag(led_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *led_title = lv_label_create(led_panel);
    lv_label_set_text(led_title, "LED Control");
    lv_obj_set_style_text_color(led_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(led_title, 5, 2);

    const char *led_names[] = {"Red", "Green", "Blue"};
    lv_color_t led_colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };

    for (int i = 0; i < NUM_LEDS; i++) {
        int y = 22 + i * 35;

        led_indicators[i] = lv_led_create(led_panel);
        lv_obj_set_size(led_indicators[i], 20, 20);
        lv_obj_set_pos(led_indicators[i], 8, y + 2);
        lv_led_set_color(led_indicators[i], led_colors[i]);
        lv_led_off(led_indicators[i]);

        lv_obj_t *name = lv_label_create(led_panel);
        lv_label_set_text(name, led_names[i]);
        lv_obj_set_style_text_color(name, lv_color_hex(0xCCCCCC), 0);
        lv_obj_set_style_text_font(name, &lv_font_montserrat_12, 0);
        lv_obj_set_pos(name, 35, y + 4);

        led_switches[i] = lv_switch_create(led_panel);
        lv_obj_set_size(led_switches[i], 45, 22);
        lv_obj_set_pos(led_switches[i], 95, y + 2);
        lv_obj_set_style_bg_color(led_switches[i], led_colors[i],
            LV_PART_INDICATOR | LV_STATE_CHECKED);
        lv_obj_add_event_cb(led_switches[i], led_switch_cb,
            LV_EVENT_VALUE_CHANGED, &led_indices[i]);

        led_status_labels[i] = lv_label_create(led_panel);
        lv_label_set_text(led_status_labels[i], "OFF");
        lv_obj_set_style_text_color(led_status_labels[i],
            lv_palette_main(LV_PALETTE_RED), 0);
        lv_obj_set_style_text_font(led_status_labels[i],
            &lv_font_montserrat_12, 0);
        lv_obj_set_pos(led_status_labels[i], 150, y + 4);
    }

    /* Brightness */
    lv_obj_t *br_lbl = lv_label_create(led_panel);
    lv_label_set_text(br_lbl, "Bright:");
    lv_obj_set_style_text_color(br_lbl, lv_color_hex(0x888888), 0);
    lv_obj_set_style_text_font(br_lbl, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(br_lbl, 8, 130);

    brightness_slider = lv_slider_create(led_panel);
    lv_obj_set_size(brightness_slider, 100, 10);
    lv_obj_set_pos(brightness_slider, 60, 134);
    lv_slider_set_range(brightness_slider, 0, 100);
    lv_slider_set_value(brightness_slider, 100, LV_ANIM_OFF);
    lv_obj_set_style_bg_color(brightness_slider,
        lv_palette_main(LV_PALETTE_AMBER), LV_PART_INDICATOR);
    lv_obj_add_event_cb(brightness_slider, brightness_cb,
        LV_EVENT_VALUE_CHANGED, NULL);

    brightness_label = lv_label_create(led_panel);
    lv_label_set_text(brightness_label, "100%");
    lv_obj_set_style_text_color(brightness_label, lv_color_hex(0xCCCCCC), 0);
    lv_obj_set_style_text_font(brightness_label, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(brightness_label, 170, 130);

    /* --- Button Status Section --- */
    lv_obj_t *btn_panel = lv_obj_create(tab);
    lv_obj_set_size(btn_panel, 220, 160);
    lv_obj_set_pos(btn_panel, 245, 5);
    lv_obj_set_style_bg_color(btn_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(btn_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(btn_panel, 1, 0);
    lv_obj_set_style_radius(btn_panel, 6, 0);
    lv_obj_clear_flag(btn_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *btn_title = lv_label_create(btn_panel);
    lv_label_set_text(btn_title, "Button Status");
    lv_obj_set_style_text_color(btn_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(btn_title, 5, 2);

    btn_led_widget = lv_led_create(btn_panel);
    lv_obj_set_size(btn_led_widget, 50, 50);
    lv_obj_align(btn_led_widget, LV_ALIGN_CENTER, 0, -15);
    lv_led_set_color(btn_led_widget, lv_palette_main(LV_PALETTE_GREEN));
    lv_led_off(btn_led_widget);

    btn_status_label = lv_label_create(btn_panel);
    lv_label_set_text(btn_status_label, "RELEASED");
    lv_obj_set_style_text_color(btn_status_label,
        lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_text_font(btn_status_label, &lv_font_montserrat_14, 0);
    lv_obj_align(btn_status_label, LV_ALIGN_CENTER, 0, 30);

    btn_counter_label = lv_label_create(btn_panel);
    lv_label_set_text(btn_counter_label, "Count: 0");
    lv_obj_set_style_text_color(btn_counter_label, lv_color_hex(0xCCCCCC), 0);
    lv_obj_set_style_text_font(btn_counter_label, &lv_font_montserrat_12, 0);
    lv_obj_align(btn_counter_label, LV_ALIGN_BOTTOM_MID, 0, -10);
}

/*******************************************************************************
 * Build Tab 2: Sensors
 ******************************************************************************/
static void build_sensors_tab(lv_obj_t *tab)
{
    lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
    lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);

    /* --- IMU Chart --- */
    lv_obj_t *imu_panel = lv_obj_create(tab);
    lv_obj_set_size(imu_panel, 310, 160);
    lv_obj_set_pos(imu_panel, 5, 5);
    lv_obj_set_style_bg_color(imu_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(imu_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(imu_panel, 1, 0);
    lv_obj_set_style_radius(imu_panel, 6, 0);
    lv_obj_clear_flag(imu_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *imu_title = lv_label_create(imu_panel);
    lv_label_set_text(imu_title, "IMU Accelerometer (via IPC)");
    lv_obj_set_style_text_color(imu_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(imu_title, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(imu_title, 5, 2);

    imu_chart = lv_chart_create(imu_panel);
    lv_obj_set_size(imu_chart, 290, 100);
    lv_obj_set_pos(imu_chart, 5, 18);
    lv_chart_set_type(imu_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(imu_chart, 60);
    lv_chart_set_range(imu_chart, LV_CHART_AXIS_PRIMARY_Y, -2000, 2000);
    lv_obj_set_style_bg_color(imu_chart, lv_color_hex(0x0a0e27), 0);
    lv_obj_set_style_line_width(imu_chart, 0, LV_PART_TICKS);
    lv_chart_set_div_line_count(imu_chart, 3, 0);

    imu_ser_x = lv_chart_add_series(imu_chart,
        lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_y = lv_chart_add_series(imu_chart,
        lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_z = lv_chart_add_series(imu_chart,
        lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y);

    imu_value_label = lv_label_create(imu_panel);
    lv_label_set_text(imu_value_label, "X:--  Y:--  Z:--");
    lv_obj_set_style_text_color(imu_value_label, lv_color_hex(0xCCCCCC), 0);
    lv_obj_set_style_text_font(imu_value_label, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(imu_value_label, 5, 125);

    /* Legend */
    lv_obj_t *legend = lv_label_create(imu_panel);
    lv_label_set_text(legend, "R:X  G:Y  B:Z");
    lv_obj_set_style_text_color(legend, lv_color_hex(0x888888), 0);
    lv_obj_set_style_text_font(legend, &lv_font_montserrat_10, 0);
    lv_obj_set_pos(legend, 220, 125);

    /* --- ADC Gauge --- */
    lv_obj_t *adc_panel = lv_obj_create(tab);
    lv_obj_set_size(adc_panel, 140, 160);
    lv_obj_set_pos(adc_panel, 325, 5);
    lv_obj_set_style_bg_color(adc_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(adc_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(adc_panel, 1, 0);
    lv_obj_set_style_radius(adc_panel, 6, 0);
    lv_obj_clear_flag(adc_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *adc_title = lv_label_create(adc_panel);
    lv_label_set_text(adc_title, "ADC (IPC)");
    lv_obj_set_style_text_color(adc_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(adc_title, &lv_font_montserrat_12, 0);
    lv_obj_set_pos(adc_title, 5, 2);

    adc_arc = lv_arc_create(adc_panel);
    lv_obj_set_size(adc_arc, 100, 100);
    lv_obj_align(adc_arc, LV_ALIGN_CENTER, 0, 5);
    lv_arc_set_range(adc_arc, 0, 100);
    lv_arc_set_value(adc_arc, 0);
    lv_arc_set_bg_angles(adc_arc, 135, 45);
    lv_obj_remove_flag(adc_arc, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_set_style_arc_color(adc_arc,
        lv_palette_main(LV_PALETTE_TEAL), LV_PART_INDICATOR);
    lv_obj_set_style_arc_width(adc_arc, 10, LV_PART_INDICATOR);
    lv_obj_set_style_arc_width(adc_arc, 10, LV_PART_MAIN);

    adc_value_label = lv_label_create(adc_arc);
    lv_label_set_text(adc_value_label, "--\n(0)");
    lv_obj_set_style_text_color(adc_value_label, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(adc_value_label, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_align(adc_value_label, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_center(adc_value_label);
}

/*******************************************************************************
 * Build Tab 3: Status
 ******************************************************************************/
static void build_status_tab(lv_obj_t *tab)
{
    lv_obj_set_style_bg_color(tab, lv_color_hex(0x0a0e27), 0);
    lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);

    /* --- IPC Statistics Panel --- */
    lv_obj_t *stats_panel = lv_obj_create(tab);
    lv_obj_set_size(stats_panel, 200, 160);
    lv_obj_set_pos(stats_panel, 5, 5);
    lv_obj_set_style_bg_color(stats_panel, lv_color_hex(0x111633), 0);
    lv_obj_set_style_border_color(stats_panel, lv_color_hex(0x333366), 0);
    lv_obj_set_style_border_width(stats_panel, 1, 0);
    lv_obj_set_style_radius(stats_panel, 6, 0);
    lv_obj_clear_flag(stats_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *s_title = lv_label_create(stats_panel);
    lv_label_set_text(s_title, "IPC Statistics");
    lv_obj_set_style_text_color(s_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(s_title, 5, 2);

    lv_obj_t **stat_labels[] = {&stat_tx_label, &stat_rx_label,
                                &stat_err_label, &stat_rtt_label,
                                &stat_uptime_label};
    const char *stat_defaults[] = {"TX: 0", "RX: 0", "Errors: 0",
                                   "RTT: --", "Uptime: 0s"};
    lv_color_t stat_colors[] = {
        lv_palette_main(LV_PALETTE_CYAN),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_AMBER),
        lv_palette_main(LV_PALETTE_GREY)
    };

    for (int i = 0; i < 5; i++) {
        *stat_labels[i] = lv_label_create(stats_panel);
        lv_label_set_text(*stat_labels[i], stat_defaults[i]);
        lv_obj_set_style_text_color(*stat_labels[i], stat_colors[i], 0);
        lv_obj_set_style_text_font(*stat_labels[i], &lv_font_montserrat_14, 0);
        lv_obj_set_pos(*stat_labels[i], 10, 25 + i * 26);
    }

    /* --- Event Log Panel --- */
    lv_obj_t *log_panel = lv_obj_create(tab);
    lv_obj_set_size(log_panel, 255, 160);
    lv_obj_set_pos(log_panel, 215, 5);
    lv_obj_set_style_bg_color(log_panel, lv_color_hex(0x0d1117), 0);
    lv_obj_set_style_border_color(log_panel, lv_color_hex(0x30363d), 0);
    lv_obj_set_style_border_width(log_panel, 1, 0);
    lv_obj_set_style_radius(log_panel, 6, 0);
    lv_obj_clear_flag(log_panel, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_t *log_title = lv_label_create(log_panel);
    lv_label_set_text(log_title, "Event Log");
    lv_obj_set_style_text_color(log_title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_pos(log_title, 5, 2);

    event_log_label = lv_label_create(log_panel);
    lv_label_set_text(event_log_label, "(waiting...)");
    lv_obj_set_style_text_color(event_log_label,
        lv_palette_main(LV_PALETTE_LIME), 0);
    lv_obj_set_style_text_font(event_log_label, &lv_font_montserrat_10, 0);
    lv_obj_set_pos(event_log_label, 5, 20);
}

/*******************************************************************************
 * Main Function: Build Full Dashboard
 ******************************************************************************/
void part4_ex7_hw_ipc_dashboard(void)
{
    start_tick = xTaskGetTickCount();

    /* Initialize event log */
    for (int i = 0; i < EVENT_LOG_MAX; i++) {
        event_log_buf[i][0] = '\0';
    }

    /* Register IPC callback */
    cm55_ipc_register_callback(dashboard_ipc_rx_cb, NULL);

    /* Background */
    lv_obj_set_style_bg_color(lv_screen_active(),
        lv_color_hex(0x0a0e27), LV_PART_MAIN);
    lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

    /* Title bar */
    lv_obj_t *title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "IPC Dashboard");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
    lv_obj_align(title, LV_ALIGN_TOP_LEFT, 10, 2);

    /* ===== TabView ===== */
    lv_obj_t *tv = lv_tabview_create(lv_screen_active());
    lv_obj_set_size(tv, 480, 260);
    lv_obj_align(tv, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_set_style_bg_color(tv, lv_color_hex(0x0a0e27), 0);

    /* Style tab bar */
    lv_obj_t *tab_bar = lv_tabview_get_tab_bar(tv);
    lv_obj_set_style_bg_color(tab_bar, lv_color_hex(0x16213e), 0);

    /* Create tabs */
    lv_obj_t *tab1 = lv_tabview_add_tab(tv, "Controls");
    lv_obj_t *tab2 = lv_tabview_add_tab(tv, "Sensors");
    lv_obj_t *tab3 = lv_tabview_add_tab(tv, "Status");

    /* Build tab contents */
    build_controls_tab(tab1);
    build_sensors_tab(tab2);
    build_status_tab(tab3);

    /* ===== Create Timers ===== */
    /* UI update timer (50ms) */
    lv_timer_create(dashboard_timer_cb, 50, NULL);

    /* Sensor request timer (100ms) - requests IMU+ADC from CM33 */
    lv_timer_create(sensor_request_timer_cb, 100, NULL);

    /* Health check timer (5s) - sends PING */
    lv_timer_create(health_timer_cb, 5000, NULL);

    CM55_LOGI("Part4 Ex7: IPC Dashboard initialized");
    log_event("Dashboard started");
}
```

#### 4.2 CM33-NS Code - Multi-Channel Handler

```c
/*******************************************************************************
 * Part 4 - Example 7: CM33-NS Multi-Channel IPC Handler
 *
 * Handles: LED commands, sensor requests, button polling, PING/PONG
 ******************************************************************************/

#include "ipc/cm33_ipc_pipe.h"
#include "../../shared/ipc_shared.h"
#include "aic-eec/aic-eec.h"

/* Button state machine (from Lab 6) */
static btn_state_t btn_state = BTN_STATE_IDLE;
/* ... (same debounce code as Lab 6) */

/* LED state (from Lab 5) */
static bool led_states[3] = {false};
static uint8_t led_brightness[3] = {100, 100, 100};

/*******************************************************************************
 * Send Sensor Data to CM55
 ******************************************************************************/
static void send_sensor_data(ipc_cmd_t sensor_type)
{
    switch (sensor_type) {
        case IPC_CMD_IMU_DATA: {
            int16_t ax, ay, az;
            imu_shared_read_accel(&ax, &ay, &az);

            ipc_msg_t msg;
            IPC_MSG_INIT(&msg, IPC_CMD_IMU_DATA);
            ipc_imu_data_t *imu = (ipc_imu_data_t *)msg.data;
            imu->accel_x = ax;
            imu->accel_y = ay;
            imu->accel_z = az;
            imu->gyro_x  = 0;  /* อ่านเพิ่มถ้ามี */
            imu->gyro_y  = 0;
            imu->gyro_z  = 0;
            imu->timestamp = Cy_SysLib_GetTicks();
            cm33_ipc_send_retry(&msg, 5);
            break;
        }
        case IPC_CMD_ADC_DATA: {
            ipc_msg_t msg;
            IPC_MSG_INIT(&msg, IPC_CMD_ADC_DATA);
            ipc_adc_data_t *adc = (ipc_adc_data_t *)msg.data;
            adc->adc_ch0 = aic_adc_read(0);
            adc->adc_ch1 = aic_adc_read(1);
            adc->adc_ch2 = 0;
            adc->adc_ch3 = 0;
            adc->timestamp = Cy_SysLib_GetTicks();
            cm33_ipc_send_retry(&msg, 5);
            break;
        }
        default:
            break;
    }
}

/*******************************************************************************
 * CM33 IPC Callback - Route ALL commands
 ******************************************************************************/
static void cm33_dashboard_handler(const ipc_msg_t *msg, void *user_data)
{
    (void)user_data;

    switch (msg->cmd) {
        /* LED Commands */
        case IPC_CMD_LED_SET:
        case IPC_CMD_LED_BRIGHTNESS:
            cm33_handle_led_cmd(msg);  /* จาก Lab 5 */
            break;

        /* Sensor Requests */
        case IPC_CMD_SENSOR_REQ:
            send_sensor_data(msg->value);
            break;

        /* Button Request */
        case IPC_CMD_BUTTON:
            send_button_event(btn_state == BTN_STATE_PRESSED, false);
            break;

        /* Health Check */
        case IPC_CMD_PING:
            cm33_ipc_send_cmd(IPC_CMD_PONG, msg->value);
            break;

        /* Logging */
        case IPC_CMD_LOG:
        case IPC_CMD_LOG_INFO:
        case IPC_CMD_LOG_WARN:
        case IPC_CMD_LOG_ERROR:
        case IPC_CMD_LOG_DEBUG:
            cm33_ipc_handle_log(msg);
            break;

        default:
            break;
    }
}

/*******************************************************************************
 * CM33-NS Main
 ******************************************************************************/
void cm33_dashboard_app(void)
{
    aic_gpio_init();
    aic_sensors_init();  /* ต้องเรียกก่อนอ่าน sensor! */
    cm33_ipc_init();
    cm33_ipc_register_callback(cm33_dashboard_handler, NULL);

    Cy_SysEnableCM55(CY_CORTEX_M55_APPL_ADDR);

    for (;;) {
        button_poll();       /* Poll button ทุก loop */
        cm33_ipc_process();  /* Process IPC messages */
        Cy_SysLib_Delay(10); /* 10ms loop */
    }
}
```

***

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

#### 5.1 Multi-Flag IPC Handler Pattern

```c
/* รวม flags ทั้งหมดใน struct สำหรับจัดการง่าย */
typedef struct {
    volatile bool led_ack;
    volatile bool btn_event;
    volatile bool imu_data;
    volatile bool adc_data;
    volatile bool pong;
} ipc_flags_t;

static ipc_flags_t flags = {0};

/* IPC callback ตั้ง flag ตาม command */
void ipc_cb(const ipc_msg_t *msg, void *data) {
    switch (msg->cmd) {
        case IPC_CMD_ACK:          flags.led_ack = true; break;
        case IPC_CMD_BUTTON_EVENT: flags.btn_event = true; break;
        case IPC_CMD_IMU_DATA:     flags.imu_data = true; break;
        case IPC_CMD_ADC_DATA:     flags.adc_data = true; break;
        case IPC_CMD_PONG:         flags.pong = true; break;
    }
}

/* LVGL timer ตรวจทุก flag */
void timer_cb(lv_timer_t *t) {
    if (flags.led_ack)   { flags.led_ack = false;   update_led(); }
    if (flags.btn_event) { flags.btn_event = false;  update_btn(); }
    if (flags.imu_data)  { flags.imu_data = false;   update_imu(); }
    if (flags.adc_data)  { flags.adc_data = false;   update_adc(); }
    if (flags.pong)      { flags.pong = false;       update_health(); }
}
```

#### 5.2 TabView Best Practices

```c
/* ===== TabView ใน Embedded: ข้อควรระวัง ===== */

/* 1. ปิด scrollable ทุก tab */
lv_obj_clear_flag(tab, LV_OBJ_FLAG_SCROLLABLE);

/* 2. Update เฉพาะ tab ที่แสดงอยู่ (ประหยัด CPU) */
uint32_t active_tab = lv_tabview_get_tab_active(tv);
if (active_tab == 1) {  /* Sensors tab */
    lv_chart_set_next_value(chart, ser, value);
}

/* 3. Chart ไม่ refresh ถ้า tab ไม่ active
 * --> ข้อมูลยัง buffer อยู่ เมื่อสลับ tab จะเห็นข้อมูลล่าสุด */

/* 4. ใช้ font ขนาดเล็กในพื้นที่จำกัด */
lv_obj_set_style_text_font(label, &lv_font_montserrat_10, 0);
```

#### 5.3 IPC RTT Measurement

```c
/* วัด Round-Trip Time (RTT) ของ IPC */

static uint32_t ping_tick = 0;

/* ส่ง PING พร้อม timestamp */
void send_ping(void) {
    ping_tick = xTaskGetTickCount();
    cm55_ipc_send_cmd(IPC_CMD_PING, ping_tick);
}

/* CM33 ตอบ PONG พร้อม timestamp เดิม */
/* cm33: cm33_ipc_send_cmd(IPC_CMD_PONG, msg->value); */

/* CM55 รับ PONG คำนวณ RTT */
void on_pong(uint32_t pong_tick) {
    uint32_t rtt = xTaskGetTickCount() - ping_tick;
    /* rtt = round-trip latency in ticks (1 tick = 1ms typically) */
}
```

#### 5.4 Sensor Request Rate Control

```c
/* ===== อย่า request sensor เร็วเกินไป! ===== */

/* WRONG: request ทุก 10ms = 100 requests/sec = IPC flood */
lv_timer_create(sensor_req, 10, NULL);

/* RIGHT: request ทุก 100ms = 10 requests/sec */
lv_timer_create(sensor_req, 100, NULL);

/* ในแต่ละ request cycle ต้อง delay ระหว่าง IPC sends */
void sensor_req(lv_timer_t *t) {
    cm55_ipc_request_sensor(IPC_CMD_IMU_DATA);
    vTaskDelay(pdMS_TO_TICKS(20));  /* 20ms delay! */
    cm55_ipc_request_sensor(IPC_CMD_ADC_DATA);
    /* รวม: 2 messages per 100ms = 20 msg/sec (acceptable) */
}
```

***

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

#### Exercise 1: System Health Monitor (Intermediate)

เพิ่ม System Health Monitor ใน Status tab:

**Requirements:**

* IPC Latency Graph: chart แสดง RTT 20 ค่าล่าสุด
* Error Rate: % ของ messages ที่ error (error/total \* 100)
* Connection Status LED: เขียว (RTT < 50ms), เหลือง (50-200ms), แดง (>200ms หรือ no pong)
* Message Rate: แสดง TX/s และ RX/s (คำนวณจาก delta per second)
* Health Score: 0-100 คำนวณจาก RTT + error rate + uptime
* Auto-alert: ถ้า health score < 50 ให้ LED widget แดงกะพริบ

**Expected Output:**

```
+-------------------------------------------+
| Status Tab                                |
|                                           |
| Health: [=========] 92/100   [G] OK       |
|                                           |
| RTT Graph: [/\/\/\/\___/\]  avg: 12ms     |
|                                           |
| TX Rate: 22/s  |  RX Rate: 20/s           |
| Error Rate: 0.5%                          |
| Connection: GOOD (RTT < 50ms)             |
+-------------------------------------------+
```

**Hints:**

* Health Score = 100 - (RTT > 100 ? 30 : RTT \* 0.3) - (error\_pct \* 2)
* Rate calculation: count\_now - count\_1s\_ago
* ใช้ `lv_chart_create()` สำหรับ RTT graph
* PING ทุก 2s แทน 5s สำหรับ responsive health monitoring

***

#### Exercise 2: Alarm Conditions (Advanced)

เพิ่มระบบ alarm ที่ sensor threshold trigger action:

**Requirements:**

* Alarm rules (configurable):
  * IMU |accel| > 1500: "VIBRATION ALERT" --> flash Red LED
  * ADC > 80%: "HIGH VOLTAGE" --> turn on Green LED as warning
  * ADC < 10%: "LOW SIGNAL" --> blink Blue LED
* Alarm panel แสดง active alarms (max 5) พร้อม timestamp
* Alarm history (last 10 alarms)
* Alarm acknowledge button: กด ACK เพื่อ silence alarm
* Sound indicator: LED widget กะพริบเมื่อมี unacknowledged alarm
* Auto-clear: alarm หายเมื่อ condition กลับปกติ

**Expected Output:**

```
+-------------------------------------------+
|  ALARM PANEL                    [ACK ALL] |
+-------------------------------------------+
| [!] VIBRATION ALERT  12:34:56  [ACTIVE]   |
| [!] HIGH VOLTAGE     12:34:58  [ACTIVE]   |
| [v] LOW SIGNAL       12:30:01  [CLEARED]  |
+-------------------------------------------+
| Active: 2  |  Total: 15  |  ACK'd: 13     |
+-------------------------------------------+
```

**Hints:**

* Alarm state: INACTIVE -> ACTIVE -> ACKNOWLEDGED -> CLEARED
* Check thresholds ใน LVGL timer หลัง update sensor data
* LED control ผ่าน IPC ด้วย 20ms delay
* ใช้ `lv_obj_set_style_bg_opa(panel, LV_OPA_100, 0)` + animation สำหรับ flash
* Timestamp: ใช้ `xTaskGetTickCount() / 1000` เป็น seconds since boot

***

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

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

1. **Multi-Channel IPC**: จัดการ 10+ IPC command types ในระบบเดียว
2. **TabView Dashboard**: แยก UI complex ออกเป็น logical tabs
3. **Bidirectional Communication**: CM55 ส่งคำสั่ง + รับ data จาก CM33 พร้อมกัน
4. **Real-Time Sensor Display**: Chart + Gauge update จาก IPC data stream
5. **System Health Monitoring**: RTT, error rate, connection status

#### Architecture Lessons

```
+-----------------------------------------------------------+
|       KEY ARCHITECTURE PRINCIPLES LEARNED                 |
+-----------------------------------------------------------+
|                                                           |
|  1. SEPARATION OF CONCERNS                                |
|     CM55 = UI + Logic    CM33 = Hardware + Sensors        |
|                                                           |
|  2. FLAG-BASED BRIDGE                                     |
|     IPC ISR --> flags --> LVGL timer --> UI update        |
|     Never cross the boundary directly!                    |
|                                                           |
|  3. RATE LIMITING                                         |
|     Sensor request: 100ms interval                        |
|     IPC send gap: 20ms minimum                            |
|     Health check: 5s interval                             |
|                                                           |
|  4. GRACEFUL DEGRADATION                                  |
|     If IPC fails: show error, keep running                |
|     If sensor fails: show stale data, flag warning        |
|     If button fails: disable controls, show status        |
|                                                           |
+-----------------------------------------------------------+
```

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

* **Mini Project Part 4**: Multi-Core Sensor Fusion System
  * รวมทุกอย่าง + Event Bus + Advanced Logging
  * ระบบ production-grade ที่ถูก grade

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

<table><thead><tr><th width="221.8551025390625">ด้าน</th><th>ตัวอย่าง</th></tr></thead><tbody><tr><td><strong>SCADA/HMI</strong></td><td>Factory floor monitoring + control dashboard</td></tr><tr><td><strong>Building Management</strong></td><td>HVAC + lighting + security ใน TabView</td></tr><tr><td><strong>Medical Devices</strong></td><td>Patient monitoring + pump control + alarm</td></tr><tr><td><strong>Automotive</strong></td><td>Instrument cluster + vehicle diagnostics</td></tr></tbody></table>


---

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

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

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

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/multi-core-communication/ipc-dashboard.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.
