ข้ามไปยังเนื้อหา

ภาพการเคลื่อนไหว 6 แกนจาก BMI270

  1. อ่าน accelerometer และ gyroscope จาก BMI270 แล้วแสดงแบบเรียลไทม์
  2. แยกความหมายของค่าเร่ง (g) กับค่าหมุน (°/s) และทายค่าที่ควรเห็นเมื่อวางบอร์ดนิ่ง
  3. ปรับอัตราการรีเฟรชหน้าจอให้เหมาะกับอัตราการอ่านเซนเซอร์

BMI270 เป็น IMU (Inertial Measurement Unit) 6 แกนของ Bosch มี accelerometer วัดความเร่งได้ 3 แกน (เลือกช่วงได้ ±2g/±4g/±8g/±16g ความละเอียด 16-bit) และ gyroscope วัดอัตราการหมุนได้ 3 แกน (±125 ถึง ±2000 องศา/วินาที) episode นี้ตั้งค่าเริ่มต้นของไลบรารี mtb_bmi270_config_default() ไว้ตามที่มากับไลบรารีคือ ±2g และ ±2000 dps (ค่ากว้าง ที่สุดของแต่ละเซนเซอร์ ไม่ใช่ ±4g/±500dps ตามที่ระบุใน README ต้นทาง) เหมาะกับการสอนเพราะรับค่าการเขย่าแรง ๆ ได้ โดยไม่ clip แต่แลกกับความละเอียดต่อบิตที่หยาบกว่าถ้าเลือกช่วงแคบกว่า

poll ทุก 200 ms ด้วย lv_timer เดียว เหมือน DPS368 — ไม่ใช่ 100 Hz ผ่าน FreeRTOS task

หัวข้อที่มีชื่อว่า “poll ทุก 200 ms ด้วย lv_timer เดียว เหมือน DPS368 — ไม่ใช่ 100 Hz ผ่าน FreeRTOS task”

เหมือนบทเรียน 3.1 (DPS368) episode นี้ก็ใช้ lv_timer_create(bmi270_poll_sensor_cb, BMI270_SAMPLE_PERIOD_MS, NULL) บน LVGL thread เดียว โดย BMI270_SAMPLE_PERIOD_MS = 200 (5 ครั้ง/วินาที) — ไม่ใช่ทุก 10 ms (100 Hz) ผ่าน FreeRTOS task + queue + lv_async_call() ตามที่ README ต้นทางอธิบาย คอมเมนต์ในซอร์สบอกเหตุผลตรง ๆ ว่า “Poll slower to reduce redraw pressure on small HMI panel” — จอเล็กวาดถี่เกินไปจะกระตุกและกินทรัพยากรโดยไม่จำเป็น

อ่านทุกรอบ แต่วาดจอแค่บางรอบ: การ throttle ที่แยกจากการอ่าน

หัวข้อที่มีชื่อว่า “อ่านทุกรอบ แต่วาดจอแค่บางรอบ: การ throttle ที่แยกจากการอ่าน”

bmi270_config.h มีค่าคงที่อีกตัวคือ BMI270_UI_UPDATE_DIV = 2 — sensor callback อ่านค่าทุก 200 ms เหมือนเดิม (และเอาไปตัดสิน ALERT ทุกรอบ) แต่จะเรียก bmi270_view_update_sample() วาด widget ใหม่แค่ ทุก 2 ตัวอย่าง เท่ากับวาดจอทุก 400 ms เท่านั้น เหตุผลคือ “Update LVGL widgets every N samples to reduce visible flicker” — การอ่าน เซนเซอร์กับการวาดจอไม่จำเป็นต้องคาบเดียวกันเสมอไป ถ้าอ่านถี่กว่าที่ตาคนแยกความต่างได้ ให้อ่านไปเก็บไว้ (เพื่อ log หรือ trigger logic อื่น) แต่วาดจอให้ถี่พอสายตาเห็นแค่นั้นพอ

