# ADC Display

## Lab 8: Hardware ADC Display

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

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

ADC (Analog-to-Digital Converter) เป็นสะพานเชื่อมระหว่างโลก Analog กับ Digital:

* **Sensor Reading**: พื้นฐานสำหรับอ่านค่า Sensor ทุกชนิด (อุณหภูมิ, แรงดัน, แสง)
* **Real-time Visualization**: แสดงค่าที่เปลี่ยนแปลงอย่างต่อเนื่องบน LVGL
* **Data Conversion**: แปลง Raw ADC -> Percentage -> Voltage
* **Industrial Monitoring**: พื้นฐาน SCADA, Process Monitoring, Quality Control

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

1. **Sensor Init**: ต้องเรียก `aic_sensors_init()` ก่อนอ่านค่า ADC
2. **ADC Read**: `aic_adc_read()` คืนค่า 0-4095 (12-bit)
3. **Value Mapping**: แปลง Raw -> Percentage -> Voltage
4. **Read-only Widgets**: ใช้ Slider/Bar แสดงค่าโดยไม่ให้ user ปรับ
5. **Timer-based Polling**: อัพเดทค่า ADC แบบ Real-time ทุก 100ms

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

1. Initialize sensors ด้วย `aic_sensors_init()`
2. สร้าง LVGL widgets: Bar, Label สำหรับแสดงค่า
3. สร้าง LVGL Timer poll ทุก 100ms
4. ใน timer callback อ่าน ADC แล้วอัพเดท UI

<figure><img src="/files/WiCk0ZJl2SXIEupauzcE" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงาน (Technical Principles)

#### 2.1 ADC Architecture on PSoC Edge E84

```
+-------------------------------------------------------------+
|              PSoC Edge E84 ADC Architecture                  |
+-------------------------------------------------------------+
|                                                              |
|   Potentiometer (VR1)                                        |
|   +---[===]---+                                              |
|   |           |                                              |
|  3.3V        GND                                             |
|       |                                                      |
|       +---> ADC Input Pin                                    |
|             |                                                |
|             v                                                |
|   +-------------------+                                      |
|   |  12-bit SAR ADC   |                                      |
|   |                   |                                      |
|   |  0V    -> 0       |                                      |
|   |  1.65V -> 2048    |                                      |
|   |  3.3V  -> 4095    |                                      |
|   +-------------------+                                      |
|             |                                                |
|             v                                                |
|   CM55 reads via aic_adc_read()                              |
|                                                              |
+-------------------------------------------------------------+
```

#### 2.2 ADC Value Conversion

```
+-------------------------------------------------------------+
|                    ADC VALUE CONVERSION                      |
+-------------------------------------------------------------+
|                                                              |
|   12-bit ADC Resolution:                                     |
|   +-- Raw Range: 0 to 4095 (2^12 - 1)                        |
|   +-- Voltage Ref: 3.3V                                      |
|                                                              |
|   Conversion Formulas:                                       |
|   =====================                                      |
|                                                              |
|   percent = (raw * 100) / 4095                               |
|   voltage = (raw * 3.3f) / 4095.0f                           |
|                                                              |
|   Examples:                                                  |
|   +--------------------+---------+---------+                 |
|   | Pot Position       | Raw     | Voltage |                 |
|   +--------------------+---------+---------+                 |
|   | Full CCW (min)     | 0       | 0.00V   |                 |
|   | 25%                | 1024    | 0.82V   |                 |
|   | 50% (middle)       | 2048    | 1.65V   |                 |
|   | 75%                | 3072    | 2.47V   |                 |
|   | Full CW (max)      | 4095    | 3.30V   |                 |
|   +--------------------+---------+---------+                 |
|                                                              |
+-------------------------------------------------------------+
```

#### 2.3 Polling Architecture

```
+-------------------------------------------------------------+
|                  ADC POLLING PATTERN                        |
+-------------------------------------------------------------+
|                                                             |
|   LVGL Timer (100ms interval)                               |
|   +--------------------------------------------------+      |
|   |  adc_poll_cb()                                   |      |
|   |  {                                               |      |
|   |    // [1] Read hardware                          |      |
|   |    uint16_t raw = aic_adc_read();                |      |
|   |                                                  |      |
|   |    // [2] Convert values                         |      |
|   |    int percent = raw * 100 / 4095;               |      |
|   |    float voltage = raw * 3.3f / 4095.0f;         |      |
|   |                                                  |      |
|   |    // [3] Update LVGL widgets                    |      |
|   |    lv_bar_set_value(bar, percent, ANIM_ON);      |      |
|   |    lv_label_set_text_fmt(lbl, "%d%%", percent);  |      |
|   |    lv_label_set_text_fmt(volt, "%.2fV", voltage);|      |
|   |  }                                               |      |
|   +--------------------------------------------------+      |
|                                                             |
|   Why 100ms interval (not 50ms)?                            |
|   +-- ADC value changes slowly (user turns pot)             |
|   +-- 100ms = smooth update, less CPU usage                 |
|   +-- 10 updates/second = enough for human eye              |
|                                                             |
+-------------------------------------------------------------+
```

#### 2.4 UI Layout

```
+-------------------------------------------------------------+
|           Part 1 Ex8: HW ADC Display                        |
+-------------------------------------------------------------+
|                                                             |
|   Raw: 2048          Voltage: 1.65V                         |
|                                                             |
|   +================================================+        |
|   |                 [BAR: 50%]                     |        |
|   +================================================+        |
|                                                             |
|                       50%                                   |
|                                                             |
|   [Part II - HW] Turn the potentiometer on the board        |
+-------------------------------------------------------------+
```

***

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

#### 3.1 Sensor/ADC Hardware API (aic-eec.h)

<table><thead><tr><th width="212.742919921875">Function</th><th width="136.6697998046875">Returns</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_sensors_init()</code></td><td>void</td><td>Initialize sensor subsystem (<strong>ต้องเรียกก่อน</strong>)</td></tr><tr><td><code>aic_adc_read()</code></td><td>uint16_t</td><td>อ่านค่า Raw ADC (0-4095)</td></tr></tbody></table>

#### 3.2 Value Conversion (ทำเองในโค้ด)

```c
/* Raw -> Percentage (0-100) */
int percent = (int)((uint32_t)raw * 100 / 4095);

/* Raw -> Voltage (0.00 - 3.30V) */
float voltage = (float)raw * 3.3f / 4095.0f;
```

#### 3.3 LVGL Functions ที่ใช้ในบทนี้

<table><thead><tr><th width="425.2890625">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_bar_create(parent)</code></td><td>สร้าง Bar widget</td></tr><tr><td><code>lv_bar_set_range(bar, min, max)</code></td><td>กำหนดช่วงค่า Bar</td></tr><tr><td><code>lv_bar_set_value(bar, value, anim)</code></td><td>ตั้งค่า Bar</td></tr><tr><td><code>lv_slider_create(parent)</code></td><td>สร้าง Slider widget</td></tr><tr><td><code>lv_slider_set_range(slider, min, max)</code></td><td>กำหนดช่วงค่า Slider</td></tr><tr><td><code>lv_slider_set_value(slider, value, anim)</code></td><td>ตั้งค่า Slider</td></tr><tr><td><code>lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE)</code></td><td>ทำให้ Slider เป็น read-only</td></tr><tr><td><code>lv_label_set_text_fmt(lbl, fmt, ...)</code></td><td>แสดงข้อความแบบ format</td></tr><tr><td><code>lv_timer_create(cb, period, data)</code></td><td>สร้าง periodic timer</td></tr></tbody></table>

#### 3.4 ทำ Slider เป็น Read-only

```c
/* สร้าง slider ที่ user drag ไม่ได้ (แสดงค่าอย่างเดียว) */
lv_obj_t * slider = lv_slider_create(parent);
lv_obj_remove_flag(slider, LV_OBJ_FLAG_CLICKABLE);  /* <-- key! */
```

***

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

#### 4.1 Minimal Example: ADC to Label

```c
#include "aic-eec.h"

static lv_obj_t * value_label;

/* Timer callback: read ADC every 100ms */
static void adc_poll_cb(lv_timer_t * t)
{
    uint16_t raw = aic_adc_read();
    int percent = (int)((uint32_t)raw * 100 / 4095);

    lv_label_set_text_fmt(value_label, "ADC: %d (%d%%)",
                          (int)raw, percent);
}

void minimal_adc_read(void)
{
    aic_sensors_init();   /* MUST call before aic_adc_read()! */

    value_label = lv_label_create(lv_screen_active());
    lv_label_set_text(value_label, "ADC: ---");
    lv_obj_center(value_label);

    lv_timer_create(adc_poll_cb, 100, NULL);
}
```

#### 4.2 Full Example: ADC with Bar + Voltage Display

```c
#include "aic-eec.h"

/* ===== Global Variables ===== */
static lv_obj_t * raw_label;
static lv_obj_t * percent_label;
static lv_obj_t * voltage_label;
static lv_obj_t * adc_bar;
static lv_obj_t * adc_slider;

/* ===== Timer Callback: Poll ADC ===== */
static void adc_poll_cb(lv_timer_t * t)
{
    /* [1] Read Hardware */
    uint16_t raw = aic_adc_read();

    /* [2] Convert */
    int percent = (int)((uint32_t)raw * 100 / 4095);
    float voltage = (float)raw * 3.3f / 4095.0f;

    /* [3] Update Labels */
    lv_label_set_text_fmt(raw_label, "Raw: %d / 4095", (int)raw);
    lv_label_set_text_fmt(percent_label, "%d%%", percent);
    lv_label_set_text_fmt(voltage_label, "Voltage: %.2fV", voltage);

    /* [4] Update Bar */
    lv_bar_set_value(adc_bar, percent, LV_ANIM_ON);

    /* [5] Update read-only Slider */
    lv_slider_set_value(adc_slider, percent, LV_ANIM_ON);
}

/* ===== Main Function ===== */
void part1_ex8_hw_adc_display(void)
{
    /* ============================== */
    /*    HARDWARE INITIALIZATION     */
    /* ============================== */
    aic_sensors_init();   /* CRITICAL: must call before adc_read! */

    /* ============================== */
    /*         LVGL UI SETUP          */
    /* ============================== */
    lv_obj_t * scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a2e), 0);

    /* --- Title --- */
    lv_obj_t * title = lv_label_create(scr);
    lv_label_set_text(title, "Lab 8: HW ADC Display");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /* --- Container --- */
    lv_obj_t * cont = lv_obj_create(scr);
    lv_obj_set_size(cont, 440, 200);
    lv_obj_align(cont, LV_ALIGN_CENTER, 0, 10);
    lv_obj_set_style_bg_color(cont, lv_color_hex(0x16213e), 0);
    lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_row(cont, 8, 0);
    lv_obj_set_style_pad_all(cont, 15, 0);

    /* --- Row: Raw + Voltage --- */
    lv_obj_t * row_top = lv_obj_create(cont);
    lv_obj_set_size(row_top, LV_PCT(100), 30);
    lv_obj_set_flex_flow(row_top, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(row_top, LV_FLEX_ALIGN_SPACE_BETWEEN,
                          LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_bg_opa(row_top, LV_OPA_TRANSP, 0);
    lv_obj_set_style_border_width(row_top, 0, 0);
    lv_obj_set_style_pad_all(row_top, 0, 0);
    lv_obj_remove_flag(row_top, LV_OBJ_FLAG_SCROLLABLE);

    raw_label = lv_label_create(row_top);
    lv_label_set_text(raw_label, "Raw: --- / 4095");
    lv_obj_set_style_text_color(raw_label, lv_color_hex(0xCCCCCC), 0);

    voltage_label = lv_label_create(row_top);
    lv_label_set_text(voltage_label, "Voltage: --.--V");
    lv_obj_set_style_text_color(voltage_label, lv_color_hex(0x00FF88), 0);

    /* --- Bar Widget --- */
    adc_bar = lv_bar_create(cont);
    lv_obj_set_size(adc_bar, LV_PCT(90), 25);
    lv_bar_set_range(adc_bar, 0, 100);
    lv_bar_set_value(adc_bar, 0, LV_ANIM_OFF);
    lv_obj_set_style_bg_color(adc_bar, lv_color_hex(0x333333), 0);
    lv_obj_set_style_bg_color(adc_bar, lv_palette_main(LV_PALETTE_BLUE),
                              LV_PART_INDICATOR);

    /* --- Percent Label (large) --- */
    percent_label = lv_label_create(cont);
    lv_label_set_text(percent_label, "0%");
    lv_obj_set_style_text_color(percent_label, lv_color_hex(0x00BFFF), 0);
    lv_obj_set_style_text_font(percent_label, &lv_font_montserrat_24, 0);

    /* --- Read-only Slider --- */
    adc_slider = lv_slider_create(cont);
    lv_obj_set_size(adc_slider, LV_PCT(90), 15);
    lv_slider_set_range(adc_slider, 0, 100);
    lv_slider_set_value(adc_slider, 0, LV_ANIM_OFF);
    lv_obj_remove_flag(adc_slider, LV_OBJ_FLAG_CLICKABLE);  /* Read-only! */
    lv_obj_set_style_bg_color(adc_slider, lv_color_hex(0x333333), 0);
    lv_obj_set_style_bg_color(adc_slider,
        lv_palette_main(LV_PALETTE_CYAN), LV_PART_INDICATOR);

    /* --- Footer --- */
    lv_obj_t * footer = lv_label_create(scr);
    lv_label_set_text(footer,
        "[Part II - HW] Turn the potentiometer on the board");
    lv_obj_set_style_text_color(footer, lv_color_hex(0x888888), 0);
    lv_obj_align(footer, LV_ALIGN_BOTTOM_MID, 0, -10);

    /* ============================== */
    /*    START POLLING TIMER         */
    /* ============================== */
    lv_timer_create(adc_poll_cb, 100, NULL);  /* 100ms = 10 Hz */
}
```

#### 4.3 อธิบายโค้ดทีละขั้นตอน

<table><thead><tr><th width="88.20452880859375">ขั้นตอน</th><th width="445.10650634765625">โค้ด</th><th>คำอธิบาย</th></tr></thead><tbody><tr><td>1</td><td><code>aic_sensors_init()</code></td><td>Initialize sensor subsystem (<strong>ห้ามลืม!</strong>)</td></tr><tr><td>2</td><td><code>lv_bar_create()</code> + <code>lv_slider_create()</code></td><td>สร้าง widgets แสดงค่า</td></tr><tr><td>3</td><td><code>lv_obj_remove_flag(slider, LV_OBJ_FLAG_CLICKABLE)</code></td><td>ทำ Slider เป็น read-only</td></tr><tr><td>4</td><td><code>lv_timer_create(adc_poll_cb, 100, NULL)</code></td><td>Poll ADC ทุก 100ms</td></tr><tr><td>5</td><td><code>aic_adc_read()</code></td><td>อ่านค่า Raw 0-4095</td></tr><tr><td>6</td><td><code>raw * 100 / 4095</code></td><td>แปลงเป็น Percentage</td></tr><tr><td>7</td><td><code>raw * 3.3f / 4095.0f</code></td><td>แปลงเป็น Voltage</td></tr><tr><td>8</td><td><code>lv_bar_set_value()</code> + <code>lv_label_set_text_fmt()</code></td><td>อัพเดท UI</td></tr></tbody></table>

***

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

#### 5.1 Pattern: ADC Polling with Smoothing

```c
/* Moving Average Filter สำหรับลดค่า noise */
#define FILTER_SIZE  8
static uint16_t filter_buf[FILTER_SIZE];
static int filter_idx = 0;
static bool filter_full = false;

static uint16_t adc_read_filtered(void)
{
    filter_buf[filter_idx] = aic_adc_read();
    filter_idx = (filter_idx + 1) % FILTER_SIZE;
    if (filter_idx == 0) filter_full = true;

    int count = filter_full ? FILTER_SIZE : filter_idx;
    uint32_t sum = 0;
    for (int i = 0; i < count; i++) {
        sum += filter_buf[i];
    }
    return (uint16_t)(sum / count);
}
```

#### 5.2 Pattern: Read-only Widget

```c
/* วิธีที่ 1: ใช้ Slider เป็น read-only gauge */
lv_obj_t * gauge = lv_slider_create(parent);
lv_obj_remove_flag(gauge, LV_OBJ_FLAG_CLICKABLE);

/* วิธีที่ 2: ใช้ Bar (อ่านอย่างเดียวโดยธรรมชาติ) */
lv_obj_t * bar = lv_bar_create(parent);
lv_bar_set_value(bar, percent, LV_ANIM_ON);
```

#### 5.3 Pattern: Value Conversion

```c
/* Integer division (ไม่มี float, เหมาะสำหรับ embedded) */
int percent = (int)((uint32_t)raw * 100 / 4095);

/* Float division (สำหรับแสดง voltage ทศนิยม) */
float voltage = (float)raw * 3.3f / 4095.0f;

/* Map to custom range (e.g., temperature 0-150 degrees) */
int temp = (int)((uint32_t)raw * 150 / 4095);
```

#### 5.4 สิ่งที่ต้องระวัง

