# 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;
```


---

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