โค้ดมีฟีเจอร์ที่ README ต้นทางไม่ได้พูดถึงเลยคือ MOTION ALERT — ถ้า magnitude ของ accel หรือ gyro เกิน threshold จะเปิด alert label บนจอ แต่ threshold เปิด (BMI270_ALERT_ACC_ON_G = 1.45g, BMI270_ALERT_GYR_ON_DPS = 280) กับ threshold ปิด (BMI270_ALERT_ACC_OFF_G = 1.25g, BMI270_ALERT_GYR_OFF_DPS = 220) เป็นคนละค่ากัน — เรียกว่า hysteresis ถ้าใช้เกณฑ์เดียวและค่าที่อ่านได้แกว่งอยู่รอบ ๆ เกณฑ์นั้นพอดี (เช่น สั่นเบา ๆ รอบ 1.4g) alert จะ กระพริบ ON/OFF ถี่ยิบ การมีสองเกณฑ์ทำให้ต้องลดลงมาต่ำกว่าเกณฑ์ปิดจริง ๆ ก่อน alert ถึงจะดับ

หน้าจอแสดง “ขนาดรวม” ของแต่ละเซนเซอร์ ไม่ใช่ 6 บาร์แยกแกน

หัวข้อที่มีชื่อว่า “หน้าจอแสดง “ขนาดรวม” ของแต่ละเซนเซอร์ ไม่ใช่ 6 บาร์แยกแกน”

bmi270_view.c สร้าง บาร์แค่ 2 อัน (accel กับ gyro) ช่วง 0–350 บวก chart เส้นเดียวที่มี 2 series (accel สีเขียว, gyro สีส้ม) ไม่ใช่ 6 บาร์แยกแกน x/y/z ตามที่ README ต้นทางอธิบาย — ค่าที่แสดงคือขนาดรวม (acc_mag_g, gyr_mag_dps) ซึ่งคำนวณจาก √(x²+y²+z²) ของทั้งสามแกน ไม่ใช่ค่าดิบต่อแกน (ค่าต่อแกนยังถูก log ผ่าน serial แต่ไม่ได้เอาขึ้นจอ)

โค้ดของ episode นี้อยู่ใน Developer Hub (อ้างอิงที่ commit 9a8e3ed) — อ่าน Why ของ README ต้นทาง เพื่อเข้าใจจุดประสงค์ แต่ โค้ดตัวอย่างด้านล่างคัดลอกจากไฟล์จริง (Apache-2.0, tesaiot/developer-hub, commit เดียวกัน) เพราะอัตรา poll, ช่วงวัด และ widget บนจอต่างจากที่ README ต้นทางอธิบาย

app_sensor/bmi270/bmi270_config.h — ค่าคงที่ตัวจริง:

/* Poll slower to reduce redraw pressure on small HMI panel. */
#define BMI270_SAMPLE_PERIOD_MS (200U)
/* Update LVGL widgets every N samples to reduce visible flicker. */
#define BMI270_UI_UPDATE_DIV (2U)
/* mtb_bmi270_config_default() uses ACC=+-2g and GYR=+-2000dps by default. */
#define BMI270_ACC_RANGE_G (2.0f)
#define BMI270_GYR_RANGE_DPS (2000.0f)
/* Motion thresholds with hysteresis to avoid ON/OFF toggling noise. */
#define BMI270_ALERT_ACC_ON_G (1.45f)
#define BMI270_ALERT_ACC_OFF_G (1.25f)
#define BMI270_ALERT_GYR_ON_DPS (280.0f)
#define BMI270_ALERT_GYR_OFF_DPS (220.0f)

app_ui/bmi270/bmi270_presenter.c — hysteresis สองเกณฑ์สำหรับ alert:

static bool bmi270_should_alert(const bmi270_sample_t *sample, bool is_alert_active)
{
if (is_alert_active)
{
bool below_acc = (sample->acc_mag_g < BMI270_ALERT_ACC_OFF_G);
bool below_gyr = (sample->gyr_mag_dps < BMI270_ALERT_GYR_OFF_DPS);
return !(below_acc && below_gyr);
}
return ((sample->acc_mag_g >= BMI270_ALERT_ACC_ON_G) ||
(sample->gyr_mag_dps >= BMI270_ALERT_GYR_ON_DPS));
}

การอ่านทุกรอบแต่วาดจอแค่บางรอบ:

s_ui_update_div_counter++;
if ((s_ui_update_div_counter >= BMI270_UI_UPDATE_DIV) ||
(sample.sample_count <= 1U))
{
bmi270_view_update_sample(&sample);
s_ui_update_div_counter = 0U;
}
  • main_example.c ส่ง I2C handle เข้า bmi270_presenter_start() ตรงตามที่ README ต้นทางอธิบาย
  • app_sensor/bmi270/bmi270_driver.c — ลำดับ bootstrap (CHIP_ID, soft-reset, อัปโหลด config ~8 KB) ตรงตามที่ README ต้นทางอธิบาย เฉพาะ BMI270 เท่านั้นที่ต้องทำขั้นนี้ (BMI160/BMI088 ไม่ต้อง)
  • ดูโฟลเดอร์เต็มที่ int_ep02_bmi270_motion_visual/
  • จำอัตรา poll ผิดเป็น 100 Hz ผ่าน FreeRTOS task — โค้ดจริง poll ทุก 200 ms ด้วย lv_timer เดียวบน LVGL thread เท่านั้น
  • คิดว่าการอ่านเซนเซอร์กับการวาดจอต้องคาบเดียวกัน — โค้ดแยกสองอย่างออกจากกันด้วย BMI270_UI_UPDATE_DIV อ่านทุกรอบ (เพื่อ log และ alert) แต่วาดจอห่างกว่า
  • ใช้ threshold เดียวสำหรับ alert — ถ้าไม่มี hysteresis (เกณฑ์เปิด/ปิดต่างกัน) alert จะกระพริบเมื่อค่าที่วัด ได้แกว่งอยู่รอบเกณฑ์พอดี
  • คิดว่าจอแสดงค่าต่อแกน x/y/z — จอแสดงแค่ขนาดรวม (magnitude) ของ accel และ gyro สองบาร์ ไม่ใช่ 6 บาร์แยกแกน
Terminal window
# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)
# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/
# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/
make build
make program # flash ผ่าน KitProg3

หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป

หน้าจอของ EP02 — BMI270 Motion Visual บน TESAIoT Dev Kit

ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน

  1. ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
  2. แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
  3. ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
  • บอร์ดวางนิ่งบนโต๊ะ แกนใดควรอ่านได้ประมาณ 1 g
  • gyroscope อ่านอะไรเมื่อบอร์ดไม่หมุน
  • ทำไมไม่ควรวาดจอทุกครั้งที่อ่านเซนเซอร์ได้

คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. วางบอร์ดนิ่งบนโต๊ะในแนวราบ ค่าที่ควรเห็นคืออะไร (เป้าหมายข้อ 2)

    1. accel ≈ 0 g ทุกแกน เพราะบอร์ดไม่ได้เคลื่อนที่
    2. accel ≈ 1 g ทุกแกนเท่ากัน
    3. gyro ≈ 1 °/s ที่แกนตั้ง เพราะโลกหมุน
    4. ขนาดของ accel (acc_mag_g) ≈ 1 g ส่วนใหญ่อยู่บนแกนที่ตั้งฉากกับแผ่นบอร์ด และ gyro ทั้งสามแกนใกล้ 0 °/s
    ดูเฉลย

    คำตอบ: D. ขนาดของ accel (acc_mag_g) ≈ 1 g ส่วนใหญ่อยู่บนแกนที่ตั้งฉากกับแผ่นบอร์ด และ gyro ทั้งสามแกนใกล้ 0 °/s

    accelerometer วัดแรงที่กระทำต่อชิปในหน่วย g ขณะวางนิ่งจึงเห็นแรงโน้มถ่วง 1 g ชี้ตั้งฉากกับพื้นโต๊ะ ส่วน gyroscope วัดอัตราการหมุนในหน่วย °/s บอร์ดไม่หมุนจึงใกล้ 0 เหลือแค่ offset เล็กน้อย การหมุนของโลกราว 0.004 °/s เล็กเกินกว่าจะเห็น

  2. ค่า acc_mag_g ของสามตัวอย่างติดกันเป็น 1.50 → 1.30 → 1.20 g (gyro ต่ำตลอด) สถานะ ALERT หลังแต่ละตัวอย่างเป็นอย่างไร (เป้าหมายข้อ 2)

    1. ON → OFF → OFF
    2. ON → ON → OFF
    3. OFF → OFF → OFF
    4. ON → ON → ON
    ดูเฉลย

    คำตอบ: B. ON → ON → OFF

    bmi270_should_alert() ใช้ hysteresis: เปิดเมื่อ ≥ BMI270_ALERT_ACC_ON_G (1.45 g) แต่ปิดเมื่อ < BMI270_ALERT_ACC_OFF_G (1.25 g) และ gyro ต่ำกว่า 220 °/s ด้วย 1.30 อยู่ระหว่างสองเกณฑ์จึงยัง ON การมีสองเกณฑ์ทำให้ ALERT ไม่กระพริบเมื่อค่าแกว่งรอบเส้นเดียว

  3. ตัวอย่างอ่านเซนเซอร์ทุก 200 ms แต่อัปเดต widget ทุก 2 ตัวอย่าง (BMI270_UI_UPDATE_DIV = 2) เหตุผลคืออะไร (เป้าหมายข้อ 3)

    1. ลดภาระการวาดและการกระพริบบนจอ ขณะที่ logic อย่าง ALERT ยังได้ใช้ทุกตัวอย่าง
    2. เพราะ BMI270 ส่งค่าใหม่ได้แค่ทุก 400 ms
    3. เพื่อเฉลี่ยสองตัวอย่างก่อนแสดง
    4. เพราะ I2C ต้องพักระหว่างการอ่าน
    ดูเฉลย

    คำตอบ: A. ลดภาระการวาดและการกระพริบบนจอ ขณะที่ logic อย่าง ALERT ยังได้ใช้ทุกตัวอย่าง

    comment ใน bmi270_config.h เขียนว่า Poll slower to reduce redraw pressure และ Update LVGL widgets every N samples to reduce visible flicker โค้ดไม่ได้เฉลี่ยค่า แค่ข้ามการวาด ตาคนไม่ต้องการภาพใหม่ถี่เท่าที่ logic ต้องการข้อมูล

  4. ถ้าเปลี่ยน BMI270_SAMPLE_PERIOD_MS เป็น 50 และคง BMI270_UI_UPDATE_DIV = 2 widget จะถูกวาดใหม่ทุกกี่มิลลิวินาที (เป้าหมายข้อ 3)

    1. 25 ms
    2. 50 ms
    3. 100 ms
    4. 200 ms
    ดูเฉลย

    คำตอบ: C. 100 ms

    timer อ่านทุก 50 ms และวาดทุก 2 ตัวอย่าง จึงวาดทุก 100 ms (10 ครั้งต่อวินาที) ส่วนการอ่านและการตัดสิน ALERT เกิดทุก 50 ms ทั้งหมดนี้รันใน LVGL timer จึงต้องระวังไม่ให้การอ่านถี่เกินจนแย่งเวลาการวาด

  5. ถ้ามีคนตั้ง BMI270 ในชิปเป็นช่วง ±4 g แต่ไม่ได้แก้ BMI270_ACC_RANGE_G (ยังเป็น 2.0) ขณะวางบอร์ดนิ่ง จอจะแสดง acc_mag_g ราวเท่าไร (เป้าหมายข้อ 1)

    1. 1 g
    2. 2 g
    3. 4 g
    4. 0.5 g
    ดูเฉลย

    คำตอบ: D. 0.5 g

    bmi270_lsb_to_g() แปลงค่าดิบด้วย val × range / 2^(bits−1) โดยใช้ BMI270_ACC_RANGE_G ซึ่งต้องตรงกับค่าที่ตั้งในชิป (mtb_bmi270_config_default() ใช้ ±2 g) ที่ ±4 g ค่าดิบของ 1 g จะเหลือครึ่งหนึ่ง เมื่อคูณด้วย 2.0 จึงได้ราว 0.5 g ค่าคงที่ในโค้ดกับการตั้งค่าในชิปต้องเปลี่ยนคู่กันเสมอ

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ภาพการเคลื่อนไหว 6 แกนจาก BMI270" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Six-axis motion from the BMI270" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/tesaiot-firmware-stack/m03-interactive-sensors/l02-bmi270-motion-visual/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/9a8e3ed1d813bfd67fabf6b7ac15c6ff9750b465/int_ep02_bmi270_motion_visual · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA