Skip to content

The accelerometer and tilt: roll and pitch

Module 3 — Sensor Visualization on HMI · Slides: slides.md · Module overview · Course page

Understand that an accelerometer measures the force holding a tiny proof mass in the chip, not an angle, then turn three acceleration axes into roll and pitch through gravity’s projection onto the board’s axes and atan2, remembering the order dsp.tilt() returns its values in.

By the end of this lesson you will be able to:

  1. Explain why a board lying still reads az of about +9.81 m/s² yet reads about 0 on all three axes in free fall, using proper acceleration and the spring-mounted proof mass inside the chip
  2. Compute roll from ay and az with atan2 to within 1 degree (e.g. ay 4.90, az 8.49 gives 30°), sanity-check the raw values with the magnitude √(ax²+ay²+az²) ≈ 9.81 m/s² first, and state why yaw cannot come from the accelerometer
  3. Write ax, ay, az, gx, gy, gz = sensors.bmi270.motion() followed by roll, pitch = dsp.tilt(ax, ay, az) in the right order, and explain why the six axes must come from a single read

Review lessons 2.7–2.9: there we turned one raw value from pot.read() into a percentage and voltage, then put it on screen with Bar + Scale + Seg7. Lessons 3.1–3.3 use the same screen layout — only the data source changes, to three raw values from the IMU, with a formula sitting in the middle. This lesson has no code file of its own yet. Have a calculator with atan2 ready, and your learning log to record which edge of your team’s board makes the value change.

Open the Sensor Dashboard menu on the board, and try three things before diving into the content: lying flat, one line (AZ) stays high and never reaches zero; lift one edge, that line drops and another line (AY) climbs to take its place; shake it then set it down, and the gyro spikes then settles at once. The line that stays up is gravity, and it is the key to the digital level we build in lesson 3.3.

A sensor never tells you an angle — we work it out ourselves from raw numbers. No chip measures “degrees” directly. An IMU measures acceleration and angular velocity, and if we compute in the wrong order or the wrong unit, the program does not crash — it just answers wrong, quietly, which is the most expensive kind of bug.

An accelerometer does not measure how fast the board is speeding up. It measures the force the housing uses to hold a tiny mass inside it from falling freely (proper acceleration). The table pushes the board up, the board pushes the mass up, so it reads +1 g pointing up; during free fall, all three axes read about 0. Inside the BMI270 is a proof mass on a silicon spring, with comb fingers interleaved with fingers fixed to the housing — each pair is a capacitor. Whenever the mass shifts, the capacitance on the two sides differs; a circuit measures that difference and feeds it to an ADC inside the chip, which comes out as a number over I2C.

Gravity always points down with a magnitude of 9.81 m/s². As the board tilts, its “shadow” moves from az onto ay, but the combined magnitude stays constant (0° gives az 9.81, ay 0 · 30° gives az 8.50, ay 4.91 · 90° gives az 0, ay 9.81). Before trusting any value, check that √(ax²+ay²+az²) comes out to about 9.81, then convert the shadow to degrees: roll = atan2(ay, az) and pitch = atan2(−ax, √(ay²+az²)), multiplied by 180/π. We use atan2, not atan, because it takes the numerator and denominator separately, so it can tell all four quadrants apart and never blows up when the denominator is zero at a right angle. Yaw (spinning the board flat on the table) never changes the shadow on any axis at all, so the accelerometer cannot see it — you need a magnetometer (lessons 3.7–3.9) or the gyro.

All of this is two lines of code, with two traps:

  • dsp.tilt(ax, ay, az) returns (roll, pitch) — roll always comes first. If you accidentally write pitch, roll = ..., the order many textbooks use, the program does not error; the screen shows numbers in full, but tilting left-right makes the PITCH bar move while the ROLL bar sits still
  • sensors.bmi270.motion() gives all six axes from the same single read (ax, ay, az in m/s²; gx, gy, gz in deg/s). On the Eva Kit it picks up one snapshot held by the display core; on the Dev Kit, CM33 reserves the I2C bus, reads six axes, then releases it. If you call acceleration() and then gyroscope(), the value could belong to a different set between the two calls, and if the board is moving, the two would come from different moments in its motion

The axis convention must always be confirmed against the real board. The slides use the Eva Kit lifting its left edge as the example (ay 4.90, az 8.49 gives 30.0°), but on the TESAIoT Dev Kit, nobody has measured yet which edge of the QWA309 base makes roll increase — the same IMU chip, mounted on a differently built board. Dev Kit teams must lift each edge and record it themselves — never borrow the Eva Kit’s “left edge” wording.

The same questions are in quiz.yaml for automatic marking.

  1. A board lies still on the table, not moving anywhere, yet az reads about +9.81 m/s². Why? (choose one · objective 1)

    • A) The sensor measures the force the housing uses to hold the proof mass from falling freely; the table pushes the board up, so it reads +1 g pointing up
    • B) It is a factory offset; zeroing it would leave 0
    • C) It is electrical noise that must be filtered out with dsp.EMA
    • D) The board is genuinely accelerating slightly upward from the table vibrating
    Solution

    A — An accelerometer measures proper acceleration, the force that holds a small mass inside from falling freely. If the board were released into free fall, all three axes would read about 0, because nothing is holding that mass any more.

  2. On the Eva Kit, lifting the left edge reads ay 4.90 and az 8.49 m/s². What should roll be? (choose one · objective 2)

    • A) 30.0°
    • B) 60.0°
    • C) 0.524°
    • D) 45.0°
    Solution

    A — roll = atan2(ay, az) = atan2(4.90, 8.49) = 0.524 rad, multiplied by 180/π gives 30.0°. The 0.524 choice is the radian value left unconverted to degrees.

  3. Which statements about finding tilt from an accelerometer are correct? Choose every correct one. (choose all that apply · objective 2)

    • A) Lying still, √(ax²+ay²+az²) should give about 9.81 m/s² no matter how the board is tilted, so it can be used as a sensor health check
    • B) atan2 is used because it can tell all four quadrants apart and never blows up when the denominator is zero at a right angle
    • C) Spinning the board flat on the table changes ay, so yaw can be found from the accelerometer
    • D) dsp.tilt() returns three angles: roll, pitch and yaw
    Solution

    A, B — Gravity’s shadow moves between axes but the combined magnitude stays constant, and atan2 takes the numerator and denominator separately. Spinning flat on the table changes the shadow on no axis at all, which is why dsp.tilt() gives only two axes — a limit of physics, not of the firmware.

  4. If you write pitch, roll = dsp.tilt(ax, ay, az) and run the level, what happens? (choose one · objective 3)

    • A) It raises TypeError immediately, because the order is wrong
    • B) No error at all; the numbers appear in full, but tilting left-right makes the PITCH bar move while the ROLL bar sits still
    • C) It works correctly, because Python matches by variable name automatically
    • D) Both angle values become 0
    Solution

    B — dsp.tilt() returns (roll, pitch), with roll always first. Swapping the unpacking order does not crash the program — it just gives a wrong answer quietly, which is the most expensive kind of bug, because nobody knows it is sitting there.

  5. Why does this set of lessons’ code read sensors.bmi270.motion() once, instead of calling acceleration() followed by gyroscope()? (choose one · objective 3)

    • A) motion() gives all six axes from the same single read; reading them separately while the board moves risks accel and gyro coming from different moments of its motion
    • B) acceleration() does not work on the Eva Kit
    • C) motion() already returns degrees, so dsp.tilt() is unnecessary
    • D) acceleration() returns units of g, while motion() returns m/s²
    Solution

    A — On the Eva Kit, motion() picks up one single snapshot; on the Dev Kit, CM33 reserves the bus, reads six axes, then releases it. Data you plan to combine must come from the same moment. acceleration() also works on both boards and returns m/s² the same way.

Find your team’s board’s axes, and compute an angle by hand (about 15 minutes). Record every item in your learning log.

  • In Sensor Dashboard, lift each edge in turn (left, right, top, bottom) and record which line drops and which climbs
  • Sum up which edge moves the ay and az pair (used by roll) and which edge moves ax (used by pitch). Dev Kit teams, write clearly which edge of the QWA309 base this is
  • Sanity-check with the Eva Kit numbers from the slides: ax 0.02, ay −0.05, az 9.79 must give a magnitude of about 9.79 m/s²
  • Compute atan2(4.90, 8.49) in radians, then convert to degrees — you should get about 0.524 rad and 30.0°
  • Compute roll from the pair ay 8.50, az 4.91 in the gravity-shadow table, and check that it gives about 60°
  • Write the two lines motion() and dsp.tilt() in your learning log, and underline the (roll, pitch) order

Lesson 3.2 continues with the gyro: how it fails in a different way from the accelerometer, where a filter helps, then it takes apart the level code move by move. If you want to understand “it is the thing sitting still that is really being accelerated”, the slides recommend a Veritasium clip, starting at 2:30.

Next lesson: Lesson 3.2 — Gyro, the complementary filter and the level code

  • Gravity is what many jobs call noise. Why does it become the signal in this lesson?
  • If you accidentally wrote pitch, roll = dsp.tilt(...), how would you notice, given that the program raises no error?
  • Does your own work need yaw? If it does, which sensor would you get it from?

Review questions

Answer on your own first, then open the answer.

  1. The board lies still on the desk and is not moving, yet az reads about +9.81 m/s². Why? (Objective 1)

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

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

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

  2. With its left edge raised, an Eva Kit reads ay 4.90 and az 8.49 m/s². What should roll be? (Objective 2)

    1. 30.0°
    2. 60.0°
    3. 0.524°
    4. 45.0°
    Show answer

    Answer: A. 30.0°

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

  3. Which statements about finding tilt from the accelerometer are true? Choose all that apply. (Objective 2)

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

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

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

  4. If you write `pitch, roll = dsp.tilt(ax, ay, az)` and run the level, what happens? (Objective 3)

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

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

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

  5. Why does this lesson's code read `sensors.bmi270.motion()` once instead of calling `acceleration()` and then `gyroscope()`? (Objective 3)

    1. motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด
    2. acceleration() ใช้ไม่ได้บน Eva Kit
    3. motion() คืนค่าเป็นองศาแล้ว จึงไม่ต้องใช้ dsp.tilt()
    4. acceleration() คืนหน่วย g ส่วน motion() คืน m/s²
    Show answer

    Answer: A. motion() ได้หกแกนจากการอ่านครั้งเดียวกัน ถ้าอ่านแยกสองครั้งขณะบอร์ดขยับ accel กับ gyro อาจมาจากคนละท่าของบอร์ด

    บน Eva Kit motion() หยิบจาก snapshot ชุดเดียว บน Dev Kit CM33 จองบัสอ่านหกแกนแล้วปล่อย ข้อมูลที่จะเอาไปรวมกันต้องมาจากเวลาเดียวกัน ส่วน acceleration() ก็ใช้ได้ทั้งสองบอร์ดและคืน m/s² เหมือนกัน

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"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

Thai attribution: "accelerometer กับมุมเอียง: roll และ pitch" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/aiot-micropython/m03-sensor-hmi/l01-accelerometer-tilt/

This lesson adapts the source below; keep its credit too.
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-06.html (slides 1–12)

Full guide: how to cite TESA

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

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA