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