diff --git a/exercises/21_six_axis/Discussion.md b/exercises/21_six_axis/Discussion.md new file mode 100644 index 0000000..3a50861 --- /dev/null +++ b/exercises/21_six_axis/Discussion.md @@ -0,0 +1,296 @@ + + +```markdown + + +# Exercise 21 — Six-Axis IMU Characterization + +## Overview + +Exercise 21 establishes a foundational understanding of the six-axis inertial measurement unit (IMU) on the T-Beam Supreme. The IMU (QMI8658) provides: + +- **Accelerometer** (X, Y, Z) — measures acceleration, including gravity +- **Gyroscope** (X, Y, Z) — measures angular velocity + +This exercise focuses on interpreting **accelerometer data** to determine device orientation relative to gravity. + +--- + +## Objective + +The goals of this exercise are: + +1. Demonstrate that IMU outputs are **frame-dependent but physically consistent** +2. Show that **startup orientation does not define the IMU axes** +3. Compute **roll and pitch** from raw accelerometer data +4. Quantify real-world deviations from ideal values +5. Identify sources of measurement error + +--- + +## Test Setup + +Two static orientations of the device were evaluated: + +### Orientation A — Sideways + +Device resting on its side (edge of AlleyCat case) + +![Sideways Orientation](./img/sideways_DSC_5195.webp) + +Measured accelerometer values: + +``` + +Ax = -0.951 +Ay = -0.043 +Az = -0.003 + +``` + +--- + +### Orientation B — Upright + +Device standing upright (antenna vertical) + +![Upright Orientation](img/upright_DSC_5194.webp) + +Measured accelerometer values: + +``` + +Ax = 0.028 +Ay = 0.987 +Az = 0.012 + +``` + +--- + +## Methodology + +Roll and pitch were computed using standard inertial navigation formulas: + +``` + +roll = atan2(Az, Ay) +pitch = atan2(-Ax, sqrt(Ay² + Az²)) + +``` + +These equations derive orientation from the **gravity vector projection** onto the sensor axes. + +--- + +## Implementation + +A Perl script was used to compute roll and pitch: + +``` + +scripts/imu_roll_pitch_demo.pl + +``` + +This script: + +- Accepts predefined accelerometer values +- Computes roll and pitch in radians and degrees +- Uses Perl’s built-in `atan2()` for accurate quadrant handling + +--- + +## Results + +### Sideways Orientation + +``` + +roll = -176.009° +pitch = 87.405° + +``` + +Interpretation: + +- Pitch ≈ 90° → confirms device is rotated onto its side +- Roll near ±180° is expected due to sign conventions when vertical + +--- + +### Upright Orientation + +``` + +roll = 0.697° +pitch = -1.625° + +``` + +Interpretation: + +- Both values near 0° → device is nearly level +- Small deviations indicate real-world imperfections + +--- + +## Key Findings + +### 1. IMU Axes Are Fixed + +The IMU coordinate system is defined by the sensor hardware and PCB layout: + +- It does **not change at startup** +- It is independent of how the device is oriented when powered on + +--- + +### 2. Orientation Is Derived from Gravity + +The accelerometer measures the gravity vector: + +- Different orientations produce different raw values +- The underlying physics is consistent + +--- + +### 3. Roll and Pitch Are Orientation-Invariant + +While raw values differ between orientations: + +- Computed roll/pitch reflect true physical orientation +- Results are consistent regardless of startup pose + +--- + +## Sources of Error + +The observed deviations (~1–2°) are expected and arise from several factors: + +### Surface Imperfection + +- Table or support surface not perfectly level +- Enclosure geometry (AlleyCat case) introduces tilt + +--- + +### Sensor Bias (Offset) + +- Example: Ax ≠ 0 when it should be +- Typical of MEMS sensors + +--- + +### Scale Error + +Measured magnitude: + +``` + +|A| ≈ 0.988 g (ideal = 1.000 g) + +``` + +Indicates slight gain inaccuracy. + +--- + +### Axis Misalignment + +- Sensor axes are not perfectly orthogonal +- Manufacturing tolerances introduce small angular errors + +--- + +### Noise and Quantization + +- Finite precision (3 decimal places) +- Minor fluctuations expected + +--- + +## Limitations of Exercise 21 + +This exercise deliberately omits several important elements: + +### No Absolute Heading + +- Without a magnetometer, yaw (heading) is undefined +- Only roll and pitch can be determined + +--- + +### No Sensor Fusion + +- Gyroscope data is not integrated +- No filtering (e.g., complementary or Kalman) + +--- + +### No Calibration + +- Raw sensor values are used directly +- Bias and scale errors are uncorrected + +--- + +### Static Analysis Only + +- No dynamic motion analysis +- Gyroscope output not utilized + +--- + +## Significance + +Exercise 21 provides a critical baseline: + +- Confirms correct IMU operation +- Establishes device coordinate frame +- Demonstrates physical interpretation of accelerometer data +- Quantifies real-world sensor error + +This forms the foundation for: + +- Magnetometer integration (Exercise 22) +- Tilt-compensated compass +- Full sensor fusion (AHRS) + +--- + +## Conclusion + +The IMU behaves as expected: + +- Raw outputs vary with orientation +- Derived angles correctly reflect physical pose +- Measured errors are consistent with typical MEMS performance + +Most importantly: + +> The IMU does not define orientation — it measures vectors. +> Orientation is derived through mathematical interpretation of those vectors. + +--- + +## Next Steps + +Exercise 22 will extend this work by introducing: + +- Magnetometer (QMC6310) +- Absolute heading (yaw) +- Tilt compensation using roll and pitch + +--- + +## References + +- Perl Script: `scripts/imu_roll_pitch_demo.pl` +- Images: + - `img/upright_DSC_5194.webp` + - `img/sideways_DSC_5195.webp` + +--- +``` + diff --git a/exercises/21_six_axis/img/sideways_DSC_5195.webp b/exercises/21_six_axis/img/sideways_DSC_5195.webp new file mode 100644 index 0000000..d233218 Binary files /dev/null and b/exercises/21_six_axis/img/sideways_DSC_5195.webp differ diff --git a/exercises/21_six_axis/img/upright_DSC_5194.webp b/exercises/21_six_axis/img/upright_DSC_5194.webp new file mode 100644 index 0000000..8e21a14 Binary files /dev/null and b/exercises/21_six_axis/img/upright_DSC_5194.webp differ diff --git a/exercises/21_six_axis/scripts/imu_roll_pitch_demo.pl b/exercises/21_six_axis/scripts/imu_roll_pitch_demo.pl new file mode 100755 index 0000000..dfd3cb3 --- /dev/null +++ b/exercises/21_six_axis/scripts/imu_roll_pitch_demo.pl @@ -0,0 +1,77 @@ +#!/usr/bin/env perl +# 20260416 ChatGPT +# $Header$ +# +# Example: +# perl imu_roll_pitch_demo.pl +# perl imu_roll_pitch_demo.pl sideways +# perl imu_roll_pitch_demo.pl upright +# +# Notes: +# * Computes roll and pitch from accelerometer values. +# * Uses: +# roll = atan2(Az, Ay) +# pitch = atan2(-Ax, sqrt(Ay^2 + Az^2)) +# * Angles are printed in both radians and degrees. +# + +use strict; +use warnings; +use POSIX qw(atan2); + +my %cases = ( + sideways => { + Ax => -0.951, + Ay => -0.043, + Az => -0.003, + }, + upright => { + Ax => 0.028, + Ay => 0.987, + Az => 0.012, + }, +); + +my @requested_cases; +if (@ARGV) { + for my $name (@ARGV) { + if (!exists $cases{$name}) { + die "Unknown case '$name'. Valid cases: sideways upright\n"; + } + push @requested_cases, $name; + } +} +else { + @requested_cases = qw(sideways upright); +} + +for my $name (@requested_cases) { + my $ax = $cases{$name}->{Ax}; + my $ay = $cases{$name}->{Ay}; + my $az = $cases{$name}->{Az}; + + my $roll_rad = atan2($az, $ay); + my $pitch_rad = atan2(-$ax, sqrt($ay * $ay + $az * $az)); + + my $roll_deg = rad_to_deg($roll_rad); + my $pitch_deg = rad_to_deg($pitch_rad); + + printf "%s\n", ucfirst($name); + printf " A: X=% .3f Y=% .3f Z=% .3f\n", $ax, $ay, $az; + printf " roll (rad) = % .6f\n", $roll_rad; + printf " roll (deg) = % .3f\n", $roll_deg; + printf " pitch (rad) = % .6f\n", $pitch_rad; + printf " pitch (deg) = % .3f\n", $pitch_deg; + print "\n"; +} + +exit 0; + +sub rad_to_deg { + my ($rad) = @_; + return $rad * 180.0 / pi(); +} + +sub pi { + return 4.0 * atan2(1.0, 1.0); +} \ No newline at end of file