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

accelerometer กับมุมเอียง: roll และ pitch

โมดูล 3 — แสดงผลเซนเซอร์บน HMI · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เข้าใจว่า accelerometer วัดแรงที่ดันมวลจิ๋วในชิปไว้ ไม่ได้วัดมุม แล้วใช้เงาของแรงโน้มถ่วงบนแกนของบอร์ดกับ atan2 เปลี่ยนความเร่งสามแกนเป็น roll และ pitch โดยจำลำดับที่ dsp.tilt() คืนค่าได้แม่น

เมื่อจบบทเรียนนี้ คุณจะ:

  1. อธิบายได้ว่าทำไมบอร์ดที่วางนิ่งบนโต๊ะอ่าน az ได้ราว +9.81 m/s² แต่ตอนตกอิสระอ่านได้ราว 0 ทั้งสามแกน โดยใช้แนวคิด proper acceleration และมวลพิสูจน์บนสปริงในชิป
  2. คำนวณ roll จาก ay และ az ด้วย atan2 ได้ถูกภายใน 1 องศา (เช่น ay 4.90, az 8.49 ได้ 30°) ตรวจค่าดิบด้วยขนาด √(ax²+ay²+az²) ≈ 9.81 m/s² ก่อนเชื่อ และบอกได้ว่าทำไม yaw หาจาก accelerometer ไม่ได้
  3. เขียน 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 และบันทึกการเรียนไว้จดว่าขอบไหนของบอร์ดทีมทำให้ค่าเปลี่ยน

เปิดเมนู 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 สำหรับระบบที่ตรวจอัตโนมัติ

  1. บอร์ดวางนิ่งบนโต๊ะ ไม่ได้เคลื่อนที่ไปไหน แต่ az อ่านได้ราว +9.81 m/s² เพราะอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น
    • ข) เป็นค่า offset จากโรงงาน กดตั้งศูนย์แล้วจะเหลือ 0
    • ค) เป็นสัญญาณรบกวนทางไฟฟ้า ต้องกรองทิ้งด้วย dsp.EMA
    • ง) บอร์ดกำลังเร่งขึ้นจริงเล็กน้อยจากการสั่นของโต๊ะ
    เฉลย

    ก — accelerometer วัด proper acceleration คือแรงที่ดันมวลเล็ก ๆ ข้างในไว้ไม่ให้ตกอิสระ ถ้าปล่อยบอร์ดตกอิสระ ทั้งสามแกนจะอ่านได้ราว 0 เพราะไม่มีใครดันมวลนั้นแล้ว

  2. 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 คือค่าเรเดียนที่ลืมแปลงเป็นองศา

  3. ข้อใดถูกเกี่ยวกับการหามุมเอียงจาก accelerometer เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)

    • ก) ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้
    • ข) ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
    • ค) หมุนบอร์ดบนโต๊ะทำให้ ay เปลี่ยน จึงหา yaw จาก accelerometer ได้
    • ง) dsp.tilt() คืนสามมุม roll pitch และ yaw
    เฉลย

    ก, ข — เงาของแรงโน้มถ่วงย้ายแกนแต่ขนาดรวมคงที่ และ atan2 รับตัวตั้งกับตัวหารแยกกัน ส่วนการหมุนบนโต๊ะไม่เปลี่ยนเงาบนแกนใดเลย dsp.tilt() จึงให้แค่สองแกน ซึ่งเป็นข้อจำกัดทางฟิสิกส์ ไม่ใช่ของเฟิร์มแวร์

  4. ถ้าเขียน pitch, roll = dsp.tilt(ax, ay, az) แล้วรันเครื่องวัดระดับ จะเกิดอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) ขึ้น TypeError ทันทีเพราะลำดับผิด
    • ข) ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง
    • ค) ทำงานถูกต้อง เพราะ Python จับคู่ตามชื่อตัวแปรให้เอง
    • ง) ค่าทั้งสองแกนกลายเป็น 0
    เฉลย

    ข — dsp.tilt() คืน (roll, pitch) roll มาก่อนเสมอ การแกะสลับไม่ทำให้โปรแกรมพัง มันแค่ให้คำตอบผิดอย่างเงียบ ๆ ซึ่งเป็นบั๊กที่แพงที่สุดเพราะไม่มีใครรู้ว่ามันอยู่ตรงนั้น

  5. ทำไมโค้ดของชุดบทเรียนนี้อ่าน 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 ไหม ถ้าต้องการ จะเอามาจากเซนเซอร์ตัวไหน

