> 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/hmi-development/sensor-to-hmi-display/hardware-interfacing-workshops/sensor-dashboard.md).

# Sensor Dashboard

## Lab 8: Real Sensor Dashboard

### Part 2 - Sensor Visualization

### 1. โครงสร้างภาพรวมของ Lab

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

* **Multi-Sensor Integration**: ในระบบ IoT จริง มี sensor หลายตัวที่ต้องอ่านค่าและแสดงผลพร้อมกัน
* **Real APIs**: ใช้ `aic_adc_read()` + `imu_shared_read_accel()` ร่วมกัน
* **TabView Organization**: จัดการข้อมูลจากหลาย sensor ด้วย tabs
* **Single Timer Pattern**: อัพเดทข้อมูลจากหลาย sensor ใน callback เดียว

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

1. **Multiple Sensor APIs**: อ่านค่าจาก ADC (potentiometer) และ IMU (accelerometer) พร้อมกัน
2. **TabView + Real Data**: รวม Lab 5 (TabView) กับ Lab 7 (Real IMU) เข้าด้วยกัน
3. **Data Mapping**: แปลงค่า sensor แต่ละตัวให้เหมาะกับ widget ที่แสดงผล
4. **Hardware Polling**: อ่านค่าจากหลาย sensor ใน timer เดียว
5. **Graceful Degradation**: จัดการเมื่อ sensor บางตัวไม่พร้อม

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

1. เรียก `aic_sensors_init()` ก่อนสร้าง UI
2. สร้าง TabView 2 tabs: IMU Charts + ADC Gauge
3. Timer callback อ่านค่าจากทุก sensor API
4. Tab 1: แสดง ADC ด้วย Bar + Value label
5. Tab 2: แสดง accelerometer X/Y/Z ด้วย Line Chart
6. Tab 3: แสดง Gyro ด้วย Arc gauge + Value label

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FsEHcr6F4W6uQD80PRwy4%2Fweek4_ex5_sensor_dashboard.gif?alt=media&amp;token=93e69090-7814-4bf0-ba13-775997dcea50" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงานและ Flowchart

#### 2.1 Dashboard Architecture

```
┌─────────────────────────────────────────────────────────────┐
│             SENSOR DASHBOARD ARCHITECTURE                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────────────────────────────────────────────────┐  │
│   │                  SENSOR LAYER                        │  │
│   │   ┌─────────┐  ┌─────────────┐  ┌─────────────┐      │  │
│   │   │   ADC   │  │Accelerometer│  │  Gyroscope  │      │  │
│   │   │ (POTEN) │  │  (BMI270)   │  │  (BMI270)   │      │  │
│   │   └────┬────┘  └──────┬──────┘  └──────┬──────┘      │  │
│   └────────┼──────────────┼─────────────────┼────────────┘  │
│            │              │                 │               │
│            ▼              ▼                 ▼               │
│   ┌──────────────────────────────────────────────────────┐  │
│   │                  API LAYER                           │  │
│   │aic_adc_read() aic_imu_read_accel() aic_imu_read_gyro()  │
│   └────────┬──────────────┬─────────────────┬───────────┘   │
│            │              │                 │               │
│            ▼              ▼                 ▼               │
│   ┌──────────────────────────────────────────────────────┐  │
│   │             TIMER CALLBACK (100ms)                   │  │
│   │   - Read all sensors                                 │  │
│   │   - Scale data for widgets                           │  │
│   │   - Update UI elements                               │  │
│   └──────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 TabView Layout

```
┌─────────────────────────────────────────────────────────────┐
│  [  ADC  ] [  Accel  ] [  Gyro  ]    ← Tab Bar (80px)       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ╔═══════════════════════════════════════════════════════╗ │
│   ║                    Tab Content                        ║ │
│   ║                    480 x 240                          ║ │
│   ║                                                       ║ │
│   ║   ADC Tab:                                            ║ │
│   ║     [=========BAR (450x60)==========]                 ║ │
│   ║            "ADC: 2048 (1.65V)"                        ║ │
│   ║                                                       ║ │
│   ║   Accel Tab:                                          ║ │
│   ║     [CHART 400x210]  X: +0.5                          ║ │
│   ║                      Y: -0.3                          ║ │
│   ║                      Z: +9.8                          ║ │
│   ║                                                       ║ │
│   ║   Gyro Tab:                                           ║ │
│   ║     (Arc)    (Arc)    (Arc)                           ║ │
│   ║     Roll     Pitch    Yaw                             ║ │
│   ║     0.00     0.00     0.00                            ║ │
│   ╚═══════════════════════════════════════════════════════╝ │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Data Mapping

```
┌─────────────────────────────────────────────────────────────┐
│                 DATA MAPPING                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ADC (0-4095):                                             │
│     pct = (adc * 100) / 4095     → Bar (0-100%)             │
│     volt = (adc / 4095) * 3.3V   → Label                    │
│                                                             │
│   Accelerometer (float m/s²):                               │
│     scaled = (int32_t)(ax * 100) → Chart (-1500 to 1500)    │
│                                                             │
│   Gyroscope (float rad/s, ±1.0):                            │
│     pct = (int32_t)((gx + 1.0) * 50) → Arc (0-100%)         │
│         -1.0 rad/s → 0%                                     │
│          0.0 rad/s → 50%                                    │
│         +1.0 rad/s → 100%                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

### 3. ฟังก์ชันสำคัญ

#### 3.1 Sensor APIs ที่ใช้ใน Lab นี้

<table><thead><tr><th width="343.31463623046875">Function</th><th width="100.86358642578125">Return</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_sensors_init()</code></td><td>void</td><td>Initialize ทุก sensor (เรียกครั้งเดียว)</td></tr><tr><td><code>aic_adc_read(AIC_ADC_CH0)</code></td><td>uint16_t</td><td>อ่าน ADC channel 0 (0-4095)</td></tr><tr><td><code>imu_shared_read_accel(&#x26;ax, &#x26;ay, &#x26;az)</code></td><td>bool</td><td>อ่าน accelerometer (m/s2)</td></tr></tbody></table>

#### 3.2 ADC API Detail

```c
/* ADC อ่านค่า potentiometer ที่ต่ออยู่กับ channel 0 */
/* Return: 0-4095 (12-bit resolution) */
/* 0    = 0V (GND) */
/* 4095 = 3.3V (VCC) */
uint16_t adc_value = aic_adc_read(AIC_ADC_CH0);
```

#### 3.3 Widget Functions ที่ใช้

<table><thead><tr><th width="87.14990234375">Tab</th><th width="156.38848876953125">Widget</th><th>Key Functions</th></tr></thead><tbody><tr><td>IMU</td><td>lv_chart (LINE)</td><td><code>lv_chart_set_next_value()</code></td></tr><tr><td>ADC</td><td>lv_bar</td><td><code>lv_bar_set_value()</code>, <code>lv_bar_set_range()</code></td></tr><tr><td>ADC</td><td>lv_arc</td><td><code>lv_arc_set_value()</code>, <code>lv_arc_set_range()</code></td></tr><tr><td>Both</td><td>lv_label</td><td><code>lv_label_set_text_fmt()</code></td></tr></tbody></table>

#### 3.4 Include Files

```c
#include "lvgl.h"
#include <math.h>
#include <stdio.h>

#ifdef CYBSP_ENABLED
#include "aic-eec/aic-eec.h"
#include "aic-eec/sensors.h"
#include "../../shared/imu_shared.h"
#endif
```

***

### 4. โค้ดตัวอย่าง

#### 4.1 Global Variables

```c
#define ADC_MAX         4095
#define CHART_POINTS    50

/* IMU Tab widgets */
static lv_obj_t * ex8_accel_chart;
static lv_chart_series_t * ex8_accel_ser[3];
static lv_obj_t * ex8_accel_labels[3];

/* ADC Tab widgets */
static lv_obj_t * ex8_adc_arc;
static lv_obj_t * ex8_adc_bar;
static lv_obj_t * ex8_adc_label;
static lv_obj_t * ex8_adc_pct_label;

/* Timer */
static lv_timer_t * ex8_timer;
```

#### 4.2 Simulation Helpers (PC Simulator)

```c
#ifndef CYBSP_ENABLED
static uint16_t simulate_adc_read(void)
{
    static float phase = 0.0f;
    phase += 0.02f;
    return (uint16_t)(2048 + 1800 * sinf(phase));
}

static bool simulate_imu_accel(float *ax, float *ay, float *az)
{
    static float t = 0.0f;
    t += 0.05f;
    *ax = 0.5f * sinf(t);
    *ay = 0.3f * cosf(t * 0.7f);
    *az = 9.81f + 0.2f * sinf(t * 2.0f);
    return true;
}
#endif
```

#### 4.3 Timer Callback - อ่านค่าจากทุก Sensor

```c
static void ex8_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    /* ===== ADC (Potentiometer) ===== */
    uint16_t adc;
#ifdef CYBSP_ENABLED
    adc = aic_adc_read(AIC_ADC_CH0);
#else
    adc = simulate_adc_read();
#endif

    int32_t pct = (adc * 100) / ADC_MAX;
    float volt = ((float)adc / ADC_MAX) * 3.3f;

    lv_arc_set_value(ex8_adc_arc, pct);
    lv_bar_set_value(ex8_adc_bar, pct, LV_ANIM_ON);
    lv_label_set_text_fmt(ex8_adc_label,
        "ADC: %d (%.2fV)", adc, (double)volt);
    lv_label_set_text_fmt(ex8_adc_pct_label, "%d%%", (int)pct);

    /* ===== ACCELEROMETER (BMI270) ===== */
    float ax, ay, az;
    bool imu_ok;
#ifdef CYBSP_ENABLED
    imu_ok = imu_shared_read_accel(&ax, &ay, &az);
#else
    imu_ok = simulate_imu_accel(&ax, &ay, &az);
#endif

    if(imu_ok) {
        /* Scale & update chart */
        lv_chart_set_next_value(ex8_accel_chart, ex8_accel_ser[0],
                                (int32_t)(ax * 100));
        lv_chart_set_next_value(ex8_accel_chart, ex8_accel_ser[1],
                                (int32_t)(ay * 100));
        lv_chart_set_next_value(ex8_accel_chart, ex8_accel_ser[2],
                                (int32_t)(az * 100));

        /* Update labels */
        lv_label_set_text_fmt(ex8_accel_labels[0], "X:%+.1f", (double)ax);
        lv_label_set_text_fmt(ex8_accel_labels[1], "Y:%+.1f", (double)ay);
        lv_label_set_text_fmt(ex8_accel_labels[2], "Z:%+.1f", (double)az);
    }

    /* ===== Debug Print (throttled) ===== */
    static uint32_t count = 0;
    count++;
    if(count % 10 == 0) {
        printf("[DASH] ADC:%d (%.2fV) IMU:%s X:%+.2f Y:%+.2f Z:%+.2f\r\n",
               adc, (double)volt,
               imu_ok ? "OK" : "N/A",
               (double)ax, (double)ay, (double)az);
    }
}
```

#### 4.4 สร้าง IMU Tab

```c
static void ex8_create_imu_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "Accelerometer - REAL BMI270");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Chart */
    ex8_accel_chart = lv_chart_create(parent);
    lv_obj_set_size(ex8_accel_chart, 380, 180);
    lv_obj_align(ex8_accel_chart, LV_ALIGN_CENTER, -30, 10);
    lv_chart_set_type(ex8_accel_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(ex8_accel_chart, CHART_POINTS);
    lv_chart_set_range(ex8_accel_chart,
        LV_CHART_AXIS_PRIMARY_Y, -1500, 1500);

    /* ซ่อน point indicator */
    lv_obj_set_style_size(ex8_accel_chart, 0, 0, LV_PART_INDICATOR);
    lv_obj_set_style_line_width(ex8_accel_chart, 2, LV_PART_ITEMS);
    lv_chart_set_div_line_count(ex8_accel_chart, 5, 8);

    /* 3 Series: X(Red), Y(Green), Z(Blue) */
    lv_color_t colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };

    for(int i = 0; i < 3; i++) {
        ex8_accel_ser[i] = lv_chart_add_series(ex8_accel_chart,
            colors[i], LV_CHART_AXIS_PRIMARY_Y);

        ex8_accel_labels[i] = lv_label_create(parent);
        lv_label_set_text(ex8_accel_labels[i], "0.0");
        lv_obj_set_style_text_color(ex8_accel_labels[i], colors[i], 0);
        lv_obj_set_style_text_font(ex8_accel_labels[i],
            &lv_font_montserrat_16, 0);
        lv_obj_align(ex8_accel_labels[i], LV_ALIGN_RIGHT_MID,
            -5, -40 + i * 40);
    }
}
```

#### 4.5 สร้าง ADC Tab (Arc + Bar)

```c
static void ex8_create_adc_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "ADC Monitor - REAL Potentiometer");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Arc Gauge */
    ex8_adc_arc = lv_arc_create(parent);
    lv_obj_set_size(ex8_adc_arc, 140, 140);
    lv_obj_align(ex8_adc_arc, LV_ALIGN_CENTER, 0, -20);
    lv_arc_set_range(ex8_adc_arc, 0, 100);
    lv_arc_set_value(ex8_adc_arc, 0);
    lv_arc_set_bg_angles(ex8_adc_arc, 135, 45);
    lv_obj_set_style_arc_color(ex8_adc_arc,
        lv_palette_main(LV_PALETTE_BLUE), LV_PART_INDICATOR);
    lv_obj_set_style_arc_width(ex8_adc_arc, 15, LV_PART_MAIN);
    lv_obj_set_style_arc_width(ex8_adc_arc, 15, LV_PART_INDICATOR);
    lv_obj_remove_style(ex8_adc_arc, NULL, LV_PART_KNOB);

    /* Percentage label ตรงกลาง arc */
    ex8_adc_pct_label = lv_label_create(parent);
    lv_label_set_text(ex8_adc_pct_label, "0%");
    lv_obj_set_style_text_font(ex8_adc_pct_label,
        &lv_font_montserrat_24, 0);
    lv_obj_center(ex8_adc_pct_label);
    lv_obj_align(ex8_adc_pct_label, LV_ALIGN_CENTER, 0, -20);

    /* Bar (ใต้ arc) */
    ex8_adc_bar = lv_bar_create(parent);
    lv_obj_set_size(ex8_adc_bar, 350, 30);
    lv_obj_align(ex8_adc_bar, LV_ALIGN_CENTER, 0, 60);
    lv_bar_set_range(ex8_adc_bar, 0, 100);
    lv_obj_set_style_bg_color(ex8_adc_bar,
        lv_palette_main(LV_PALETTE_BLUE), LV_PART_INDICATOR);

    /* Value label */
    ex8_adc_label = lv_label_create(parent);
    lv_label_set_text(ex8_adc_label, "ADC: 0 (0.00V)");
    lv_obj_set_style_text_font(ex8_adc_label,
        &lv_font_montserrat_18, 0);
    lv_obj_align(ex8_adc_label, LV_ALIGN_CENTER, 0, 95);
}
```

#### 4.6 Main Function

```c
void part2_ex8_real_sensor_dashboard(void)
{
    /* ===== HARDWARE INITIALIZATION ===== */
#ifdef CYBSP_ENABLED
    aic_sensors_init();
#endif

    /* ===== CREATE TABVIEW ===== */
    lv_obj_t * tabview = lv_tabview_create(lv_screen_active());
    lv_tabview_set_tab_bar_position(tabview, LV_DIR_TOP);
    lv_tabview_set_tab_bar_size(tabview, 80);

    /* Style Tab Bar */
    lv_obj_t * tab_btns = lv_tabview_get_tab_bar(tabview);
    lv_obj_set_style_bg_color(tab_btns,
        lv_palette_darken(LV_PALETTE_GREY, 3), 0);
    lv_obj_set_style_text_color(tab_btns,
        lv_palette_lighten(LV_PALETTE_GREY, 5), 0);

    /* ===== CREATE TABS ===== */
    lv_obj_t * tab_imu = lv_tabview_add_tab(tabview, "IMU");
    lv_obj_t * tab_adc = lv_tabview_add_tab(tabview, "ADC");

    /* Style Tab Content */
    lv_obj_set_style_bg_color(tab_imu, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_bg_opa(tab_imu, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(tab_adc, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_bg_opa(tab_adc, LV_OPA_COVER, 0);

    /* ===== POPULATE TABS ===== */
    ex8_create_imu_tab(tab_imu);
    ex8_create_adc_tab(tab_adc);

    /* ===== CRITICAL: ปิด scrolling ===== */
    lv_obj_remove_flag(lv_tabview_get_content(tabview),
                       LV_OBJ_FLAG_SCROLLABLE);

    /* ===== TIMER: อัพเดทข้อมูลทุก 100ms ===== */
    ex8_timer = lv_timer_create(ex8_timer_cb, 100, NULL);
}
```

***

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

#### 5.1 Single Timer สำหรับ Multiple Sensors

```c
/* ข้อดีของการใช้ timer เดียวอ่านทุก sensor: */

/* 1. Synchronized data - ทุก sensor อ่านค่าพร้อมกัน */
/* 2. Consistent UI update - ทุก widget อัพเดทพร้อมกัน */
/* 3. Less memory - timer object เดียว */
/* 4. Easier to manage - ลบ timer เดียว cleanup หมด */

static void single_timer_cb(lv_timer_t * timer)
{
    /* อ่านทุก sensor ใน callback เดียว */
    read_and_update_adc();
    read_and_update_imu();
    read_and_update_gyro();
}
```

#### 5.2 Graceful Degradation

```c
/* เมื่อ sensor บางตัวไม่พร้อม ให้ UI ยังทำงานต่อได้ */

static void robust_timer_cb(lv_timer_t * timer)
{
    /* ADC มักพร้อมเสมอ */
    uint16_t adc = aic_adc_read(AIC_ADC_CH0);
    update_adc_widgets(adc);  /* อัพเดทได้เลย */

    /* IMU อาจไม่พร้อมตอนเริ่มต้น */
    float ax, ay, az;
    if(imu_shared_read_accel(&ax, &ay, &az)) {
        update_imu_widgets(ax, ay, az);  /* อัพเดทเมื่อพร้อม */
    }
    /* ถ้า IMU ไม่พร้อม → Chart + Labels ยังแสดงค่าเดิม */
}
```

#### 5.3 ADC Arc Color Change ตามค่า

```c
/* เปลี่ยนสี arc ตาม % value - visual feedback */
static void update_adc_color(int32_t pct)
{
    lv_color_t color;
    if(pct < 30) {
        color = lv_palette_main(LV_PALETTE_GREEN);
    } else if(pct < 70) {
        color = lv_palette_main(LV_PALETTE_YELLOW);
    } else {
        color = lv_palette_main(LV_PALETTE_RED);
    }
    lv_obj_set_style_arc_color(ex8_adc_arc, color, LV_PART_INDICATOR);
    lv_obj_set_style_bg_color(ex8_adc_bar, color, LV_PART_INDICATOR);
}
```

#### 5.4 Active Tab Optimization

```c
/* Performance: อัพเดทเฉพาะ tab ที่มองเห็น */
static lv_obj_t * tabview_ref;

static void optimized_timer_cb(lv_timer_t * timer)
{
    uint32_t active = lv_tabview_get_tab_active(tabview_ref);

    switch(active) {
        case 0:  /* IMU Tab */
            /* อ่าน IMU อย่างเดียว */
            read_and_update_imu();
            break;
        case 1:  /* ADC Tab */
            /* อ่าน ADC อย่างเดียว */
            read_and_update_adc();
            break;
    }

    /* ข้อเสีย: ข้อมูล tab ที่ซ่อนอยู่จะหยุดอัพเดท */
    /* ข้อดี: ลด CPU load 50% (อ่าน sensor 1 ตัวแทน 2 ตัว) */
}
```

#### 5.5 Tab Content Styling Tips

```c
/* ตั้งสีพื้นหลัง tab content เป็นสีขาว */
lv_obj_set_style_bg_color(tab, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_bg_opa(tab, LV_OPA_COVER, 0);

/* ปิด scrolling - สำคัญมาก! */
lv_obj_remove_flag(lv_tabview_get_content(tabview),
                   LV_OBJ_FLAG_SCROLLABLE);

/* ถ้าต้องการปิด scroll เฉพาะ tab: */
lv_obj_remove_flag(tab_imu, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_remove_flag(tab_adc, LV_OBJ_FLAG_SCROLLABLE);
```

***

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

#### Exercise 1: Min/Max Tracking

**โจทย์:** เพิ่มระบบ Min/Max Tracking สำหรับแต่ละ sensor:

**Requirements:**

* สำหรับ ADC: แสดง Min voltage, Max voltage, Current voltage
* สำหรับ IMU: แสดง Min/Max ของแต่ละแกน (X, Y, Z)
* เพิ่มปุ่ม "Reset" เพื่อ clear ค่า min/max
* แสดงเป็น label ใต้ widget หลัก

**Layout:**

```bash
ADC Tab:
  [Arc Gauge]
  [Bar]
  Current: 1.65V  |  Min: 0.12V  |  Max: 3.28V
  [RESET]

IMU Tab:
  [Chart]
  X: +0.50 | Min:-0.82 | Max:+0.91
  Y: -0.30 | Min:-0.65 | Max:+0.48
  Z: +9.81 | Min:+9.52 | Max:+10.12
```

**การประยุกต์ใช้งานจริง:**

* Quality Control: ตรวจสอบว่าค่า sensor อยู่ในช่วงที่กำหนด
* Predictive Maintenance: ติดตาม peak values เพื่อวิเคราะห์สภาพเครื่องจักร
* Environmental Monitoring: บันทึกค่าสูงสุด/ต่ำสุดของอุณหภูมิ

**Hint:**

* ใช้ `static float min_x = FLT_MAX, max_x = -FLT_MAX;`
* ปุ่ม Reset: `lv_btn_create()` + event callback

***

#### Exercise 2: Data Logging Table

**โจทย์:** เพิ่ม Tab ที่ 3 "Log" แสดงข้อมูล 10 readings ล่าสุดในรูปแบบตาราง:

**Requirements:**

* ใช้ `lv_table_create()` สร้างตาราง
* 4 คอลัมน์: Time, ADC, Accel X, Accel Z
* 10 แถว (circular buffer - เมื่อครบ 10 แถวให้เริ่มเขียนทับแถวเก่า)
* บันทึกค่าทุก 1 วินาที (ไม่ใช่ทุก 100ms)
* แถวล่าสุดแสดงด้วยสีพื้นหลังเด่น

**Layout:**

```bash
Log Tab:
  | Time  | ADC  | X(m/s2) | Z(m/s2) |
  |-------|------|---------|---------|
  | 00:01 | 2048 |  +0.50  |  +9.81  |
  | 00:02 | 1825 |  +0.32  |  +9.79  |
  | ...   | ...  |   ...   |   ...   |
  | 00:10 | 3012 |  -0.15  |  +9.85  |
```

**การประยุกต์ใช้งานจริง:**

* Data Logger ในอุตสาหกรรมอาหาร (บันทึกอุณหภูมิห้องเย็น)
* Flight Data Recorder (บันทึกข้อมูลระหว่างบิน)
* Process Control: บันทึกค่า parameter ย้อนหลัง

**Hint:**

* `lv_table_set_cell_value(table, row, col, "text")`
* Circular buffer: `write_idx = (write_idx + 1) % 10`
* Time format: \`lv\_label\_set\_text\_fmt(label, "%02d:%02d", min, sec)

***

### 7. References

* [LVGL TabView](https://docs.lvgl.io/9.2/widgets/tabview.html)
* [LVGL Arc](https://docs.lvgl.io/9.2/widgets/arc.html)
* [lv\_example\_tabview\_2](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/tabview/lv_example_tabview_2.c)

***
