> For the complete documentation index, see [llms.txt](https://docs.aic-eec.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/spectrum-analyzer/ux-ui-design/waveform-generator.md).

# 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="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FFzEuP8yuoHEFcnYrKkp6%2Fweek5_ex1_waveform_generator.gif?alt=media&amp;token=fe3c2593-6b18-4d05-a8ce-95d8a212dae6" 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/)

***