<table><thead><tr><th width="218.5526123046875">หัวข้อ</th><th>รายละเอียด</th></tr></thead><tbody><tr><td><strong>aic_sensors_init()</strong></td><td>ต้องเรียกก่อน <code>aic_adc_read()</code> เสมอ ไม่งั้น return 0</td></tr><tr><td><strong>Integer Overflow</strong></td><td><code>raw * 100</code> อาจ overflow ถ้า raw เป็น uint16_t; ใช้ <code>(uint32_t)raw * 100</code></td></tr><tr><td><strong>Float Format</strong></td><td>ใช้ <code>%.2f</code> สำหรับ 2 ทศนิยม; ใช้ <code>%.1f</code> สำหรับ 1 ทศนิยม</td></tr><tr><td><strong>ADC Noise</strong></td><td>ค่า ADC อาจกระเพื่อมเล็กน้อย; ใช้ Moving Average Filter</td></tr><tr><td><strong>Timer Period</strong></td><td>100ms เหมาะสำหรับ ADC; เร็วกว่านี้ไม่จำเป็นเพราะ pot หมุนช้า</td></tr><tr><td><strong>Animation</strong></td><td>ใช้ <code>LV_ANIM_ON</code> ให้ Bar เคลื่อนไหวนุ่มนวล</td></tr></tbody></table>

#### 5.5 Application ในอุตสาหกรรม

* **Process Monitoring**: อ่านค่า Pressure Sensor แสดง Bar graph
* **Quality Control**: วัดค่า Dimension sensor แสดง Pass/Fail
* **HVAC System**: อ่าน Temperature sensor แสดง Gauge
* **Water Treatment**: วัดค่า pH, Turbidity แสดง Dashboard

***

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

#### แบบฝึกหัดที่ 1: Precision Voltage Display

**โจทย์**: สร้างหน้าจอ Voltmeter ที่แสดงค่าแรงดันจาก Potentiometer

**ข้อกำหนด**:

* แสดงค่า Voltage เป็นตัวเลขใหญ่ (font 24) แบบ 2 ทศนิยม: "1.65 V"
* แสดง Raw ADC value: "Raw: 2048 / 4095"
* แสดง Bar graph แบบเปลี่ยนสี:
  * 0.0V - 1.0V: สีเขียว (Safe)
  * 1.0V - 2.5V: สีเหลือง (Warning)
  * 2.5V - 3.3V: สีแดง (Danger)
* แสดง Min/Max ที่เคยอ่านได้ (เช่น "Min: 0.12V Max: 3.28V")
* มีปุ่ม "Reset Min/Max" สำหรับ clear ค่า

**Hints**:

* ใช้ `static float min_v = 3.3f, max_v = 0.0f;` เก็บ min/max
* เปลี่ยนสี Bar: `lv_obj_set_style_bg_color(bar, color, LV_PART_INDICATOR)`
* เปรียบเทียบ voltage กับ threshold ใน timer callback

#### แบบฝึกหัดที่ 2: ADC Threshold Alarm System

**โจทย์**: สร้างระบบ Alarm ที่ trigger เมื่อค่า ADC เกิน threshold

**ข้อกำหนด**:

* มี Slider สำหรับตั้ง Threshold (0-100%)
* แสดง Bar graph ค่า ADC ปัจจุบัน
* เมื่อ ADC > Threshold:
  * พื้นหลัง Container เปลี่ยนเป็นสีแดง
  * Label แสดง "ALARM!" สีแดงกะพริบ
  * Hardware LED (Red) ติด
* เมื่อ ADC <= Threshold:
  * พื้นหลังกลับปกติ
  * Label แสดง "Normal" สีเขียว
  * Hardware LED (Red) ดับ
* แสดง Threshold value: "Threshold: 75%"
* แสดง Current value: "Current: 80%"

**Hints**:

* ใช้ `aic_gpio_led_set(AIC_LED_RED, true/false)` ควบคุม LED
* กะพริบ Label: สลับ `lv_obj_add_flag(lbl, LV_OBJ_FLAG_HIDDEN)` / `remove_flag` ใน timer

***

### 7. References

* aic-eec Sensors API
* [LVGL Slider Widget](https://docs.lvgl.io/9.2/widgets/slider.html)
* [LVGL Bar Widget](https://docs.lvgl.io/9.2/widgets/bar.html)

***


---

# 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/gpio-to-hmi-display/hardware-interfacing-workshops/adc-display.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.
