# Arc Gauge

## Lab 2: Arc Widget (Circular Gauge)

***

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

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

* **Circular Display**: แสดงค่าแบบวงกลมที่สวยงามและอ่านง่าย เหมาะกับ gauge แบบมืออาชีพ
* **Angular Data**: เหมาะอย่างยิ่งกับข้อมูลที่มีขอบเขต เช่น Gyroscope, Compass, Temperature, Speed
* **Space Efficient**: Arc ใช้พื้นที่แบบ compact กว่า bar แต่ให้ข้อมูลเท่ากัน
* **Dashboard UI**: Circular gauge เป็นองค์ประกอบหลักของ dashboard สมัยใหม่ทั้งในรถยนต์และ Smart Home

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

1. **Arc Widget**: สร้าง circular gauge ด้วย `lv_arc_create()`
2. **Rotation & Angles**: กำหนดมุมเริ่มต้น, sweep angle (270 degree gauge)
3. **Arc Styling**: ปรับความหนา, สี background, สี indicator
4. **Center Label**: แสดงค่าตัวเลขตรงกลาง arc
5. **Timer Animation**: อัพเดทค่าแบบ smooth ด้วย timer

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

1. สร้าง Arc widget กำหนด rotation ที่ 135 degree (เริ่มจากล่างซ้าย)
2. ตั้งค่า background angles เป็น 0-270 degree (sweep 270 degree)
3. เพิ่ม label ตรงกลางแสดงค่าปัจจุบัน
4. ใช้ timer อัพเดทค่าจาก simulated sensor
5. ปรับ style ให้ดูเป็น professional gauge

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

***

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

#### 2.1 Arc Geometry (ระบบมุมของ LVGL)

```
┌─────────────────────────────────────────────────────────────┐
│                   ARC ANGLE SYSTEM (LVGL)                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│    LVGL ใช้ระบบมุม clockwise จาก 3 o'clock:                   │
│                                                             │
│                    0 (3 o'clock)                            │
│                       │                                     │
│                       │                                     │
│    270 (12 o'clock)───●───90 (6 o'clock)                    │
│                       │                                     │
│                       │                                     │
│                   180 (9 o'clock)                           │
│                                                             │
│    *** สำคัญ: มุมเพิ่มตามเข็มนาฬิกา (CW) ***                      │
│                                                             │
│    Default rotation = 0  → เริ่มที่ 3 o'clock                   │
│    rotation = 135       → เริ่มที่ bottom-left                  │
│    rotation = 90        → เริ่มที่ 6 o'clock                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Gauge Configuration (270-degree sweep)

```
┌─────────────────────────────────────────────────────────────┐
│            270-DEGREE GAUGE CONFIGURATION                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│    การตั้งค่า gauge แบบ 270 degree (เปิดด้านล่าง):                │
│                                                             │
│    lv_arc_set_rotation(arc, 135);      ← เริ่มที่ล่างซ้าย         │
│    lv_arc_set_bg_angles(arc, 0, 270);  ← sweep 270 degree   │
│                                                             │
│              MIN                  MAX                       │
│               \    ╭────────╮    /                          │
│                \  ╱          ╲  /                           │
│                 ╱   135 degC  ╲                             │
│                │    ┌─────┐    │                            │
│                │    │ 45  │    │                            │
│                │    │ degC│    │  ← Center label            │
│                │    └─────┘    │                            │
│                 ╲  ████████  ╱   ← Indicator (filled)       │
│                  ╲  ██████  ╱                               │
│                   ╰════╝╔══╯                                │
│                    GAP (90 degree at bottom)                │
│                                                             │
│    ████ = Indicator (ค่าปัจจุบัน)                               │
│    ──── = Background (ส่วนที่เหลือ)                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Arc Parts (สำหรับ styling)

```
┌─────────────────────────────────────────────────────────────┐
│                    ARC PARTS                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   LV_PART_MAIN       → พื้นหลัง (background track)             │
│   LV_PART_INDICATOR  → ส่วนที่แสดงค่า (filled arc)              │
│   LV_PART_KNOB       → จุดหมุน (ถ้ามี interactive)              │
│                                                             │
│                  ╭────────────╮                             │
│                ╱  ░░░░░░░░░░░░ ╲  ← LV_PART_MAIN (gray)     │
│               │ ░░░░░░░░░░░░░░ │                            │
│               │                │                            │
│               │ ████████       │  ← LV_PART_INDICATOR (blue)│
│                ╲ ██████████  ╱                              │
│                  ╰════●════╯      ← LV_PART_KNOB            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

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

#### 3.1 Arc Creation & Configuration

<table><thead><tr><th width="299.241455078125">Function</th><th>Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_arc_create(parent)</code></td><td>parent: lv_obj_t*</td><td>สร้าง arc widget</td></tr><tr><td><code>lv_arc_set_range(obj, min, max)</code></td><td>min, max: int32_t</td><td>กำหนดช่วงค่า</td></tr><tr><td><code>lv_arc_set_value(obj, val)</code></td><td>val: int32_t</td><td>ตั้งค่า indicator</td></tr><tr><td><code>lv_arc_get_value(obj)</code></td><td>-</td><td>อ่านค่าปัจจุบัน</td></tr></tbody></table>

#### 3.2 Angle & Rotation

<table><thead><tr><th width="340.03692626953125">Function</th><th>Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_arc_set_rotation(obj, deg)</code></td><td>deg: int32_t</td><td>หมุนจุดเริ่มต้น (องศา)</td></tr><tr><td><code>lv_arc_set_bg_angles(obj, start, end)</code></td><td>start, end: uint32_t</td><td>กำหนดมุม background track</td></tr><tr><td><code>lv_arc_set_angles(obj, start, end)</code></td><td>start, end: uint32_t</td><td>กำหนดมุม indicator</td></tr><tr><td><code>lv_arc_set_mode(obj, mode)</code></td><td>mode: lv_arc_mode_t</td><td>NORMAL / REVERSE / SYMMETRICAL</td></tr></tbody></table>

#### 3.3 Arc Styling

<table><thead><tr><th width="421.75213623046875">Function / Property</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_obj_set_size(arc, w, h)</code></td><td>ขนาด (ต้องเป็นสี่เหลี่ยมจัตุรัส w=h)</td></tr><tr><td><code>lv_obj_set_style_arc_width(obj, px, part)</code></td><td>ความหนาของ arc (pixels)</td></tr><tr><td><code>lv_obj_set_style_arc_color(obj, color, part)</code></td><td>สีของ arc</td></tr><tr><td><code>lv_obj_set_style_arc_rounded(obj, bool, part)</code></td><td>ปลาย arc โค้งมน</td></tr></tbody></table>

#### 3.4 Make Arc Non-Interactive (Gauge Mode)

```c
/* ลบ knob และ click ออก → เป็น gauge แสดงค่าอย่างเดียว */
lv_obj_remove_style(arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);
```

***

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

#### 4.1 Step 1: Global Variables

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

/* ---- Global Widget Handles ---- */
static lv_obj_t * gauge_arc;        /* Arc หลัก */
static lv_obj_t * value_label;      /* Label ค่าตรงกลาง */
static lv_obj_t * unit_label;       /* Label หน่วย */
static lv_obj_t * min_label;        /* Label ค่า min */
static lv_obj_t * max_label;        /* Label ค่า max */
static lv_timer_t * sensor_timer;   /* Timer อัพเดท sensor */
```

#### 4.2 Step 2: Simulated Sensor Data

```c
/*
 * simulate_temperature() - จำลองค่าอุณหภูมิ
 * Range: 15-45 degC, เปลี่ยนแบบ smooth
 */
static int32_t simulate_temperature(void)
{
    static uint32_t tick = 0;
    tick++;

    /* Slow sine wave: center 30, amplitude 15 */
    float angle = (float)tick * 0.02f;
    int32_t temp = 30 + (int32_t)(15.0f * sinf(angle));

    /* Add small noise +/- 1 degree */
    temp += (lv_rand(0, 2) - 1);

    /* Clamp to range */
    if (temp < 15) temp = 15;
    if (temp > 45) temp = 45;

    return temp;
}
```

#### 4.3 Step 3: Timer Callback

```c
/*
 * sensor_timer_cb() - อัพเดทค่าทุก 200ms
 */
static void sensor_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    int32_t temp = simulate_temperature();

    /* อัพเดท Arc value */
    lv_arc_set_value(gauge_arc, temp);

    /* อัพเดท Center label */
    lv_label_set_text_fmt(value_label, "%d", (int)temp);

    /* เปลี่ยนสี indicator ตามค่า */
    lv_color_t color;
    if (temp <= 20) {
        color = lv_palette_main(LV_PALETTE_BLUE);       /* เย็น */
    } else if (temp <= 35) {
        color = lv_palette_main(LV_PALETTE_GREEN);      /* ปกติ */
    } else {
        color = lv_palette_main(LV_PALETTE_RED);        /* ร้อน */
    }

    lv_obj_set_style_arc_color(gauge_arc, color, LV_PART_INDICATOR);
}
```

#### 4.4 Step 4: Main Function - สร้าง Gauge UI

```c
void part2_ex2_arc_gauge(void)
{
    /* ---- Background สีเข้ม ---- */
    lv_obj_set_style_bg_color(lv_screen_active(),
                               lv_color_hex(0x0f0f23), 0);

    /* ---- Title ---- */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Temperature Gauge");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00d4ff), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);

    /* ---- Arc Gauge ---- */
    gauge_arc = lv_arc_create(lv_screen_active());
    lv_obj_set_size(gauge_arc, 180, 180);
    lv_obj_align(gauge_arc, LV_ALIGN_CENTER, 0, 5);

    /* ตั้งค่า gauge 270 degree */
    lv_arc_set_rotation(gauge_arc, 135);
    lv_arc_set_bg_angles(gauge_arc, 0, 270);
    lv_arc_set_range(gauge_arc, 15, 45);
    lv_arc_set_value(gauge_arc, 30);

    /* ลบ knob ทำเป็น read-only gauge */
    lv_obj_remove_style(gauge_arc, NULL, LV_PART_KNOB);
    lv_obj_remove_flag(gauge_arc, LV_OBJ_FLAG_CLICKABLE);

    /* Styling: background track */
    lv_obj_set_style_arc_width(gauge_arc, 15, LV_PART_MAIN);
    lv_obj_set_style_arc_color(gauge_arc,
                                lv_color_hex(0x2d2d44), LV_PART_MAIN);

    /* Styling: indicator */
    lv_obj_set_style_arc_width(gauge_arc, 15, LV_PART_INDICATOR);
    lv_obj_set_style_arc_color(gauge_arc,
                                lv_palette_main(LV_PALETTE_GREEN),
                                LV_PART_INDICATOR);
    lv_obj_set_style_arc_rounded(gauge_arc, true, LV_PART_INDICATOR);

    /* ---- Center Value Label ---- */
    value_label = lv_label_create(gauge_arc);
    lv_label_set_text(value_label, "30");
    lv_obj_set_style_text_color(value_label, lv_color_white(), 0);
    lv_obj_set_style_text_font(value_label, &lv_font_montserrat_24, 0);
    lv_obj_center(value_label);

    /* ---- Unit Label ---- */
    unit_label = lv_label_create(gauge_arc);
    lv_label_set_text(unit_label, "degC");
    lv_obj_set_style_text_color(unit_label, lv_color_hex(0x888888), 0);
    lv_obj_align(unit_label, LV_ALIGN_CENTER, 0, 22);

    /* ---- Min/Max Labels ---- */
    min_label = lv_label_create(lv_screen_active());
    lv_label_set_text(min_label, "15");
    lv_obj_set_style_text_color(min_label, lv_color_hex(0x6666ff), 0);
    lv_obj_align_to(min_label, gauge_arc, LV_ALIGN_OUT_BOTTOM_LEFT, 20, -15);

    max_label = lv_label_create(lv_screen_active());
    lv_label_set_text(max_label, "45");
    lv_obj_set_style_text_color(max_label, lv_color_hex(0xff4444), 0);
    lv_obj_align_to(max_label, gauge_arc, LV_ALIGN_OUT_BOTTOM_RIGHT, -20, -15);

    /* ---- Status label ---- */
    lv_obj_t * status = lv_label_create(lv_screen_active());
    lv_label_set_text(status, "Live Monitoring");
    lv_obj_set_style_text_color(status, lv_color_hex(0x555555), 0);
    lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -8);

    /* ---- Timer: อัพเดททุก 200ms ---- */
    sensor_timer = lv_timer_create(sensor_timer_cb, 200, NULL);
}
```

#### 4.5 UI Layout Summary

```
┌──────────────────────────────────────┐
│         Temperature Gauge            │
│                                      │
│              ╭═══════╮               │
│            ╱ ░░░░░░░░░ ╲             │
│           │ ░░░░░░░░░░░ │            │
│           │     30      │            │
│           │    degC     │            │
│            ╲ ████████ ╱              │
│        15   ╰═══╝╔══╯  45            │
│                                      │
│          Live Monitoring             │
└──────────────────────────────────────┘
```

***

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

#### 5.1 Arc Size ต้องเป็นสี่เหลี่ยมจัตุรัส

```c
/* ถูกต้อง: Width = Height */
lv_obj_set_size(arc, 200, 200);   /* สี่เหลี่ยมจัตุรัส */

/* ผิด: Width != Height → arc จะบิดเบี้ยว */
lv_obj_set_size(arc, 200, 150);   /* ไม่แนะนำ */
```

#### 5.2 Common Rotation Patterns

```c
/* 270-degree gauge (เปิดด้านล่าง) - ยอดนิยม */
lv_arc_set_rotation(arc, 135);
lv_arc_set_bg_angles(arc, 0, 270);

/* 180-degree gauge (ครึ่งวงกลมบน) */
lv_arc_set_rotation(arc, 180);
lv_arc_set_bg_angles(arc, 0, 180);

/* 360-degree gauge (วงกลมเต็ม เริ่มจาก 12 นาฬิกา) */
lv_arc_set_rotation(arc, 270);
lv_arc_set_bg_angles(arc, 0, 360);
```

#### 5.3 Center Label Pattern

```c
/* วิธีที่ดีที่สุด: สร้าง label เป็น child ของ arc */
lv_obj_t * label = lv_label_create(arc);  /* parent = arc */
lv_obj_center(label);                      /* อยู่ตรงกลาง arc เสมอ */

/* ไม่แนะนำ: สร้างแยกแล้ว align_to */
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_align_to(label, arc, LV_ALIGN_CENTER, 0, 0);
/* ถ้าย้าย arc, label จะไม่ตามไป */
```

#### 5.4 Multiple Arcs (Nested Gauges)

```c
/* สร้าง arc ซ้อนกัน สำหรับแสดงหลายค่า */
lv_obj_t * outer = lv_arc_create(lv_screen_active());
lv_obj_set_size(outer, 200, 200);
lv_obj_set_style_arc_width(outer, 12, LV_PART_INDICATOR);

lv_obj_t * inner = lv_arc_create(lv_screen_active());
lv_obj_set_size(inner, 170, 170);   /* เล็กกว่า outer */
lv_obj_set_style_arc_width(inner, 12, LV_PART_INDICATOR);

lv_obj_align(outer, LV_ALIGN_CENTER, 0, 0);
lv_obj_align(inner, LV_ALIGN_CENTER, 0, 0);
```

#### 5.5 Color Zones ด้วย Multiple Indicator Colors

```c
/* Pattern: เปลี่ยนสี indicator ตามค่า
 * ใช้ในงาน speedometer, temperature gauge */
void update_arc_color_zone(lv_obj_t * arc, int32_t value,
                           int32_t yellow_threshold,
                           int32_t red_threshold)
{
    lv_color_t color;

    if (value < yellow_threshold) {
        color = lv_palette_main(LV_PALETTE_GREEN);
    } else if (value < red_threshold) {
        color = lv_palette_main(LV_PALETTE_YELLOW);
    } else {
        color = lv_palette_main(LV_PALETTE_RED);
    }

    lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR);
}
```

#### 5.6 Performance Tips

```c
/* 1. Arc width ไม่ควรเกิน 30px (render ช้า) */
lv_obj_set_style_arc_width(arc, 15, LV_PART_INDICATOR);  /* OK */
lv_obj_set_style_arc_width(arc, 50, LV_PART_INDICATOR);  /* ช้า */

/* 2. ลด update rate ถ้าไม่ต้องการ real-time */
lv_timer_create(cb, 200, NULL);  /* 5 FPS เพียงพอสำหรับ temp */
lv_timer_create(cb, 50, NULL);   /* 20 FPS สำหรับ speedometer */

/* 3. ใช้ LV_ANIM_OFF ถ้า update ถี่มาก */
```

***

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

#### Exercise 1: Speedometer (0-200 km/h)

สร้าง Speedometer ที่แสดงความเร็ว 0-200 km/h:

**Requirements:**

* Arc 270-degree gauge แสดงความเร็ว
* Center label แสดง "120 km/h" (ค่าปัจจุบัน)
* **3 Color zones** บน indicator:
  * 0-80 km/h: สีเขียว (Safe zone)
  * 81-140 km/h: สีเหลือง (Caution zone)
  * 141-200 km/h: สีแดง (Danger zone)
* Timer จำลองความเร็วที่เปลี่ยนแปลง (เร่ง/ลดสลับกัน)
* Min/Max labels ที่ขอบ gauge ("0" และ "200")

```
┌──────────────────────────────────────┐
│           Speedometer                │
│                                      │
│              ╭═══════╮               │
│            ╱ ████░░░░░ ╲             │
│           │ ████░░░░░░░ │            │
│           │    120      │            │
│           │    km/h     │            │
│            ╲ ████████ ╱              │
│        0    ╰═══╝╔══╯  200           │
│                                      │
└──────────────────────────────────────┘
```

**Hints:**

* `lv_arc_set_range(arc, 0, 200)`
* ใช้ `update_arc_color_zone()` pattern จาก Section 5.5
* จำลองความเร็ว: เริ่ม 0, เพิ่มทีละ 3-5, ถึง 200 แล้วลดกลับ

***

#### Exercise 2: Temperature Gauge with Animated Needle (-10 to 50 degC)

สร้าง Temperature Gauge ที่มี animation แบบ smooth:

**Requirements:**

* Arc 270-degree แสดงอุณหภูมิ -10 ถึง 50 degC
* Center label แสดงค่าอุณหภูมิ + unit
* **Line needle** ชี้ค่าปัจจุบัน (ใช้ `lv_line_create()`)
* Color zones ตามอุณหภูมิ:
  * -10 ถึง 5 degC: สีฟ้า (Cold)
  * 6 ถึง 35 degC: สีเขียว (Normal)
  * 36 ถึง 50 degC: สีแดง (Hot)
* Status label แสดง "Cold" / "Normal" / "Hot"
* Timer จำลองอุณหภูมิเปลี่ยนทุก 300ms

```
┌──────────────────────────────────────┐
│         Temperature Gauge            │
│                                      │
│              ╭═══════╮               │
│            ╱ ████████░ ╲             │
│           │ █████  ╱░░░ │            │
│           │    22 ╱     │ ← needle   │
│           │   degC      │            │
│            ╲ ████████ ╱              │
│       -10   ╰═══╝╔══╯  50            │
│                                      │
│          Status: Normal              │
└──────────────────────────────────────┘
```

**Hints:**

* Needle: ใช้ `lv_line_create()` กับ **static** point array
* คำนวณมุมเข็ม: `angle = 135 + (value - min) * 270 / (max - min)`
* ใช้ `sin()` / `cos()` คำนวณ endpoint ของ line
* อัพเดท line points ใน timer callback

***

### 7. References

* [LVGL Arc](https://docs.lvgl.io/9.2/widgets/arc.html)
* [lv\_example\_arc\_1.c](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/arc/lv_example_arc_1.c)

***


---

# 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/ux-ui-workshops/arc-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.
