From fe367a5bee792b2bffd72d496b9d5ca0084a347c Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Thu, 20 Aug 2026 15:30:10 +0100 Subject: [PATCH 1/3] DEV-958: use the ST datasheet accel sensitivity for the gen-2 LSM6DSV SensorLSM6DSV derived accel sensitivity as 32768/(FS_g*9.80665), while the firmware seed, the web SDK catalog and the driver's own gen-1 classes all use the ST datasheet mg/LSB figure - a 0.0576% split, and the only such divergence left in the gen-2 chain. +-2 g 1670.703 -> 1671.665922915 +-4 g 835.3517 -> 835.832961457 +-8 g 417.6759 -> 417.916480729 +-16 g 208.8379 -> 208.958240364 Worth being explicit that the old values were not a mistake, and this is not a correctness fix. Checked against ST's own vendored driver (lsm6dsv_reg.c): every printed accel figure - 0.061 / 0.122 / 0.244 / 0.488 mg/LSB - is exactly 32768/FS rounded to three significant figures, because the accel really does span the full 16-bit range at nominal full scale. So the derived form was the more precise of the two, and moving to the datasheet figure trades a little accuracy for agreement. Agreement is worth more here. The firmware seeds these values into the on-device calibration blob, so they are what the device reports and what becomes the source of truth once per-unit calibration is honoured; keeping the exact form would leave a 0.0576% step change in calibrated output waiting to surface on that day. 0.0576% is far inside per-unit sensitivity variation. The gyro is a genuinely different case and is deliberately left alone: 4.375 mdps/LSB implies a real full scale of 143.4 dps on the +-125 dps setting, which is where the part is observed to saturate, so there a 32768/FS derivation is wrong by ~14.7% rather than by a rounding. The comment now spells out the distinction so the two are not "fixed" to match each other later. API_00008 CAL locks updated (accel +-4 g literal); 51 driver tests pass. Co-Authored-By: Claude Opus 5 --- .../verisense/sensors/SensorLSM6DSV.java | 38 +++++++++++++++---- ...008_VerisenseLsm6dsvTaggedFifoParsing.java | 12 +++--- 2 files changed, 37 insertions(+), 13 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 190b079d..5caf62d1 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -41,7 +41,7 @@ * {@code VerisenseDevice.parseDataBlockDataLsm6dsv(...)}. This class provides the * channel definitions, configuration and calibration. *

- * Sensitivities: 32768/(FS*9.80665) LSB per m/s^2 for accel, the ST angular-rate + * Sensitivities: the ST datasheet nominal for every axis - 0.061 mg/LSB at +-2 g * 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 @@ -285,16 +285,38 @@ public static final class DatabaseConfigHandle { * 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}}; - public static final double[][] SENS_ACCEL_4G = {{835.3517,0,0},{0,835.3517,0},{0,0,835.3517}}; - public static final double[][] SENS_ACCEL_8G = {{417.6759,0,0},{0,417.6759,0},{0,0,417.6759}}; - public static final double[][] SENS_ACCEL_16G = {{208.8379,0,0},{0,208.8379,0},{0,0,208.8379}}; + // Accel sensitivity (LSB per m/s^2) from the ST datasheet linear-acceleration + // sensitivity: 0.061 / 0.122 / 0.244 / 0.488 mg/LSB, i.e. 1/(mg_per_LSB/1000)/9.80665. + // + // These were previously derived as 32768/(FS_g*9.80665), which is the exact form: + // unlike the gyro (see below) the accel really does span the full 16-bit range at + // its nominal full scale, so ST's printed figures are just 32768/FS rounded to + // three significant figures (2/32768 g = 0.06103516 mg/LSB -> "0.061"). The exact + // derivation is therefore 0.0576% larger and, on its own terms, slightly more + // accurate. + // + // We use the datasheet figures anyway, for agreement rather than precision. The + // firmware seeds these exact values into the on-device calibration blob + // (SC_ACCEL_SENS in asm_calibration.c), so they are what the device itself + // reports and what becomes the source of truth once per-unit calibration is + // loaded; the web SDK catalog and the gen-1 SensorLSM6DS3/SensorLIS2DW12 classes + // use them too, as does ST's own reference driver (lsm6dsv_from_fs2_to_mg + // multiplies by 0.061f). Keeping the exact form here would leave a 0.0576% step + // change in calibrated output waiting to appear the day the device blob is + // honoured. 0.0576% is far inside per-unit sensitivity variation, so consistency + // is worth more than the last digit. + public static final double[][] SENS_ACCEL_2G = {{1671.665922915,0,0},{0,1671.665922915,0},{0,0,1671.665922915}}; + public static final double[][] SENS_ACCEL_4G = {{835.832961457,0,0},{0,835.832961457,0},{0,0,835.832961457}}; + public static final double[][] SENS_ACCEL_8G = {{417.916480729,0,0},{0,417.916480729,0},{0,0,417.916480729}}; + public static final double[][] SENS_ACCEL_16G = {{208.958240364,0,0},{0,208.958240364,0},{0,0,208.958240364}}; // Gyro sensitivity (LSB per dps) from the ST datasheet angular-rate sensitivity // (4.375 mdps/LSB at +-125 dps, doubling per range) - the same spec/values as the - // gen-1 LSM6DS3. NOTE: the gyro does NOT span the full 16-bit range at its nominal - // full scale (unlike the accel), so a 32768/FS derivation is ~12.8% off. + // gen-1 LSM6DS3. NOTE: unlike the accel above, the gyro genuinely does NOT span + // the full 16-bit range at its nominal full scale - 4.375 mdps/LSB implies a real + // full scale of 143.4 dps for the +-125 dps setting, which is exactly where the + // part is observed to saturate - so here a 32768/FS derivation is not a rounding + // difference but plainly wrong (~14.7%). public static final double[][] SENS_GYRO_125DPS = {{228.571428571,0,0},{0,228.571428571,0},{0,0,228.571428571}}; public static final double[][] SENS_GYRO_250DPS = {{114.285714286,0,0},{0,114.285714286,0},{0,0,114.285714286}}; public static final double[][] SENS_GYRO_500DPS = {{57.142857143,0,0},{0,57.142857143,0},{0,0,57.142857143}}; 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 75fe05de..0a11c2dd 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 @@ -142,14 +142,16 @@ public void test001_accelGyroMagInterleaved() throws Exception { // CAL assertions - regression-locks the calibration constants against // 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); + // constants would defeat the lock). Defaults: accel +/-4 g = 835.832961457 + // LSB/(m/s^2) (ST 0.122 mg/LSB - see the sensitivity note in SensorLSM6DSV + // for why the datasheet figure is used and not the exact 32768/FS form); + // 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(-200 / 835.832961457, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_X), 0.0001); + assertEquals(300 / 835.832961457, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_Y), 0.0001); + assertEquals(100 / 835.832961457, 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); From aa61da17a1e6cb5bfbe74cf99e6d5ec5101c79fe Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Thu, 20 Aug 2026 20:52:12 +0100 Subject: [PATCH 2/3] DEV-958: correct the rationale - 0.061 mg/LSB is real, not a rounding My previous comment claimed ST's accel figures were just 32768/FS rounded to three significant figures, and that the old derived values were therefore the more accurate of the two. That was wrong, and the datasheet settles it. AN5922 Table 27 (FS_XL = +-2 g) gives worked register contents: 1 g reads 0x4009 = 16393 LSB and 350 mg reads 0x1669 = 5737 LSB. 0.061 mg/LSB predicts 16393.4 and 5737.7; a 32768/FS derivation predicts 16384 and 5734.4. The accelerometer does not map its nominal full scale onto exactly 2^15 counts - it has a small margin, the same phenomenon as the gyro, just far smaller. So the old values were wrong by 0.0576%, and this is a correctness fix rather than a consistency trade-off. The constants themselves are unchanged from the previous commit; only the reasoning recorded beside them. Corroborated four ways: the datasheet worked example, ST's upstream driver (STMicroelectronics/lsm6dsv-pid, byte-identical to the copy this firmware vendors), every example in STMems_Standard_C_drivers/lsm6dsv_STdC/examples, and lsm6dsv_self_test.c in particular - its pass/fail limits are specified in mg, so ST's own self-test only produces correct results if 0.122 mg/LSB is right. Comment-only; 51 driver tests still pass. Co-Authored-By: Claude Opus 5 --- .../verisense/sensors/SensorLSM6DSV.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 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 5caf62d1..181ed2f5 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -288,23 +288,27 @@ public static final class DatabaseConfigHandle { // Accel sensitivity (LSB per m/s^2) from the ST datasheet linear-acceleration // sensitivity: 0.061 / 0.122 / 0.244 / 0.488 mg/LSB, i.e. 1/(mg_per_LSB/1000)/9.80665. // - // These were previously derived as 32768/(FS_g*9.80665), which is the exact form: - // unlike the gyro (see below) the accel really does span the full 16-bit range at - // its nominal full scale, so ST's printed figures are just 32768/FS rounded to - // three significant figures (2/32768 g = 0.06103516 mg/LSB -> "0.061"). The exact - // derivation is therefore 0.0576% larger and, on its own terms, slightly more - // accurate. + // These were previously derived as 32768/(FS_g*9.80665), on the assumption that + // the accel spans exactly the full 16-bit range at nominal full scale and that + // ST's printed figures were just that quantity rounded to three significant + // figures. They are not. The datasheet's own worked example (AN5922 Table 27, + // FS_XL = +-2 g) settles it: 1 g reads 0x4009 = 16393 LSB and 350 mg reads + // 0x1669 = 5737 LSB. 0.061 mg/LSB predicts 16393.4 and 5737.7; the 32768/FS + // derivation predicts 16384 and 5734.4. So the sensor really is 0.061 mg/LSB - + // full scale sits a little inside +-2 g - and the old derivation was wrong by + // 0.0576%, not merely a rounding apart from the datasheet. // - // We use the datasheet figures anyway, for agreement rather than precision. The - // firmware seeds these exact values into the on-device calibration blob - // (SC_ACCEL_SENS in asm_calibration.c), so they are what the device itself - // reports and what becomes the source of truth once per-unit calibration is - // loaded; the web SDK catalog and the gen-1 SensorLSM6DS3/SensorLIS2DW12 classes - // use them too, as does ST's own reference driver (lsm6dsv_from_fs2_to_mg - // multiplies by 0.061f). Keeping the exact form here would leave a 0.0576% step - // change in calibrated output waiting to appear the day the device blob is - // honoured. 0.0576% is far inside per-unit sensitivity variation, so consistency - // is worth more than the last digit. + // Same story as the gyro below, just with a much smaller margin: neither part + // maps its nominal full scale onto exactly 2^15 counts, so for both of them the + // ST mg/LSB and mdps/LSB figures are the authority and a 32768/FS derivation is + // simply incorrect. These values also match the firmware calibration seed + // (SC_ACCEL_SENS in asm_calibration.c), the web SDK catalog, the gen-1 + // SensorLSM6DS3/SensorLIS2DW12 classes and ST's own reference driver + // (lsm6dsv_from_fs2_to_mg multiplies by 0.061f). Every example in + // STMems_Standard_C_drivers/lsm6dsv_STdC/examples converts through those + // helpers and none derives 32768/FS - including lsm6dsv_self_test.c, whose + // pass/fail limits are specified in mg, so ST's own self-test only comes out + // right if 0.122 mg/LSB is the true sensitivity. public static final double[][] SENS_ACCEL_2G = {{1671.665922915,0,0},{0,1671.665922915,0},{0,0,1671.665922915}}; public static final double[][] SENS_ACCEL_4G = {{835.832961457,0,0},{0,835.832961457,0},{0,0,835.832961457}}; public static final double[][] SENS_ACCEL_8G = {{417.916480729,0,0},{0,417.916480729,0},{0,0,417.916480729}}; From 64ca844423aaf1b0d09629d546a103a8cf60ba7f Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Thu, 20 Aug 2026 21:01:58 +0100 Subject: [PATCH 3/3] DEV-958: clarify the class Javadoc sensitivity summary Copilot review on PR #288: my earlier edit left the sentence ungrammatical - dropping "the ST angular-rate" from the first line orphaned the "spec of 4.375 mdps/LSB..." that followed, running the accel and gyro clauses together, and it listed only the +-2 g accel case. Now a list giving all three sensors' nominals with their ranges. Javadoc-only; 51 driver tests still pass. Co-Authored-By: Claude Opus 5 --- .../verisense/sensors/SensorLSM6DSV.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 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 181ed2f5..d9daa35c 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java @@ -41,11 +41,16 @@ * {@code VerisenseDevice.parseDataBlockDataLsm6dsv(...)}. This class provides the * channel definitions, configuration and calibration. *

- * Sensitivities: the ST datasheet nominal for every axis - 0.061 mg/LSB at +-2 g - * 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. + * Sensitivities are the ST datasheet nominals throughout: + *

+ * The mag figure is 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. *

* 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