accelerometer กับมุมเอียง: roll และ pitch
โมดูล 3 — แสดงผลเซนเซอร์บน HMI · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เข้าใจว่า accelerometer วัดแรงที่ดันมวลจิ๋วในชิปไว้ ไม่ได้วัดมุม แล้วใช้เงาของแรงโน้มถ่วงบนแกนของบอร์ดกับ atan2 เปลี่ยนความเร่งสามแกนเป็น roll และ pitch โดยจำลำดับที่ dsp.tilt() คืนค่าได้แม่น
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- อธิบายได้ว่าทำไมบอร์ดที่วางนิ่งบนโต๊ะอ่าน az ได้ราว +9.81 m/s² แต่ตอนตกอิสระอ่านได้ราว 0 ทั้งสามแกน โดยใช้แนวคิด proper acceleration และมวลพิสูจน์บนสปริงในชิป
- คำนวณ roll จาก ay และ az ด้วย atan2 ได้ถูกภายใน 1 องศา (เช่น ay 4.90, az 8.49 ได้ 30°) ตรวจค่าดิบด้วยขนาด √(ax²+ay²+az²) ≈ 9.81 m/s² ก่อนเชื่อ และบอกได้ว่าทำไม yaw หาจาก accelerometer ไม่ได้
- เขียน
ax, ay, az, gx, gy, gz = sensors.bmi270.motion()ตามด้วยroll, pitch = dsp.tilt(ax, ay, az)ในลำดับที่ถูก และอธิบายว่าทำไมต้องได้หกแกนจากการอ่านครั้งเดียว
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ทบทวนบทเรียน 2.7–2.9: ที่นั่นเราแปลงค่าดิบหนึ่งตัวจาก pot.read() เป็นเปอร์เซ็นต์กับโวลต์ แล้วขึ้นจอด้วย Bar + Scale + Seg7
ชุดบทเรียน 3.1–3.3 ใช้โครงหน้าจอเดิม เปลี่ยนแค่แหล่งข้อมูลเป็นค่าดิบสามตัวจาก IMU กับสูตรที่อยู่ตรงกลาง
บทเรียนนี้ยังไม่มีไฟล์โค้ดของตัวเอง เตรียมเครื่องคิดเลขที่มี atan2 และบันทึกการเรียนไว้จดว่าขอบไหนของบอร์ดทีมทำให้ค่าเปลี่ยน
- อุปกรณ์: บอร์ด Eva Kit หรือ TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE
- เรียนมาก่อน: บทเรียน 2.9 — ลงมือทำ: เกจลูกบิดกับแถบสัมผัส
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”เปิดเมนู Sensor Dashboard บนบอร์ด แล้วลองสามอย่างก่อนเข้าเนื้อหา: วางนิ่ง จะมีเส้นหนึ่ง (AZ) ค้างสูง ไม่ลงศูนย์ · ยกขอบด้านหนึ่ง เส้นนั้นลด แล้วอีกเส้น (AY) ไต่ขึ้นมาแทน · เขย่าแล้ววาง gyro พุ่งแล้วนิ่งทันที เส้นที่ค้างอยู่คือ แรงโน้มถ่วง และมันคือกุญแจของเครื่องวัดระดับที่เราจะสร้างในบทเรียน 3.3
เซนเซอร์ไม่ได้บอกมุมมาให้ เราเป็นคนคิดมันขึ้นมาจากตัวเลขดิบ ไม่มีชิปตัวไหนวัด “องศา” โดยตรง IMU วัดความเร่งกับความเร็วเชิงมุม และถ้าเราคำนวณผิดลำดับหรือผิดหน่วย โปรแกรมไม่พัง มันแค่ตอบผิดอย่างเงียบ ๆ ซึ่งเป็นบั๊กที่แพงที่สุด
accelerometer ไม่ได้วัดว่าบอร์ดเคลื่อนที่เร็วขึ้นแค่ไหน มันวัด แรงที่ตัวเรือนดันมวลเล็ก ๆ ข้างในไว้ไม่ให้ตกอิสระ (proper acceleration) โต๊ะดันบอร์ดขึ้น บอร์ดดันมวลขึ้น จึงอ่านได้ +1 g ในทิศขึ้น ส่วนตอนตกอิสระทั้งสามแกนอ่านได้ราว 0 ข้างใน BMI270 คือมวลพิสูจน์ (proof mass) บนสปริงซิลิคอน มีซี่หวีสอดสลับกับซี่หวีที่ยึดกับตัวเรือน แต่ละคู่คือตัวเก็บประจุ มวลเลื่อนเมื่อไร ความจุสองฝั่งก็ต่างกัน วงจรวัดผลต่างนั้นส่งเข้า ADC ในชิป แล้วออกมาเป็นตัวเลขบน I2C
แรงโน้มถ่วงชี้ลงเสมอด้วยขนาด 9.81 m/s² พอบอร์ดเอียง “เงา” ของมันย้ายจาก az ไปอยู่บน ay แต่ขนาดรวมคงที่
(0° ได้ az 9.81 ay 0 · 30° ได้ az 8.50 ay 4.91 · 90° ได้ az 0 ay 9.81) ก่อนเชื่อค่าใด ให้ตรวจ √(ax²+ay²+az²) ว่าได้ราว 9.81
แล้วค่อยแปลงเงาเป็นองศา: roll = atan2(ay, az) และ pitch = atan2(−ax, √(ay²+az²)) คูณ 180/π
ที่ต้องใช้ atan2 ไม่ใช่ atan เพราะมันรับตัวตั้งกับตัวหารแยกกัน จึงแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
ส่วน yaw (หมุนบอร์ดบนโต๊ะ) ไม่ทำให้เงาบนแกนไหนเปลี่ยนเลย accelerometer จึงมองไม่เห็นมัน ต้องใช้ magnetometer (บทเรียน 3.7–3.9) หรือ gyro
ในโค้ดทั้งหมดนี้คือสองบรรทัด และมีกับดักสองจุด:
dsp.tilt(ax, ay, az)คืน(roll, pitch)roll มาก่อนเสมอ ถ้าเผลอเขียนpitch, roll = ...ตามที่ตำราหลายเล่มเรียง โปรแกรมไม่ error จอขึ้นเลขครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH ดันวิ่ง ส่วนแถบ ROLL นิ่งsensors.bmi270.motion()ให้หกแกนจาก การอ่านครั้งเดียวกัน (ax ay az หน่วย m/s² · gx gy gz หน่วย deg/s) บน Eva Kit มันหยิบจาก snapshot ชุดเดียวที่คอร์จออ่านค้างไว้ บน Dev Kit CM33 จองบัส I2C อ่านหกแกนแล้วปล่อย ถ้าเรียกacceleration()แล้วค่อยgyroscope()ระหว่างสองครั้งนั้นค่าอาจเปลี่ยนชุด และถ้าบอร์ดกำลังขยับ สองค่าจะมาจากคนละท่าของบอร์ด
ข้อตกลงแกนต้องยืนยันกับบอร์ดจริงเสมอ สไลด์ใช้ Eva Kit ตอนยกขอบซ้ายเป็นตัวอย่าง (ay 4.90, az 8.49 ได้ 30.0°) แต่บน TESAIoT Dev Kit ยังไม่มีใครวัด ว่าขอบไหนของฐาน QWA309 ทำให้ roll เพิ่ม IMU ตัวเดียวกันแต่ประกอบบนบอร์ดคนละแบบ ทีม Dev Kit จึงต้องยกทีละขอบแล้วจดเอง ห้ามยกคำว่า “ขอบซ้าย” ของ Eva Kit มาใช้
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
บอร์ดวางนิ่งบนโต๊ะ ไม่ได้เคลื่อนที่ไปไหน แต่ az อ่านได้ราว +9.81 m/s² เพราะอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น
- ข) เป็นค่า offset จากโรงงาน กดตั้งศูนย์แล้วจะเหลือ 0
- ค) เป็นสัญญาณรบกวนทางไฟฟ้า ต้องกรองทิ้งด้วย dsp.EMA
- ง) บอร์ดกำลังเร่งขึ้นจริงเล็กน้อยจากการสั่นของโต๊ะ
เฉลย
ก — accelerometer วัด proper acceleration คือแรงที่ดันมวลเล็ก ๆ ข้างในไว้ไม่ให้ตกอิสระ ถ้าปล่อยบอร์ดตกอิสระ ทั้งสามแกนจะอ่านได้ราว 0 เพราะไม่มีใครดันมวลนั้นแล้ว
-
Eva Kit ตอนยกขอบซ้ายอ่านได้ ay 4.90 และ az 8.49 m/s² roll ควรเป็นเท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) 30.0°
- ข) 60.0°
- ค) 0.524°
- ง) 45.0°
เฉลย
ก — roll = atan2(ay, az) = atan2(4.90, 8.49) = 0.524 rad คูณ 180/π ได้ 30.0° ตัวเลือก 0.524 คือค่าเรเดียนที่ลืมแปลงเป็นองศา
-
ข้อใดถูกเกี่ยวกับการหามุมเอียงจาก accelerometer เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)
- ก) ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้
- ข) ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
- ค) หมุนบอร์ดบนโต๊ะทำให้ ay เปลี่ยน จึงหา yaw จาก accelerometer ได้
- ง) dsp.tilt() คืนสามมุม roll pitch และ yaw
เฉลย
ก, ข — เงาของแรงโน้มถ่วงย้ายแกนแต่ขนาดรวมคงที่ และ atan2 รับตัวตั้งกับตัวหารแยกกัน ส่วนการหมุนบนโต๊ะไม่เปลี่ยนเงาบนแกนใดเลย dsp.tilt() จึงให้แค่สองแกน ซึ่งเป็นข้อจำกัดทางฟิสิกส์ ไม่ใช่ของเฟิร์มแวร์
-
ถ้าเขียน
pitch, roll = dsp.tilt(ax, ay, az)แล้วรันเครื่องวัดระดับ จะเกิดอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)- ก) ขึ้น TypeError ทันทีเพราะลำดับผิด
- ข) ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง
- ค) ทำงานถูกต้อง เพราะ Python จับคู่ตามชื่อตัวแปรให้เอง
- ง) ค่าทั้งสองแกนกลายเป็น 0
เฉลย
ข — dsp.tilt() คืน (roll, pitch) roll มาก่อนเสมอ การแกะสลับไม่ทำให้โปรแกรมพัง มันแค่ให้คำตอบผิดอย่างเงียบ ๆ ซึ่งเป็นบั๊กที่แพงที่สุดเพราะไม่มีใครรู้ว่ามันอยู่ตรงนั้น
-
ทำไมโค้ดของชุดบทเรียนนี้อ่าน
sensors.bmi270.motion()ครั้งเดียว แทนการเรียกacceleration()แล้วตามด้วยgyroscope()(เลือกหนึ่งข้อ · เป้าหมายข้อ 3)- ก) motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด
- ข) acceleration() ใช้ไม่ได้บน Eva Kit
- ค) motion() คืนค่าเป็นองศาแล้ว จึงไม่ต้องใช้ dsp.tilt()
- ง) acceleration() คืนหน่วย g ส่วน motion() คืน m/s²
เฉลย
ก — บน Eva Kit motion() หยิบจาก snapshot ชุดเดียว บน Dev Kit CM33 จองบัสอ่านหกแกนแล้วปล่อย ข้อมูลที่จะเอาไปรวมกันต้องมาจากเวลาเดียวกัน ส่วน acceleration() ก็ใช้ได้ทั้งสองบอร์ดและคืน m/s² เหมือนกัน
หาแกนของบอร์ดทีม และคำนวณมุมด้วยมือ (ราว 15 นาที) จดทุกข้อลงบันทึกการเรียน
- ใน Sensor Dashboard ยกทีละขอบ (ซ้าย ขวา บน ล่าง) แล้วจดว่าเส้นไหนลด เส้นไหนไต่ขึ้น
- สรุปว่าขอบไหนขยับคู่ ay กับ az (ที่ roll ใช้) และขอบไหนขยับ ax (ที่ pitch ใช้) · ทีม Dev Kit เขียนชัด ๆ ว่าเป็นขอบไหนของฐาน QWA309
- ตรวจสุขภาพด้วยตัวเลขจาก Eva Kit ในสไลด์: ax 0.02, ay −0.05, az 9.79 ต้องได้ขนาดราว 9.79 m/s²
- คำนวณ atan2(4.90, 8.49) เป็นเรเดียนแล้วแปลงเป็นองศา ต้องได้ราว 0.524 rad และ 30.0°
- คำนวณ roll จากคู่ ay 8.50, az 4.91 ในตารางเงาแรงโน้มถ่วง แล้วตรวจว่าได้ราว 60°
- เขียนสองบรรทัด
motion()กับdsp.tilt()ลงบันทึกการเรียน แล้วขีดเส้นใต้ลำดับ(roll, pitch)
บทเรียน 3.2 ต่อด้วย gyro ว่าเสียคนละแบบกับ accelerometer อย่างไร ฟิลเตอร์เข้ามาช่วยตรงไหน แล้วแกะโค้ดเครื่องวัดระดับทีละท่า ถ้าอยากเข้าใจว่า “ตัวที่วางนิ่งต่างหากที่กำลังถูกเร่ง” สไลด์แนะนำคลิป Veritasium โดยเริ่มดูที่นาที 2:30
บทเรียนถัดไป: บทเรียน 3.2 — gyro ฟิลเตอร์ complementary และโค้ดเครื่องวัดระดับ
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- แรงโน้มถ่วงที่หลายงานเรียกว่าสัญญาณรบกวน ทำไมในบทเรียนนี้มันกลายเป็นสัญญาณ
- ถ้าเผลอเขียน
pitch, roll = dsp.tilt(...)คุณจะรู้ตัวได้อย่างไร ในเมื่อโปรแกรมไม่ error - งานของคุณเองต้องการ yaw ไหม ถ้าต้องการ จะเอามาจากเซนเซอร์ตัวไหน
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
บอร์ดวางนิ่งบนโต๊ะ ไม่ได้เคลื่อนที่ไปไหน แต่ az อ่านได้ราว +9.81 m/s² เพราะอะไร (เป้าหมายข้อ 1)
- เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น
- เป็นค่า offset จากโรงงาน กดตั้งศูนย์แล้วจะเหลือ 0
- เป็นสัญญาณรบกวนทางไฟฟ้า ต้องกรองทิ้งด้วย dsp.EMA
- บอร์ดกำลังเร่งขึ้นจริงเล็กน้อยจากการสั่นของโต๊ะ
ดูเฉลย
คำตอบ: A. เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น
accelerometer วัด proper acceleration คือแรงที่ดันมวลเล็ก ๆ ข้างในไว้ไม่ให้ตกอิสระ ถ้าปล่อยบอร์ดตกอิสระ ทั้งสามแกนจะอ่านได้ราว 0 เพราะไม่มีใครดันมวลนั้นแล้ว
-
Eva Kit ตอนยกขอบซ้ายอ่านได้ ay 4.90 และ az 8.49 m/s² roll ควรเป็นเท่าไร (เป้าหมายข้อ 2)
- 30.0°
- 60.0°
- 0.524°
- 45.0°
ดูเฉลย
คำตอบ: A. 30.0°
roll = atan2(ay, az) = atan2(4.90, 8.49) = 0.524 rad คูณ 180/π ได้ 30.0° ตัวเลือก 0.524 คือค่าเรเดียนที่ลืมแปลงเป็นองศา
-
ข้อใดถูกเกี่ยวกับการหามุมเอียงจาก accelerometer เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)
- ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้
- ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
- หมุนบอร์ดบนโต๊ะทำให้ ay เปลี่ยน จึงหา yaw จาก accelerometer ได้
- dsp.tilt() คืนสามมุม roll pitch และ yaw
ดูเฉลย
คำตอบ: A. ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้ · B. ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
เงาของแรงโน้มถ่วงย้ายแกนแต่ขนาดรวมคงที่ และ atan2 รับตัวตั้งกับตัวหารแยกกัน ส่วนการหมุนบนโต๊ะไม่เปลี่ยนเงาบนแกนใดเลย dsp.tilt() จึงให้แค่สองแกน ซึ่งเป็นข้อจำกัดทางฟิสิกส์ ไม่ใช่ของเฟิร์มแวร์
-
ถ้าเขียน `pitch, roll = dsp.tilt(ax, ay, az)` แล้วรันเครื่องวัดระดับ จะเกิดอะไร (เป้าหมายข้อ 3)
- ขึ้น TypeError ทันทีเพราะลำดับผิด
- ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง
- ทำงานถูกต้อง เพราะ Python จับคู่ตามชื่อตัวแปรให้เอง
- ค่าทั้งสองแกนกลายเป็น 0
ดูเฉลย
คำตอบ: B. ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง
dsp.tilt() คืน (roll, pitch) roll มาก่อนเสมอ การแกะสลับไม่ทำให้โปรแกรมพัง มันแค่ให้คำตอบผิดอย่างเงียบ ๆ ซึ่งเป็นบั๊กที่แพงที่สุดเพราะไม่มีใครรู้ว่ามันอยู่ตรงนั้น
-
ทำไมโค้ดของชุดบทเรียนนี้อ่าน `sensors.bmi270.motion()` ครั้งเดียว แทนการเรียก `acceleration()` แล้วตามด้วย `gyroscope()` (เป้าหมายข้อ 3)
- motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด
- acceleration() ใช้ไม่ได้บน Eva Kit
- motion() คืนค่าเป็นองศาแล้ว จึงไม่ต้องใช้ dsp.tilt()
- acceleration() คืนหน่วย g ส่วน motion() คืน m/s²
ดูเฉลย
คำตอบ: A. motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด
บน Eva Kit motion() หยิบจาก snapshot ชุดเดียว บน Dev Kit CM33 จองบัสอ่านหกแกนแล้วปล่อย ข้อมูลที่จะเอาไปรวมกันต้องมาจากเวลาเดียวกัน ส่วน acceleration() ก็ใช้ได้ทั้งสองบอร์ดและคืน m/s² เหมือนกัน
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"accelerometer กับมุมเอียง: roll และ pitch" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "The accelerometer and tilt: roll and pitch" 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/aiot-micropython/m03-sensor-hmi/l01-accelerometer-tilt/
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-06.html (slides 1–12)
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA