# Scale Gauge

## Lab 10: Real Scale Gauge (Pitch Angle)

### Part 2 - Sensor Visualization

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

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

* **Real Gauge + Real Sensor**: นำ Scale widget จาก Lab 4 (simulation) มาใช้กับข้อมูล Pitch Angle จริงจาก IMU
* **Pitch Angle**: วัดการเอียงหน้า/หลังของบอร์ด ซึ่งใช้งานได้จริงในหลายแอปพลิเคชัน เช่น ระบบเตือนความลาดชัน
* **Complementary Filter**: เรียนรู้อัลกอริทึมที่รวม Accelerometer + Gyroscope เพื่อผลลัพธ์ที่แม่นยำ
* **Scale Widget Mastery**: เข้าใจการใช้ lv\_scale แบบ ROUND\_OUTER กับ needle line, color sections, custom labels
* **Roll vs Pitch**: เข้าใจความแตกต่างระหว่าง Roll (Lab 9) กับ Pitch (Lab 10)

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

1. **Tilt API (Pitch)**: ใช้ `aic_tilt_get_pitch()` และ `aic_tilt_get_pitch_percent()` อ่านมุมเอียงหน้า/หลัง
2. **Scale Widget**: สร้าง round gauge ด้วย ticks, labels, sections, needle
3. **Color Sections**: กำหนดโซนสี (75-100 = แดง) สำหรับ warning zone
4. **Needle Positioning**: ใช้ `lv_scale_set_line_needle_value()` ขยับเข็มตามค่า sensor
5. **Custom Labels**: แสดง "0", "25", "50", "75", "100" แทน default labels
6. **Style Layers**: จัดการ 3 layers ของ scale: MAIN (arc), INDICATOR (major ticks), ITEMS (minor ticks)

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

1. Initialize hardware ด้วย `aic_sensors_init()` + `aic_tilt_init(NULL)`
2. สร้าง Scale widget ในโหมด `LV_SCALE_MODE_ROUND_OUTER` ขนาด 200x200
3. กำหนด 21 ticks, major ทุก 5 ticks, range 0-100
4. เพิ่ม color section สำหรับ high zone (75-100 = สีแดง)
5. สร้าง needle line ด้วย `lv_line_create()` บน scale
6. Timer callback (100ms) เรียก `aic_tilt_update_from_imu()` แล้วอัพเดท needle + labels

<figure><img src="/files/4kvlWO80Jf3Y3w5PZuvr" alt=""><figcaption></figcaption></figure>

***

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

#### 2.1 Pitch Angle Measurement

```
┌─────────────────────────────────────────────────────────────┐
│              PITCH ANGLE (Forward-Backward Tilt)            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Side View of Board:                                       │
│                                                             │
│       Pitch = 0°            Pitch = +45°                    │
│       (Level)              (Tilt Forward)                   │
│                                                             │
│       ─────────              ╱────────                      │
│       │ PSOC  │            ╱  PSOC  │                       │
│       │ EDGE  │          ╱    EDGE  │                       │
│       ─────────        ╱──────────                          │
│                                                             │
│   Formula: pitch = atan2(ax, az) × 180/π                    │
│                                                             │
│   Range:                                                    │
│     -90° (tilt backward) to +90° (tilt forward)             │
│     0° = level                                              │
│                                                             │
│   Note: Pitch range is smaller than Roll because            │
│         at ±90° the Z-axis becomes horizontal               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Scale Widget Structure

```
┌─────────────────────────────────────────────────────────────┐
│                 SCALE WIDGET STRUCTURE                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      LV_PART_MAIN                           │
│                   (Arc background)                          │
│                         ╱╲                                  │
│                        ╱  ╲                                 │
│                       ╱    ╲                                │
│               100    ╱      ╲    0                          │
│                ┃    │        │    ┃   LV_PART_INDICATOR     │
│             75 ┃    │   50   │    ┃ 25  (Major ticks)       │
│                ┃    │   ▲    │    ┃                         │
│                 ╲   │ NEEDLE │   ╱    LV_PART_ITEMS         │
│                  ╲  │        │  ╱      (Minor ticks)        │
│                   ╲ │        │ ╱                            │
│                    ╲│        │╱                             │
│                                                             │
│   Sections:                                                 │
│     Blue: 0-75 (Normal zone)                                │
│     Red: 75-100 (High alert zone)                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 UI Layout

```
┌─────────────────────────────────────────────────────────────┐
│       "Week 4 Ex10: Scale Gauge (Pitch Angle)"              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                   100    75                                 │
│                    ╲     │     ╱ 25                         │
│                     ╲    │    ╱                             │
│                      ╲   │50 ╱                              │
│                       ╲  │  ╱                               │
│                        ╲ ▲ ╱      ← Needle (orange)         │
│                         ─┴─                                 │
│                          0                                  │
│                                                             │
│                        "50"  ← Value Label                  │
│                                                             │
│                 Pitch: 0.0 deg ← Raw Angle Label            │
│                                                             │
│     "[Part II] Complementary Filter (Accel + Gyro)"         │
│     "Tilt board forward/backward to move the needle"        │
└─────────────────────────────────────────────────────────────┘
```

***

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

#### 3.1 Tilt API Functions

<table><thead><tr><th width="278.26141357421875">Function</th><th width="97.48370361328125">Return</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_sensors_init()</code></td><td>void</td><td>Initialize sensor subsystem (เรียกครั้งเดียว)</td></tr><tr><td><code>aic_tilt_init(NULL)</code></td><td>bool</td><td>Initialize Complementary Filter (defaults: alpha=0.98, dt=0.1)</td></tr><tr><td><code>aic_tilt_update_from_imu()</code></td><td>bool</td><td>อัพเดท filter จาก BMI270 (true = สำเร็จ)</td></tr><tr><td><code>aic_tilt_get_pitch()</code></td><td>float</td><td>Pitch angle in degrees (-90 to +90)</td></tr><tr><td><code>aic_tilt_get_pitch_percent()</code></td><td>uint8_t</td><td>Pitch as percentage (0-100%)</td></tr><tr><td><code>aic_tilt_get_roll()</code></td><td>float</td><td>Roll angle in degrees (-180 to +180)</td></tr><tr><td><code>aic_tilt_get_roll_percent()</code></td><td>uint8_t</td><td>Roll as percentage (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(float)</code></td><td>void</td><td>เปลี่ยน filter coefficient (0.0-1.0)</td></tr></tbody></table>

#### 3.2 Scale Widget APIs

<table><thead><tr><th width="480.34234619140625">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_scale_create(parent)</code></td><td>สร้าง Scale widget</td></tr><tr><td><code>lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_OUTER)</code></td><td>ตั้ง mode เป็น round gauge</td></tr><tr><td><code>lv_scale_set_total_tick_count(scale, 21)</code></td><td>จำนวน tick ทั้งหมด</td></tr><tr><td><code>lv_scale_set_major_tick_every(scale, 5)</code></td><td>Major tick ทุก N ticks</td></tr><tr><td><code>lv_scale_set_range(scale, 0, 100)</code></td><td>กำหนดช่วงค่า min-max</td></tr><tr><td><code>lv_scale_set_label_show(scale, true)</code></td><td>แสดง labels</td></tr><tr><td><code>lv_scale_set_text_src(scale, labels)</code></td><td>ใช้ custom labels</td></tr><tr><td><code>lv_scale_set_line_needle_value(scale, line, len, val)</code></td><td>ตั้งตำแหน่งเข็ม</td></tr><tr><td><code>lv_scale_add_section(scale)</code></td><td>เพิ่ม color section</td></tr><tr><td><code>lv_scale_section_set_range(section, min, max)</code></td><td>กำหนดช่วงของ section</td></tr><tr><td><code>lv_scale_section_set_style(section, part, style)</code></td><td>ตั้ง style ของ section</td></tr></tbody></table>

#### 3.3 Pitch Angle to Percentage Mapping

```c
/* Pitch: -90 to +90 degrees -> 0% to 100% */
/*   -90 deg = 0%   (tilted backward fully)  */
/*     0 deg = 50%  (level)                   */
/*   +90 deg = 100% (tilted forward fully)    */

/* Built-in API: */
uint8_t pct = aic_tilt_get_pitch_percent();

/* Manual calculation (for reference): */
float pitch = aic_tilt_get_pitch();  /* -90 to +90 */
uint8_t pct = (uint8_t)((pitch + 90.0f) * (100.0f / 180.0f));
```

#### 3.4 Include Files

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

/* Sensor & Tilt API headers (hardware only) */
#ifdef CYBSP_ENABLED
#include "aic-eec/aic-eec.h"
#include "aic-eec/sensors.h"
#include "aic-eec/tilt.h"
#include "../../shared/imu_shared.h"
#endif
```

***

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

#### 4.1 Global Variables

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

/* Sensor & Tilt API headers */
#ifdef CYBSP_ENABLED
#include "aic-eec/aic-eec.h"
#include "aic-eec/sensors.h"
#include "aic-eec/tilt.h"
#include "../../shared/imu_shared.h"
#endif

/* Scale gauge widgets */
static lv_obj_t * ex10_scale;
static lv_obj_t * ex10_value_label;
static lv_obj_t * ex10_raw_label;      /* Shows pitch angle in degrees */
static lv_obj_t * ex10_needle_line;
static lv_timer_t * ex10_timer;

static int32_t ex10_needle_length = 85; /* Needle length - almost reaching labels */
```

#### 4.2 Timer Callback - อ่านค่า Pitch จาก Complementary Filter

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

    float pitch;
    uint8_t pct;

#ifdef CYBSP_ENABLED
    /* ===== HARDWARE: Update tilt from BMI270 ===== */
    if(!aic_tilt_update_from_imu()) {
        return;  /* IMU not ready - skip this cycle */
    }

    /* Get pitch angle and percentage */
    pitch = aic_tilt_get_pitch();          /* -90 to +90 degrees */
    pct   = aic_tilt_get_pitch_percent();  /* 0 to 100% */
#else
    /* ===== PC SIMULATOR: Simulated pitch ===== */
    static float t = 0.0f;
    t += 0.03f;
    pitch = 45.0f * sinf(t);              /* -45 to +45 degrees */
    pct   = (uint8_t)((pitch + 90.0f) * (100.0f / 180.0f));
#endif

    /* ===== Update value label (center of gauge) ===== */
    lv_label_set_text_fmt(ex10_value_label, "%d", (int)pct);

    /* ===== Update raw pitch angle label ===== */
    lv_label_set_text_fmt(ex10_raw_label, "Pitch: %.1f deg", (double)pitch);

    /* ===== Update needle position ===== */
    lv_scale_set_line_needle_value(ex10_scale, ex10_needle_line,
                                    ex10_needle_length, pct);
}
```

#### 4.3 Main Function - สร้าง UI ทั้งหมด

```c
void part2_ex10_real_scale_gauge(void)
{
    /* ===== HARDWARE INITIALIZATION ===== */
#ifdef CYBSP_ENABLED
    aic_sensors_init();
    aic_tilt_init(NULL);  /* Initialize Complementary Filter with defaults */
#endif

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

    /* ===== Title ===== */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Part 2 Ex10: Scale Gauge (Pitch Angle)");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /*=========================================================================
     * Create Scale - Following lv_example_scale_4 pattern
     *=========================================================================*/
    ex10_scale = lv_scale_create(lv_screen_active());
    lv_obj_set_size(ex10_scale, 200, 200);
    lv_scale_set_label_show(ex10_scale, true);
    lv_scale_set_mode(ex10_scale, LV_SCALE_MODE_ROUND_OUTER);
    lv_obj_align(ex10_scale, LV_ALIGN_CENTER, 0, 0);

    /* Range: 0-100 */
    lv_scale_set_total_tick_count(ex10_scale, 21);
    lv_scale_set_major_tick_every(ex10_scale, 5);

    /* Tick length styling */
    lv_obj_set_style_length(ex10_scale, 5, LV_PART_ITEMS);      /* Minor tick */
    lv_obj_set_style_length(ex10_scale, 10, LV_PART_INDICATOR); /* Major tick */
    lv_scale_set_range(ex10_scale, 0, 100);

    /* Custom labels for gauge */
    static const char * custom_labels[] = {"0", "25", "50", "75", "100", NULL};
    lv_scale_set_text_src(ex10_scale, custom_labels);

    /*=========================================================================
     * Indicator style (Major ticks) - Blue color
     *=========================================================================*/
    static lv_style_t ex10_indicator_style;
    lv_style_init(&ex10_indicator_style);

    /* Label style properties */
    lv_style_set_text_font(&ex10_indicator_style, LV_FONT_DEFAULT);
    lv_style_set_text_color(&ex10_indicator_style,
                             lv_palette_darken(LV_PALETTE_BLUE, 3));

    /* Major tick properties */
    lv_style_set_line_color(&ex10_indicator_style,
                             lv_palette_darken(LV_PALETTE_BLUE, 3));
    lv_style_set_width(&ex10_indicator_style, 10);       /* Tick length */
    lv_style_set_line_width(&ex10_indicator_style, 2);   /* Tick width */
    lv_obj_add_style(ex10_scale, &ex10_indicator_style, LV_PART_INDICATOR);

    /*=========================================================================
     * Minor ticks style - Lighter blue
     *=========================================================================*/
    static lv_style_t ex10_minor_ticks_style;
    lv_style_init(&ex10_minor_ticks_style);
    lv_style_set_line_color(&ex10_minor_ticks_style,
                             lv_palette_lighten(LV_PALETTE_BLUE, 2));
    lv_style_set_width(&ex10_minor_ticks_style, 5);      /* Tick length */
    lv_style_set_line_width(&ex10_minor_ticks_style, 2); /* Tick width */
    lv_obj_add_style(ex10_scale, &ex10_minor_ticks_style, LV_PART_ITEMS);

    /*=========================================================================
     * Main arc/line style
     *=========================================================================*/
    static lv_style_t ex10_main_line_style;
    lv_style_init(&ex10_main_line_style);
    lv_style_set_arc_color(&ex10_main_line_style,
                            lv_palette_darken(LV_PALETTE_BLUE, 3));
    lv_style_set_arc_width(&ex10_main_line_style, 3);
    lv_obj_add_style(ex10_scale, &ex10_main_line_style, LV_PART_MAIN);

    /*=========================================================================
     * Section: High zone (75-100) - Red color
     *=========================================================================*/
    lv_scale_section_t * high_section = lv_scale_add_section(ex10_scale);
    lv_scale_section_set_range(high_section, 75, 100);

    static lv_style_t ex10_high_section_main_style;
    lv_style_init(&ex10_high_section_main_style);
    lv_style_set_arc_color(&ex10_high_section_main_style,
                            lv_palette_main(LV_PALETTE_RED));
    lv_style_set_arc_width(&ex10_high_section_main_style, 3);
    lv_scale_section_set_style(high_section, LV_PART_MAIN,
                                &ex10_high_section_main_style);

    static lv_style_t ex10_high_section_indicator_style;
    lv_style_init(&ex10_high_section_indicator_style);
    lv_style_set_line_color(&ex10_high_section_indicator_style,
                             lv_palette_darken(LV_PALETTE_RED, 3));
    lv_style_set_text_color(&ex10_high_section_indicator_style,
                             lv_palette_darken(LV_PALETTE_RED, 3));
    lv_scale_section_set_style(high_section, LV_PART_INDICATOR,
                                &ex10_high_section_indicator_style);

    static lv_style_t ex10_high_section_items_style;
    lv_style_init(&ex10_high_section_items_style);
    lv_style_set_line_color(&ex10_high_section_items_style,
                             lv_palette_main(LV_PALETTE_RED));
    lv_scale_section_set_style(high_section, LV_PART_ITEMS,
                                &ex10_high_section_items_style);

    /*=========================================================================
     * Needle Line - Shows current value position
     *=========================================================================*/
    ex10_needle_line = lv_line_create(ex10_scale);

    /* Needle style - Orange color for visibility */
    static lv_style_t ex10_needle_style;
    lv_style_init(&ex10_needle_style);
    lv_style_set_line_width(&ex10_needle_style, 2);  /* Thin needle */
    lv_style_set_line_color(&ex10_needle_style,
                             lv_palette_main(LV_PALETTE_ORANGE));
    lv_style_set_line_rounded(&ex10_needle_style, true);
    lv_obj_add_style(ex10_needle_line, &ex10_needle_style, 0);

    /* Set initial needle position */
    lv_scale_set_line_needle_value(ex10_scale, ex10_needle_line,
                                    ex10_needle_length, 50);

    /*=========================================================================
     * Value label (center of gauge)
     *=========================================================================*/
    ex10_value_label = lv_label_create(lv_screen_active());
    lv_label_set_text(ex10_value_label, "50");
    lv_obj_set_style_text_color(ex10_value_label,
                                 lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(ex10_value_label,
                                &lv_font_montserrat_24, 0);
    lv_obj_align(ex10_value_label, LV_ALIGN_CENTER, 0, 0);

    /*=========================================================================
     * Pitch angle label - shows actual degrees
     *=========================================================================*/
    ex10_raw_label = lv_label_create(lv_screen_active());
    lv_label_set_text(ex10_raw_label, "Pitch: 0.0 deg");
    lv_obj_set_style_text_color(ex10_raw_label,
                                 lv_color_hex(0x00FF00), 0);
    lv_obj_align(ex10_raw_label, LV_ALIGN_CENTER, 0, 120);

    /*=========================================================================
     * Description labels
     *=========================================================================*/
    lv_obj_t * desc = lv_label_create(lv_screen_active());
    lv_label_set_text(desc, "[Part II] Complementary Filter (Accel + Gyro)\n"
                           "Tilt board forward/backward to move the needle");
    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, -25);

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

    /*=========================================================================
     * Timer for Tilt Analysis updates (100ms = 10 Hz)
     *=========================================================================*/
    ex10_timer = lv_timer_create(ex10_timer_cb, 100, NULL);

    printf("[Part2] Ex10: Scale Gauge (Pitch Angle) "
           "with Complementary Filter\r\n");
}
```

***

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

#### 5.1 Scale Style Layers - 3 ชั้นของ Style

Scale widget มี 3 ชั้นที่ต้องกำหนด style แยกกัน:

<table><thead><tr><th width="84.21588134765625">Layer</th><th width="187.95098876953125">Part</th><th>ควบคุม</th><th>Properties</th></tr></thead><tbody><tr><td>1</td><td><code>LV_PART_MAIN</code></td><td>เส้น arc หลัก</td><td><code>arc_color</code>, <code>arc_width</code></td></tr><tr><td>2</td><td><code>LV_PART_INDICATOR</code></td><td>Major ticks + Labels</td><td><code>line_color</code>, <code>text_color</code>, <code>width</code>, <code>line_width</code></td></tr><tr><td>3</td><td><code>LV_PART_ITEMS</code></td><td>Minor ticks</td><td><code>line_color</code>, <code>width</code>, <code>line_width</code></td></tr></tbody></table>

```c
/* ตัวอย่างย่อ: ตั้ง style ทั้ง 3 layers */
static lv_style_t main_s, ind_s, items_s;

lv_style_init(&main_s);
lv_style_set_arc_color(&main_s, lv_palette_darken(LV_PALETTE_BLUE, 3));
lv_style_set_arc_width(&main_s, 3);
lv_obj_add_style(scale, &main_s, LV_PART_MAIN);

lv_style_init(&ind_s);
lv_style_set_line_color(&ind_s, lv_palette_darken(LV_PALETTE_BLUE, 3));
lv_style_set_text_color(&ind_s, lv_palette_darken(LV_PALETTE_BLUE, 3));
lv_style_set_width(&ind_s, 10);        /* Major tick length */
lv_style_set_line_width(&ind_s, 2);    /* Major tick thickness */
lv_obj_add_style(scale, &ind_s, LV_PART_INDICATOR);

lv_style_init(&items_s);
lv_style_set_line_color(&items_s, lv_palette_lighten(LV_PALETTE_BLUE, 2));
lv_style_set_width(&items_s, 5);       /* Minor tick length */
lv_obj_add_style(scale, &items_s, LV_PART_ITEMS);
```

#### 5.2 Section Coloring - เพิ่มโซนสี

```c
/* Section เปลี่ยนสีของ arc, ticks, labels ในช่วงที่กำหนด */
/* ต้องตั้ง style ให้ครบทั้ง 3 parts: */
lv_scale_section_t * section = lv_scale_add_section(scale);
lv_scale_section_set_range(section, 75, 100);

lv_scale_section_set_style(section, LV_PART_MAIN,      &red_arc_style);
lv_scale_section_set_style(section, LV_PART_INDICATOR,  &red_tick_style);
lv_scale_section_set_style(section, LV_PART_ITEMS,      &red_minor_style);
/* สามารถเพิ่มหลาย sections: Green(0-25), Yellow(25-75), Red(75-100) */
```

#### 5.3 Needle Line vs Image Needle

```c
/* Line Needle (ใช้ใน Lab นี้) - ง่าย, ไม่ต้องมี image asset */
lv_obj_t * needle = lv_line_create(scale);
lv_scale_set_line_needle_value(scale, needle, 85, 50);
/* ข้อดี: ง่าย, RAM น้อย, ปรับ style ได้ */
/* ข้อจำกัด: เส้นตรงเท่านั้น, ไม่มีหัวลูกศร */

/* Image Needle (Advanced) - สวยกว่าแต่ต้องมี image */
/* lv_scale_set_image_needle_value(scale, img, 50); */
```

#### 5.4 Pitch vs Roll - เมื่อไหร่ใช้อันไหน

<table><thead><tr><th width="151.07745361328125">รายการ</th><th>Pitch (Lab 10)</th><th>Roll (Lab 9)</th></tr></thead><tbody><tr><td><strong>ทิศทาง</strong></td><td>เอียงหน้า/หลัง</td><td>เอียงซ้าย/ขวา</td></tr><tr><td><strong>Range</strong></td><td>-90 to +90 deg</td><td>-180 to +180 deg</td></tr><tr><td><strong>สูตร</strong></td><td>atan2(-ax, sqrt(ay^2+az^2))</td><td>atan2(ay, az)</td></tr><tr><td><strong>Use Case</strong></td><td>Slope, crane arm, nose up/down</td><td>Anti-tip, balance, bank L/R</td></tr><tr><td><strong>Widget</strong></td><td>Scale (lv_scale)</td><td>Arc (lv_arc)</td></tr></tbody></table>

#### 5.5 Custom Labels vs Auto Labels

```c
/* Auto labels: LVGL สร้างตัวเลขอัตโนมัติจาก range */
/* Custom labels: กำหนดเอง (ต้องเป็น static, ปิดด้วย NULL) */
static const char * labels[] = {"0", "25", "50", "75", "100", NULL};
lv_scale_set_text_src(scale, labels);
/* จำนวน labels = จำนวน major ticks (21 ticks / 5 = 5 labels + NULL) */
```

#### 5.6 Complementary Filter Tuning

```c
/* Default: alpha=0.98, dt=0.1 (เหมาะสำหรับส่วนใหญ่) */
aic_tilt_init(NULL);

/* Custom: ปรับ alpha ตามการใช้งาน */
aic_tilt_config_t config = { .alpha = 0.98f, .dt = 0.1f };
aic_tilt_init(&config);
/* alpha สูง (0.99): smooth แต่ drift มากขึ้น */
/* alpha ต่ำ (0.90): noise มากขึ้น แต่ stable กว่า */
```

***

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

#### Exercise 1: Multi-Zone Scale Gauge

**โจทย์:** เพิ่ม color sections ให้ Scale gauge ครอบคลุมทั้ง 3 โซน:

**Requirements:**

* Low zone (0-25): สีเขียว - บอร์ดเอียงหลังมาก
* Normal zone (25-75): สีน้ำเงิน (default) - บอร์ดอยู่ในช่วงปกติ
* High zone (75-100): สีแดง - บอร์ดเอียงหน้ามาก
* เปลี่ยนสี value label ตาม zone ปัจจุบัน (เขียว/ขาว/แดง)
* เพิ่ม status label: "BACKWARD", "LEVEL", "FORWARD"

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

* Road Grade Warning, Anti-Rollover, Digital Level

**Hint:**

* สร้าง section เพิ่มสำหรับ low zone (0-25)
* ใน timer callback: ตรวจ `pct < 25`, `pct > 75` เพื่อเปลี่ยนสี label
* `lv_obj_set_style_text_color(label, color, 0)` เปลี่ยนสี label

***

#### Exercise 2: Dual Scale Dashboard (Roll + Pitch)

**โจทย์:** สร้าง dashboard แสดง 2 Scale gauges: Roll (ซ้าย) กับ Pitch (ขวา) พร้อมกัน

**Requirements:**

* Scale 2 อัน ขนาด 160x160 วางซ้าย-ขวา
* Scale ซ้าย: Roll angle (สี Cyan)
* Scale ขวา: Pitch angle (สี Orange)
* แต่ละอันมี needle line + value label
* เพิ่ม label กลาง: "Tilt Dashboard"
* Tilt magnitude label ด้านล่าง: "Tilt: XX.X deg"

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

* Flight Instrument: Artificial Horizon
* Crane Safety Monitor: ตรวจสอบความเอียง 2 แกน
* Platform Leveling: ปรับระดับแท่นรองรับอุปกรณ์

**Hint:**

* Scale ซ้าย: `lv_obj_align(scale, LV_ALIGN_CENTER, -110, -20)`
* Scale ขวา: `lv_obj_align(scale, LV_ALIGN_CENTER, 110, -20)`
* ขนาด 160x160 (เล็กกว่า 200x200 ของ Lab 10 เดิม)
* Tilt magnitude: `sqrtf(roll*roll + pitch*pitch)`
* ทั้งคู่ใช้ `aic_tilt_update_from_imu()` ครั้งเดียวใน timer

***

### 7. References

* [LVGL Scale](https://docs.lvgl.io/9.2/widgets/scale.html)
* [lv\_example\_scale\_4](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/scale/lv_example_scale_4.c)
* [Complementary Filter Tutorial](https://www.pieter-jan.com/node/11)

***


---

# Agent Instructions: Querying This Documentation

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

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

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/sensor-to-hmi-display/hardware-interfacing-workshops/scale-gauge.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
