218 lines
8.9 KiB
Markdown
218 lines
8.9 KiB
Markdown
# Exercise 22: Compass / Magnetometer Executive Summary
|
|
|
|
This exercise will target the T-Beam Supreme magnetometer and build toward a real-time compass / field monitor. The manufacturer examples reviewed were:
|
|
|
|
- `QMC6310_CalibrateExample`
|
|
- `QMC6310_CompassExample`
|
|
- `QMC6310_GetDataExample`
|
|
- `QMC6310_GetPolarExample`
|
|
|
|
## What The Sensor Is Useful For
|
|
|
|
The QMC6310-class sensor is a 3-axis magnetometer. In practical terms, it is useful for:
|
|
|
|
- Magnetic heading estimation: a compass that points toward magnetic north.
|
|
- Relative orientation work: detecting yaw changes and turn direction.
|
|
- Magnetic field observation: watching X/Y/Z field components and total field strength change in real time.
|
|
- Sensor fusion: combining magnetometer data with the IMU to stabilize heading over time.
|
|
- Hardware assay: identifying board population differences by probing the magnetometer address and response.
|
|
|
|
It is not a complete navigation solution by itself. A magnetometer is sensitive to nearby ferrous metal, current-carrying wires, speakers, magnets, and board-level bias. For good heading, it needs calibration and a local magnetic declination correction if the goal is true north rather than magnetic north.
|
|
|
|
## What Each Example Actually Does
|
|
|
|
### `QMC6310_GetDataExample`
|
|
|
|
This is the basic bring-up example. It:
|
|
|
|
- Uses `setupBoards()` and the board I2C scan to discover the magnetometer address.
|
|
- Initializes the sensor.
|
|
- Configures continuous measurement mode.
|
|
- Prints compensated values and raw values for X/Y/Z to serial.
|
|
|
|
This is the best starting point for an exercise whose first goal is "show me live data."
|
|
|
|
### `QMC6310_GetPolarExample`
|
|
|
|
This example is the smallest heading-oriented demo. It:
|
|
|
|
- Reads magnetometer data.
|
|
- Converts the reading to a polar heading.
|
|
- Applies a declination correction.
|
|
- Prints heading, Gauss, and microtesla values.
|
|
|
|
This is useful once raw axis data is already trusted.
|
|
|
|
### `QMC6310_CompassExample`
|
|
|
|
This is a user-interface example. It:
|
|
|
|
- Reads X/Y values.
|
|
- Computes a heading with a hard-coded declination.
|
|
- Draws a compass arrow on the OLED.
|
|
- Also emits debug values on serial.
|
|
|
|
This demonstrates a real-time visual display, but it is still only as good as the underlying calibration.
|
|
|
|
### `QMC6310_CalibrateExample`
|
|
|
|
This example computes hard-iron offsets by watching the min/max raw values while the board is slowly rotated. It then applies:
|
|
|
|
- `x_offset = (x_max + x_min) / 2`
|
|
- `y_offset = (y_max + y_min) / 2`
|
|
- `z_offset = (z_max + z_min) / 2`
|
|
|
|
Those offsets are passed to `qmc.setOffset(...)`, after which subsequent readings are shifted by those values.
|
|
|
|
This is a runtime calibration aid, not a persistent factory calibration workflow.
|
|
|
|
## Calibration: Does It Need To Happen Every Boot?
|
|
|
|
Short answer: no, not if you save the offsets somewhere and re-apply them at boot.
|
|
|
|
Important detail from the local driver:
|
|
|
|
- `setOffset(...)` only stores offsets in the driver object's RAM.
|
|
- The offsets are not written into nonvolatile sensor storage.
|
|
- `begin(...)` calls `initImpl(...)`, and `initImpl(...)` performs `reset()`.
|
|
|
|
Implications:
|
|
|
|
- A normal reboot loses the offsets unless your firmware stores them elsewhere.
|
|
- Removing main power loses them.
|
|
- Keeping a battery attached does not make these software offsets persist in the magnetometer.
|
|
- If the board environment has not changed, you do not need to physically recalibrate at every boot, but you do need to reload previously measured offsets from persistent storage.
|
|
|
|
Recommended practice for this exercise:
|
|
|
|
- Add a one-time calibration mode.
|
|
- Save the resulting offsets in persistent storage on the ESP32.
|
|
- Re-apply offsets automatically at startup.
|
|
- Re-run calibration only when the enclosure, mounting, nearby wiring, or magnetic environment changes.
|
|
|
|
## Declination Offset
|
|
|
|
Declination is separate from magnetometer calibration.
|
|
|
|
- Calibration removes local sensor bias and board-level hard-iron offset.
|
|
- Declination converts magnetic north into true north for a specific latitude, longitude, and date.
|
|
|
|
So yes: if you want true heading, the user should specify a coordinate, obtain the current declination for that coordinate, and store that value with provenance.
|
|
|
|
For this exercise, the practical design is:
|
|
|
|
1. Choose the operating coordinates.
|
|
2. Obtain declination on the host, not on the ESP32.
|
|
3. Save the fetched value in a simple text file such as `declination.txt`.
|
|
4. Load that value into firmware at build time or into ESP32 persistent storage at provisioning time.
|
|
5. Re-fetch when the unit is moved a meaningful distance or after enough time has passed that the stored date is stale.
|
|
|
|
Why host-side instead of device-side:
|
|
|
|
- The ESP32 firmware should not depend on live network access to a geomagnetic service.
|
|
- Declination changes slowly, so caching is appropriate.
|
|
- A text file with comments gives you traceability for how the value was obtained.
|
|
|
|
Recommended file shape:
|
|
|
|
```text
|
|
# procured: April 16, 2026
|
|
# for coordinates: 44.93642012667761, -123.02203699545396
|
|
# source: NOAA/NCEI geomagnetic declination calculator
|
|
# sign convention: east positive, west negative
|
|
declination_deg=14.47
|
|
```
|
|
|
|
For your current manually captured value:
|
|
|
|
```text
|
|
2026-04-16 14.47° E ± 0.36° changing by 0.11° W per year
|
|
```
|
|
|
|
the appropriate build-time value is:
|
|
|
|
```text
|
|
MAG_DECLINATION_DEG = 14.47
|
|
```
|
|
|
|
using the convention:
|
|
|
|
- East declination is positive.
|
|
- West declination is negative.
|
|
|
|
That convention should be written directly into the firmware and README so there is no hidden sign ambiguity.
|
|
|
|
Operational guidance:
|
|
|
|
- For a fixed station, one fetched value is usually sufficient and should simply be refreshed occasionally.
|
|
- For a mobile unit used across a region, a single fixed declination is still acceptable for an early exercise, but it should be understood as an approximation.
|
|
- For this project stage, storing one deployment-specific declination value is the right tradeoff.
|
|
|
|
## Multi-Unit Assay For Seven Boards
|
|
|
|
Lewis He's current note says T-Beam Supreme units may contain `QMC6310N`, `QMC6310U`, or `QMC6309`, each with a different I2C address.
|
|
|
|
What the local LilyGo code currently supports:
|
|
|
|
- `0x1C` -> treated as `QMC6310U`
|
|
- `0x3C` -> treated as `QMC6310N` after distinguishing it from the OLED at the same address family
|
|
|
|
What is missing locally:
|
|
|
|
- I do not see `QMC6309` support anywhere in the local clone.
|
|
- The referenced GitHub commit is not present in the local repository, so the current local examples cannot yet identify `QMC6309`.
|
|
|
|
Practical assay recommendation for units `A` through `G`:
|
|
|
|
1. Build a small probe sketch for a selected PlatformIO environment such as `cy`.
|
|
2. Run the same board scan that `setupBoards()` already performs.
|
|
3. Print the detected magnetometer I2C address.
|
|
4. Print `getChipID()`.
|
|
5. Record the result per unit label: `AMY`, `BOB`, `CY`, `DAN`, `ED`, `FLO`, `GUY`.
|
|
|
|
Expected result set:
|
|
|
|
- If the unit answers at `0x1C`, classify it as `QMC6310U`.
|
|
- If the unit answers at `0x3C`, classify it as `QMC6310N`.
|
|
- If a unit answers at some third address, that is the candidate path for `QMC6309`, but the local code will need to be extended to name it explicitly.
|
|
|
|
## Recommended Design Direction For This Exercise
|
|
|
|
The clean design is a real-time serial-first field monitor, with OLED support as a secondary display.
|
|
|
|
Suggested output in real time:
|
|
|
|
- Unit identifier, for example `CY`
|
|
- Detected magnetometer address
|
|
- Chip ID
|
|
- Build UTC tag
|
|
- Raw X/Y/Z
|
|
- Offset-corrected X/Y/Z
|
|
- Declination in degrees and its source date
|
|
- Heading in degrees
|
|
- Total field magnitude
|
|
- Calibration status
|
|
|
|
Suggested workflow:
|
|
|
|
1. Boot and identify the attached board and detected magnetometer address.
|
|
2. Load saved calibration offsets if present.
|
|
3. Stream readings continuously over serial for capture and inspection.
|
|
4. Optionally mirror heading and a simple arrow on the OLED.
|
|
5. Provide a serial command or compile-time mode to enter calibration.
|
|
6. Save newly computed offsets for future boots.
|
|
7. Apply stored declination before reporting true heading.
|
|
|
|
## Bottom Line
|
|
|
|
The examples are enough to establish that this sensor is suitable for a live heading and magnetic field display exercise. The only caution is that the example labeled "calibration" is session-local unless we explicitly persist the offsets. The first useful deliverable for this exercise should therefore be a per-unit magnetometer assay plus a serial real-time monitor, followed by persistent calibration storage and then an OLED compass view.
|
|
|
|
## Local Helper Scripts
|
|
|
|
Exercise 22 now includes:
|
|
|
|
- `scripts/set_build_epoch.py` copied from Exercise 18 so the same unique build timestamp mechanism is available here.
|
|
- `scripts/fetch_declination.pl` to fetch a declination value on the host and write a provenance-rich `declination.txt`.
|
|
- `platformio.ini` scaffolded with the build-stamp hook and manual declination build defines.
|
|
|
|
The NOAA fetch helper remains available, but given the current registration requirement, the recommended workflow is manual lookup plus a checked-in text record such as `declination_manual.txt`.
|