# Waveform Generator

## Lab 1: Waveform Generator

### Part 3 - Oscilloscope & Signal Processing

***

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

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

* **Signal Fundamentals**: สัญญาณพื้นฐาน (Sine, Square, Triangle, Sawtooth) เป็นรากฐานของวิศวกรรมไฟฟ้าทุกสาขา ไม่ว่าจะเป็น Power Systems, Communications หรือ Control Systems
* **DSP Foundation**: การสร้างและแสดงผล Waveform เป็นก้าวแรกสู่ Digital Signal Processing ที่ใช้ในงานวิเคราะห์ Power Quality, Audio Processing และ Vibration Analysis
* **Testing & Calibration**: Function Generator เป็นเครื่องมือพื้นฐานในห้อง Lab สำหรับทดสอบวงจร การสร้างได้ด้วยซอฟต์แวร์ช่วยลดต้นทุนและเพิ่มความยืดหยุ่น
* **EE Application**: ใน Power Systems ใช้สร้างสัญญาณจำลอง 50 Hz AC, ใน Communications ใช้สร้าง Carrier Wave, ใน Control ใช้สร้าง Test Signal

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

1. **Waveform Mathematics**: สูตรคณิตศาสตร์ของ Square, Sine, Triangle, Sawtooth
2. **Chart as Oscilloscope**: ใช้ `lv_chart` แสดง waveform 200+ จุด แบบ real-time
3. **User Controls**: Dropdown เลือกชนิด waveform, Slider ปรับ frequency และ amplitude
4. **Buffer Management**: จัดการ sample buffer สำหรับ waveform generation
5. **Mapping Technique**: แปลงค่าจาก signal domain (-1000..+1000) เป็น display domain (0..100)

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

1. สร้าง Chart แสดง waveform ด้วย `lv_chart_create()` กำหนด 200 จุด
2. เขียนฟังก์ชัน generate สำหรับ waveform แต่ละชนิด
3. เพิ่ม Dropdown เลือกประเภท wave
4. เพิ่ม Slider สำหรับ frequency (10-1000 Hz) และ amplitude (100-1000)
5. เชื่อม callbacks เพื่ออัพเดท waveform ตามการเปลี่ยนแปลง

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

***

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

#### 2.1 Waveform Generation Functions

| Function              | Description          | สูตร                             |
| --------------------- | -------------------- | -------------------------------- |
| `generate_sine()`     | สร้างคลื่นไซน์       | y = A \* sin(2*PI*f\*n/Fs)       |
| `generate_square()`   | สร้างคลื่นสี่เหลี่ยม | y = A \* sign(sin(2*PI*f\*n/Fs)) |
| `generate_triangle()` | สร้างคลื่นสามเหลี่ยม | y = A \* (4\*                    |
| `generate_sawtooth()` | สร้างคลื่นฟันเลื่อย  | y = A \* (2\*phase - 1)          |

#### 2.2 LVGL Chart API

| Function                                        | Description          |
| ----------------------------------------------- | -------------------- |
| `lv_chart_create(parent)`                       | สร้าง chart widget   |
| `lv_chart_set_type(chart, LV_CHART_TYPE_LINE)`  | กำหนดเป็น line chart |
| `lv_chart_set_point_count(chart, 200)`          | จำนวนจุดที่แสดง      |
| `lv_chart_set_range(chart, axis, min, max)`     | กำหนดช่วงแกน Y       |
| `lv_chart_add_series(chart, color, axis)`       | เพิ่ม data series    |
| `lv_chart_set_value_by_id(chart, ser, id, val)` | กำหนดค่าจุด          |
| `lv_chart_refresh(chart)`                       | อัพเดทการแสดงผล      |
| `lv_chart_set_div_line_count(chart, h, v)`      | เส้น grid            |

#### 2.3 Waveform Type Enum

```c
typedef enum {
    WAVE_SQUARE = 0,
    WAVE_SINE,
    WAVE_TRIANGLE,
    WAVE_SAWTOOTH,
    WAVE_COUNT
} waveform_type_t;
```

***

### 3. Code เต็ม

#### 3.1 Constants and Global Variables

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

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define BUFFER_SIZE     256
#define DISPLAY_POINTS  200
#define SAMPLE_RATE     10000
#define DEFAULT_FREQ    100
#define DEFAULT_AMP     1000

/* Waveform type enum */
typedef enum {
    WAVE_SQUARE = 0,
    WAVE_SINE,
    WAVE_TRIANGLE,
    WAVE_SAWTOOTH,
    WAVE_COUNT
} waveform_type_t;

/* Global variables */
static int16_t wave_buffer[BUFFER_SIZE];
static lv_obj_t *wave_chart = NULL;
static lv_chart_series_t *wave_series = NULL;
static lv_obj_t *freq_label = NULL;
static lv_obj_t *amp_label = NULL;
static lv_obj_t *info_label = NULL;
static waveform_type_t current_wave = WAVE_SINE;
static uint32_t current_freq = DEFAULT_FREQ;
static int16_t current_amplitude = DEFAULT_AMP;
```

#### 3.2 Waveform Generation Functions

```c
/* สร้างคลื่นไซน์ */
static void generate_sine(int16_t *buf, uint16_t count,
                           uint32_t freq, uint32_t fs, int16_t amp)
{
    for (uint16_t i = 0; i < count; i++) {
        double t = (double)i / (double)fs;
        buf[i] = (int16_t)(amp * sin(2.0 * M_PI * freq * t));
    }
}

/* สร้างคลื่นสี่เหลี่ยม */
static void generate_square(int16_t *buf, uint16_t count,
                              uint32_t freq, uint32_t fs, int16_t amp)
{
    for (uint16_t i = 0; i < count; i++) {
        double t = (double)i / (double)fs;
        double val = sin(2.0 * M_PI * freq * t);
        buf[i] = (val >= 0) ? amp : -amp;
    }
}

/* สร้างคลื่นสามเหลี่ยม */
static void generate_triangle(int16_t *buf, uint16_t count,
                                uint32_t freq, uint32_t fs, int16_t amp)
{
    for (uint16_t i = 0; i < count; i++) {
        double phase = fmod((double)i * freq / fs, 1.0);
        double val = 4.0 * fabs(phase - 0.5) - 1.0;
        buf[i] = (int16_t)(amp * val);
    }
}

/* สร้างคลื่นฟันเลื่อย */
static void generate_sawtooth(int16_t *buf, uint16_t count,
                                uint32_t freq, uint32_t fs, int16_t amp)
{
    for (uint16_t i = 0; i < count; i++) {
        double phase = fmod((double)i * freq / fs, 1.0);
        double val = 2.0 * phase - 1.0;
        buf[i] = (int16_t)(amp * val);
    }
}
```

#### 3.3 Waveform Update and Chart Display

```c
/* สร้าง waveform ตาม type ที่เลือก */
static void generate_waveform(void)
{
    switch (current_wave) {
        case WAVE_SQUARE:
            generate_square(wave_buffer, BUFFER_SIZE,
                           current_freq, SAMPLE_RATE, current_amplitude);
            break;
        case WAVE_SINE:
            generate_sine(wave_buffer, BUFFER_SIZE,
                         current_freq, SAMPLE_RATE, current_amplitude);
            break;
        case WAVE_TRIANGLE:
            generate_triangle(wave_buffer, BUFFER_SIZE,
                             current_freq, SAMPLE_RATE, current_amplitude);
            break;
        case WAVE_SAWTOOTH:
            generate_sawtooth(wave_buffer, BUFFER_SIZE,
                             current_freq, SAMPLE_RATE, current_amplitude);
            break;
        default:
            break;
    }

    /* อัพเดท Chart - Map จาก -amplitude..+amplitude ไปเป็น 0..100 */
    if (wave_chart && wave_series) {
        for (int i = 0; i < DISPLAY_POINTS; i++) {
            int32_t val = ((int32_t)wave_buffer[i] + current_amplitude) * 100
                          / (2 * current_amplitude);
            if (val < 0) val = 0;
            if (val > 100) val = 100;
            lv_chart_set_value_by_id(wave_chart, wave_series, i, val);
        }
        lv_chart_refresh(wave_chart);
    }

    /* อัพเดท Info label */
    if (info_label) {
        float cycles = (float)current_freq * DISPLAY_POINTS / SAMPLE_RATE;
        float period_ms = 1000.0f / current_freq;
        lv_label_set_text_fmt(info_label,
            "Cycles: %.1f  |  Period: %.1f ms",
            (double)cycles, (double)period_ms);
    }
}
```

#### 3.4 Event Callbacks

```c
/* Dropdown callback - เลือกชนิด waveform */
static void wave_dropdown_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t *dropdown = lv_event_get_target(e);
    current_wave = (waveform_type_t)lv_dropdown_get_selected(dropdown);
    generate_waveform();
}

/* Frequency slider callback */
static void freq_slider_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t *slider = lv_event_get_target(e);
    current_freq = (uint32_t)lv_slider_get_value(slider);
    lv_label_set_text_fmt(freq_label, "%u Hz", (unsigned int)current_freq);
    generate_waveform();
}

/* Amplitude slider callback */
static void amp_slider_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t *slider = lv_event_get_target(e);
    current_amplitude = (int16_t)lv_slider_get_value(slider);
    lv_label_set_text_fmt(amp_label, "Amp: %d", (int)current_amplitude);
    generate_waveform();
}
```

#### 3.5 Main Function

```c
void part3_ex1_waveform_generator(void)
{
    lv_obj_t *scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0a0a1e), 0);

    /* ===== Title ===== */
    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "Part 3 Ex1: Waveform Generator");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00ff88), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* ===== Waveform Chart ===== */
    wave_chart = lv_chart_create(scr);
    lv_obj_set_size(wave_chart, 380, 150);
    lv_obj_align(wave_chart, LV_ALIGN_TOP_MID, 0, 30);
    lv_chart_set_type(wave_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(wave_chart, DISPLAY_POINTS);
    lv_chart_set_range(wave_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
    lv_chart_set_div_line_count(wave_chart, 4, 5);

    /* สไตล์ oscilloscope - พื้นหลังดำ เส้น grid สีเข้ม */
    lv_obj_set_style_bg_color(wave_chart, lv_color_hex(0x001a00), 0);
    lv_obj_set_style_line_color(wave_chart, lv_color_hex(0x003300), LV_PART_MAIN);
    lv_obj_set_style_border_color(wave_chart, lv_color_hex(0x00ff00), 0);
    lv_obj_set_style_border_width(wave_chart, 1, 0);
    lv_obj_set_style_size(wave_chart, 0, 0, LV_PART_INDICATOR);

    /* เพิ่ม series สีเขียว (oscilloscope style) */
    wave_series = lv_chart_add_series(wave_chart,
                     lv_color_hex(0x00ff00),
                     LV_CHART_AXIS_PRIMARY_Y);

    /* ===== Dropdown เลือก Waveform ===== */
    lv_obj_t *dropdown = lv_dropdown_create(scr);
    lv_dropdown_set_options(dropdown, "Square\nSine\nTriangle\nSawtooth");
    lv_dropdown_set_selected(dropdown, WAVE_SINE);
    lv_obj_set_width(dropdown, 120);
    lv_obj_align(dropdown, LV_ALIGN_BOTTOM_LEFT, 10, -55);
    lv_obj_add_event_cb(dropdown, wave_dropdown_cb,
                        LV_EVENT_VALUE_CHANGED, NULL);

    /* ===== Frequency Slider ===== */
    lv_obj_t *freq_lbl = lv_label_create(scr);
    lv_label_set_text(freq_lbl, "Freq:");
    lv_obj_set_style_text_color(freq_lbl, lv_color_hex(0xaaaaaa), 0);
    lv_obj_align(freq_lbl, LV_ALIGN_BOTTOM_LEFT, 145, -70);

    lv_obj_t *freq_slider = lv_slider_create(scr);
    lv_slider_set_range(freq_slider, 10, 1000);
    lv_slider_set_value(freq_slider, DEFAULT_FREQ, LV_ANIM_OFF);
    lv_obj_set_width(freq_slider, 160);
    lv_obj_align(freq_slider, LV_ALIGN_BOTTOM_LEFT, 185, -70);
    lv_obj_add_event_cb(freq_slider, freq_slider_cb,
                        LV_EVENT_VALUE_CHANGED, NULL);

    freq_label = lv_label_create(scr);
    lv_label_set_text_fmt(freq_label, "%u Hz", DEFAULT_FREQ);
    lv_obj_set_style_text_color(freq_label, lv_color_hex(0x00ffff), 0);
    lv_obj_align(freq_label, LV_ALIGN_BOTTOM_RIGHT, -10, -70);

    /* ===== Amplitude Slider ===== */
    lv_obj_t *amp_lbl_title = lv_label_create(scr);
    lv_label_set_text(amp_lbl_title, "Amp:");
    lv_obj_set_style_text_color(amp_lbl_title, lv_color_hex(0xaaaaaa), 0);
    lv_obj_align(amp_lbl_title, LV_ALIGN_BOTTOM_LEFT, 145, -45);

    lv_obj_t *amp_slider = lv_slider_create(scr);
    lv_slider_set_range(amp_slider, 100, 1000);
    lv_slider_set_value(amp_slider, DEFAULT_AMP, LV_ANIM_OFF);
    lv_obj_set_width(amp_slider, 160);
    lv_obj_align(amp_slider, LV_ALIGN_BOTTOM_LEFT, 185, -45);
    lv_obj_add_event_cb(amp_slider, amp_slider_cb,
                        LV_EVENT_VALUE_CHANGED, NULL);

    amp_label = lv_label_create(scr);
    lv_label_set_text_fmt(amp_label, "Amp: %d", DEFAULT_AMP);
    lv_obj_set_style_text_color(amp_label, lv_color_hex(0xffff00), 0);
    lv_obj_align(amp_label, LV_ALIGN_BOTTOM_RIGHT, -10, -45);

    /* ===== Info Label ===== */
    info_label = lv_label_create(scr);
    lv_obj_set_style_text_color(info_label, lv_color_hex(0x888888), 0);
    lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -10);

    /* สร้าง waveform แรก */
    generate_waveform();
}
```

***

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

#### 4.1 Nyquist Theorem - ทฤษฎีไนควิสต์

```c
/* ทฤษฎี Nyquist: sample_rate >= 2 * max_frequency
 *
 * Sample rate ของเรา: 10000 Hz
 * ความถี่สูงสุดที่แสดงได้: 5000 Hz
 *
 * ถ้า freq > sample_rate/2 จะเกิด Aliasing
 * (สัญญาณบิดเบี้ยว ไม่ตรงกับความเป็นจริง)
 *
 * ในงาน Power Systems:
 * - สัญญาณ 50 Hz -> ต้อง sample อย่างน้อย 100 Hz
 * - วิเคราะห์ harmonics ถึง 50th (2500 Hz) -> sample >= 5000 Hz
 */
```

#### 4.2 Cycles per Display

```c
/* จำนวนรอบที่เห็นบนจอ:
 *
 * display_points = 200 samples
 * sample_rate = 10000 Hz
 * display_time = 200/10000 = 0.02s = 20ms
 *
 * ที่ 50 Hz (ไฟฟ้ากระแสสลับ): 50 * 0.02 = 1 cycle
 * ที่ 100 Hz:                  100 * 0.02 = 2 cycles
 * ที่ 500 Hz:                  500 * 0.02 = 10 cycles
 * ที่ 1000 Hz:                 1000 * 0.02 = 20 cycles
 *
 * สูตร: cycles = freq * display_points / sample_rate
 */
```

#### 4.3 Amplitude Mapping

```c
/* การ Map ค่าจาก Signal Domain ไปเป็น Display Domain
 *
 * Signal: -amplitude to +amplitude (int16_t)
 * Chart:  0 to 100 (percentage)
 *
 * สูตร: chart_val = (signal_val + amplitude) * 100 / (2 * amplitude)
 *
 * ตัวอย่าง amplitude = 1000:
 * -1000 -> (0)    * 100/2000 = 0
 *     0 -> (1000) * 100/2000 = 50
 * +1000 -> (2000) * 100/2000 = 100
 *
 * ต้อง clamp ค่าให้อยู่ในช่วง 0-100 เสมอ
 */