คำถามทบทวน

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

  1. บอร์ดวางนิ่งบนโต๊ะ ไม่ได้เคลื่อนที่ไปไหน แต่ az อ่านได้ราว +9.81 m/s² เพราะอะไร (เป้าหมายข้อ 1)

    1. เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น
    2. เป็นค่า offset จากโรงงาน กดตั้งศูนย์แล้วจะเหลือ 0
    3. เป็นสัญญาณรบกวนทางไฟฟ้า ต้องกรองทิ้งด้วย dsp.EMA
    4. บอร์ดกำลังเร่งขึ้นจริงเล็กน้อยจากการสั่นของโต๊ะ
    ดูเฉลย

    คำตอบ: A. เซนเซอร์วัดแรงที่ตัวเรือนดันมวลพิสูจน์ไว้ไม่ให้ตกอิสระ โต๊ะดันบอร์ดขึ้น จึงอ่านได้ +1 g ในทิศขึ้น

    accelerometer วัด proper acceleration คือแรงที่ดันมวลเล็ก ๆ ข้างในไว้ไม่ให้ตกอิสระ ถ้าปล่อยบอร์ดตกอิสระ ทั้งสามแกนจะอ่านได้ราว 0 เพราะไม่มีใครดันมวลนั้นแล้ว

  2. Eva Kit ตอนยกขอบซ้ายอ่านได้ ay 4.90 และ az 8.49 m/s² roll ควรเป็นเท่าไร (เป้าหมายข้อ 2)

    1. 30.0°
    2. 60.0°
    3. 0.524°
    4. 45.0°
    ดูเฉลย

    คำตอบ: A. 30.0°

    roll = atan2(ay, az) = atan2(4.90, 8.49) = 0.524 rad คูณ 180/π ได้ 30.0° ตัวเลือก 0.524 คือค่าเรเดียนที่ลืมแปลงเป็นองศา

  3. ข้อใดถูกเกี่ยวกับการหามุมเอียงจาก accelerometer เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)

    1. ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้
    2. ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก
    3. หมุนบอร์ดบนโต๊ะทำให้ ay เปลี่ยน จึงหา yaw จาก accelerometer ได้
    4. dsp.tilt() คืนสามมุม roll pitch และ yaw
    ดูเฉลย

    คำตอบ: A. ถ้าวางนิ่ง √(ax²+ay²+az²) ควรได้ราว 9.81 m/s² ไม่ว่าจะเอียงบอร์ดแค่ไหน จึงใช้ตรวจสุขภาพเซนเซอร์ได้ · B. ใช้ atan2 เพราะแยกควอดรันต์ได้ครบสี่ และไม่ระเบิดเมื่อตัวหารเป็นศูนย์ตอนบอร์ดตั้งฉาก

    เงาของแรงโน้มถ่วงย้ายแกนแต่ขนาดรวมคงที่ และ atan2 รับตัวตั้งกับตัวหารแยกกัน ส่วนการหมุนบนโต๊ะไม่เปลี่ยนเงาบนแกนใดเลย dsp.tilt() จึงให้แค่สองแกน ซึ่งเป็นข้อจำกัดทางฟิสิกส์ ไม่ใช่ของเฟิร์มแวร์

  4. ถ้าเขียน `pitch, roll = dsp.tilt(ax, ay, az)` แล้วรันเครื่องวัดระดับ จะเกิดอะไร (เป้าหมายข้อ 3)

    1. ขึ้น TypeError ทันทีเพราะลำดับผิด
    2. ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง
    3. ทำงานถูกต้อง เพราะ Python จับคู่ตามชื่อตัวแปรให้เอง
    4. ค่าทั้งสองแกนกลายเป็น 0
    ดูเฉลย

    คำตอบ: B. ไม่มี error ตัวเลขขึ้นครบ แต่เอียงซ้าย-ขวาแล้วแถบ PITCH วิ่ง ส่วนแถบ ROLL นิ่ง

    dsp.tilt() คืน (roll, pitch) roll มาก่อนเสมอ การแกะสลับไม่ทำให้โปรแกรมพัง มันแค่ให้คำตอบผิดอย่างเงียบ ๆ ซึ่งเป็นบั๊กที่แพงที่สุดเพราะไม่มีใครรู้ว่ามันอยู่ตรงนั้น

  5. ทำไมโค้ดของชุดบทเรียนนี้อ่าน `sensors.bmi270.motion()` ครั้งเดียว แทนการเรียก `acceleration()` แล้วตามด้วย `gyroscope()` (เป้าหมายข้อ 3)

    1. motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด
    2. acceleration() ใช้ไม่ได้บน Eva Kit
    3. motion() คืนค่าเป็นองศาแล้ว จึงไม่ต้องใช้ dsp.tilt()
    4. 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 ฉบับเต็ม

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

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