From 08ddcdcfb080f9516286d2f6ec88b0b982effb25 Mon Sep 17 00:00:00 2001 From: JongChern Date: Fri, 31 Jul 2026 15:46:52 +0800 Subject: [PATCH 1/6] Update SensorLSM6DSV.java --- .../verisense/sensors/SensorLSM6DSV.java | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java index 72555ab3..4aa548a6 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -28,6 +28,7 @@ import com.shimmerresearch.driverUtilities.ChannelDetails.CHANNEL_TYPE; import com.shimmerresearch.sensors.AbstractSensor; import com.shimmerresearch.sensors.ActionSetting; +import com.shimmerresearch.sensors.lisxmdl.SensorLIS2MDL; /** * Second-generation Verisense IMU: LSM6DSV (accelerometer + gyroscope) with the @@ -38,10 +39,16 @@ * tagged FIFO ([TAG_CNT][X][Y][Z]) interleaving the three streams; the byte-level * extraction + per-stream timestamping lives in * {@code VerisenseDevice.parseDataBlockDataLsm6dsv(...)}. This class provides the - * channel definitions, configuration and calibration. Calibration matches the - * firmware/SDK nominal model: value = raw / sensitivity (identity alignment, - * zero offset) where sensitivity = 32768/(FS*9.80665) for accel, 32768/FS for - * gyro and 1/0.15 for mag. + * channel definitions, configuration and calibration. + *

+ * Sensitivities: 32768/(FS*9.80665) LSB per m/s^2 for accel, the ST angular-rate + * spec of 4.375 mdps/LSB at +-125 dps for gyro, and 667 LSB/Gauss for the LIS2MDL + * mag - the last taken from {@link SensorLIS2MDL} rather than duplicated here, so + * calibrated magnetometer output is in GAUSS, consistent with every other Shimmer + * magnetometer and with the per-unit calibration the device stores. + *

+ * Alignment is left as identity here and corrected by the file parser per hardware + * revision; offsets are zero. * * @author Mark Nolan */ @@ -230,8 +237,10 @@ public static final class DatabaseConfigHandle { CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV); // ----------------- Calibration Start ----------------------- - // Identity alignment + zero offset so calibrated = raw / sensitivity, matching - // the firmware/SDK nominal model (and the validated standalone decoder). + // Identity alignment is a placeholder, NOT the real sensor->ASM map: the file + // parser overrides it per hardware revision at parse time (see + // CalibrationFileManager.applyGen2DefaultAlignment in the VerisenseDriver repo). + // Correcting it here as well would give two sources of truth for the same values. public static final double[][] DEFAULT_OFFSET_VECTOR_LSM6DSV = {{0},{0},{0}}; public static final double[][] DEFAULT_ALIGNMENT_MATRIX_LSM6DSV = {{1,0,0},{0,1,0},{0,0,1}}; @@ -251,8 +260,17 @@ public static final class DatabaseConfigHandle { public static final double[][] SENS_GYRO_1000DPS = {{28.571428571,0,0},{0,28.571428571,0},{0,0,28.571428571}}; public static final double[][] SENS_GYRO_2000DPS = {{14.285714286,0,0},{0,14.285714286,0},{0,0,14.285714286}}; - // Mag sensitivity (LSB per uT) = 1/0.15 - public static final double[][] SENS_MAG = {{6.666667,0,0},{0,6.666667,0},{0,0,6.666667}}; + // Mag sensitivity: taken from the LIS2MDL's own sensor class rather than + // re-declared here, because it is a property of the chip (1.5 mGauss/LSB) and not + // of the LSM6DSV sensor hub the samples arrive through. 667 LSB/Gauss, matching + // the firmware calibration seed (SC_LIS2MDL_MAG_SENS), the web SDK catalog, and + // every other Shimmer magnetometer (LSM303DLHC etc. are all LSB/Gauss). + // + // This was previously a local copy of 6.666667 = 1/0.15 LSB/uT, which made + // gen-2 magnetometer output 100x larger than every other Shimmer device for the + // same field, and 100x out of step with the per-unit calibration the device + // stores. Reference the shared constant so the two cannot diverge again. + public static final double[][] SENS_MAG = SensorLIS2MDL.DefaultSensitivityMatrixMagShimmer3r; public CalibDetailsKinematic calibDetailsAccel2g = new CalibDetailsKinematic( LSM6DSV_ACCEL_RANGE.RANGE_2G.configValue, LSM6DSV_ACCEL_RANGE.RANGE_2G.label, From fea5a79f6d6bc67b73053172ce2c74f72cbeb450 Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Sun, 2 Aug 2026 09:35:20 +0100 Subject: [PATCH 2/6] DEV-922 Upstream gen-2 default alignment into SensorLSM6DSV Replace the identity default-alignment placeholder with the real sensor->ASM matrices, making the driver the single source of truth for gen-2 (LSM6DSV/LIS2MDL) default calibration and letting the parser-side applyGen2DefaultAlignment override in ASM_PC/VerisenseDriver be deleted rather than corrected in two places. The literals are stored in APPLIED form (physical = applied . (raw-bias)/sens), reading exactly as verisense-device-console displays them and as the web SDK declares them (calibrationDefaults.ts, CALIBRATION_SENSORS_GEN2); the driver-form AM handed to the calibration blocks is derived from them by a true matrix inverse, since UtilCalibration applies AM^-1. Accel/gyro share the chip mounting (det +1); the LIS2MDL frame is left-handed (det -1). No hardware-revision gate: only 2nd-generation revisions (SR61>=5, SR68>=9) carry this IMU, so any device instantiating this sensor class is gen-2 by construction. Verisense-only: com.shimmerresearch.sensors .lsm6dsv.SensorLSM6DSV (Shimmer3R) is a separate class and untouched. Builds on the DEV-922 mag-sensitivity fix (PR #281): stacked so the 667 LSB/Gauss change and this land together with one reference-CSV regeneration pass. Co-Authored-By: Claude Fable 5 --- .../verisense/sensors/SensorLSM6DSV.java | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java index 4aa548a6..fd139aa2 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -47,8 +47,12 @@ * calibrated magnetometer output is in GAUSS, consistent with every other Shimmer * magnetometer and with the per-unit calibration the device stores. *

- * Alignment is left as identity here and corrected by the file parser per hardware - * revision; offsets are zero. + * Default alignment is the real sensor->ASM frame map (accel/gyro share the chip + * mounting; the LIS2MDL frame is left-handed), matching the web SDK's + * CALIBRATION_SENSORS_GEN2 and what verisense-device-console writes to the device. + * No hardware-revision gate is needed: every revision carrying this IMU (SR61 rev + * >= 5, SR68 rev >= 9) shares the same mounting, so a device that has an + * LSM6DSV is second-generation by construction. Offsets are zero. * * @author Mark Nolan */ @@ -237,12 +241,28 @@ public static final class DatabaseConfigHandle { CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV); // ----------------- Calibration Start ----------------------- - // Identity alignment is a placeholder, NOT the real sensor->ASM map: the file - // parser overrides it per hardware revision at parse time (see - // CalibrationFileManager.applyGen2DefaultAlignment in the VerisenseDriver repo). - // Correcting it here as well would give two sources of truth for the same values. public static final double[][] DEFAULT_OFFSET_VECTOR_LSM6DSV = {{0},{0},{0}}; - public static final double[][] DEFAULT_ALIGNMENT_MATRIX_LSM6DSV = {{1,0,0},{0,1,0},{0,0,1}}; + + // Default alignment, stored first in APPLIED form - the sensor->ASM map, + // physical = applied . (raw - bias) / sens - so the literals below read exactly + // as verisense-device-console displays them and as the web SDK declares them + // (calibrationDefaults.ts, CALIBRATION_SENSORS_GEN2). Those two and this class + // must stay in agreement; they are the same physical mounting. + /** Applied sensor->ASM alignment for the LSM6DSV accel + gyro; det +1 (a proper rotation). */ + public static final double[][] APPLIED_ALIGNMENT_LSM6DSV_ACCEL_GYRO = {{0,1,0},{0,0,1},{1,0,0}}; + /** Applied sensor->ASM alignment for the LIS2MDL mag; its frame is left-handed, so det -1 (a reflection). */ + public static final double[][] APPLIED_ALIGNMENT_LIS2MDL_MAG = {{1,0,0},{0,0,1},{0,1,0}}; + + // The driver stores the opposite convention: CalibDetailsKinematic holds AM and + // UtilCalibration computes AM^-1 . SM^-1 . (data - OV), so the matrices handed to + // the calibration blocks below are the INVERSES of the applied form. A true + // inverse, not a transpose: the two coincide only for orthogonal + // signed-permutation defaults like these, and would differ for a rig-measured + // matrix with cross-axis terms. + public static final double[][] DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO = + UtilCalibration.matrixInverse3x3(APPLIED_ALIGNMENT_LSM6DSV_ACCEL_GYRO); + public static final double[][] DEFAULT_ALIGNMENT_LIS2MDL_MAG = + UtilCalibration.matrixInverse3x3(APPLIED_ALIGNMENT_LIS2MDL_MAG); // Accel sensitivity (LSB per m/s^2) = 32768/(FS_g*9.80665) public static final double[][] SENS_ACCEL_2G = {{1670.703,0,0},{0,1670.703,0},{0,0,1670.703}}; @@ -274,35 +294,35 @@ public static final class DatabaseConfigHandle { public CalibDetailsKinematic calibDetailsAccel2g = new CalibDetailsKinematic( LSM6DSV_ACCEL_RANGE.RANGE_2G.configValue, LSM6DSV_ACCEL_RANGE.RANGE_2G.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_ACCEL_2G, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_ACCEL_2G, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsAccel4g = new CalibDetailsKinematic( LSM6DSV_ACCEL_RANGE.RANGE_4G.configValue, LSM6DSV_ACCEL_RANGE.RANGE_4G.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_ACCEL_4G, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_ACCEL_4G, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsAccel8g = new CalibDetailsKinematic( LSM6DSV_ACCEL_RANGE.RANGE_8G.configValue, LSM6DSV_ACCEL_RANGE.RANGE_8G.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_ACCEL_8G, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_ACCEL_8G, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsAccel16g = new CalibDetailsKinematic( LSM6DSV_ACCEL_RANGE.RANGE_16G.configValue, LSM6DSV_ACCEL_RANGE.RANGE_16G.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_ACCEL_16G, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_ACCEL_16G, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsGyro125dps = new CalibDetailsKinematic( LSM6DSV_GYRO_RANGE.RANGE_125DPS.configValue, LSM6DSV_GYRO_RANGE.RANGE_125DPS.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_GYRO_125DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_GYRO_125DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsGyro250dps = new CalibDetailsKinematic( LSM6DSV_GYRO_RANGE.RANGE_250DPS.configValue, LSM6DSV_GYRO_RANGE.RANGE_250DPS.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_GYRO_250DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_GYRO_250DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsGyro500dps = new CalibDetailsKinematic( LSM6DSV_GYRO_RANGE.RANGE_500DPS.configValue, LSM6DSV_GYRO_RANGE.RANGE_500DPS.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_GYRO_500DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_GYRO_500DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsGyro1000dps = new CalibDetailsKinematic( LSM6DSV_GYRO_RANGE.RANGE_1000DPS.configValue, LSM6DSV_GYRO_RANGE.RANGE_1000DPS.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_GYRO_1000DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_GYRO_1000DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsGyro2000dps = new CalibDetailsKinematic( LSM6DSV_GYRO_RANGE.RANGE_2000DPS.configValue, LSM6DSV_GYRO_RANGE.RANGE_2000DPS.label, - DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_GYRO_2000DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); + DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO, SENS_GYRO_2000DPS, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic calibDetailsMag = new CalibDetailsKinematic( - 0, "Default", DEFAULT_ALIGNMENT_MATRIX_LSM6DSV, SENS_MAG, DEFAULT_OFFSET_VECTOR_LSM6DSV); + 0, "Default", DEFAULT_ALIGNMENT_LIS2MDL_MAG, SENS_MAG, DEFAULT_OFFSET_VECTOR_LSM6DSV); public CalibDetailsKinematic mCurrentCalibDetailsAccel = calibDetailsAccel4g; public CalibDetailsKinematic mCurrentCalibDetailsGyro = calibDetailsGyro500dps; From 74119a1d8c1d52b37a6ce94fc997397b63fa8fa0 Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Sun, 2 Aug 2026 09:59:43 +0100 Subject: [PATCH 3/6] DEV-922 Write mag driver-form alignment as a literal (Copilot review) matrixInverse3x3 on the mag applied matrix (determinant -1) stamps -0.0 into all six zero entries, which survives serialization and fails Arrays.deepEquals against 0.0. The matrix is its own inverse, so write the driver-form literal directly; ASM_PC_00032 guards that it really is the inverse of the applied form. The accel/gyro inverse (det +1) computes canonical 0.0 entries and stays derived. Co-Authored-By: Claude Fable 5 --- .../shimmerresearch/verisense/sensors/SensorLSM6DSV.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java index fd139aa2..37c222fa 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -261,8 +261,13 @@ public static final class DatabaseConfigHandle { // matrix with cross-axis terms. public static final double[][] DEFAULT_ALIGNMENT_LSM6DSV_ACCEL_GYRO = UtilCalibration.matrixInverse3x3(APPLIED_ALIGNMENT_LSM6DSV_ACCEL_GYRO); - public static final double[][] DEFAULT_ALIGNMENT_LIS2MDL_MAG = - UtilCalibration.matrixInverse3x3(APPLIED_ALIGNMENT_LIS2MDL_MAG); + /* The mag applied matrix is its own inverse (an involution), but deriving it via + * matrixInverse3x3 would stamp -0.0 into the zero entries (its determinant is -1), + * which survives serialization and fails Arrays.deepEquals against 0.0 - so the + * driver-form matrix is written out as a literal. ASM_PC_00032 guards that it + * really is the inverse of the applied form. (The accel/gyro inverse above is + * det +1 and computes canonical 0.0 entries, so it stays derived.) */ + public static final double[][] DEFAULT_ALIGNMENT_LIS2MDL_MAG = {{1,0,0},{0,0,1},{0,1,0}}; // Accel sensitivity (LSB per m/s^2) = 32768/(FS_g*9.80665) public static final double[][] SENS_ACCEL_2G = {{1670.703,0,0},{0,1670.703,0},{0,0,1670.703}}; From cef9e7e8836185303e7392fdf61aa55660b48e46 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 06:56:39 +0000 Subject: [PATCH 4/6] DEV-922 Update API_00008 CAL expectations for the gen-2 default alignment The four CAL regression-lock assertions in test001 still expected identity alignment and fail against the upstreamed gen-2 defaults: calibrated accel/gyro X/Y/Z now come from raw Y/Z/X. Recompute the literals for the applied alignment, extend the lock to all three axes of accel and gyro, and add mag CAL assertions so the 667 LSB/Gauss sensitivity and the left-handed mag alignment (X/Z/Y) are pinned at the data level for the first time. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WaUPkLZVnndWPEixyHcbVy --- ...0008_VerisenseLsm6dsvTaggedFifoParsing.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/API_00008_VerisenseLsm6dsvTaggedFifoParsing.java b/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/API_00008_VerisenseLsm6dsvTaggedFifoParsing.java index 0bce8705..75fe05de 100644 --- a/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/API_00008_VerisenseLsm6dsvTaggedFifoParsing.java +++ b/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/API_00008_VerisenseLsm6dsvTaggedFifoParsing.java @@ -143,11 +143,19 @@ public void test001_accelGyroMagInterleaved() throws Exception { // LITERAL expected values (the gyro sensitivity was ~12.8% wrong before the // DEV-793 round-2 review fix; deriving the expectation from the class // constants would defeat the lock). Defaults: accel +/-4 g = 835.3517 - // LSB/(m/s^2); gyro +/-500 dps = 57.142857 LSB/dps (ST 17.50 mdps/LSB). - assertEquals(100 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_X), 0.0001); - assertEquals(-200 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_Y), 0.0001); - assertEquals(10 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_X), 0.0001); - assertEquals(-20 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_Y), 0.0001); + // LSB/(m/s^2); gyro +/-500 dps = 57.142857 LSB/dps (ST 17.50 mdps/LSB); + // mag 667 LSB/Gauss (LIS2MDL 1.5 mGauss/LSB, legacy rounding of 666.67). + // Since DEV-922 the defaults also apply the gen-2 sensor->ASM alignment: + // accel/gyro calibrated X/Y/Z come from raw Y/Z/X, mag X/Y/Z from raw X/Z/Y. + assertEquals(-200 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_X), 0.0001); + assertEquals(300 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_Y), 0.0001); + assertEquals(100 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_Z), 0.0001); + assertEquals(-20 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_X), 0.0001); + assertEquals(30 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_Y), 0.0001); + assertEquals(10 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_Z), 0.0001); + assertEquals(-150 / 667.0, cal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LIS2MDL_MAG_X), 0.0001); + assertEquals(150 / 667.0, cal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LIS2MDL_MAG_Y), 0.0001); + assertEquals(5 / 667.0, cal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LIS2MDL_MAG_Z), 0.0001); } /** Gyro-only: gyro acts as the aligned reference stream. */ From 7b83b812f66187a435dc6dc4b00f7c1f38d6b420 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 08:56:00 +0000 Subject: [PATCH 5/6] DEV-922 Correct calibration comments after cross-repo verification Review of the web SDK (calibrationDefaults.ts, commit 687a31d) and the firmware seed (asm_calibration.c) against these constants surfaced three comment-level corrections: - Document the applied-form trap on the alignment block: the device and console store/write alignment in APPLIED form, but the existing per-unit load paths copy bytes into the AM slot without inverting. Harmless today (neither path serves this sensor) but calibration would be applied backwards if gen-2 per-unit loading is enabled without inverting first. - The no-hardware-gate rationale cited hardware revisions, but the actual gate is the firmware payload-design check in VerisenseDevice.sensorAndConfigMapsCreate(); reword to match the real mechanism. - Note that 667 LSB/Gauss is the established rounding of the exact 666.67 (1.5 mGauss/LSB) rather than the datasheet value itself. Comment-only change; ShimmerDriver test suite green (51 tests). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WaUPkLZVnndWPEixyHcbVy --- .../verisense/sensors/SensorLSM6DSV.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java index 37c222fa..86345fb2 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -50,9 +50,11 @@ * Default alignment is the real sensor->ASM frame map (accel/gyro share the chip * mounting; the LIS2MDL frame is left-handed), matching the web SDK's * CALIBRATION_SENSORS_GEN2 and what verisense-device-console writes to the device. - * No hardware-revision gate is needed: every revision carrying this IMU (SR61 rev - * >= 5, SR68 rev >= 9) shares the same mounting, so a device that has an - * LSM6DSV is second-generation by construction. Offsets are zero. + * No hardware-revision gate is needed here: this sensor class is only instantiated + * for firmware payload design v13 and above (see + * {@code VerisenseDevice.sensorAndConfigMapsCreate()}), i.e. second-generation + * firmware, and every second-generation mounting (SR61 rev >= 5, SR68 rev + * >= 9) shares this frame. Offsets are zero. * * @author Mark Nolan */ @@ -248,6 +250,15 @@ public static final class DatabaseConfigHandle { // as verisense-device-console displays them and as the web SDK declares them // (calibrationDefaults.ts, CALIBRATION_SENSORS_GEN2). Those two and this class // must stay in agreement; they are the same physical mounting. + // + // WARNING for future per-unit calibration work: the device stores alignment in + // this same APPLIED form (the firmware seeds it in asm_calibration.c and the + // console writes it back), but the existing load paths + // (CalibDetailsKinematic.parseCalParamByteArray and the file parser's + // CalibrationFileManager) copy bytes into the AM slot WITHOUT inverting, and + // UtilCalibration applies AM^-1. Neither path is used for this sensor today; + // if gen-2 per-unit calibration is ever enabled, the loaded alignment must be + // inverted to driver form at load or calibration will be applied backwards. /** Applied sensor->ASM alignment for the LSM6DSV accel + gyro; det +1 (a proper rotation). */ public static final double[][] APPLIED_ALIGNMENT_LSM6DSV_ACCEL_GYRO = {{0,1,0},{0,0,1},{1,0,0}}; /** Applied sensor->ASM alignment for the LIS2MDL mag; its frame is left-handed, so det -1 (a reflection). */ @@ -286,7 +297,8 @@ public static final class DatabaseConfigHandle { public static final double[][] SENS_GYRO_2000DPS = {{14.285714286,0,0},{0,14.285714286,0},{0,0,14.285714286}}; // Mag sensitivity: taken from the LIS2MDL's own sensor class rather than - // re-declared here, because it is a property of the chip (1.5 mGauss/LSB) and not + // re-declared here, because it is a property of the chip (1.5 mGauss/LSB, i.e. + // 666.67 LSB/Gauss exactly; 667 is the established Shimmer rounding) and not // of the LSM6DSV sensor hub the samples arrive through. 667 LSB/Gauss, matching // the firmware calibration seed (SC_LIS2MDL_MAG_SENS), the web SDK catalog, and // every other Shimmer magnetometer (LSM303DLHC etc. are all LSB/Gauss). From 316df2dfd2dca31f06ed80d9d2dead22e04ab220 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 10:00:23 +0000 Subject: [PATCH 6/6] DEV-922 Comment corrections from skeptic re-review - The applied-form WARNING now covers both directions: the write path (generateCalParamByteArray) also skips inversion, so writing calibration to a gen-2 device would send driver-form matrices where the firmware and console expect applied form. - The no-hardware-gate javadoc no longer implies the FW/HW pairing is enforced; a gen-1 board flashed with gen-2 firmware would get this frame too. - 666.67 LSB/Gauss is itself a rounding of 2000/3, not an exact value. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WaUPkLZVnndWPEixyHcbVy --- .../verisense/sensors/SensorLSM6DSV.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java index 86345fb2..190b079d 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -54,7 +54,9 @@ * for firmware payload design v13 and above (see * {@code VerisenseDevice.sensorAndConfigMapsCreate()}), i.e. second-generation * firmware, and every second-generation mounting (SR61 rev >= 5, SR68 rev - * >= 9) shares this frame. Offsets are zero. + * >= 9) shares this frame. Note the FW/HW pairing itself is not enforced + * anywhere: a first-generation board flashed with gen-2 firmware would get this + * frame too. Offsets are zero. * * @author Mark Nolan */ @@ -259,6 +261,9 @@ public static final class DatabaseConfigHandle { // UtilCalibration applies AM^-1. Neither path is used for this sensor today; // if gen-2 per-unit calibration is ever enabled, the loaded alignment must be // inverted to driver form at load or calibration will be applied backwards. + // The trap is bidirectional: generateCalParamByteArray() likewise writes the + // driver-form AM out uninverted, so writing calibration TO a gen-2 device + // would send the inverse of the applied form the firmware and console expect. /** Applied sensor->ASM alignment for the LSM6DSV accel + gyro; det +1 (a proper rotation). */ public static final double[][] APPLIED_ALIGNMENT_LSM6DSV_ACCEL_GYRO = {{0,1,0},{0,0,1},{1,0,0}}; /** Applied sensor->ASM alignment for the LIS2MDL mag; its frame is left-handed, so det -1 (a reflection). */ @@ -297,8 +302,8 @@ public static final class DatabaseConfigHandle { public static final double[][] SENS_GYRO_2000DPS = {{14.285714286,0,0},{0,14.285714286,0},{0,0,14.285714286}}; // Mag sensitivity: taken from the LIS2MDL's own sensor class rather than - // re-declared here, because it is a property of the chip (1.5 mGauss/LSB, i.e. - // 666.67 LSB/Gauss exactly; 667 is the established Shimmer rounding) and not + // re-declared here, because it is a property of the chip (1.5 mGauss/LSB = + // 2000/3 = 666.67 LSB/Gauss; 667 is the established Shimmer rounding) and not // of the LSM6DSV sensor hub the samples arrive through. 667 LSB/Gauss, matching // the firmware calibration seed (SC_LIS2MDL_MAG_SENS), the web SDK catalog, and // every other Shimmer magnetometer (LSM303DLHC etc. are all LSB/Gauss).