ภาพการเคลื่อนไหว 6 แกนจาก BMI270
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”- อ่าน accelerometer และ gyroscope จาก BMI270 แล้วแสดงแบบเรียลไทม์
- แยกความหมายของค่าเร่ง (g) กับค่าหมุน (°/s) และทายค่าที่ควรเห็นเมื่อวางบอร์ดนิ่ง
- ปรับอัตราการรีเฟรชหน้าจอให้เหมาะกับอัตราการอ่านเซนเซอร์
BMI270 คืออะไร และตัวเลขในสเปกหมายถึงอะไร
หัวข้อที่มีชื่อว่า “BMI270 คืออะไร และตัวเลขในสเปกหมายถึงอะไร”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 อื่น) แต่วาดจอให้ถี่พอสายตาเห็นแค่นั้นพอ
motion alert ใช้ hysteresis สองเกณฑ์ ไม่ใช่เกณฑ์เดียว
หัวข้อที่มีชื่อว่า “motion alert ใช้ hysteresis สองเกณฑ์ ไม่ใช่เกณฑ์เดียว”โค้ดมีฟีเจอร์ที่ 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 บาร์แยกแกน
build และ flash
หัวข้อที่มีชื่อว่า “build และ flash”# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/make buildmake program # flash ผ่าน KitProg3หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”
ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน
- ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
- แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
- ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”- บอร์ดวางนิ่งบนโต๊ะ แกนใดควรอ่านได้ประมาณ 1 g
- gyroscope อ่านอะไรเมื่อบอร์ดไม่หมุน
- ทำไมไม่ควรวาดจอทุกครั้งที่อ่านเซนเซอร์ได้
คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- README ของ episode · โฟลเดอร์โค้ด · commit
9a8e3ed - เปิดตัวอย่างนี้บน Developer Hub
- โค้ดเป็นของ Developer Hub และอ้างอิงด้วยลิงก์ ไม่ได้คัดลอกเข้าคลังนี้
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
วางบอร์ดนิ่งบนโต๊ะในแนวราบ ค่าที่ควรเห็นคืออะไร (เป้าหมายข้อ 2)
- accel ≈ 0 g ทุกแกน เพราะบอร์ดไม่ได้เคลื่อนที่
- accel ≈ 1 g ทุกแกนเท่ากัน
- gyro ≈ 1 °/s ที่แกนตั้ง เพราะโลกหมุน
- ขนาดของ 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 เล็กเกินกว่าจะเห็น
-
ค่า acc_mag_g ของสามตัวอย่างติดกันเป็น 1.50 → 1.30 → 1.20 g (gyro ต่ำตลอด) สถานะ ALERT หลังแต่ละตัวอย่างเป็นอย่างไร (เป้าหมายข้อ 2)
- ON → OFF → OFF
- ON → ON → OFF
- OFF → OFF → OFF
- 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 ไม่กระพริบเมื่อค่าแกว่งรอบเส้นเดียว
-
ตัวอย่างอ่านเซนเซอร์ทุก 200 ms แต่อัปเดต widget ทุก 2 ตัวอย่าง (BMI270_UI_UPDATE_DIV = 2) เหตุผลคืออะไร (เป้าหมายข้อ 3)
- ลดภาระการวาดและการกระพริบบนจอ ขณะที่ logic อย่าง ALERT ยังได้ใช้ทุกตัวอย่าง
- เพราะ BMI270 ส่งค่าใหม่ได้แค่ทุก 400 ms
- เพื่อเฉลี่ยสองตัวอย่างก่อนแสดง
- เพราะ 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 ต้องการข้อมูล
-
ถ้าเปลี่ยน BMI270_SAMPLE_PERIOD_MS เป็น 50 และคง BMI270_UI_UPDATE_DIV = 2 widget จะถูกวาดใหม่ทุกกี่มิลลิวินาที (เป้าหมายข้อ 3)
- 25 ms
- 50 ms
- 100 ms
- 200 ms
ดูเฉลย
คำตอบ: C. 100 ms
timer อ่านทุก 50 ms และวาดทุก 2 ตัวอย่าง จึงวาดทุก 100 ms (10 ครั้งต่อวินาที) ส่วนการอ่านและการตัดสิน ALERT เกิดทุก 50 ms ทั้งหมดนี้รันใน LVGL timer จึงต้องระวังไม่ให้การอ่านถี่เกินจนแย่งเวลาการวาด
-
ถ้ามีคนตั้ง BMI270 ในชิปเป็นช่วง ±4 g แต่ไม่ได้แก้ BMI270_ACC_RANGE_G (ยังเป็น 2.0) ขณะวางบอร์ดนิ่ง จอจะแสดง acc_mag_g ราวเท่าไร (เป้าหมายข้อ 1)
- 1 g
- 2 g
- 4 g
- 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://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 Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA