> 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/arc-gauge.md).

# Arc Gauge

## Lab 9: Real Arc Gauge (Roll Angle)

### Part 2 - Sensor Visualization

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

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

* **Sensor Fusion**: เข้าใจวิธีการรวมข้อมูลจาก sensor 2 ตัว (Accelerometer + Gyroscope) เข้าด้วยกันเพื่อให้ได้ผลลัพธ์ที่ดีกว่าการใช้ sensor ตัวเดียว
* **Complementary Filter**: เรียนรู้ algorithm พื้นฐานที่ใช้จริงในอุตสาหกรรม (drone, robot, motion tracker)
* **Tilt API**: ใช้ module `aic_tilt` ที่ abstract ความซับซ้อนของ filter ให้เรียกใช้ง่าย
* **Arc Gauge + Real Data**: นำ Arc Gauge จาก Lab 2 มาแสดงข้อมูล Roll Angle จริงจาก IMU

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

1. **Complementary Filter**: หลักการรวม Accel (ช้าแต่ stable) กับ Gyro (เร็วแต่ drift)
2. **Tilt API**: ใช้ `aic_tilt_init()`, `aic_tilt_update_from_imu()`, `aic_tilt_get_roll()`, `aic_tilt_get_roll_percent()`
3. **Arc Gauge Integration**: แสดงมุมเอียง (Roll Angle) บน Arc widget แบบ real-time
4. **Percentage Mapping**: แปลงมุม (-90 to +90) เป็นเปอร์เซ็นต์ (0-100%) สำหรับ widget
5. **Hardware Init Pattern**: `aic_sensors_init()` + `aic_tilt_init(NULL)` ก่อนสร้าง UI

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

1. เรียก `aic_sensors_init()` เพื่อ initialize BMI270 IMU
2. เรียก `aic_tilt_init(NULL)` เพื่อ initialize Complementary Filter (ค่า default: alpha=0.98, dt=0.1s)
3. สร้าง Arc Gauge UI เหมือน Lab 2 (part2\_ex2\_arc\_gauge)
4. ใน timer callback (100ms): เรียก `aic_tilt_update_from_imu()` แล้วอ่าน `aic_tilt_get_roll()` + `aic_tilt_get_roll_percent()`
5. อัพเดท Arc value + labels ด้วยค่ามุมจริง

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2Fl4zyKaOhLOlFVX3eozp5%2Fweek4_ex2_arc_gauge.gif?alt=media&amp;token=ef169857-eafa-4ec5-87f6-f65abb0e3604" alt=""><figcaption></figcaption></figure>

***

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

#### 2.1 Why Complementary Filter?

```
┌─────────────────────────────────────────────────────────────┐
│         SENSOR CHARACTERISTICS                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ACCELEROMETER:                                            │
│   ┌─────────────────────────────────────────┐               │
│   │  + Measures gravity direction           │               │
│   │  + Stable over time (no drift)          │               │
│   │  - Noisy (vibration sensitive)          │               │
│   │  - Slow response                        │               │
│   │  = Good for LOW frequency               │               │
│   └─────────────────────────────────────────┘               │
│                                                             │
│   GYROSCOPE:                                                │
│   ┌─────────────────────────────────────────┐               │
│   │  + Fast response                        │               │
│   │  + Smooth readings                      │               │
│   │  - Drifts over time (integration error) │               │
│   │  = Good for HIGH frequency              │               │
│   └─────────────────────────────────────────┘               │
│                                                             │
│   COMPLEMENTARY FILTER = Best of both!                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Complementary Filter Formula

```
┌─────────────────────────────────────────────────────────────┐
│         COMPLEMENTARY FILTER                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   angle = α × (angle + gyro × dt) + (1-α) × accel_angle     │
│                                                             │
│   where:                                                    │
│     α = 0.98 (trust gyro 98%)                               │
│     dt = time delta (e.g., 0.01 sec at 100Hz)               │
│     gyro = angular velocity (rad/s → deg/s)                 │
│     accel_angle = atan2(ay, az) for Roll                    │
│                 = atan2(ax, az) for Pitch                   │
│                                                             │
│   ┌───────────────────────────────────────────────────────┐ │
│   │   HIGH PASS FILTER        LOW PASS FILTER             │ │
│   │   (Gyro: fast changes)    (Accel: slow/stable)        │ │
│   │                                                       │ │
│   │   0.98 × gyro_angle   +   0.02 × accel_angle          │ │
│   │        ↓                       ↓                      │ │
│   │   Quick response         Drift correction             │ │
│   └───────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Roll Angle Measurement

```
┌─────────────────────────────────────────────────────────────┐
│              ROLL ANGLE (Left-Right Tilt)                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Top View of Board:                                        │
│                                                             │
│         Roll = 0°              Roll = +45°                  │
│       (Level)                (Tilt Right)                   │
│                                                             │
│       ┌─────────┐              ┌─────────┐                  │
│       │  PSOC   │              │  PSOC  ╲│                  │
│       │  EDGE   │              │  EDGE   ╲                  │
│       │  E84    │              │  E84    │                  │
│       └─────────┘              └─────────╱                  │
│                                         ╱                   │
│                                                             │
│   Formula: roll = atan2(ay, az) × 180/π                     │
│                                                             │
│   Range:                                                    │
│     -180° (tilt far left) to +180° (tilt far right)         │
│     0° = level                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

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

#### 3.1 Tilt API Functions

<table><thead><tr><th width="261.890625">Function</th><th width="97.53125">Return</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_tilt_init(config)</code></td><td>bool</td><td>Initialize Complementary Filter (NULL = defaults)</td></tr><tr><td><code>aic_tilt_update_from_imu()</code></td><td>bool</td><td>อ่าน Accel+Gyro แล้วอัพเดท filter (เรียกทุก 100ms)</td></tr><tr><td><code>aic_tilt_get_roll()</code></td><td>float</td><td>Roll angle (degrees, -180 to +180)</td></tr><tr><td><code>aic_tilt_get_pitch()</code></td><td>float</td><td>Pitch angle (degrees, -90 to +90)</td></tr><tr><td><code>aic_tilt_get_roll_percent()</code></td><td>uint8_t</td><td>Roll mapped to 0-100%</td></tr><tr><td><code>aic_tilt_get_pitch_percent()</code></td><td>uint8_t</td><td>Pitch mapped to 0-100%</td></tr><tr><td><code>aic_tilt_reset()</code></td><td>void</td><td>Reset filter state to zero</td></tr><tr><td><code>aic_tilt_set_alpha(alpha)</code></td><td>void</td><td>ตั้งค่า filter coefficient (0.0-1.0)</td></tr><tr><td><code>aic_tilt_set_dt(dt)</code></td><td>void</td><td>ตั้งค่า sample period (seconds)</td></tr></tbody></table>

#### 3.2 Tilt Init API Signature

```c
/**
 * @brief Initialize the tilt analysis module
 *
 * @param config  Pointer to configuration (NULL for defaults):
 *                - alpha = 0.98 (98% gyro, 2% accel)
 *                - dt = 0.1s (100ms sample period)
 * @return true if successful
 */
bool aic_tilt_init(const aic_tilt_config_t *config);

/* Configuration structure */
typedef struct {
    float alpha;   /* Complementary filter coefficient (0.0-1.0) */
    float dt;      /* Sample period in seconds */
} aic_tilt_config_t;
```

#### 3.3 Tilt Update API Signature

```c
/**
 * @brief Convenience function: Update from IMU shared memory
 *
 * Reads accelerometer AND gyroscope from the sensors module,
 * then applies Complementary Filter to estimate tilt angles.
 *
 * Internally calls:
 *   aic_imu_read_accel() + aic_imu_read_gyro() + aic_tilt_update()
 *
 * @return true if update successful (both accel and gyro available)
 */
bool aic_tilt_update_from_imu(void);
```

#### 3.4 Key Differences: Lab 2 (Sim) vs Lab 9 (HW)

<table><thead><tr><th width="150.7578125">รายการ</th><th width="263.852294921875">Lab 2 (Simulator)</th><th>Lab 9 (Hardware)</th></tr></thead><tbody><tr><td>Init</td><td>ไม่ต้อง</td><td><code>aic_sensors_init()</code> + <code>aic_tilt_init(NULL)</code></td></tr><tr><td>Data Source</td><td>Counter (0-100 loop)</td><td>Complementary Filter (Accel+Gyro)</td></tr><tr><td>Update</td><td><code>value = (value + 1) % 101</code></td><td><code>aic_tilt_update_from_imu()</code></td></tr><tr><td>Value</td><td>Simulated percentage</td><td><code>aic_tilt_get_roll_percent()</code></td></tr><tr><td>Error Check</td><td>ไม่มี</td><td>ตรวจ return false จาก update</td></tr><tr><td>Data Range</td><td>0-100 fixed</td><td>-90 to +90 (mapped to 0-100)</td></tr><tr><td>Behavior</td><td>Linear sweep</td><td>Real physical tilt angle</td></tr></tbody></table>

#### 3.5 Include Files

```c
/* Tilt Analysis module */
#include "../aic-eec/aic-eec.h"       /* aic_sensors_init() */
#include "../aic-eec/sensors.h"        /* sensor type definitions */
#include "../aic-eec/tilt.h"           /* aic_tilt_init(), aic_tilt_get_roll() */
#include "../../shared/imu_shared.h"   /* imu_shared_read_accel/gyro() */
```

***

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

#### 4.1 Global Variables

```c
#include "part2_examples.h"
#include "../aic-eec/aic-eec.h"
#include "../aic-eec/sensors.h"
#include "../aic-eec/tilt.h"      /* Tilt Analysis with Complementary Filter */
#include "../../shared/imu_shared.h"
#include <stdio.h>
#include <math.h>

/* Static variables - same as Ex2 */
static lv_obj_t * ex9_arc;
static lv_obj_t * ex9_value_label;
static lv_obj_t * ex9_raw_label;
static lv_timer_t * ex9_timer;
```

#### 4.2 Timer Callback - Tilt Analysis (Complementary Filter)

```c
/* Timer callback - uses Tilt Analysis (Complementary Filter) */
static void ex9_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    /* Update tilt estimation from IMU (accel + gyro fusion) */
    if(!aic_tilt_update_from_imu()) {
        return;  /* IMU not ready */
    }

    /* Get roll angle and percentage */
    float roll = aic_tilt_get_roll();
    uint8_t pct = aic_tilt_get_roll_percent();

    lv_arc_set_value(ex9_arc, pct);
    lv_label_set_text_fmt(ex9_value_label, "%d%%", (int)pct);
    lv_label_set_text_fmt(ex9_raw_label, "Roll: %.1f deg", (double)roll);
}
```

**ภายใน `aic_tilt_update_from_imu()` จะทำ 3 ขั้นตอน:** อ่าน Accel -> อ่าน Gyro -> คำนวณ Complementary Filter โดยอัตโนมัติ ถ้า sensor ตัวใดตัวหนึ่งไม่พร้อมจะ return false

#### 4.3 Main Function

```c
void part2_ex9_real_arc_gauge(void)
{
    /* ===== HARDWARE INITIALIZATION (Part II addition) ===== */
    aic_sensors_init();
    aic_tilt_init(NULL);  /* Initialize Tilt Analysis with defaults */

    /* ===== UI CODE FROM Ex2 (part2_ex2_arc_gauge) ===== */

    /* Background */
    lv_obj_set_style_bg_color(lv_screen_active(),
                              lv_color_hex(0x0f0f23), LV_PART_MAIN);

    /* Title - Updated for Tilt */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Part 2 Ex9: Arc Gauge (Roll Angle)");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /* Create Arc - same as Ex2 */
    ex9_arc = lv_arc_create(lv_screen_active());
    lv_obj_set_size(ex9_arc, 200, 200);
    lv_arc_set_rotation(ex9_arc, 135);
    lv_arc_set_bg_angles(ex9_arc, 0, 270);
    lv_arc_set_range(ex9_arc, 0, 100);
    lv_arc_set_value(ex9_arc, 50);
    lv_obj_center(ex9_arc);

    /* Style the arc - same as Ex2 */
    lv_obj_set_style_arc_width(ex9_arc, 20, LV_PART_MAIN);
    lv_obj_set_style_arc_width(ex9_arc, 20, LV_PART_INDICATOR);
    lv_obj_set_style_arc_color(ex9_arc,
                               lv_palette_main(LV_PALETTE_CYAN),
                               LV_PART_INDICATOR);

    /* Remove knob for read-only display */
    lv_obj_remove_style(ex9_arc, NULL, LV_PART_KNOB);
    lv_obj_remove_flag(ex9_arc, LV_OBJ_FLAG_CLICKABLE);

    /* Value label in center - same as Ex2 */
    ex9_value_label = lv_label_create(lv_screen_active());
    lv_label_set_text(ex9_value_label, "50%");
    lv_obj_set_style_text_color(ex9_value_label, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(ex9_value_label, &lv_font_montserrat_24, 0);
    lv_obj_center(ex9_value_label);

    /* Roll angle label */
    ex9_raw_label = lv_label_create(lv_screen_active());
    lv_label_set_text(ex9_raw_label, "Roll: 0.0 deg");
    lv_obj_set_style_text_color(ex9_raw_label, lv_color_hex(0x00FF00), 0);
    lv_obj_align(ex9_raw_label, LV_ALIGN_CENTER, 0, 130);

    /* Auto-update timer - uses Tilt Analysis */
    ex9_timer = lv_timer_create(ex9_timer_cb, 100, NULL);

    /* Description - Updated for Tilt */
    lv_obj_t * desc = lv_label_create(lv_screen_active());
    lv_label_set_text(desc, "[Part II] Complementary Filter (Accel + Gyro)\n"
                           "Tilt board left/right to change gauge");
    lv_obj_set_style_text_color(desc, lv_color_hex(0xAAAAAA), 0);
    lv_obj_set_style_text_align(desc, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_align(desc, LV_ALIGN_BOTTOM_MID, 0, -30);

    /* Footer */
    aic_create_footer(lv_screen_active());

    printf("[Week4] Ex9: Arc Gauge (Roll Angle) with Complementary Filter\r\n");
}
```

***

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

#### 5.1 Complementary Filter - ทำไมต้องใช้?

ปัญหาของการใช้ sensor ตัวเดียว:

```
┌─────────────────────────────────────────────────────────────┐
│   ACCELEROMETER ONLY (atan2 of gravity vector)              │
│                                                             │
│   Problem 1: NOISY (สัญญาณรบกวน)                             │
│                                                             │
│   angle  ~~~~╱╲~~╱╲~~╱╲~~  ← ค่ากระเพื่อมตลอดเวลา              │
│          ───────────────── ← ค่าจริงที่ต้องการ                   │
│                                                             │
│   Problem 2: AFFECTED BY VIBRATION                          │
│   เมื่อบอร์ดสั่น accelerometer อ่านค่า linear acceleration         │
│   ผิดพลาดเพราะไม่ใช่เฉพาะ gravity                              │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│   GYROSCOPE ONLY (integration of angular velocity)          │
│                                                             │
│   Problem: DRIFT (ค่าเลื่อนไปเรื่อยๆ)                            │
│                                                             │
│   angle  ──────────╱                                        │
│          ─────────────── ← ค่าจริงที่ต้องการ                     │
│                   ▲                                         │
│                   │ drift ที่เพิ่มขึ้นเรื่อยๆ                       │
│                                                             │
│   เมื่อ integrate gyro rate: angle += gyro * dt               │
│   noise/bias เล็กๆ สะสมเป็น drift ใหญ่ขึ้นเรื่อยๆ                  │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│   COMPLEMENTARY FILTER (Best of Both Worlds!)               │
│                                                             │
│   angle  ──────────────── ← Smooth + No drift!              │
│          ──────────────── ← ค่าจริง                           │
│                                                             │
│   Gyro: ให้ fast response (98% weight)                       │
│   Accel: ให้ long-term stability, correct drift (2% weight)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 5.2 Tilt API Usage Pattern

```c
/* Pattern 1: Basic usage (recommended for Lab 9) */
aic_sensors_init();
aic_tilt_init(NULL);  /* Default: alpha=0.98, dt=0.1s */

/* In timer callback (every 100ms): */
if(aic_tilt_update_from_imu()) {
    float roll = aic_tilt_get_roll();          /* -180..+180 deg */
    uint8_t pct = aic_tilt_get_roll_percent(); /* 0..100 */
    /* Update UI */
}
```

```c
/* Pattern 2: Custom configuration */
aic_tilt_config_t config = {
    .alpha = 0.95f,  /* Less gyro weight = more stable but slower */
    .dt = 0.05f      /* 50ms update rate */
};
aic_tilt_init(&config);

/* Pattern 3: Runtime adjustment */
aic_tilt_set_alpha(0.90f);  /* ลด gyro weight */
aic_tilt_set_dt(0.05f);     /* เปลี่ยนเป็น 50ms */
aic_tilt_reset();            /* Reset filter (เมื่อวางบอร์ดใหม่) */
```

#### 5.3 Alpha Parameter - ผลกระทบต่อการตอบสนอง

```c
/* alpha สูง (0.98-0.99): Fast response, อาจ drift เล็กน้อย */
/*   เหมาะสำหรับ: Motion tracking, game controller            */
aic_tilt_set_alpha(0.98f);

/* alpha ปานกลาง (0.90-0.95): Balanced */
/*   เหมาะสำหรับ: General purpose, tilt display               */
aic_tilt_set_alpha(0.93f);

/* alpha ต่ำ (0.80-0.90): Very stable, slow response */
/*   เหมาะสำหรับ: Static measurement, structural monitoring   */
aic_tilt_set_alpha(0.85f);

/* Comparison:
 *   alpha=0.98 → Gyro 98%, Accel 2%  → เร็วแต่อาจ drift
 *   alpha=0.90 → Gyro 90%, Accel 10% → สมดุล
 *   alpha=0.80 → Gyro 80%, Accel 20% → ช้าแต่ stable มาก
 */
```

#### 5.4 Complementary Filter - สูตรคำนวณภายใน (tilt.c)

```c
/* Step 1: Accel angle = atan2(gravity components) */
accel_roll  = atan2f(ay, az) * RAD_TO_DEG;
accel_pitch = atan2f(-ax, sqrtf(ay*ay + az*az)) * RAD_TO_DEG;

/* Step 2: Gyro integration = previous angle + rate * dt */
gyro_roll  = prev_roll  + (gx * RAD_TO_DEG) * dt;
gyro_pitch = prev_pitch + (gy * RAD_TO_DEG) * dt;

/* Step 3: Complementary Filter fusion */
roll  = 0.98f * gyro_roll  + 0.02f * accel_roll;
pitch = 0.98f * gyro_pitch + 0.02f * accel_pitch;

/* Step 4: Map to percentage for UI widget */
/* -90 deg = 0%,  0 deg = 50%,  +90 deg = 100% */
uint8_t pct = (uint8_t)((clamp(roll, -90, 90) + 90.0f) / 180.0f * 100.0f);
```

#### 5.5 Calibration Tips - เมื่อค่าไม่ตรง

```c
/* Tip 1: ตรวจสอบว่า board วางระดับแล้ว roll/pitch ควรใกล้ 0 */
/* ถ้าไม่เป็น 0 แสดงว่า sensor มี offset */

/* Tip 2: Reset filter หลังวางบอร์ดระดับ */
aic_tilt_reset();
/* filter จะ reinitialize จาก accelerometer ในรอบถัดไป */

/* Tip 3: ถ้า drift เยอะ ลอง alpha ต่ำลง */
aic_tilt_set_alpha(0.93f);  /* ให้ accel correct drift มากขึ้น */

/* Tip 4: ตรวจสอบ dt ให้ตรงกับ timer period */
/* ถ้า timer = 50ms แต่ dt = 0.1s → ผลคำนวณจะผิด */
aic_tilt_set_dt(0.05f);  /* ตรงกับ 50ms timer */
```

#### 5.6 Comparison: Tilt API vs Manual Calculation

```c
/* วิธี 1: ใช้ Tilt API (แนะนำ) */
aic_tilt_update_from_imu();
float roll = aic_tilt_get_roll();  /* Easy, correct, handles init/normalize */

/* วิธี 2: Accel only (noisy, no gyro fusion) */
float roll_accel = atan2f(ay, az) * 57.2957795f;

/* วิธี 3: Manual Complementary Filter */
static float roll = 0.0f;
roll = 0.98f * (roll + gx * 57.2957795f * 0.1f) + 0.02f * atan2f(ay, az) * 57.2957795f;
/* Must handle init, normalize, clamp manually */
```

***

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

#### Exercise 1: Dual Arc Gauge (Roll + Pitch)

**โจทย์:** สร้างหน้าจอที่แสดง Arc Gauge 2 วงสำหรับ Roll และ Pitch พร้อมกัน

**Requirements:**

* Arc 2 วง: Roll (ซ้าย, สี Cyan) และ Pitch (ขวา, สี Orange)
* ขนาด 150x150 pixels ต่อวง
* แต่ละวงมี percentage label ตรงกลาง + angle label ใต้วง
* ใช้ `aic_tilt_get_roll_percent()` และ `aic_tilt_get_pitch_percent()`
* เปลี่ยนสี indicator เมื่อเอียงเกิน 45 องศา (สีแดง)
* ใช้ timer 100ms เดียวอัพเดททั้ง 2 วง

**Layout:**

```
┌─────────────────────────────────────────────┐
│     "Dual Tilt Monitor"                     │
│                                             │
│    "Roll"             "Pitch"               │
│   ╭━━━━━╮            ╭━━━━━╮                │
│  ╱ CYAN  ╲          ╱ ORANGE╲               │
│  │  45%   │          │  62%  │              │
│  ╲       ╱          ╲       ╱               │
│   ╰━━━━━╯            ╰━━━━━╯                │
│  "-9.5 deg"         "+21.6 deg"             │
│                                             │
│     "[HW] Complementary Filter"             │
└─────────────────────────────────────────────┘
```

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

* **Drone Flight Controller**: แสดงมุม Roll (เอียงซ้าย-ขวา) และ Pitch (เอียงหน้า-หลัง) ของ quadcopter เพื่อให้ pilot เห็น attitude ของ drone แบบ real-time
* **เครน/รถยก (Crane/Forklift)**: ระบบ Anti-Tipping ที่แจ้งเตือนเมื่อรถเอียงเกินมุมปลอดภัย ป้องกันการพลิกคว่ำขณะยกของหนัก
* **Marine Vessel Monitoring**: แสดง Roll/Pitch ของเรือเพื่อตรวจสอบเสถียรภาพ

**Hint:**

* วาง Arc ด้วย `LV_ALIGN_CENTER` offset X = -90 (Roll) และ +90 (Pitch)
* เปลี่ยนสี: `lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR)`
* ตรวจมุมเกิน: `if(fabsf(roll) > 45.0f) { /* change to red */ }`

***

#### Exercise 2: Tilt Alarm with History Chart

**โจทย์:** สร้างระบบตรวจจับมุมเอียงเกินกำหนดพร้อม Line Chart แสดงประวัติ:

**Requirements:**

* Arc Gauge แสดง Roll angle ปัจจุบัน (เหมือน Lab 9)
* Line Chart (350x120) ใต้ Arc แสดง history 50 จุดของ Roll angle
* ระบบเตือน 3 ระดับ:
  * Safe: |roll| < 15 deg (สีเขียว)
  * Warning: |roll| 15-30 deg (สีเหลือง)
  * Danger: |roll| > 30 deg (สีแดง + label กระพริบ)
* แสดง alarm count, max tilt angle ที่เคยวัดได้
* ปุ่ม "RESET" สำหรับ clear alarm count + tilt history

**Layout:**

```
┌─────────────────────────────────────────────┐
│     "Tilt Alarm Monitor"                    │
│                                             │
│        ╭━━━━━╮     Status: SAFE (green)     │
│       ╱       ╲    Alarms: 3                │
│       │  45%   │    Max: 28.5 deg           │
│       ╲       ╱                             │
│        ╰━━━━━╯                              │
│     "Roll: -5.2 deg"                        │
│                                             │
│   ┌─────────────────────────────────────┐   │
│   │    CHART: Roll History (50 pts)     │   │
│   │   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~      │   │
│   │   ---  15 deg threshold ---         │   │
│   └─────────────────────────────────────┘   │
│                              [RESET]        │
└─────────────────────────────────────────────┘
```

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

* **Structural Health Monitoring**: ติดตั้งบนเสา/อาคาร ตรวจจับความเอียงและแจ้งเตือนเมื่อเกินเกณฑ์ปลอดภัย เก็บข้อมูลย้อนหลังเพื่อวิเคราะห์แนวโน้ม
* **Industrial Equipment Leveling**: ตรวจสอบว่าเครื่องจักร CNC หรือเครื่องพิมพ์ 3D อยู่ระดับหรือไม่ แจ้งเตือนเมื่อพื้นทรุดตัว
* **Vehicle Roll-over Prevention**: ระบบ ESP/ESC ในรถยนต์ที่ตรวจจับมุมเอียงเพื่อป้องกันรถพลิกคว่ำ

**Hint:**

* Chart range: `-90` to `+90` (raw degrees, not percentage)
* `lv_chart_set_next_value(chart, ser, (int32_t)roll)`
* กระพริบ label: toggle visibility ใน timer `lv_obj_add_flag()/lv_obj_remove_flag(LV_OBJ_FLAG_HIDDEN)`
* Peak tracking: `if(fabsf(roll) > max_tilt) max_tilt = fabsf(roll);`

***

### 7. References

* [Complementary Filter Explained](https://www.pieter-jan.com/node/11)
* [LVGL Arc](https://docs.lvgl.io/9.2/widgets/arc.html)
* [BMI270 IMU Datasheet](https://www.bosch-sensortec.com/products/motion-sensors/imus/bmi270/)

***
