From 20781b3243b384c4dbf82ebca4e35cb963304ea6 Mon Sep 17 00:00:00 2001 From: John Poole Date: Sun, 19 Apr 2026 15:08:49 -0700 Subject: [PATCH 01/10] Before integrating with jlpoole standard services, this platofrmio.ini works directly against LilyGO code --- examples/platformio.ini | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/platformio.ini diff --git a/examples/platformio.ini b/examples/platformio.ini new file mode 100644 index 0000000..9466bd8 --- /dev/null +++ b/examples/platformio.ini @@ -0,0 +1,35 @@ +[platformio] +default_envs = T_BEAM_S3_SUPREME_SX1262 + +; Change this line when you want to build a different copied LilyGO example. +;src_dir = QMC6310_CompassExample +src_dir = Sensor/QMC63xx_GetDataExample + +; Reuse LilyGO's custom board definitions without editing the LilyGO checkout. +boards_dir = /usr/local/src/LilyGo-LoRa-Series/boards + +[env] +platform = espressif32@6.12.0 +framework = arduino +upload_speed = 921600 +monitor_speed = 115200 + +; Reuse LilyGO's bundled libraries: +; SensorLib, ESP8266_SSD1306, XPowersLib, RadioLib, etc. +lib_extra_dirs = /usr/local/src/LilyGo-LoRa-Series/lib + +monitor_filters = + default + esp32_exception_decoder + +[esp32s3_base] +build_flags = + -DARDUINO_USB_CDC_ON_BOOT=1 + -DCORE_DEBUG_LEVEL=0 + +[env:T_BEAM_S3_SUPREME_SX1262] +board = t-beams3-supreme +build_flags = ${esp32s3_base.build_flags} + -DT_BEAM_S3_SUPREME_SX1262 + -DBOARD_HAS_PSRAM +board_build.partitions = huge_app.csv From 59e5887cec54f91e5761f6abc2dca6a8c061dddc Mon Sep 17 00:00:00 2001 From: John Poole Date: Sun, 19 Apr 2026 15:09:59 -0700 Subject: [PATCH 02/10] ignore log files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ea3bd3..baf586b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ test script build LilyGoLoRaBoard -record.* \ No newline at end of file +record.* +*log + From a2e34659a680c43a02324ded3e32872bdd0102d0 Mon Sep 17 00:00:00 2001 From: John Poole Date: Sun, 19 Apr 2026 16:33:10 -0700 Subject: [PATCH 03/10] Added expanded settings requested by ChatGPT for magnetic fields; the values remain pinned and when a magnet is moved about 10mm away, still the values remain pinned. Going to log an Issue, but preserving here, and will revert to LillyGO version for Issue submission --- .../QMC63xx_GetDataExample.ino | 29 +++ .../QMC63xx_GetDataExample/QmcServices.cpp | 174 ++++++++++++++++++ .../QMC63xx_GetDataExample/QmcServices.h | 19 ++ examples/platformio.ini | 10 +- 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 examples/Sensor/QMC63xx_GetDataExample/QmcServices.cpp create mode 100644 examples/Sensor/QMC63xx_GetDataExample/QmcServices.h diff --git a/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino b/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino index 6bba26e..c815827 100644 --- a/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino +++ b/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino @@ -31,6 +31,9 @@ #include #include #include "LoRaBoards.h" +#include "QmcServices.h" + +#define Serial QmcSerial void calibrate(); MagnetometerBase *magnetometer; @@ -41,6 +44,7 @@ void setup() while (!Serial); setupBoards(); + qmcServicesBegin(); // The desired output data rate in Hz. Allowed values are 1.0, 10.0, 50.0, 100.0 and 200.0HZ. float data_rate_hz = 200.0f; @@ -184,6 +188,7 @@ void setup() void loop() { + qmcServicesUpdate(); MagnetometerData data; @@ -194,6 +199,20 @@ void loop() float y = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.y); float z = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.z); + float deg_xy = atan2(y, x) * 180.0f / PI; + float deg_xny = atan2(-y, x) * 180.0f / PI; + float deg_yx = atan2(x, y) * 180.0f / PI; + float deg_nxy = atan2(y, -x) * 180.0f / PI; + float deg_xz = atan2(z, x) * 180.0f / PI; + float deg_zx = atan2(x, z) * 180.0f / PI; + float deg_yz = atan2(z, y) * 180.0f / PI; + float deg_zy = atan2(y, z) * 180.0f / PI; + auto norm360 = [](float d) { + while (d < 0) d += 360.0f; + while (d >= 360.0f) d -= 360.0f; + return d; + }; + Serial.print("Mag:"); Serial.print(" X:"); Serial.print(x); Serial.print(" Y:"); Serial.print(y); @@ -222,10 +241,20 @@ void loop() Serial.print(strength, 2); Serial.println(" μT"); + Serial.print(" XY:"); Serial.print(norm360(deg_xy), 2); + Serial.print(" XnY:"); Serial.print(norm360(deg_xny), 2); + Serial.print(" YX:"); Serial.print(norm360(deg_yx), 2); + Serial.print(" nXY:"); Serial.print(norm360(deg_nxy), 2); + Serial.print(" XZ:"); Serial.print(norm360(deg_xz), 2); + Serial.print(" ZX:"); Serial.print(norm360(deg_zx), 2); + Serial.print(" YZ:"); Serial.print(norm360(deg_yz), 2); + Serial.print(" ZY:"); Serial.println(norm360(deg_zy), 2); + if (data.overflow) { Serial.println("\tWarning: Data Overflow occurred!"); } } + qmcServicesUpdate(); delay(10); } diff --git a/examples/Sensor/QMC63xx_GetDataExample/QmcServices.cpp b/examples/Sensor/QMC63xx_GetDataExample/QmcServices.cpp new file mode 100644 index 0000000..2f82764 --- /dev/null +++ b/examples/Sensor/QMC63xx_GetDataExample/QmcServices.cpp @@ -0,0 +1,174 @@ +#include "QmcServices.h" + +#include +#include +#include + +#ifndef BOARD_ID +#define BOARD_ID "NODE" +#endif + +#ifndef NODE_LABEL +#define NODE_LABEL BOARD_ID +#endif + +#ifndef LOG_AP_IP_OCTET +#define LOG_AP_IP_OCTET 25 +#endif + +namespace { + +tbeam::TBeamClock clockService(Wire1); +tbeam::TBeamStorage storageService(::Serial); +tbeam::TBeamWeb webService(::Serial); + +bool servicesStarted = false; +uint32_t lastFlushMs = 0; +uint32_t lastClockUpdateMs = 0; +char logPath[128] = {}; + +void openRunLog() +{ + if (!storageService.ready()) { + return; + } + + char runId[64] = {}; + tbeam::DateTime rtc; + int64_t epoch = 0; + if (clockService.readValidRtc(rtc, &epoch)) { + tbeam::TBeamClock::makeRunId(rtc, BOARD_ID, runId, sizeof(runId)); + } else { + snprintf(runId, sizeof(runId), "%s_%lu", BOARD_ID, static_cast(millis() / 1000)); + } + + snprintf(logPath, sizeof(logPath), "/logs/qmc63xx/%s.log", runId); + if (!storageService.openLog(logPath)) { + ::Serial.printf("[qmc-services] log open failed: %s\n", storageService.lastError()); + logPath[0] = '\0'; + return; + } + + storageService.println("# test: qmc63xx"); + storageService.print("# board_id: "); + storageService.println(BOARD_ID); + storageService.print("# node_label: "); + storageService.println(NODE_LABEL); + storageService.print("# log_path: "); + storageService.println(logPath); + if (clockService.valid()) { + char iso[32] = {}; + tbeam::TBeamClock::formatIsoUtc(clockService.lastRtc(), iso, sizeof(iso)); + storageService.print("# rtc_utc: "); + storageService.println(iso); + } + storageService.println("# serial_tee: enabled"); + storageService.flush(); +} + +} // namespace + +QmcSerialTee QmcSerial; + +void QmcSerialTee::begin(unsigned long baud) +{ + ::Serial.begin(baud); +} + +size_t QmcSerialTee::write(uint8_t value) +{ + const size_t serialWritten = ::Serial.write(value); + if (servicesStarted && storageService.isLogOpen()) { + storageService.write(&value, 1); + } + return serialWritten; +} + +size_t QmcSerialTee::write(const uint8_t* buffer, size_t size) +{ + const size_t serialWritten = ::Serial.write(buffer, size); + if (servicesStarted && storageService.isLogOpen()) { + storageService.write(buffer, size); + } + return serialWritten; +} + +void QmcSerialTee::flush() +{ + ::Serial.flush(); + if (servicesStarted && storageService.isLogOpen()) { + storageService.flush(); + } +} + +void qmcServicesBegin() +{ + if (servicesStarted) { + return; + } + + tbeam::ClockConfig clockConfig; + clockConfig.beginWire = false; + clockService.begin(clockConfig); + clockService.update(); + + tbeam::StorageConfig storageConfig; + storageConfig.logDir = "/logs/qmc63xx"; + storageConfig.enablePinDumps = false; + storageService.begin(storageConfig); + + openRunLog(); + + tbeam::WebConfig webConfig; + webConfig.ssidPrefix = "GPSQA"; + webConfig.boardId = BOARD_ID; + webConfig.password = nullptr; + webConfig.ipOctet = LOG_AP_IP_OCTET; + webConfig.enableDelete = true; + webService.begin(storageService, webConfig); + + servicesStarted = true; + + ::Serial.printf("[qmc-services] board=%s label=%s\n", BOARD_ID, NODE_LABEL); + if (clockService.valid()) { + char iso[32] = {}; + tbeam::TBeamClock::formatIsoUtc(clockService.lastRtc(), iso, sizeof(iso)); + ::Serial.printf("[qmc-services] rtc=%s\n", iso); + } else { + ::Serial.printf("[qmc-services] rtc invalid: %s\n", clockService.lastError()); + } + ::Serial.printf("[qmc-services] log=%s\n", logPath[0] ? logPath : "(not open)"); + if (webService.ready()) { + ::Serial.printf("[qmc-services] web=http://%s/ ssid=%s\n", + webService.ip().toString().c_str(), + webService.ssid()); + } else { + ::Serial.printf("[qmc-services] web unavailable: %s\n", webService.lastError()); + } +} + +void qmcServicesUpdate() +{ + if (!servicesStarted) { + return; + } + + const uint32_t now = millis(); + storageService.update(); + webService.update(); + + if (now - lastClockUpdateMs >= 1000) { + lastClockUpdateMs = now; + clockService.update(); + } + + if (now - lastFlushMs >= 2000) { + lastFlushMs = now; + storageService.flush(); + } +} + +const char* qmcServicesLogPath() +{ + return logPath; +} diff --git a/examples/Sensor/QMC63xx_GetDataExample/QmcServices.h b/examples/Sensor/QMC63xx_GetDataExample/QmcServices.h new file mode 100644 index 0000000..53e82fc --- /dev/null +++ b/examples/Sensor/QMC63xx_GetDataExample/QmcServices.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class QmcSerialTee : public Print { + public: + void begin(unsigned long baud); + explicit operator bool() const { return true; } + + size_t write(uint8_t value) override; + size_t write(const uint8_t* buffer, size_t size) override; + void flush(); +}; + +extern QmcSerialTee QmcSerial; + +void qmcServicesBegin(); +void qmcServicesUpdate(); +const char* qmcServicesLogPath(); diff --git a/examples/platformio.ini b/examples/platformio.ini index 9466bd8..5e07652 100644 --- a/examples/platformio.ini +++ b/examples/platformio.ini @@ -16,7 +16,9 @@ monitor_speed = 115200 ; Reuse LilyGO's bundled libraries: ; SensorLib, ESP8266_SSD1306, XPowersLib, RadioLib, etc. -lib_extra_dirs = /usr/local/src/LilyGo-LoRa-Series/lib +lib_extra_dirs = + /usr/local/src/LilyGo-LoRa-Series/lib + /usr/local/src/microreticulum/microReticulumTbeam/lib monitor_filters = default @@ -26,6 +28,12 @@ monitor_filters = build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DCORE_DEBUG_LEVEL=0 + -I /usr/local/src/microreticulum/microReticulumTbeam/shared/boards + -I /usr/local/src/microreticulum/microReticulumTbeam/external/microReticulum_Firmware + -D BOARD_MODEL=BOARD_TBEAM_S_V1 + -D BOARD_ID=\"CY\" + -D NODE_LABEL=\"Cy\" + -D LOG_AP_IP_OCTET=25 [env:T_BEAM_S3_SUPREME_SX1262] board = t-beams3-supreme From 49e384329271a69632c158e4f863749cb4239240 Mon Sep 17 00:00:00 2001 From: John Poole Date: Mon, 20 Apr 2026 02:09:39 -0700 Subject: [PATCH 04/10] Adding Perl parser for script results --- .../calc_heading_strength_stats.pl | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100755 examples/Sensor/QMC63xx_GetDataExample/calc_heading_strength_stats.pl diff --git a/examples/Sensor/QMC63xx_GetDataExample/calc_heading_strength_stats.pl b/examples/Sensor/QMC63xx_GetDataExample/calc_heading_strength_stats.pl new file mode 100755 index 0000000..7504f29 --- /dev/null +++ b/examples/Sensor/QMC63xx_GetDataExample/calc_heading_strength_stats.pl @@ -0,0 +1,157 @@ +#!/usr/bin/perl +# +# calc_heading_strength_stats.pl +# +# 20260420 ChatGPT +# $Id$ +# $HeadURL$ +# +# Example: +# chmod 755 calc_heading_strength_stats.pl +# ./calc_heading_strength_stats.pl magnetometer_readings.log +# ./calc_heading_strength_stats.pl ~/sessions/T-Beam_calibrate_20260420_Mon_014614.script +# +# This script scans a console/script log and extracts only lines like: +# +# 3005 00:36.756 Mag: X:289.20 Y:216.86 Z:608.78 μT Metadata: X:10845 Y:8132 Z:22829 Heading (rad): 0.585220 rad Heading (deg): 33.53° Magnetic Strength: 708.01 μT +# +# It ignores non-matching lines and computes statistics for: +# 1) Heading (deg) +# 2) Magnetic Strength +# +# Reported: +# - count +# - min / max / range +# - mean +# - population variance +# - sample variance +# - population std dev +# - sample std dev +# - line number / timestamp / text for min and max +# + +use strict; +use warnings; +use utf8; +use open ':std', ':encoding(UTF-8)'; + +my $input_file = shift @ARGV + or die "Usage: $0 input_log_file\n"; + +open my $fh, '<', $input_file + or die "Cannot open '$input_file': $!\n"; + +my %stats = ( + heading_deg => init_stat('Heading (deg)'), + mag_strength => init_stat('Magnetic Strength'), +); + +my $matched_lines = 0; + +while (my $line = <$fh>) { + chomp $line; + + next unless $line =~ /^\s*(\d+)\s+(\d{2}:\d{2}\.\d{3})\s+Mag:/; + my $log_lineno = $1; + my $time_stamp = $2; + + next unless $line =~ /Heading \(deg\):\s*([-+]?\d+(?:\.\d+)?)°/; + my $heading_deg = $1 + 0; + + next unless $line =~ /Magnetic Strength:\s*([-+]?\d+(?:\.\d+)?)\s*μT/; + my $mag_strength = $1 + 0; + + $matched_lines++; + + update_stat($stats{heading_deg}, $heading_deg, $log_lineno, $time_stamp, $line); + update_stat($stats{mag_strength}, $mag_strength, $log_lineno, $time_stamp, $line); +} + +close $fh; + +if (!$matched_lines) { + die "No matching data lines were found in '$input_file'.\n"; +} + +print_report($stats{heading_deg}); +print "\n"; +print_report($stats{mag_strength}); + +exit 0; + +sub init_stat { + my ($label) = @_; + + return { + label => $label, + count => 0, + mean => 0, + m2 => 0, # Welford running sum of squared deviations + min => undef, + max => undef, + min_line => undef, + min_time => undef, + min_text => undef, + max_line => undef, + max_time => undef, + max_text => undef, + }; +} + +sub update_stat { + my ($s, $x, $log_lineno, $time_stamp, $text) = @_; + + $s->{count}++; + + if (!defined $s->{min} || $x < $s->{min}) { + $s->{min} = $x; + $s->{min_line} = $log_lineno; + $s->{min_time} = $time_stamp; + $s->{min_text} = $text; + } + + if (!defined $s->{max} || $x > $s->{max}) { + $s->{max} = $x; + $s->{max_line} = $log_lineno; + $s->{max_time} = $time_stamp; + $s->{max_text} = $text; + } + + my $delta = $x - $s->{mean}; + $s->{mean} += $delta / $s->{count}; + my $delta2 = $x - $s->{mean}; + $s->{m2} += $delta * $delta2; + + return; +} + +sub print_report { + my ($s) = @_; + + my $count = $s->{count}; + my $range = $s->{max} - $s->{min}; + + my $pop_variance = ($count > 0) ? ($s->{m2} / $count) : 0; + my $samp_variance = ($count > 1) ? ($s->{m2} / ($count-1)) : 0; + + my $pop_stddev = sqrt($pop_variance); + my $samp_stddev = sqrt($samp_variance); + + print "$s->{label}\n"; + print "=" x length($s->{label}), "\n"; + printf "count : %d\n", $count; + printf "min : %.6f\n", $s->{min}; + printf "max : %.6f\n", $s->{max}; + printf "range : %.6f\n", $range; + printf "mean : %.6f\n", $s->{mean}; + printf "population variance : %.6f\n", $pop_variance; + printf "sample variance : %.6f\n", $samp_variance; + printf "population std dev : %.6f\n", $pop_stddev; + printf "sample std dev : %.6f\n", $samp_stddev; + printf "min occurred at : line %s, time %s\n", $s->{min_line}, $s->{min_time}; + printf "max occurred at : line %s, time %s\n", $s->{max_line}, $s->{max_time}; + print "min line text : $s->{min_text}\n"; + print "max line text : $s->{max_text}\n"; + + return; +} From f4e2a211e2a1f6eafb374d754c3067ea40b94f23 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:06:05 -0700 Subject: [PATCH 05/10] jlpoole modifications with mean instead of average for offset --- ...CalibrateExample_mean_offsets_20260421.ino | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino diff --git a/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino new file mode 100644 index 0000000..4254a65 --- /dev/null +++ b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino @@ -0,0 +1,424 @@ +/** + * + * @license MIT License + * + * Copyright (c) 2026 lewis he + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file QMC6310_CalibrateExample.ino + * @author Lewis He (lewishe@outlook.com) + * @date 2026-01-26 + * + * Modified 2026-04-21 to compute mean-based offsets from CALRAW samples + * instead of midpoint-of-extrema offsets. + * + */ +#include +#include +#include +#include "SensorQMC6310.hpp" +#define ARDUINO_T_BEAM_S3_SUPREME +#ifdef ARDUINO_T_BEAM_S3_SUPREME +#include //PMU Library https://github.com/lewisxhe/XPowersLib.git +#endif + +#ifndef SENSOR_SDA +#define SENSOR_SDA 17 +#endif + +#ifndef SENSOR_SCL +#define SENSOR_SCL 18 +#endif + +SensorQMC6310 magnetometer; + +void beginPower() +{ +#if defined(ARDUINO_T_BEAM_S3_SUPREME) + XPowersAXP2101 power; + power.begin(Wire1, AXP2101_SLAVE_ADDRESS, 42, 41); + power.disableALDO1(); + power.disableALDO2(); + delay(250); + power.setALDO1Voltage(3300); + power.enableALDO1(); + power.setALDO2Voltage(3300); + power.enableALDO2(); +#endif +} + +void calibrate() +{ + if (!magnetometer.setOutputDataRate(200.0f)) { + Serial.println("Failed to set output data rate"); + return ; + } + + Serial.println("========================================"); + Serial.println("Calibration Instructions:"); + Serial.println("1. Rotate sensor in FIGURE-8 pattern"); + Serial.println("2. Cover all axes (X, Y, Z directions)"); + Serial.println("3. Rotate slowly and completely"); + Serial.println("4. Wait for progress bar to complete"); + Serial.println("5. Expected: Magnetic Strength ~25-65 uT"); + Serial.println("========================================"); + Serial.println(); + + Serial.println("Place the sensor on the plane and slowly rotate the sensor..."); + Serial.println("Rotate in FIGURE-8 pattern to cover all directions!"); + Serial.println(); + + int32_t x_min = 65535; + int32_t x_max = -65535; + int32_t y_min = 65535; + int32_t y_max = -65535; + int32_t z_min = 65535; + int32_t z_max = -65535; + + int32_t range = 2000; + int32_t i = 0; + int32_t raw_x = 0, raw_y = 0, raw_z = 0; + int16_t x_offset = 0; + int16_t y_offset = 0; + int16_t z_offset = 0; + + uint32_t sample_counter = 0; + + // Mean-based accumulation of raw calibration samples. + int64_t x_sum = 0; + int64_t y_sum = 0; + int64_t z_sum = 0; + + MagnetometerData data; + while (i < range) { + i += 1; + + if (magnetometer.isDataReady()) { + + magnetometer.readData(data); + sample_counter++; + + raw_x = data.raw.x; + raw_y = data.raw.y; + raw_z = data.raw.z; + + x_sum += raw_x; + y_sum += raw_y; + z_sum += raw_z; + + Serial.print("CALRAW "); + Serial.print("X:"); + Serial.print(raw_x); + Serial.print(" Y:"); + Serial.print(raw_y); + Serial.print(" Z:"); + Serial.println(raw_z); + + // Track extrema on the direct raw sample, not on a running average. + if (raw_x < x_min) { + x_min = raw_x; + i = 0; + } + if (raw_x > x_max) { + x_max = raw_x; + i = 0; + } + if (raw_y < y_min) { + y_min = raw_y; + i = 0; + } + if (raw_y > y_max) { + y_max = raw_y; + i = 0; + } + if (raw_z < z_min) { + z_min = raw_z; + i = 0; + } + if (raw_z > z_max) { + z_max = raw_z; + i = 0; + } + + if ((sample_counter % 100) == 0) { + Serial.print("CALSTAT i="); + Serial.print(i); + Serial.print(" samples="); + Serial.print(sample_counter); + Serial.print(" x_min="); + Serial.print(x_min); + Serial.print(" x_max="); + Serial.print(x_max); + Serial.print(" y_min="); + Serial.print(y_min); + Serial.print(" y_max="); + Serial.print(y_max); + Serial.print(" z_min="); + Serial.print(z_min); + Serial.print(" z_max="); + Serial.println(z_max); + } + } + delay(5); + } + + if (sample_counter == 0) { + Serial.println("No calibration samples were collected."); + return; + } + + // Mean/centroid-based offsets. + x_offset = (int16_t)lround((double)x_sum / (double)sample_counter); + y_offset = (int16_t)lround((double)y_sum / (double)sample_counter); + z_offset = (int16_t)lround((double)z_sum / (double)sample_counter); + + // Also compute the old midpoint offsets for comparison/debugging. + int16_t x_midpoint_offset = (x_max + x_min) / 2; + int16_t y_midpoint_offset = (y_max + y_min) / 2; + int16_t z_midpoint_offset = (z_max + z_min) / 2; + + Serial.print("samples:"); + Serial.println(sample_counter); + + Serial.print("x_min:"); + Serial.println(x_min); + + Serial.print("x_max:"); + Serial.println(x_max); + + Serial.print("y_min:"); + Serial.println(y_min); + + Serial.print("y_max:"); + Serial.println(y_max); + + Serial.print("z_min:"); + Serial.println(z_min); + + Serial.print("z_max:"); + Serial.println(z_max); + + Serial.print("x_midpoint_offset:"); + Serial.println(x_midpoint_offset); + + Serial.print("y_midpoint_offset:"); + Serial.println(y_midpoint_offset); + + Serial.print("z_midpoint_offset:"); + Serial.println(z_midpoint_offset); + + Serial.print("x_mean_offset:"); + Serial.println(x_offset); + + Serial.print("y_mean_offset:"); + Serial.println(y_offset); + + Serial.print("z_mean_offset:"); + Serial.println(z_offset); + + // Set the mean-based calibration values. + magnetometer.setOffset(x_offset, y_offset, z_offset); + + Serial.println(); + Serial.println("Calibration complete!"); + Serial.println("Offsets applied using raw-sample means."); + Serial.println("Check if Magnetic Strength is ~25-65 uT"); + Serial.println("If too low, repeat calibration with better rotation"); +} + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + // LilyGo T-Beam-Supreme sensor requires a power source to function. + beginPower(); + + /** + * Supports QMC6310U and QMC6310N; simply pass the corresponding device address + * during initialization. + * - QMC6310U_SLAVE_ADDRESS + * - QMC6310N_SLAVE_ADDRESS + */ + uint8_t address = QMC6310U_SLAVE_ADDRESS; + // uint8_t address = QMC6310N_SLAVE_ADDRESS; + + if (!magnetometer.begin(Wire, address, SENSOR_SDA, SENSOR_SCL)) { + while (1) { + Serial.println("Failed to find QMC6310 - check your wiring!"); + delay(1000); + } + } + + // The desired output data rate in Hz. Allowed values are 10.0, 50.0, 100.0 and 200.0HZ. + float data_rate_hz = 200.0f; + // op_mode: Allowed values are SUSPEND, NORMAL, SINGLE_MEASUREMENT, CONTINUOUS_MEASUREMENT + OperationMode op_mode = OperationMode::CONTINUOUS_MEASUREMENT; + // full_scale: Allowed values are FS_2G, FS_8G, FS_12G ,FS_30G + MagFullScaleRange full_scale = MagFullScaleRange::FS_8G; + // over_sample_ratio: Allowed values are OSR_1, OSR_2, OSR_4, OSR_8 + MagOverSampleRatio over_sample_ratio = MagOverSampleRatio::OSR_1; + // down_sample_ratio: Allowed values are DSR_1, DSR_2, DSR_4, DSR_8 + MagDownSampleRatio down_sample_ratio = MagDownSampleRatio::DSR_1; + + /* Config Magnetometer */ + if (magnetometer.configMagnetometer( + op_mode, + full_scale, + data_rate_hz, + over_sample_ratio, + down_sample_ratio)) { + Serial.println("Magnetometer configured successfully."); + } else { + Serial.println("Magnetometer configuration failed."); + while (1); + } + + calibrate(); + Serial.println("Calibration done."); + + Serial.println(); + + // Print Sensor ID and basic configuration. + SensorInfo info = magnetometer.getSensorInfo(); + Serial.print("Manufacturer: "); + Serial.println(info.manufacturer); + + Serial.print("Model: "); + Serial.println(info.model); + + Serial.print("I2C Address: 0x"); + Serial.println(info.i2c_address, HEX); + + Serial.print("Version: "); + Serial.println(info.version); + + Serial.print("UID: 0x"); + Serial.println(info.uid, HEX); + + Serial.print("Type: "); + Serial.print("Type: "); Serial.println(SensorUtils::typeToString(info.type)); + Serial.println(); + + /* Print Sensor settings. */ + // TODO: Add getSensorSettings() method to retrieve current sensor settings and print them here. + // auto settings = magnetometer.getSensorSettings(); + // Serial.print("DataRate: "); + // Serial.println(settings.outputDataRate); + + // Serial.print("FullScaleRange: "); + // Serial.println(settings.fullScaleRange); + + // Serial.print("Mode: "); + // Serial.println(settings.mode); + + // Serial.println(); + + // User's location magnetic declination offset. + //float declination_offset_deg = -3.33f; // Lewis's + float declination_offset_deg = 14.28f; // Salem, Oregon + magnetometer.setDeclination(declination_offset_deg); + Serial.print(" Magnetic Declination: "); + Serial.print(declination_offset_deg); + Serial.println("°"); + + // Print current sensitivity. + // Full scale 2G: 0.000067 Gauss/LSB + // Full scale 8G: 0.000267 Gauss/LSB + // Full scale 12G: 0.000400 Gauss/LSB + // Full scale 30G: 0.001000 Gauss/LSB + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.println(" Gauss/LSB"); +} + + +void loop() +{ + MagnetometerData data; + + if (magnetometer.readData(data)) { + + // Gauss to μT + float x = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.x); + float y = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.y); + float z = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.z); + + Serial.print("Mag:"); + Serial.print(" X:"); Serial.print(x); + Serial.print(" Y:"); Serial.print(y); + Serial.print(" Z:"); Serial.print(z); + Serial.print(" μT"); + + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.print(" Gauss/LSB"); + + Serial.print(" Metadata:"); + Serial.print(" X:"); + Serial.print(data.raw.x); + Serial.print(" Y:"); + Serial.print(data.raw.y); + Serial.print(" Z:"); + Serial.print(data.raw.z); + + //Serial.print(" Heading (rad): "); + //Serial.print(data.heading, 6); + //Serial.print(" rad"); + + //Serial.print(" Heading (deg): "); + //Serial.print(data.heading_degrees, 2); + //Serial.print("°"); + // Use raw values (already offset-corrected by calibration) + float Xc = data.raw.x; + float Yc = data.raw.y; + + // Compute corrected heading + //float heading_rad = atan2(Yc, Xc); // this was mirrored + float heading_rad = atan2(-Yc, Xc); + // Convert to degrees + float heading_deg = heading_rad * 180.0 / PI; + if (heading_deg < 0) heading_deg += 360; + + // Print corrected values + Serial.print(" HeadingCorr (rad): "); + Serial.print(heading_rad, 6); + Serial.print(" rad"); + + Serial.print(" HeadingCorr (deg): "); + Serial.print(heading_deg, 2); + Serial.print("°"); + + float strength = MagnetometerUtils::calculateMagneticStrength(data); + strength = MagnetometerUtils::gaussToMicroTesla(strength); + Serial.print(" Magnetic Strength: "); + Serial.print(strength, 2); + Serial.println(" μT"); + + if (data.overflow) { + Serial.println("\tWarning: Data Overflow occurred!"); + } + } + delay(10); +} + + + From fd37ad6b50413cd17dc6dd5066a492c3a0377b44 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:23:13 -0700 Subject: [PATCH 06/10] first stage of clean-up and recovery from linking attempt --- .../QMC6310_CalibrateExample.ino | 325 ------------------ .../QMC63xx_GetDataExample.ino | 33 +- examples/platformio.ini | 3 +- 3 files changed, 32 insertions(+), 329 deletions(-) delete mode 100644 examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino diff --git a/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino deleted file mode 100644 index 967afaa..0000000 --- a/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino +++ /dev/null @@ -1,325 +0,0 @@ -/** - * - * @license MIT License - * - * Copyright (c) 2026 lewis he - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * @file QMC6310_CalibrateExample.ino - * @author Lewis He (lewishe@outlook.com) - * @date 2026-01-26 - * - */ -#include -#include -#include -#include "SensorQMC6310.hpp" -#define ARDUINO_T_BEAM_S3_SUPREME -#ifdef ARDUINO_T_BEAM_S3_SUPREME -#include //PMU Library https://github.com/lewisxhe/XPowersLib.git -#endif - -#ifndef SENSOR_SDA -#define SENSOR_SDA 17 -#endif - -#ifndef SENSOR_SCL -#define SENSOR_SCL 18 -#endif - -SensorQMC6310 magnetometer; - -void beginPower() -{ -#if defined(ARDUINO_T_BEAM_S3_SUPREME) - XPowersAXP2101 power; - power.begin(Wire1, AXP2101_SLAVE_ADDRESS, 42, 41); - power.disableALDO1(); - power.disableALDO2(); - delay(250); - power.setALDO1Voltage(3300); - power.enableALDO1(); - power.setALDO2Voltage(3300); - power.enableALDO2(); -#endif -} - -void calibrate() -{ - if (!magnetometer.setOutputDataRate(200.0f)) { - Serial.println("Failed to set output data rate"); - return ; - } - - Serial.println("========================================"); - Serial.println("Calibration Instructions:"); - Serial.println("1. Rotate sensor in FIGURE-8 pattern"); - Serial.println("2. Cover all axes (X, Y, Z directions)"); - Serial.println("3. Rotate slowly and completely"); - Serial.println("4. Wait for progress bar to complete"); - Serial.println("5. Expected: Magnetic Strength ~25-65 uT"); - Serial.println("========================================"); - Serial.println(); - - Serial.println("Place the sensor on the plane and slowly rotate the sensor..."); - Serial.println("Rotate in FIGURE-8 pattern to cover all directions!"); - Serial.println(); - - int32_t x_min = 65535; - int32_t x_max = -65535; - int32_t y_min = 65535; - int32_t y_max = -65535; - int32_t z_min = 65535; - int32_t z_max = -65535; - - int32_t range = 1000; - int32_t i = 0; - int32_t x = 0, y = 0, z = 0;; - int16_t x_offset = 0; - int16_t y_offset = 0; - int16_t z_offset = 0; - - MagnetometerData data; - while (i < range) { - i += 1; - - if (magnetometer.isDataReady()) { - - magnetometer.readData(data); - - x = (data.raw.x + x) / 2; - y = (data.raw.y + y) / 2; - z = (data.raw.z + z) / 2; - if (x < x_min) { - x_min = x; - i = 0; - } - if (x > x_max) { - x_max = x; - i = 0; - } - if (y < y_min) { - y_min = y; - i = 0; - } - if (y > y_max) { - y_max = y; - i = 0; - } - if (z < z_min) { - z_min = z; - i = 0; - } - if (z > z_max) { - z_max = z; - i = 0; - } - int j = round(10 * i / range); - - Serial.print("["); - for (int k = 0; k < j; ++k) { - Serial.print("*"); - } - Serial.println("]"); - } - delay(5); - } - - x_offset = (x_max + x_min) / 2; - y_offset = (y_max + y_min) / 2; - z_offset = (z_max + z_min) / 2; - - Serial.print("x_min:"); - Serial.println(x_min); - - Serial.print("x_max:"); - Serial.println(x_max); - - Serial.print("y_min:"); - Serial.println(y_min); - - Serial.print("y_max:"); - Serial.println(y_max); - - Serial.print("z_min:"); - Serial.println(z_min); - - Serial.print("z_max:"); - Serial.println(z_max); - - Serial.print("x_offset:"); - Serial.println(x_offset); - - Serial.print("y_offset:"); - Serial.println(y_offset); - - Serial.print("z_offset:"); - Serial.println(z_offset); - - - // Set the calibration value and the user calculates the deviation - magnetometer.setOffset(x_offset, y_offset, z_offset); - - Serial.println(); - Serial.println("Calibration complete!"); - Serial.println("Check if Magnetic Strength is ~25-65 uT"); - Serial.println("If too low, repeat calibration with better rotation"); -} - - -void setup() -{ - Serial.begin(115200); - while (!Serial); - - // LilyGo T-Beam-Supreme sensor requires a power source to function. - beginPower(); - - /** - * Supports QMC6310U and QMC6310N; simply pass the corresponding device address - * during initialization. - * - QMC6310U_SLAVE_ADDRESS - * - QMC6310N_SLAVE_ADDRESS - */ - uint8_t address = QMC6310U_SLAVE_ADDRESS; - // uint8_t address = QMC6310N_SLAVE_ADDRESS; - - if (!magnetometer.begin(Wire, address, SENSOR_SDA, SENSOR_SCL)) { - while (1) { - Serial.println("Failed to find QMC6310 - check your wiring!"); - delay(1000); - } - } - - // The desired output data rate in Hz. Allowed values are 10.0, 50.0, 100.0 and 200.0HZ. - float data_rate_hz = 200.0f; - // op_mode: Allowed values are SUSPEND, NORMAL, SINGLE_MEASUREMENT, CONTINUOUS_MEASUREMENT - OperationMode op_mode = OperationMode::CONTINUOUS_MEASUREMENT; - // full_scale: Allowed values are FS_2G, FS_8G, FS_12G ,FS_30G - MagFullScaleRange full_scale = MagFullScaleRange::FS_8G; - // over_sample_ratio: Allowed values are OSR_1, OSR_2, OSR_4, OSR_8 - MagOverSampleRatio over_sample_ratio = MagOverSampleRatio::OSR_1; - // down_sample_ratio: Allowed values are DSR_1, DSR_2, DSR_4, DSR_8 - MagDownSampleRatio down_sample_ratio = MagDownSampleRatio::DSR_1; - - /* Config Magnetometer */ - if (magnetometer.configMagnetometer( - op_mode, - full_scale, - data_rate_hz, - over_sample_ratio, - down_sample_ratio)) { - Serial.println("Magnetometer configured successfully."); - } else { - Serial.println("Magnetometer configuration failed."); - while (1); - } - - // Calibration algorithm reference from - // https://github.com/CoreElectronics/CE-PiicoDev-QMC6310-MicroPython-Module - calibrate(); - - Serial.println("Calibration done."); - delay(5000); - - SensorInfo info = magnetometer.getSensorInfo(); - Serial.print("Manufacturer: "); Serial.println(info.manufacturer); - Serial.print("Model: "); Serial.println(info.model); - Serial.print("I2C Address: 0x"); Serial.println(info.i2c_address, HEX); - Serial.print("Version: "); Serial.println(info.version); - Serial.print("UID: 0x"); Serial.println(info.uid); - Serial.print("Type: "); Serial.println(SensorUtils::typeToString(info.type)); - - SensorConfig cfg = magnetometer.getConfig(); - Serial.print("DataRate: "); Serial.println(cfg.sample_rate); - Serial.print("FullScaleRange: "); Serial.println(cfg.range); - Serial.print("Mode: "); Serial.println((uint8_t)cfg.mode); - Serial.println(); - - //Find the magnetic declination : https://www.magnetic-declination.com/ - float declination_deg = MagnetometerUtils::dmsToDecimalDegrees(-3, 20); // -3.3333 - - magnetometer.setDeclination(declination_deg); - - Serial.print(" Magnetic Declination: "); - Serial.print(declination_deg, 2); - Serial.println("°"); - - Serial.print(" Sensitivity: "); - Serial.print(magnetometer.getSensitivity(), 6); - Serial.println(" Gauss/LSB"); - - delay(3000); - - Serial.println("Read data now..."); -} - -void loop() -{ - MagnetometerData data; - - if (magnetometer.readData(data)) { - - // Gauss to μT - float x = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.x); - float y = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.y); - float z = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.z); - - Serial.print("Mag:"); - Serial.print(" X:"); Serial.print(x); - Serial.print(" Y:"); Serial.print(y); - Serial.print(" Z:"); Serial.print(z); - Serial.print(" μT"); - - Serial.print(" Sensitivity: "); - Serial.print(magnetometer.getSensitivity(), 6); - Serial.print(" Gauss/LSB"); - - Serial.print(" Metadata:"); - Serial.print(" X:"); - Serial.print(data.raw.x); - Serial.print(" Y:"); - Serial.print(data.raw.y); - Serial.print(" Z:"); - Serial.print(data.raw.z); - - Serial.print(" Heading (rad): "); - Serial.print(data.heading, 6); - Serial.print(" rad"); - - Serial.print(" Heading (deg): "); - Serial.print(data.heading_degrees, 2); - Serial.print("°"); - - float strength = MagnetometerUtils::calculateMagneticStrength(data); - strength = MagnetometerUtils::gaussToMicroTesla(strength); - Serial.print(" Magnetic Strength: "); - Serial.print(strength, 2); - Serial.println(" μT"); - - if (data.overflow) { - Serial.println("\tWarning: Data Overflow occurred!"); - } - } - delay(10); -} - - - diff --git a/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino b/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino index c815827..874936e 100644 --- a/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino +++ b/examples/Sensor/QMC63xx_GetDataExample/QMC63xx_GetDataExample.ino @@ -298,14 +298,25 @@ void calibrate() int16_t y_offset = 0; int16_t z_offset = 0; + uint32_t sample_counter = 0; + + MagnetometerData data; while (i < range) { i += 1; if (magnetometer->isDataReady()) { - + sample_counter++; magnetometer->readData(data); + Serial.print("CALRAW "); + Serial.print("X:"); + Serial.print(data.raw.x); + Serial.print(" Y:"); + Serial.print(data.raw.y); + Serial.print(" Z:"); + Serial.println(data.raw.z); + x = (data.raw.x + x) / 2; y = (data.raw.y + y) / 2; z = (data.raw.z + z) / 2; @@ -333,13 +344,29 @@ void calibrate() z_max = z; i = 0; } - int j = round(10 * i / range); +/* int j = round(10 * i / range); Serial.print("["); for (int k = 0; k < j; ++k) { Serial.print("*"); } - Serial.println("]"); + Serial.println("]"); */ + if ((sample_counter % 100) == 0) { + Serial.print("CALSTAT i="); + Serial.print(i); + Serial.print(" x_min="); + Serial.print(x_min); + Serial.print(" x_max="); + Serial.print(x_max); + Serial.print(" y_min="); + Serial.print(y_min); + Serial.print(" y_max="); + Serial.print(y_max); + Serial.print(" z_min="); + Serial.print(z_min); + Serial.print(" z_max="); + Serial.println(z_max); + } } delay(5); } diff --git a/examples/platformio.ini b/examples/platformio.ini index 5e07652..dacc758 100644 --- a/examples/platformio.ini +++ b/examples/platformio.ini @@ -3,7 +3,8 @@ default_envs = T_BEAM_S3_SUPREME_SX1262 ; Change this line when you want to build a different copied LilyGO example. ;src_dir = QMC6310_CompassExample -src_dir = Sensor/QMC63xx_GetDataExample +;src_dir = Sensor/QMC63xx_GetDataExample +src_dir = Sensor/QMC6310_CalibrateExample ; Reuse LilyGO's custom board definitions without editing the LilyGO checkout. boards_dir = /usr/local/src/LilyGo-LoRa-Series/boards From 22f0cea6fc4486c00bfb7377a573fa9f5babeaa1 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:24:14 -0700 Subject: [PATCH 07/10] re-introducing the modified version with mean calculations --- ...le_mean_offsets_20260421.ino.safety_backup | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino.safety_backup diff --git a/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino.safety_backup b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino.safety_backup new file mode 100644 index 0000000..4254a65 --- /dev/null +++ b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample_mean_offsets_20260421.ino.safety_backup @@ -0,0 +1,424 @@ +/** + * + * @license MIT License + * + * Copyright (c) 2026 lewis he + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file QMC6310_CalibrateExample.ino + * @author Lewis He (lewishe@outlook.com) + * @date 2026-01-26 + * + * Modified 2026-04-21 to compute mean-based offsets from CALRAW samples + * instead of midpoint-of-extrema offsets. + * + */ +#include +#include +#include +#include "SensorQMC6310.hpp" +#define ARDUINO_T_BEAM_S3_SUPREME +#ifdef ARDUINO_T_BEAM_S3_SUPREME +#include //PMU Library https://github.com/lewisxhe/XPowersLib.git +#endif + +#ifndef SENSOR_SDA +#define SENSOR_SDA 17 +#endif + +#ifndef SENSOR_SCL +#define SENSOR_SCL 18 +#endif + +SensorQMC6310 magnetometer; + +void beginPower() +{ +#if defined(ARDUINO_T_BEAM_S3_SUPREME) + XPowersAXP2101 power; + power.begin(Wire1, AXP2101_SLAVE_ADDRESS, 42, 41); + power.disableALDO1(); + power.disableALDO2(); + delay(250); + power.setALDO1Voltage(3300); + power.enableALDO1(); + power.setALDO2Voltage(3300); + power.enableALDO2(); +#endif +} + +void calibrate() +{ + if (!magnetometer.setOutputDataRate(200.0f)) { + Serial.println("Failed to set output data rate"); + return ; + } + + Serial.println("========================================"); + Serial.println("Calibration Instructions:"); + Serial.println("1. Rotate sensor in FIGURE-8 pattern"); + Serial.println("2. Cover all axes (X, Y, Z directions)"); + Serial.println("3. Rotate slowly and completely"); + Serial.println("4. Wait for progress bar to complete"); + Serial.println("5. Expected: Magnetic Strength ~25-65 uT"); + Serial.println("========================================"); + Serial.println(); + + Serial.println("Place the sensor on the plane and slowly rotate the sensor..."); + Serial.println("Rotate in FIGURE-8 pattern to cover all directions!"); + Serial.println(); + + int32_t x_min = 65535; + int32_t x_max = -65535; + int32_t y_min = 65535; + int32_t y_max = -65535; + int32_t z_min = 65535; + int32_t z_max = -65535; + + int32_t range = 2000; + int32_t i = 0; + int32_t raw_x = 0, raw_y = 0, raw_z = 0; + int16_t x_offset = 0; + int16_t y_offset = 0; + int16_t z_offset = 0; + + uint32_t sample_counter = 0; + + // Mean-based accumulation of raw calibration samples. + int64_t x_sum = 0; + int64_t y_sum = 0; + int64_t z_sum = 0; + + MagnetometerData data; + while (i < range) { + i += 1; + + if (magnetometer.isDataReady()) { + + magnetometer.readData(data); + sample_counter++; + + raw_x = data.raw.x; + raw_y = data.raw.y; + raw_z = data.raw.z; + + x_sum += raw_x; + y_sum += raw_y; + z_sum += raw_z; + + Serial.print("CALRAW "); + Serial.print("X:"); + Serial.print(raw_x); + Serial.print(" Y:"); + Serial.print(raw_y); + Serial.print(" Z:"); + Serial.println(raw_z); + + // Track extrema on the direct raw sample, not on a running average. + if (raw_x < x_min) { + x_min = raw_x; + i = 0; + } + if (raw_x > x_max) { + x_max = raw_x; + i = 0; + } + if (raw_y < y_min) { + y_min = raw_y; + i = 0; + } + if (raw_y > y_max) { + y_max = raw_y; + i = 0; + } + if (raw_z < z_min) { + z_min = raw_z; + i = 0; + } + if (raw_z > z_max) { + z_max = raw_z; + i = 0; + } + + if ((sample_counter % 100) == 0) { + Serial.print("CALSTAT i="); + Serial.print(i); + Serial.print(" samples="); + Serial.print(sample_counter); + Serial.print(" x_min="); + Serial.print(x_min); + Serial.print(" x_max="); + Serial.print(x_max); + Serial.print(" y_min="); + Serial.print(y_min); + Serial.print(" y_max="); + Serial.print(y_max); + Serial.print(" z_min="); + Serial.print(z_min); + Serial.print(" z_max="); + Serial.println(z_max); + } + } + delay(5); + } + + if (sample_counter == 0) { + Serial.println("No calibration samples were collected."); + return; + } + + // Mean/centroid-based offsets. + x_offset = (int16_t)lround((double)x_sum / (double)sample_counter); + y_offset = (int16_t)lround((double)y_sum / (double)sample_counter); + z_offset = (int16_t)lround((double)z_sum / (double)sample_counter); + + // Also compute the old midpoint offsets for comparison/debugging. + int16_t x_midpoint_offset = (x_max + x_min) / 2; + int16_t y_midpoint_offset = (y_max + y_min) / 2; + int16_t z_midpoint_offset = (z_max + z_min) / 2; + + Serial.print("samples:"); + Serial.println(sample_counter); + + Serial.print("x_min:"); + Serial.println(x_min); + + Serial.print("x_max:"); + Serial.println(x_max); + + Serial.print("y_min:"); + Serial.println(y_min); + + Serial.print("y_max:"); + Serial.println(y_max); + + Serial.print("z_min:"); + Serial.println(z_min); + + Serial.print("z_max:"); + Serial.println(z_max); + + Serial.print("x_midpoint_offset:"); + Serial.println(x_midpoint_offset); + + Serial.print("y_midpoint_offset:"); + Serial.println(y_midpoint_offset); + + Serial.print("z_midpoint_offset:"); + Serial.println(z_midpoint_offset); + + Serial.print("x_mean_offset:"); + Serial.println(x_offset); + + Serial.print("y_mean_offset:"); + Serial.println(y_offset); + + Serial.print("z_mean_offset:"); + Serial.println(z_offset); + + // Set the mean-based calibration values. + magnetometer.setOffset(x_offset, y_offset, z_offset); + + Serial.println(); + Serial.println("Calibration complete!"); + Serial.println("Offsets applied using raw-sample means."); + Serial.println("Check if Magnetic Strength is ~25-65 uT"); + Serial.println("If too low, repeat calibration with better rotation"); +} + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + // LilyGo T-Beam-Supreme sensor requires a power source to function. + beginPower(); + + /** + * Supports QMC6310U and QMC6310N; simply pass the corresponding device address + * during initialization. + * - QMC6310U_SLAVE_ADDRESS + * - QMC6310N_SLAVE_ADDRESS + */ + uint8_t address = QMC6310U_SLAVE_ADDRESS; + // uint8_t address = QMC6310N_SLAVE_ADDRESS; + + if (!magnetometer.begin(Wire, address, SENSOR_SDA, SENSOR_SCL)) { + while (1) { + Serial.println("Failed to find QMC6310 - check your wiring!"); + delay(1000); + } + } + + // The desired output data rate in Hz. Allowed values are 10.0, 50.0, 100.0 and 200.0HZ. + float data_rate_hz = 200.0f; + // op_mode: Allowed values are SUSPEND, NORMAL, SINGLE_MEASUREMENT, CONTINUOUS_MEASUREMENT + OperationMode op_mode = OperationMode::CONTINUOUS_MEASUREMENT; + // full_scale: Allowed values are FS_2G, FS_8G, FS_12G ,FS_30G + MagFullScaleRange full_scale = MagFullScaleRange::FS_8G; + // over_sample_ratio: Allowed values are OSR_1, OSR_2, OSR_4, OSR_8 + MagOverSampleRatio over_sample_ratio = MagOverSampleRatio::OSR_1; + // down_sample_ratio: Allowed values are DSR_1, DSR_2, DSR_4, DSR_8 + MagDownSampleRatio down_sample_ratio = MagDownSampleRatio::DSR_1; + + /* Config Magnetometer */ + if (magnetometer.configMagnetometer( + op_mode, + full_scale, + data_rate_hz, + over_sample_ratio, + down_sample_ratio)) { + Serial.println("Magnetometer configured successfully."); + } else { + Serial.println("Magnetometer configuration failed."); + while (1); + } + + calibrate(); + Serial.println("Calibration done."); + + Serial.println(); + + // Print Sensor ID and basic configuration. + SensorInfo info = magnetometer.getSensorInfo(); + Serial.print("Manufacturer: "); + Serial.println(info.manufacturer); + + Serial.print("Model: "); + Serial.println(info.model); + + Serial.print("I2C Address: 0x"); + Serial.println(info.i2c_address, HEX); + + Serial.print("Version: "); + Serial.println(info.version); + + Serial.print("UID: 0x"); + Serial.println(info.uid, HEX); + + Serial.print("Type: "); + Serial.print("Type: "); Serial.println(SensorUtils::typeToString(info.type)); + Serial.println(); + + /* Print Sensor settings. */ + // TODO: Add getSensorSettings() method to retrieve current sensor settings and print them here. + // auto settings = magnetometer.getSensorSettings(); + // Serial.print("DataRate: "); + // Serial.println(settings.outputDataRate); + + // Serial.print("FullScaleRange: "); + // Serial.println(settings.fullScaleRange); + + // Serial.print("Mode: "); + // Serial.println(settings.mode); + + // Serial.println(); + + // User's location magnetic declination offset. + //float declination_offset_deg = -3.33f; // Lewis's + float declination_offset_deg = 14.28f; // Salem, Oregon + magnetometer.setDeclination(declination_offset_deg); + Serial.print(" Magnetic Declination: "); + Serial.print(declination_offset_deg); + Serial.println("°"); + + // Print current sensitivity. + // Full scale 2G: 0.000067 Gauss/LSB + // Full scale 8G: 0.000267 Gauss/LSB + // Full scale 12G: 0.000400 Gauss/LSB + // Full scale 30G: 0.001000 Gauss/LSB + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.println(" Gauss/LSB"); +} + + +void loop() +{ + MagnetometerData data; + + if (magnetometer.readData(data)) { + + // Gauss to μT + float x = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.x); + float y = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.y); + float z = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.z); + + Serial.print("Mag:"); + Serial.print(" X:"); Serial.print(x); + Serial.print(" Y:"); Serial.print(y); + Serial.print(" Z:"); Serial.print(z); + Serial.print(" μT"); + + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.print(" Gauss/LSB"); + + Serial.print(" Metadata:"); + Serial.print(" X:"); + Serial.print(data.raw.x); + Serial.print(" Y:"); + Serial.print(data.raw.y); + Serial.print(" Z:"); + Serial.print(data.raw.z); + + //Serial.print(" Heading (rad): "); + //Serial.print(data.heading, 6); + //Serial.print(" rad"); + + //Serial.print(" Heading (deg): "); + //Serial.print(data.heading_degrees, 2); + //Serial.print("°"); + // Use raw values (already offset-corrected by calibration) + float Xc = data.raw.x; + float Yc = data.raw.y; + + // Compute corrected heading + //float heading_rad = atan2(Yc, Xc); // this was mirrored + float heading_rad = atan2(-Yc, Xc); + // Convert to degrees + float heading_deg = heading_rad * 180.0 / PI; + if (heading_deg < 0) heading_deg += 360; + + // Print corrected values + Serial.print(" HeadingCorr (rad): "); + Serial.print(heading_rad, 6); + Serial.print(" rad"); + + Serial.print(" HeadingCorr (deg): "); + Serial.print(heading_deg, 2); + Serial.print("°"); + + float strength = MagnetometerUtils::calculateMagneticStrength(data); + strength = MagnetometerUtils::gaussToMicroTesla(strength); + Serial.print(" Magnetic Strength: "); + Serial.print(strength, 2); + Serial.println(" μT"); + + if (data.overflow) { + Serial.println("\tWarning: Data Overflow occurred!"); + } + } + delay(10); +} + + + From ffd936a99e343ad562facdb3831fd5ccb6bad0d5 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:40:24 -0700 Subject: [PATCH 08/10] Steal trying to clean up, I accidentally saved the safety file instead of this one --- .../QMC6310_CalibrateExample.ino | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino diff --git a/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino new file mode 100644 index 0000000..4254a65 --- /dev/null +++ b/examples/Sensor/QMC6310_CalibrateExample/QMC6310_CalibrateExample.ino @@ -0,0 +1,424 @@ +/** + * + * @license MIT License + * + * Copyright (c) 2026 lewis he + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file QMC6310_CalibrateExample.ino + * @author Lewis He (lewishe@outlook.com) + * @date 2026-01-26 + * + * Modified 2026-04-21 to compute mean-based offsets from CALRAW samples + * instead of midpoint-of-extrema offsets. + * + */ +#include +#include +#include +#include "SensorQMC6310.hpp" +#define ARDUINO_T_BEAM_S3_SUPREME +#ifdef ARDUINO_T_BEAM_S3_SUPREME +#include //PMU Library https://github.com/lewisxhe/XPowersLib.git +#endif + +#ifndef SENSOR_SDA +#define SENSOR_SDA 17 +#endif + +#ifndef SENSOR_SCL +#define SENSOR_SCL 18 +#endif + +SensorQMC6310 magnetometer; + +void beginPower() +{ +#if defined(ARDUINO_T_BEAM_S3_SUPREME) + XPowersAXP2101 power; + power.begin(Wire1, AXP2101_SLAVE_ADDRESS, 42, 41); + power.disableALDO1(); + power.disableALDO2(); + delay(250); + power.setALDO1Voltage(3300); + power.enableALDO1(); + power.setALDO2Voltage(3300); + power.enableALDO2(); +#endif +} + +void calibrate() +{ + if (!magnetometer.setOutputDataRate(200.0f)) { + Serial.println("Failed to set output data rate"); + return ; + } + + Serial.println("========================================"); + Serial.println("Calibration Instructions:"); + Serial.println("1. Rotate sensor in FIGURE-8 pattern"); + Serial.println("2. Cover all axes (X, Y, Z directions)"); + Serial.println("3. Rotate slowly and completely"); + Serial.println("4. Wait for progress bar to complete"); + Serial.println("5. Expected: Magnetic Strength ~25-65 uT"); + Serial.println("========================================"); + Serial.println(); + + Serial.println("Place the sensor on the plane and slowly rotate the sensor..."); + Serial.println("Rotate in FIGURE-8 pattern to cover all directions!"); + Serial.println(); + + int32_t x_min = 65535; + int32_t x_max = -65535; + int32_t y_min = 65535; + int32_t y_max = -65535; + int32_t z_min = 65535; + int32_t z_max = -65535; + + int32_t range = 2000; + int32_t i = 0; + int32_t raw_x = 0, raw_y = 0, raw_z = 0; + int16_t x_offset = 0; + int16_t y_offset = 0; + int16_t z_offset = 0; + + uint32_t sample_counter = 0; + + // Mean-based accumulation of raw calibration samples. + int64_t x_sum = 0; + int64_t y_sum = 0; + int64_t z_sum = 0; + + MagnetometerData data; + while (i < range) { + i += 1; + + if (magnetometer.isDataReady()) { + + magnetometer.readData(data); + sample_counter++; + + raw_x = data.raw.x; + raw_y = data.raw.y; + raw_z = data.raw.z; + + x_sum += raw_x; + y_sum += raw_y; + z_sum += raw_z; + + Serial.print("CALRAW "); + Serial.print("X:"); + Serial.print(raw_x); + Serial.print(" Y:"); + Serial.print(raw_y); + Serial.print(" Z:"); + Serial.println(raw_z); + + // Track extrema on the direct raw sample, not on a running average. + if (raw_x < x_min) { + x_min = raw_x; + i = 0; + } + if (raw_x > x_max) { + x_max = raw_x; + i = 0; + } + if (raw_y < y_min) { + y_min = raw_y; + i = 0; + } + if (raw_y > y_max) { + y_max = raw_y; + i = 0; + } + if (raw_z < z_min) { + z_min = raw_z; + i = 0; + } + if (raw_z > z_max) { + z_max = raw_z; + i = 0; + } + + if ((sample_counter % 100) == 0) { + Serial.print("CALSTAT i="); + Serial.print(i); + Serial.print(" samples="); + Serial.print(sample_counter); + Serial.print(" x_min="); + Serial.print(x_min); + Serial.print(" x_max="); + Serial.print(x_max); + Serial.print(" y_min="); + Serial.print(y_min); + Serial.print(" y_max="); + Serial.print(y_max); + Serial.print(" z_min="); + Serial.print(z_min); + Serial.print(" z_max="); + Serial.println(z_max); + } + } + delay(5); + } + + if (sample_counter == 0) { + Serial.println("No calibration samples were collected."); + return; + } + + // Mean/centroid-based offsets. + x_offset = (int16_t)lround((double)x_sum / (double)sample_counter); + y_offset = (int16_t)lround((double)y_sum / (double)sample_counter); + z_offset = (int16_t)lround((double)z_sum / (double)sample_counter); + + // Also compute the old midpoint offsets for comparison/debugging. + int16_t x_midpoint_offset = (x_max + x_min) / 2; + int16_t y_midpoint_offset = (y_max + y_min) / 2; + int16_t z_midpoint_offset = (z_max + z_min) / 2; + + Serial.print("samples:"); + Serial.println(sample_counter); + + Serial.print("x_min:"); + Serial.println(x_min); + + Serial.print("x_max:"); + Serial.println(x_max); + + Serial.print("y_min:"); + Serial.println(y_min); + + Serial.print("y_max:"); + Serial.println(y_max); + + Serial.print("z_min:"); + Serial.println(z_min); + + Serial.print("z_max:"); + Serial.println(z_max); + + Serial.print("x_midpoint_offset:"); + Serial.println(x_midpoint_offset); + + Serial.print("y_midpoint_offset:"); + Serial.println(y_midpoint_offset); + + Serial.print("z_midpoint_offset:"); + Serial.println(z_midpoint_offset); + + Serial.print("x_mean_offset:"); + Serial.println(x_offset); + + Serial.print("y_mean_offset:"); + Serial.println(y_offset); + + Serial.print("z_mean_offset:"); + Serial.println(z_offset); + + // Set the mean-based calibration values. + magnetometer.setOffset(x_offset, y_offset, z_offset); + + Serial.println(); + Serial.println("Calibration complete!"); + Serial.println("Offsets applied using raw-sample means."); + Serial.println("Check if Magnetic Strength is ~25-65 uT"); + Serial.println("If too low, repeat calibration with better rotation"); +} + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + // LilyGo T-Beam-Supreme sensor requires a power source to function. + beginPower(); + + /** + * Supports QMC6310U and QMC6310N; simply pass the corresponding device address + * during initialization. + * - QMC6310U_SLAVE_ADDRESS + * - QMC6310N_SLAVE_ADDRESS + */ + uint8_t address = QMC6310U_SLAVE_ADDRESS; + // uint8_t address = QMC6310N_SLAVE_ADDRESS; + + if (!magnetometer.begin(Wire, address, SENSOR_SDA, SENSOR_SCL)) { + while (1) { + Serial.println("Failed to find QMC6310 - check your wiring!"); + delay(1000); + } + } + + // The desired output data rate in Hz. Allowed values are 10.0, 50.0, 100.0 and 200.0HZ. + float data_rate_hz = 200.0f; + // op_mode: Allowed values are SUSPEND, NORMAL, SINGLE_MEASUREMENT, CONTINUOUS_MEASUREMENT + OperationMode op_mode = OperationMode::CONTINUOUS_MEASUREMENT; + // full_scale: Allowed values are FS_2G, FS_8G, FS_12G ,FS_30G + MagFullScaleRange full_scale = MagFullScaleRange::FS_8G; + // over_sample_ratio: Allowed values are OSR_1, OSR_2, OSR_4, OSR_8 + MagOverSampleRatio over_sample_ratio = MagOverSampleRatio::OSR_1; + // down_sample_ratio: Allowed values are DSR_1, DSR_2, DSR_4, DSR_8 + MagDownSampleRatio down_sample_ratio = MagDownSampleRatio::DSR_1; + + /* Config Magnetometer */ + if (magnetometer.configMagnetometer( + op_mode, + full_scale, + data_rate_hz, + over_sample_ratio, + down_sample_ratio)) { + Serial.println("Magnetometer configured successfully."); + } else { + Serial.println("Magnetometer configuration failed."); + while (1); + } + + calibrate(); + Serial.println("Calibration done."); + + Serial.println(); + + // Print Sensor ID and basic configuration. + SensorInfo info = magnetometer.getSensorInfo(); + Serial.print("Manufacturer: "); + Serial.println(info.manufacturer); + + Serial.print("Model: "); + Serial.println(info.model); + + Serial.print("I2C Address: 0x"); + Serial.println(info.i2c_address, HEX); + + Serial.print("Version: "); + Serial.println(info.version); + + Serial.print("UID: 0x"); + Serial.println(info.uid, HEX); + + Serial.print("Type: "); + Serial.print("Type: "); Serial.println(SensorUtils::typeToString(info.type)); + Serial.println(); + + /* Print Sensor settings. */ + // TODO: Add getSensorSettings() method to retrieve current sensor settings and print them here. + // auto settings = magnetometer.getSensorSettings(); + // Serial.print("DataRate: "); + // Serial.println(settings.outputDataRate); + + // Serial.print("FullScaleRange: "); + // Serial.println(settings.fullScaleRange); + + // Serial.print("Mode: "); + // Serial.println(settings.mode); + + // Serial.println(); + + // User's location magnetic declination offset. + //float declination_offset_deg = -3.33f; // Lewis's + float declination_offset_deg = 14.28f; // Salem, Oregon + magnetometer.setDeclination(declination_offset_deg); + Serial.print(" Magnetic Declination: "); + Serial.print(declination_offset_deg); + Serial.println("°"); + + // Print current sensitivity. + // Full scale 2G: 0.000067 Gauss/LSB + // Full scale 8G: 0.000267 Gauss/LSB + // Full scale 12G: 0.000400 Gauss/LSB + // Full scale 30G: 0.001000 Gauss/LSB + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.println(" Gauss/LSB"); +} + + +void loop() +{ + MagnetometerData data; + + if (magnetometer.readData(data)) { + + // Gauss to μT + float x = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.x); + float y = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.y); + float z = MagnetometerUtils::gaussToMicroTesla(data.magnetic_field.z); + + Serial.print("Mag:"); + Serial.print(" X:"); Serial.print(x); + Serial.print(" Y:"); Serial.print(y); + Serial.print(" Z:"); Serial.print(z); + Serial.print(" μT"); + + Serial.print(" Sensitivity: "); + Serial.print(magnetometer.getSensitivity(), 6); + Serial.print(" Gauss/LSB"); + + Serial.print(" Metadata:"); + Serial.print(" X:"); + Serial.print(data.raw.x); + Serial.print(" Y:"); + Serial.print(data.raw.y); + Serial.print(" Z:"); + Serial.print(data.raw.z); + + //Serial.print(" Heading (rad): "); + //Serial.print(data.heading, 6); + //Serial.print(" rad"); + + //Serial.print(" Heading (deg): "); + //Serial.print(data.heading_degrees, 2); + //Serial.print("°"); + // Use raw values (already offset-corrected by calibration) + float Xc = data.raw.x; + float Yc = data.raw.y; + + // Compute corrected heading + //float heading_rad = atan2(Yc, Xc); // this was mirrored + float heading_rad = atan2(-Yc, Xc); + // Convert to degrees + float heading_deg = heading_rad * 180.0 / PI; + if (heading_deg < 0) heading_deg += 360; + + // Print corrected values + Serial.print(" HeadingCorr (rad): "); + Serial.print(heading_rad, 6); + Serial.print(" rad"); + + Serial.print(" HeadingCorr (deg): "); + Serial.print(heading_deg, 2); + Serial.print("°"); + + float strength = MagnetometerUtils::calculateMagneticStrength(data); + strength = MagnetometerUtils::gaussToMicroTesla(strength); + Serial.print(" Magnetic Strength: "); + Serial.print(strength, 2); + Serial.println(" μT"); + + if (data.overflow) { + Serial.println("\tWarning: Data Overflow occurred!"); + } + } + delay(10); +} + + + From 9d43ce67ff00a997de0b563a480311ed058fecb1 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:43:31 -0700 Subject: [PATCH 09/10] safety, these may need using environment variable CURRENT_UNIT --- examples/run_calibration.sh | 19 +++++++++++++++++++ examples/run_test_after_calibration.sh | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100755 examples/run_calibration.sh create mode 100755 examples/run_test_after_calibration.sh diff --git a/examples/run_calibration.sh b/examples/run_calibration.sh new file mode 100755 index 0000000..fa30c67 --- /dev/null +++ b/examples/run_calibration.sh @@ -0,0 +1,19 @@ +#!/usr/bin/bash +# +# for capturing magnet readings during calibration and then +# buildling 3D model +# +CURRENT_UNIT=${CURRENT_UNIT} +LOG=~/work/tbeam/${CURRENT_UNIT}_calibration_$(date +%Y%m%d_%H%M%S).log +echo "Logging to: $LOG" + +pio run -e T_BEAM_S3_SUPREME_SX1262 -t upload --upload-port /dev/ttyt${CURRENT_UNIT} && \ +pio device monitor -b 115200 -p /dev/ttyt${CURRENT_UNIT} | \ +perl -MTime::HiRes=time -ne ' +BEGIN { $t0 = time() } +$elapsed = time() - $t0; +$mm = int($elapsed / 60); +$ss = int($elapsed % 60); +$ms = int(($elapsed - int($elapsed)) * 1000); +printf "%6d %02d:%02d.%03d %s", $. , $mm, $ss, $ms, $_; +' | tee "$LOG" diff --git a/examples/run_test_after_calibration.sh b/examples/run_test_after_calibration.sh new file mode 100755 index 0000000..5e15708 --- /dev/null +++ b/examples/run_test_after_calibration.sh @@ -0,0 +1,17 @@ +#!/usr/bin/bash +# +# for capturing magnet readings during calibration and then +# buildling 3D model +# +LOG=~/work/tbeam/ED_TEST_after_calibration_$(date +%Y%m%d_%H%M%S).log +echo "Logging to: $LOG" + +pio device monitor -b 115200 -p /dev/ttytED | \ +perl -MTime::HiRes=time -ne ' +BEGIN { $t0 = time() } +$elapsed = time() - $t0; +$mm = int($elapsed / 60); +$ss = int($elapsed % 60); +$ms = int(($elapsed - int($elapsed)) * 1000); +printf "%6d %02d:%02d.%03d %s", $. , $mm, $ss, $ms, $_; +' | tee "$LOG" From 69c1ce596d9893f25e1767ba1c1dbe5dc12e0e66 Mon Sep 17 00:00:00 2001 From: John Poole Date: Wed, 22 Apr 2026 19:45:34 -0700 Subject: [PATCH 10/10] added .codex --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index baf586b..41e7ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ build LilyGoLoRaBoard record.* *log +.codex