```

#### 4.4 Chart Performance Tips

```c
/* เทคนิคเพิ่มประสิทธิภาพ Chart:
 *
 * 1. ซ่อน indicator dots (ลด rendering load)
 *    lv_obj_set_style_size(chart, 0, 0, LV_PART_INDICATOR);
 *
 * 2. ใช้จำนวนจุดที่เหมาะสม (200 จุดเพียงพอสำหรับ oscilloscope)
 *    lv_chart_set_point_count(chart, 200);
 *
 * 3. ใช้ lv_chart_set_value_by_id() + lv_chart_refresh()
 *    แทน lv_chart_set_next_value() เมื่อต้องอัพเดททุกจุดพร้อมกัน
 *
 * 4. Dark background ลดการ redraw:
 *    lv_obj_set_style_bg_color(chart, lv_color_hex(0x001a00), 0);
 */
```

***

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

#### Exercise 1: Noise Injection (เพิ่ม Noise ให้สัญญาณ)

**โจทย์:** เพิ่ม Slider สำหรับ Noise Level (0-500) ที่จะบวก random noise เข้าไปในสัญญาณ

* เพิ่ม slider ปรับระดับ noise (0 = clean, 500 = maximum noise)
* ใช้ `rand()` สร้าง random values แล้วบวกเข้าไปหลัง generate
* แสดงค่า SNR (Signal-to-Noise Ratio) โดยประมาณ

**คำใบ้:**

```c
/* Noise injection pattern */
for (int i = 0; i < BUFFER_SIZE; i++) {
    int16_t noise = (rand() % (2 * noise_level + 1)) - noise_level;
    wave_buffer[i] += noise;
}
```

#### Exercise 2: Dual-Channel Display (แสดง 2 ช่องสัญญาณ)

**โจทย์:** สร้างการแสดงผล 2 ช่องสัญญาณบน chart เดียวกัน (เหมือน oscilloscope 2 channels)

* Channel 1 (สีเขียว): waveform ที่เลือกจาก dropdown
* Channel 2 (สีเหลือง): sine wave ที่ความถี่ 2 เท่าของ Channel 1
* ใช้ `lv_chart_add_series()` เพิ่ม series ที่สอง
* เพิ่มปุ่ม toggle เปิด/ปิด Channel 2

**คำใบ้:**

```c
/* เพิ่ม series ที่สอง */
static lv_chart_series_t *ch2_series = NULL;
ch2_series = lv_chart_add_series(wave_chart,
                 lv_color_hex(0xffff00),  /* สีเหลือง */
                 LV_CHART_AXIS_PRIMARY_Y);
```

***

### 6. References

* [LVGL Chart](https://docs.lvgl.io/9.2/widgets/chart.html)
* [LVGL Dropdown](https://docs.lvgl.io/9.2/widgets/dropdown.html)
* [Signal Processing Basics](https://www.dspguide.com/)

***


---

# 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/spectrum-analyzer/ux-ui-design/waveform-generator.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.
