# 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="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FKJY901WeBUuJ7vYBLwza%2Fweek30_ex8_hw_adc_display.gif?alt=media&#x26;token=fb5a4367-5167-4cdc-8d4f-f4d43f0de088" 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)

***
