Added documentation

This commit is contained in:
MATBckh22 2025-09-22 00:18:55 +08:00
parent 162ab54a16
commit 934c14bfc4

211
docs/IMU-QMI8658-QMC6310.md Normal file
View File

@ -0,0 +1,211 @@
# IMU and Magnetometer Integration (QMI8658 + QMC6310)
This document explains the implementation work added for the LilyGo T-Beam S3 Supreme to support:
- QMI8658 6axis IMU over SPI (accelerometer + gyroscope) with a debug stream and a UI page
- QMC6310 3axis magnetometer over I2C with live hardiron calibration, heading computation, and a UI page
- I2C scanner improvements for robust IMU detection
- A small “live data” layer so UI screens do not reset sensors
The focus is on the math used and how the code is wired together.
---
## Files and Components
- QMI8658 (SPI) driver wrapper
- `src/motion/QMI8658Sensor.h/.cpp`
- QMC6310 (I2C) driver wrapper
- `src/motion/QMC6310Sensor.h/.cpp`
- Live data shared with UI (prevents sensor resets by UI)
- `src/motion/SensorLiveData.h/.cpp` (globals `g_qmi8658Live`, `g_qmc6310Live`)
- I2C scanner and main wiring
- `src/detect/ScanI2CTwoWire.cpp`, `src/detect/ScanI2C.cpp`, `src/main.cpp`
- UI Screens (added after the GPS screen)
- `src/graphics/draw/DebugRenderer.h/.cpp`
- `src/graphics/Screen.cpp`
Dependency pulled via PlatformIO:
- Lewis He SensorLib (provides `SensorQMI8658.hpp`, `SensorQMC6310.hpp`) pinned in `platformio.ini`.
---
## QMI8658 (SPI) Implementation and Math
### Bus + Initialization
- On ESP32S3, we use HSPI for the IMU to avoid clashing with radio SPI:
- Pins (TBeam S3 Supreme): `MOSI=35`, `MISO=37`, `SCK=36`, `IMU_CS=34`.
- Code creates a local `SPIClass(HSPI)` and calls `begin(SCK, MISO, MOSI, -1)`; sets `IMU_CS` HIGH.
- The driver is configured as:
- Accelerometer range: ±4 g, ODR ≈ 1000 Hz, LPF mode 0
- Gyroscope range: ±64 dps, ODR ≈ 897 Hz, LPF mode 3
- The thread (`QMI8658Sensor`) runs continuously. When `QMI8658_DEBUG_STREAM` is enabled, it samples every pass but logs once per second.
### Units
- Accelerometer is reported in m/s² by the library. To measure total acceleration (for wakeonmotion), we compute:
```
|a| = sqrt(ax² + ay² + az²)
|a|_g = |a| / 9.80665
Δ = |a|_g 1.0
```
If `Δ` exceeds a small threshold (`0.15 g`), we wake the screen.
### Debug Stream
- The debug line (1 Hz) prints:
`QMI8658: ready=<0/1> ACC[x y z] m/s^2 GYR[x y z] dps`
This is also mirrored into the live data struct `g_qmi8658Live` that the UI reads.
---
## QMC6310 (I2C) Implementation and Math
### Bus + Initialization
- Address: `0x1C` on the sensors bus (Wire). The I2C scanner detects it and exposes it as `ScanI2C::QMC6310`.
- Configuration via SensorLib wrapper:
- Mode: continuous
- Range: 2 G
- ODR: 50 Hz
- Oversample: 8×, Downsample: 1×
### HardIron Calibration (live)
We continuously track min/max per axis and compute offsets as the center:
```
minX = min(minX, rawX) maxX = max(maxX, rawX)
minY = min(minY, rawY) maxY = max(maxY, rawY)
minZ = min(minZ, rawZ) maxZ = max(maxZ, rawZ)
offsetX = (maxX + minX) / 2
offsetY = (maxY + minY) / 2
offsetZ = (maxZ + minZ) / 2
mx = rawX offsetX
my = rawY offsetY
mz = rawZ offsetZ
```
This removes hardiron bias (DC offset) and is adequate for realtime heading stabilization. For best results, slowly rotate the device on all axes for several seconds to let min/max settle.
Softiron distortion (elliptical scaling) is NOT corrected here. A future enhancement can compute peraxis scale from `(maxmin)/2` or use an ellipsoid fit.
### Heading Computation
Raw 2D horizontal heading (no tilt compensation):
```
heading_deg = atan2(my, mx) * 180/π
heading_true = wrap_0_360( heading_deg + declination_deg + yaw_mount_offset )
```
Where:
- `declination_deg` compensates for local magnetic declination (positive East, negative West). We support a buildtime macro `QMC6310_DECLINATION_DEG`.
- `yaw_mount_offset` lets you nudge heading for how the board is mounted; buildtime macro `QMC6310_YAW_MOUNT_OFFSET`.
- `wrap_0_360(θ)` folds θ into `[0, 360)` by repeated add/subtract 360.
Screen orientation (0/90/180/270) is applied after heading is computed and normalized.
### TiltCompensated Heading (future option)
If pitch/roll are available (from IMU), heading can be tiltcompensated:
```
mx' = mx*cos(θ) + mz*sin(θ)
my' = mx*sin(φ)*sin(θ) + my*cos(φ) mz*sin(φ)*cos(θ)
heading = atan2(my', mx')
```
Where `φ` is roll and `θ` is pitch (radians), derived from accelerometer. This is not implemented yet but can be added.
### Live Data
The magnetometer thread writes the latest raw XYZ, offsets and heading into `g_qmc6310Live` for the UI to display without touching hardware.
---
## I2C Scanner Improvements
### Dualaddress detection for IMUs
- QMI8658 is now probed at both `0x6B` and `0x6A`.
- To avoid collisions with chargers on `0x6B`, we first check:
- BQ24295 ID via register `0x0A == 0xC0`
- BQ25896 via `0x14` (bits 1:0 == `0b10`)
- If not a charger, we read `0x0F`:
- `0x6A` → classify as LSM6DS3
- otherwise → classify as QMI8658
### IMUonly late rescan
- After a normal pass, if neither QMI8658 nor LSM6DS3 is found on a port, we wait 700 ms and probe just `0x6A/0x6B` again. This helps boards that power the IMU slightly late.
---
## UI Screens
Two additional screens are inserted right after the GPS screen:
1) QMC6310 screen
- Shows `Heading`, `offX/offY`, and `rawX/rawY` (1 Hz)
- Data source: `g_qmc6310Live`
2) QMI8658 screen
- Shows `ACC x/y/z` (m/s²) and `GYR x/y/z` (dps) (1 Hz)
- Data source: `g_qmi8658Live`
Because these screens read the live data structs, they do NOT call `begin()` on sensors (which would reset them). This resolved the issue where the screen showed all zeros after switching.
---
## Build Flags and Configuration
- Global debug stream (QMI8658):
- `-D QMI8658_DEBUG_STREAM` (enabled in the TBeam S3 Core variant)
- When enabled, the main will also start a parallel IMU debug thread even if an I2C accelerometer/magnetometer is present.
- Declination and mount offset for QMC6310 heading (optional):
- `-D QMC6310_DECLINATION_DEG=<deg>` (e.g., `-0.25` for ≈ 0°15 W)
- `-D QMC6310_YAW_MOUNT_OFFSET=<deg>` (tune to match a known reference)
- SensorLib dependency (Lewis He):
- Pinned in `platformio.ini` under `[arduino_base]`:
`https://github.com/lewisxhe/SensorLib/archive/769b48472278aeaa62d5d0526eccceb74abe649a.zip`
---
## Example Logs
```
QMC6310: head=137.5 off[x=-12900 y=9352 z=12106] raw[x=-12990 y=9435 z=12134]
QMI8658: ready=1 ACC[x=-0.782 y=0.048 z=0.539] m/s^2 GYR[x=11.742 y=4.570 z=1.836] dps
```
The values on the UI screens should match these, because both screens read from the live data updated by the threads.
---
## Known Limitations and Next Steps
- QMC6310 uses live hardiron calibration only; no softiron compensation yet. We can add peraxis scale from `(maxmin)/2` or use an ellipsoid fit for better accuracy.
- Heading is not tiltcompensated; adding pitch/roll from the IMU and applying the standard compensation will stabilize heading during motion.
- We currently do not persist calibration offsets across boots; adding storage (NVS) would improve user experience.
- The QMI8658 debug stream is designed for development and can be disabled via build flag to reduce log noise.
---
## Troubleshooting
- If QMI8658 shows zeros on the UI screen, ensure `QMI8658_DEBUG_STREAM` is enabled or let the background IMU thread initialize first (it sets `g_qmi8658Live.initialized`).
- If QMC6310 heading appears constrained or jumps, rotate the device slowly on all axes for 1020 seconds to update min/max; verify youre on the correct I2C port and address (`0x1C`).
- If the I2C scan does not find the IMU on poweron, check that the laterescan log appears; some boards power the sensor rail slightly later.