> 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/sensor-to-hmi-display/hardware-interfacing-workshops.md).

# Hardware Interfacing Workshops

## Part II: Sensor Hardware Integration & Visualization

***

## โค้ดโปรเจคสำหรับ Sensor-to-HMI Display

{% embed url="<https://github.com/Advance-Innovation-Centre-AIC/psoc-e84-e2-lvgl-aic-eec>" %}

{% hint style="info" %}

### Part 2 Examples (Sensor Visualization)**:**

Section II (Real Hardware):

* 7 = Real IMU Visualization (based on Ex3)
* 8 = Real Sensor Dashboard (based on Ex5)
* 9 = Real Arc Gauge (based on Ex2, uses POTEN)
* 10 = Real Scale Gauge (based on Ex4, uses POTEN)
* 11 = Real Chart Dashboard (based on Ex6, uses IMU)
  {% endhint %}

***

### สารบัญ

1. ภาพรวม Part II
2. aic-eec Sensor API
3. ตัวอย่างที่ 7: Real IMU Visualization
4. ตัวอย่างที่ 8: Real Sensor Dashboard
5. ตัวอย่างที่ 9: Real Arc Gauge with Tilt Analysis
6. ตัวอย่างที่ 10: Real Scale Gauge with Tilt Analysis 6B. ตัวอย่างที่ 11: Real Chart Dashboard (4 Chart Types)
7. Complementary Filter - การวิเคราะห์มุมเอียง
8. แบบฝึกหัด

***

### 1. ภาพรวม Part II

#### Sensor Hardware บนบอร์ด

<table><thead><tr><th width="193.19671630859375">Sensor</th><th width="143.30328369140625">Chip</th><th width="140.3770751953125">Interface</th><th>ข้อมูล</th></tr></thead><tbody><tr><td><strong>ADC</strong></td><td>SAR ADC</td><td>Analog</td><td>Potentiometer (0-3.3V)</td></tr><tr><td><strong>IMU</strong></td><td>BMI270</td><td>I2C</td><td>Accelerometer, Gyroscope</td></tr><tr><td><strong>Temperature</strong></td><td>Internal</td><td>ADC</td><td>Chip temperature</td></tr></tbody></table>

***

### 2. aic-eec Sensor API

#### 2.1 ADC API

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

/* ADC Channels */
typedef enum {
    AIC_ADC_CH0 = 0,    // Potentiometer
    AIC_ADC_CH_COUNT
} aic_adc_channel_t;

/* ADC Functions */
void aic_sensors_init(void);                          // Initialize sensors
uint16_t aic_adc_read(aic_adc_channel_t ch);          // Raw (0-4095)
float aic_adc_read_voltage(aic_adc_channel_t ch);     // Voltage (0-3.3V)
uint8_t aic_adc_read_percent(aic_adc_channel_t ch);   // Percent (0-100%)
float aic_adc_read_temperature(void);                  // Chip temp (°C)
```

#### 2.2 IMU API (BMI270)

```c
/* Orientation Types */
typedef enum {
    AIC_ORIENT_UNKNOWN = 0,
    AIC_ORIENT_FLAT_UP,       // หงายขึ้น
    AIC_ORIENT_FLAT_DOWN,     // คว่ำลง
    AIC_ORIENT_PORTRAIT,      // แนวตั้ง
    AIC_ORIENT_LANDSCAPE,     // แนวนอน
    AIC_ORIENT_TILT_LEFT,     // เอียงซ้าย
    AIC_ORIENT_TILT_RIGHT     // เอียงขวา
} aic_orientation_t;

/* IMU Functions */
bool aic_imu_read_accel(float *ax, float *ay, float *az);  // Accelerometer (g)
bool aic_imu_read_gyro(float *gx, float *gy, float *gz);   // Gyroscope (rad/s)
aic_orientation_t aic_imu_get_orientation(void);            // Orientation
const char* aic_imu_orientation_name(aic_orientation_t o);  // Name string
```

#### API Summary

| Function                       | หน้าที่            | Return     |
| ------------------------------ | ------------------ | ---------- |
| `aic_sensors_init()`           | เริ่มต้น Sensors   | void       |
| `aic_adc_read(ch)`             | อ่าน ADC raw       | 0-4095     |
| `aic_adc_read_voltage(ch)`     | อ่าน Voltage       | 0.0-3.3V   |
| `aic_adc_read_percent(ch)`     | อ่าน Percent       | 0-100%     |
| `aic_imu_read_accel(ax,ay,az)` | อ่าน Accelerometer | true/false |
| `aic_imu_read_gyro(gx,gy,gz)`  | อ่าน Gyroscope     | true/false |
| `aic_imu_get_orientation()`    | คำนวณ Orientation  | enum       |

#### 2.3 Tilt Analysis API (NEW)

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

/* Tilt Functions - Complementary Filter */
bool aic_tilt_init(const aic_tilt_config_t *config);  // NULL for defaults
bool aic_tilt_update_from_imu(void);                  // Auto-read IMU
float aic_tilt_get_roll(void);                         // Roll angle (deg)
float aic_tilt_get_pitch(void);                        // Pitch angle (deg)
uint8_t aic_tilt_get_roll_percent(void);               // -90..+90 → 0..100%
uint8_t aic_tilt_get_pitch_percent(void);              // -90..+90 → 0..100%
void aic_tilt_reset(void);                             // Reset filter
void aic_tilt_set_alpha(float alpha);                  // Adjust filter (0.0-1.0)
```

| Function                       | หน้าที่                       | Return      |
| ------------------------------ | ----------------------------- | ----------- |
| `aic_tilt_init(config)`        | เริ่มต้น Complementary Filter | true/false  |
| `aic_tilt_update_from_imu()`   | อ่าน IMU และอัพเดท filter     | true/false  |
| `aic_tilt_get_roll()`          | มุม Roll (เอียงซ้าย-ขวา)      | -180..+180° |
| `aic_tilt_get_pitch()`         | มุม Pitch (เอียงหน้า-หลัง)    | -90..+90°   |
| `aic_tilt_get_roll_percent()`  | Roll เป็นเปอร์เซ็นต์          | 0-100%      |
| `aic_tilt_get_pitch_percent()` | Pitch เป็นเปอร์เซ็นต์         | 0-100%      |

#### WHY: ทำไมใช้ aic-eec API?

1. **Abstraction** - ซ่อนความซับซ้อนของ HAL/Driver
2. **Consistency** - API รูปแบบเดียวกันทั้งโปรเจค
3. **Testability** - สามารถ mock ได้ง่าย
4. **Reusability** - ใช้ซ้ำได้หลาย examples

***

### 3. ตัวอย่างที่ 7: Real ADC Visualization

#### เป้าหมาย

แสดงค่า ADC จาก Potentiometer จริงด้วย Slider และ Bar

#### Comparison: Part I vs Part II

```c
/* Part I (Simulated) */
uint16_t value = simulate_adc_read();  // Random value

/* Part II (Real Hardware) */
uint16_t value = aic_adc_read(AIC_ADC_CH0);  // Real potentiometer
```

#### โค้ดหลัก

```c
#include "week4_examples.h"
#include "../aic-eec/sensors.h"

static lv_obj_t *real_adc_slider = NULL;
static lv_obj_t *real_adc_bar = NULL;
static lv_obj_t *real_adc_value_label = NULL;
static lv_obj_t *real_adc_voltage_label = NULL;
static lv_timer_t *real_adc_timer = NULL;

static void real_adc_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* อ่าน ADC จริง */
    uint16_t raw = aic_adc_read(AIC_ADC_CH0);
    uint8_t percent = aic_adc_read_percent(AIC_ADC_CH0);
    float voltage = aic_adc_read_voltage(AIC_ADC_CH0);

    /* Update Slider (read-only display) */
    lv_slider_set_value(real_adc_slider, percent, LV_ANIM_ON);

    /* Update Bar */
    lv_bar_set_value(real_adc_bar, percent, LV_ANIM_ON);

    /* Update Labels */
    lv_label_set_text_fmt(real_adc_value_label,
                          "Raw: %u  (%u%%)", raw, percent);
    lv_label_set_text_fmt(real_adc_voltage_label,
                          "Voltage: %.3f V", voltage);
}

void week4_ex7_real_adc_display(void)
{
    /* Initialize sensors */
    aic_sensors_init();

    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, "Week 4 Ex7: Real ADC Visualization");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00ff88), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /* Slider (read-only) */
    real_adc_slider = lv_slider_create(scr);
    lv_obj_set_width(real_adc_slider, 350);
    lv_slider_set_range(real_adc_slider, 0, 100);
    lv_obj_align(real_adc_slider, LV_ALIGN_CENTER, 0, -30);
    lv_obj_remove_flag(real_adc_slider, LV_OBJ_FLAG_CLICKABLE);

    /* Vertical Bar */
    real_adc_bar = lv_bar_create(scr);
    lv_obj_set_size(real_adc_bar, 40, 120);
    lv_bar_set_range(real_adc_bar, 0, 100);
    lv_obj_align(real_adc_bar, LV_ALIGN_RIGHT_MID, -40, 0);

    /* Value Labels */
    real_adc_value_label = lv_label_create(scr);
    lv_label_set_text(real_adc_value_label, "Raw: 0  (0%)");
    lv_obj_set_style_text_color(real_adc_value_label, lv_color_hex(0xffff00), 0);
    lv_obj_align(real_adc_value_label, LV_ALIGN_CENTER, -50, 40);

    real_adc_voltage_label = lv_label_create(scr);
    lv_label_set_text(real_adc_voltage_label, "Voltage: 0.000 V");
    lv_obj_set_style_text_color(real_adc_voltage_label, lv_color_hex(0x00ffff), 0);
    lv_obj_align(real_adc_voltage_label, LV_ALIGN_CENTER, -50, 70);

    /* Timer - อ่านทุก 100ms */
    real_adc_timer = lv_timer_create(real_adc_timer_cb, 100, NULL);
}
```

#### Layout

```ini
┌─────────────────────────────────────────────────────────┐
│       Week 4 Ex7: Real ADC Visualization                │
├─────────────────────────────────────────────────────────┤
│                      ADC Level                          │
│  ╔════════════════════════════════════════╗      ┌───┐  │
│  ║▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░  ║      │   │  │
│  ╚════════════════════════════════════════╝      │BAR│  │
│                                                  │   │  │
│            Raw: 2048  (50%)                      └───┘  │
│            Voltage: 1.650 V                             │
│                                                         │
│         Turn the potentiometer on the board             │
└─────────────────────────────────────────────────────────┘
```

***

### 4. ตัวอย่างที่ 8: Real IMU Visualization

#### เป้าหมาย

แสดงข้อมูล Accelerometer 3 แกน จาก BMI270 แบบ Real-time

#### Comparison: Part I vs Part II

```c
/* Part I (Simulated) */
float ax, ay, az;
simulate_imu_accel(&ax, &ay, &az);  // Sine wave simulation

/* Part II (Real Hardware) */
float ax, ay, az;
aic_imu_read_accel(&ax, &ay, &az);  // Real BMI270 data
```

#### โค้ดหลัก

```c
static lv_obj_t *imu_chart = NULL;
static lv_chart_series_t *imu_ser_x = NULL;
static lv_chart_series_t *imu_ser_y = NULL;
static lv_chart_series_t *imu_ser_z = NULL;
static lv_obj_t *imu_orient_label = NULL;
static lv_timer_t *imu_timer = NULL;

static void imu_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* อ่าน IMU จริง */
    float ax, ay, az;
    if (!aic_imu_read_accel(&ax, &ay, &az)) {
        return;  // Read failed
    }

    /* Map จาก -2g..+2g เป็น 0..100 สำหรับ Chart */
    int32_t x_val = (int32_t)((ax + 2.0f) * 25.0f);
    int32_t y_val = (int32_t)((ay + 2.0f) * 25.0f);
    int32_t z_val = (int32_t)((az + 2.0f) * 25.0f);

    /* Clamp values */
    if (x_val < 0) x_val = 0; if (x_val > 100) x_val = 100;
    if (y_val < 0) y_val = 0; if (y_val > 100) y_val = 100;
    if (z_val < 0) z_val = 0; if (z_val > 100) z_val = 100;

    /* Update Chart */
    lv_chart_set_next_value(imu_chart, imu_ser_x, x_val);
    lv_chart_set_next_value(imu_chart, imu_ser_y, y_val);
    lv_chart_set_next_value(imu_chart, imu_ser_z, z_val);

    /* Update Labels */
    lv_label_set_text_fmt(imu_x_label, "X: %.2f g", ax);
    lv_label_set_text_fmt(imu_y_label, "Y: %.2f g", ay);
    lv_label_set_text_fmt(imu_z_label, "Z: %.2f g", az);

    /* Update Orientation */
    aic_orientation_t orient = aic_imu_get_orientation();
    lv_label_set_text_fmt(imu_orient_label, "Orientation: %s",
                          aic_imu_orientation_name(orient));
}

void week4_ex8_real_imu_display(void)
{
    aic_sensors_init();

    lv_obj_t *scr = lv_screen_active();

    /* Chart สำหรับ 3 แกน */
    imu_chart = lv_chart_create(scr);
    lv_obj_set_size(imu_chart, 320, 140);
    lv_obj_align(imu_chart, LV_ALIGN_CENTER, -50, 10);
    lv_chart_set_type(imu_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(imu_chart, 100);
    lv_chart_set_range(imu_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);

    /* Add series (X=Red, Y=Green, Z=Blue) */
    imu_ser_x = lv_chart_add_series(imu_chart, lv_color_hex(0xff0000),
                                     LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_y = lv_chart_add_series(imu_chart, lv_color_hex(0x00ff00),
                                     LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_z = lv_chart_add_series(imu_chart, lv_color_hex(0x0088ff),
                                     LV_CHART_AXIS_PRIMARY_Y);

    /* Initialize with center value (50 = 0g) */
    lv_chart_set_all_value(imu_chart, imu_ser_x, 50);
    lv_chart_set_all_value(imu_chart, imu_ser_y, 50);
    lv_chart_set_all_value(imu_chart, imu_ser_z, 50);

    /* Timer - อ่านทุก 50ms (20 Hz) */
    imu_timer = lv_timer_create(imu_timer_cb, 50, NULL);
}
```

#### Data Mapping

```ini
IMU Accelerometer Data → Chart Value
────────────────────────────────────
   -2.0g  →   0  (bottom)
   -1.0g  →  25
    0.0g  →  50  (center/rest)
   +1.0g  →  75
   +2.0g  → 100  (top)

Formula: chart_value = (accel + 2.0) * 25
```

#### WHY: ทำไมต้อง Map ค่า?

* LVGL Chart รับค่าเป็น integer (0-100)
* IMU Accelerometer ส่งค่าเป็น float (-2.0 ถึง +2.0 g)
* ต้อง map เพื่อแสดงผลได้ถูกต้อง

***

### 5. ตัวอย่างที่ 9: Real Sensor Dashboard

#### เป้าหมาย

รวม ADC + IMU + Orientation + Temperature ในหน้าเดียว

#### Layout

```ini
┌─────────────────────────────────────────────────────────┐
│          Week 4 Ex9: Real Sensor Dashboard              │
├────────────────┬────────────────────────┬───────────────┤
│   ADC (Pot)    │   IMU Accelerometer    │  Orientation  │
│    ┌────┐      │  ┌──────────────────┐  │               │
│    │    │      │  │  X ─────         │  │   FLAT_UP     │
│    │ 65%│      │  │  Y ─────         │  │               │
│    │1.2V│      │  │  Z ─────         │  │  Temp: 32°C   │
│    └────┘      │  └──────────────────┘  │               │
└────────────────┴────────────────────────┴───────────────┘
```

#### โค้ดโครงสร้าง

```c
static void dash_sensor_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* Read ADC */
    uint8_t adc_percent = aic_adc_read_percent(AIC_ADC_CH0);
    float voltage = aic_adc_read_voltage(AIC_ADC_CH0);
    lv_arc_set_value(dash_adc_arc, adc_percent);
    lv_label_set_text_fmt(dash_adc_label, "%u%%\n%.2fV", adc_percent, voltage);

    /* Read IMU */
    float ax, ay, az;
    if (aic_imu_read_accel(&ax, &ay, &az)) {
        /* Update chart... */

        /* Update orientation */
        aic_orientation_t orient = aic_imu_get_orientation();
        lv_label_set_text_fmt(dash_orient_label, "%s",
                              aic_imu_orientation_name(orient));
    }

    /* Read Temperature */
    float temp = aic_adc_read_temperature();
    lv_label_set_text_fmt(dash_temp_label, "Temp: %.1f C", temp);
}

void week4_ex9_real_sensor_dashboard(void)
{
    aic_sensors_init();

    /* ADC Panel with Arc gauge */
    /* IMU Panel with Chart */
    /* Orientation Panel */

    /* Timer - 100ms interval */
    dash_timer = lv_timer_create(dash_sensor_timer_cb, 100, NULL);
}
```

***

### 5B. ตัวอย่างที่ 9: Real Arc Gauge (Roll Angle)

#### เป้าหมาย

แสดงมุม Roll (เอียงซ้าย-ขวา) ด้วย Arc Gauge โดยใช้ Complementary Filter

#### Algorithm

```
angle = 0.98 * (angle + gyro*dt) + 0.02 * accel_angle
```

#### Code Pattern

```c
static void ex9_timer_cb(lv_timer_t *timer)
{
    /* Update tilt from IMU (Complementary Filter) */
    aic_tilt_update_from_imu();

    /* Get Roll angle and percentage */
    float roll = aic_tilt_get_roll();      /* -180 to +180 degrees */
    uint8_t pct = aic_tilt_get_roll_percent();  /* 0-100% */

    lv_arc_set_value(arc, pct);
    lv_label_set_text_fmt(label, "Roll: %.1f°", roll);
}

void week4_ex9_real_arc_gauge(void)
{
    aic_sensors_init();
    aic_tilt_init(NULL);  /* Use defaults: alpha=0.98, dt=0.1s */

    /* Create Arc (same as Ex2) */
    ex9_arc = lv_arc_create(lv_screen_active());
    lv_arc_set_range(ex9_arc, 0, 100);
    /* ... */

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

**Status:** ✅ Approved

***

### 6. ตัวอย่างที่ 10: Real Scale Gauge (Pitch Angle)

#### เป้าหมาย

แสดงมุม Pitch (เอียงหน้า-หลัง) ด้วย Scale Gauge with Needle

#### Pitch vs Roll

| Angle     | Axis   | Motion         | Example            |
| --------- | ------ | -------------- | ------------------ |
| **Roll**  | X-axis | เอียงซ้าย-ขวา  | เครื่องบินเอียงปีก |
| **Pitch** | Y-axis | เอียงหน้า-หลัง | เครื่องบินก้ม-เงย  |

#### Percentage Mapping

```
-90° (ก้มลง)  → 0%
  0° (ระนาบ)  → 50%
+90° (เงยขึ้น) → 100%
```

#### Code Pattern

```c
static void ex10_timer_cb(lv_timer_t *timer)
{
    /* Update tilt from IMU (Complementary Filter) */
    aic_tilt_update_from_imu();

    /* Get Pitch angle and percentage */
    float pitch = aic_tilt_get_pitch();      /* -90 to +90 degrees */
    uint8_t pct = aic_tilt_get_pitch_percent();  /* 0-100% */

    lv_label_set_text_fmt(value_label, "%d", pct);
    lv_label_set_text_fmt(raw_label, "Pitch: %.1f°", pitch);

    /* Update needle position on scale gauge */
    lv_scale_set_line_needle_value(scale, needle, needle_len, pct);
}

void week4_ex10_real_scale_gauge(void)
{
    aic_sensors_init();
    aic_tilt_init(NULL);  /* Use defaults */

    /* Create Scale (based on Ex4) */
    ex10_scale = lv_scale_create(lv_screen_active());
    lv_scale_set_mode(ex10_scale, LV_SCALE_MODE_ROUND_OUTER);
    lv_scale_set_range(ex10_scale, 0, 100);

    /* Custom labels */
    static const char *labels[] = {"0", "25", "50", "75", "100", NULL};
    lv_scale_set_text_src(ex10_scale, labels);

    /* Create needle */
    ex10_needle_line = lv_line_create(lv_screen_active());
    lv_scale_set_line_needle_value(ex10_scale, ex10_needle_line, 85, 50);

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

#### Layout

```
┌───────────────────────────────────────┐
│   Week 4 Ex10: Scale Gauge (Pitch)    │
├───────────────────────────────────────┤
│                                       │
│          0    25    50   75   100     │
│           ╲         │        ╱        │
│            ╲   ─────┼─────  ╱         │
│             ╲       │      ╱          │
│              ╲  ┌───────┐ ╱           │
│               ╲ │  50%  │╱            │
│                ╲│       │             │
│                 └───────┘             │
│          Pitch: 0.0 deg               │
│                                       │
│  [Part II] Complementary Filter       │
│  Tilt board forward/backward          │
├───────────────────────────────────────┤
│  ADC: 50% │ IMU: OK │ Err: 0          │
└───────────────────────────────────────┘
```

**Status:** ✅ Approved

***

### 6B. ตัวอย่างที่ 11: Real Chart Dashboard (4 Chart Types)

#### เป้าหมาย

แสดง Real Sensor Data ใน 4 รูปแบบ Chart ผ่าน TabView

#### 4 Tabs Overview

| Tab         | Chart Type   | Data Source    | Technique                     |
| ----------- | ------------ | -------------- | ----------------------------- |
| **Bar**     | Bar Chart    | Accel X/Y/Z    | 3 Colored Series (R/G/B)      |
| **Area**    | Faded Area   | Tilt Magnitude | lv\_example\_chart\_5 pattern |
| **Scatter** | Scatter Plot | Roll vs Pitch  | Tilt Ball effect              |
| **Line**    | Line Chart   | Gyroscope      | Ex7 stable pattern            |

#### Tab 1: Bar Chart (Accel X/Y/Z)

```c
/* 3 colored series: Red=X, Green=Y, Blue=Z */
ex11_bar_ser_x = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), ...);
ex11_bar_ser_y = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_GREEN), ...);
ex11_bar_ser_z = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_BLUE), ...);

/* Scale: -5 to +5 m/s² → 0 to 100 */
int32_t bar_x = (int32_t)((ax + 5.0f) * 10.0f);
lv_chart_set_value_by_id(chart, ex11_bar_ser_x, 0, bar_x);
```

#### Tab 2: Faded Area Chart (Tilt Magnitude)

**เทคนิค lv\_example\_chart\_5:**

```c
/* Enable draw task events */
lv_obj_add_event_cb(chart, ex11_area_draw_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
lv_obj_add_flag(chart, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);

/* In callback: draw triangle + rectangle with gradient */
static void ex11_area_draw_cb(lv_event_t *e) {
    if(base_dsc->part == LV_PART_ITEMS &&
       lv_draw_task_get_type(draw_task) == LV_DRAW_TASK_TYPE_LINE) {
        /* Draw triangle below line segment */
        lv_draw_triangle_dsc_t tri_dsc;
        tri_dsc.bg_grad.dir = LV_GRAD_DIR_VER;
        tri_dsc.bg_grad.stops[0].opa = 255 - fract_upper;  /* Top: opaque */
        tri_dsc.bg_grad.stops[1].opa = 255 - fract_lower;  /* Bottom: less opaque */
        lv_draw_triangle(base_dsc->layer, &tri_dsc);

        /* Draw rectangle below triangle (fades to transparent) */
        lv_draw_rect_dsc_t rect_dsc;
        rect_dsc.bg_grad.stops[1].opa = 0;  /* Bottom: transparent */
        lv_draw_rect(base_dsc->layer, &rect_dsc, &rect_area);
    }
}
```

**Data: Tilt Magnitude**

```c
float tilt_mag = sqrtf(roll*roll + pitch*pitch);  /* 0-90 degrees */
lv_chart_set_next_value(chart, series, (int32_t)tilt_mag);
```

#### Tab 3: Scatter Chart (Roll vs Pitch)

**แนวคิด: Tilt Ball**

* เอียงบอร์ดเหมือนกระดานกลิ้งลูกบอล
* Roll = ซ้าย-ขวา, Pitch = หน้า-หลัง

```c
/* Map -90..+90 degrees → 0..200 (center = 100) */
int32_t scatter_x = (int32_t)((roll + 90.0f) * (200.0f / 180.0f));
int32_t scatter_y = (int32_t)((pitch + 90.0f) * (200.0f / 180.0f));
lv_chart_set_next_value2(chart, series, scatter_x, scatter_y);
```

#### Tab 4: Line Chart (Gyroscope)

**ใช้ Ex7 Pattern ที่ stable:**

```c
/* Range: -1500 to 1500 (proven stable) */
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -1500, 1500);

/* Scale: gyro * 100 */
int32_t gx_scaled = (int32_t)(gx * 100);
lv_chart_set_next_value(chart, series, gx_scaled);
```

#### Optimization: Update Active Tab Only

```c
/* Get active tab index */
uint32_t active_tab = lv_tabview_get_tab_active(ex11_tabview);

switch(active_tab) {
    case 0: /* Update Bar chart only */ break;
    case 1: /* Update Area chart only */ break;
    case 2: /* Update Scatter chart only */ break;
    case 3: /* Update Line chart only */ break;
}
```

**เหตุผล:** ลด CPU load เพราะไม่ต้องอัพเดท 4 charts ทุก 100ms

**Status:** ✅ Approved

***

### 7. Complementary Filter - การวิเคราะห์มุมเอียง

#### ทำไมต้องใช้ Complementary Filter?

| Sensor            | ข้อดี                    | ข้อเสีย                               |
| ----------------- | ------------------------ | ------------------------------------- |
| **Accelerometer** | วัดทิศทาง gravity ได้ตรง | มี noise และถูก vibration รบกวน       |
| **Gyroscope**     | ตอบสนองเร็ว, ไม่มี noise | Drift ตลอดเวลา (integration ของ bias) |
| **Complementary** | รวมข้อดีทั้งสอง          | ต้อง tune alpha parameter             |

#### Algorithm

```
angle = alpha * (angle + gyro_rate * dt) + (1-alpha) * accel_angle

where:
  alpha = 0.98  → trust gyro 98% (fast response)
  (1-alpha) = 0.02 → trust accel 2% (drift correction)
  dt = 0.1s → sample period (100ms timer)
```

#### การคำนวณ Accel Angle

```c
/* Roll from Accelerometer */
float roll_accel = atan2f(ay, az) * (180.0f / M_PI);

/* Pitch from Accelerometer */
float pitch_accel = atan2f(-ax, sqrtf(ay*ay + az*az)) * (180.0f / M_PI);
```

#### ข้อควรระวัง

1. **Gimbal Lock**: เมื่อ Pitch ใกล้ ±90° จะมีปัญหาในการคำนวณ Roll
2. **Initial Condition**: ต้องให้ accel angle เป็นค่าเริ่มต้น
3. **Sampling Rate**: dt ต้องตรงกับ timer interval

***

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

#### แบบฝึกหัดที่ 1: ADC Threshold Alert

* ตั้ง threshold ด้วย Slider (0-100%)
* เมื่อ ADC เกิน threshold → LED ติด + เปลี่ยนสี Bar
* แสดงสถานะ "NORMAL" / "ALERT"

#### แบบฝึกหัดที่ 2: IMU Tilt Indicator

* แสดง orientation ด้วยรูปภาพ/icon
* เปลี่ยนสีพื้นหลังตาม orientation
* เพิ่ม Gyroscope chart (angular velocity)

#### แบบฝึกหัดที่ 3: Multi-Channel ADC

* แสดง ADC หลาย channels (ถ้ามี)
* เปรียบเทียบค่าด้วย grouped bar chart
* Export data ผ่าน UART

#### แบบฝึกหัดที่ 4: Data Logger

* บันทึกข้อมูล ADC/IMU ทุก 1 วินาที
* แสดงกราฟย้อนหลัง 60 วินาที
* เพิ่มปุ่ม Start/Stop/Clear

***

### 5A. CM33-CM55 IPC Architecture (Ex7-11)

#### สถาปัตยกรรม Dual-Core

PSoC Edge E84 ใช้ dual-core architecture:

* **CM33 (Non-Secure)**: อ่าน BMI270 ผ่าน I2C, เขียนลง Shared Memory
* **CM55 (FreeRTOS + LVGL)**: อ่าน Shared Memory, แสดงผลบน Display

```
┌─────────────┐    I2C     ┌─────────────┐
│   BMI270    │◄──────────►│    CM33     │
│  IMU Sensor │            │ (I2C Read)  │
└─────────────┘            └──────┬──────┘
                                  │ Write
                           ┌──────▼──────┐
                           │   Shared    │  0x261C0000
                           │   Memory    │  (256KB)
                           └──────┬──────┘
                                  │ Read
                           ┌──────▼──────┐
                           │    CM55     │
                           │  (LVGL UI)  │
                           └─────────────┘
```

#### Shared Memory Structure (imu\_shared.h)

```c
typedef struct {
    uint32_t magic;         /* 0x1AACC00D - verification */
    uint32_t write_lock;    /* Odd=writing, Even=done */
    float accel_x, accel_y, accel_z;  /* m/s² */
    float gyro_x, gyro_y, gyro_z;     /* rad/s */
    uint32_t error_count;   /* I2C error counter */
} imu_shared_t;

#define IMU_SHARED_PTR ((volatile imu_shared_t *)0x261C0040)
```

#### Synchronization Pattern

```c
/* CM33 Writer - imu_shared_update() */
imu->write_lock++;    // Make odd (writing)
__DSB();              // Memory barrier
imu->accel_x = ax;    // Write data
imu->accel_y = ay;
imu->accel_z = az;
__DSB();
imu->write_lock++;    // Make even (done)

/* CM55 Reader - aic_imu_read_accel() */
uint32_t lock1 = imu->write_lock;
if (lock1 & 1) return false;  // Odd = skip (CM33 writing)
/* Read data */
uint32_t lock2 = imu->write_lock;
if (lock1 != lock2) return false;  // Changed = inconsistent
return true;
```
