IMU Updates (#694)

* IMU Updates

Updated BMI270 and MPU6886 with the mount-matrix.
Updated devices i can test with the mount-matrix. papers3, stackchan (cores3), sticks3, tab5, waveshare-s3-lcd-13, waveshare-s3-lcd-128
Devices not changed and not tested because i don't own them are:

MPU6886 - core2, stickc-plus2.
BMI270 - cardputer-adv

* Update device.properties

* No USB here! CH343P
This commit is contained in:
Shadowtrance 2026-10-15 07:26:43 +10:00 committed by GitHub
parent 6a3c549324
commit d0ad1b014c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 118 additions and 126 deletions

View File

@ -65,7 +65,9 @@
bmi270 { bmi270 {
compatible = "bosch,bmi270"; compatible = "bosch,bmi270";
reg = <0x69>; reg = <0x69>;
invert-x; mount-matrix = <1 0 0
0 1 0
0 0 1>;
}; };
bm8563 { bm8563 {

View File

@ -44,6 +44,9 @@
bmi270 { bmi270 {
compatible = "bosch,bmi270"; compatible = "bosch,bmi270";
reg = <0x68>; reg = <0x68>;
mount-matrix = <0 1 0
1 0 0
0 0 -1>;
}; };
bm8563 { bm8563 {

View File

@ -70,7 +70,9 @@
bmi270 { bmi270 {
compatible = "bosch,bmi270"; compatible = "bosch,bmi270";
reg = <0x69>; reg = <0x69>;
invert-x; mount-matrix = <1 0 0
0 1 0
0 0 1>;
}; };
bm8563 { bm8563 {

View File

@ -63,6 +63,9 @@
bmi270 { bmi270 {
compatible = "bosch,bmi270"; compatible = "bosch,bmi270";
reg = <0x68>; reg = <0x68>;
mount-matrix = <0 1 0
-1 0 0
0 0 1>;
}; };
es8311 { es8311 {

View File

@ -83,6 +83,9 @@
bmi270 { bmi270 {
compatible = "bosch,bmi270"; compatible = "bosch,bmi270";
reg = <0x68>; reg = <0x68>;
mount-matrix = <-1 0 0
0 1 0
0 0 -1>;
}; };
es8388 { es8388 {

View File

@ -8,7 +8,6 @@ hardware.flashSize=16MB
hardware.spiRam=true hardware.spiRam=true
hardware.spiRamMode=OCT hardware.spiRamMode=OCT
hardware.spiRamSpeed=120M hardware.spiRamSpeed=120M
hardware.tinyUsbMsc=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true

View File

@ -9,7 +9,6 @@
#include <bindings/qmi8658.h> #include <bindings/qmi8658.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_pwm_ledc.h> #include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/esp32_usbdevice.h>
#include <tactility/bindings/pwm_backlight.h> #include <tactility/bindings/pwm_backlight.h>
#include <bindings/st7789.h> #include <bindings/st7789.h>
@ -41,6 +40,9 @@
qmi8658 { qmi8658 {
compatible = "qst,qmi8658"; compatible = "qst,qmi8658";
reg = <0x6B>; reg = <0x6B>;
mount-matrix = <-1 0 0
0 -1 0
0 0 -1>;
}; };
}; };
@ -89,12 +91,4 @@
frequency-khz = <20000>; frequency-khz = <20000>;
}; };
}; };
usbdevice0 {
compatible = "espressif,esp32-usbdevice";
usbdevicemsc0 {
compatible = "espressif,esp32-usbdevice-msc";
};
};
}; };

View File

@ -9,7 +9,6 @@ hardware.spiRam=true
hardware.spiRamMode=QUAD hardware.spiRamMode=QUAD
hardware.spiRamSpeed=120M hardware.spiRamSpeed=120M
hardware.spiRamXipDisabled=true hardware.spiRamXipDisabled=true
hardware.tinyUsbMsc=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true

View File

@ -9,7 +9,6 @@
#include <bindings/qmi8658.h> #include <bindings/qmi8658.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_pwm_ledc.h> #include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/esp32_usbdevice.h>
#include <tactility/bindings/pwm_backlight.h> #include <tactility/bindings/pwm_backlight.h>
#include <bindings/cst816t.h> #include <bindings/cst816t.h>
#include <bindings/gc9a01.h> #include <bindings/gc9a01.h>
@ -42,6 +41,9 @@
qmi8658 { qmi8658 {
compatible = "qst,qmi8658"; compatible = "qst,qmi8658";
reg = <0x6B>; reg = <0x6B>;
mount-matrix = <0 1 0
1 0 0
0 0 1>;
}; };
touch0 { touch0 {
@ -93,12 +95,4 @@
backlight = <&display_backlight>; backlight = <&display_backlight>;
}; };
}; };
usbdevice0 {
compatible = "espressif,esp32-usbdevice";
usbdevicemsc0 {
compatible = "espressif,esp32-usbdevice-msc";
};
};
}; };

View File

@ -5,27 +5,11 @@ include: [ "i2c-device.yaml" ]
compatible: "bosch,bmi270" compatible: "bosch,bmi270"
properties: properties:
swap-xy: mount-matrix:
type: boolean type: values
default: false default: [1, 0, 0, 0, 1, 0, 0, 0, 1]
description: Swap the X and Y axes description: >
swap-xz: Row-major 3x3 rotation matrix that maps the sensor frame onto the device frame, following the
type: boolean Linux IIO "mount-matrix" convention (x' = m[0]*x + m[1]*y + m[2]*z, and so on). It is applied
default: false to the accelerometer and gyroscope readings so consumers always see the device frame,
description: Swap the X and Z axes regardless of how the IMU is physically oriented on the PCB. Defaults to the identity matrix.
swap-yz:
type: boolean
default: false
description: Swap the Y and Z axes
invert-x:
type: boolean
default: false
description: Invert the X axis
invert-y:
type: boolean
default: false
description: Invert the Y axis
invert-z:
type: boolean
default: false
description: Invert the Z axis

View File

@ -14,12 +14,12 @@ extern "C" {
struct Bmi270Config { struct Bmi270Config {
/** Address on bus */ /** Address on bus */
uint8_t address; uint8_t address;
bool swap_xy; /**
bool swap_xz; * Row-major 3x3 rotation matrix mapping the sensor frame onto the device frame (Linux IIO
bool swap_yz; * "mount-matrix" convention: `out[i] = m[i * 3] * x + m[i * 3 + 1] * y + m[i * 3 + 2] * z`).
bool invert_x; * Identity means the sensor frame already matches the device frame.
bool invert_y; */
bool invert_z; int32_t mount_matrix[9];
}; };
/** /**

View File

@ -5,8 +5,6 @@
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <algorithm>
#define TAG "BMI270" #define TAG "BMI270"
static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24 static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24
@ -90,6 +88,18 @@ static bool configure(Device* i2c_controller, uint8_t address) {
// region Driver lifecycle // region Driver lifecycle
// Applies the devicetree mount matrix (sensor frame -> device frame) to a 3-axis reading.
// Flat row-major [9]: x' = m0*x + m1*y + m2*z, y' = m3*x + m4*y + m5*z, z' = m6*x + m7*y + m8*z.
static void applyMountMatrix(const Bmi270Config& config, float& x, float& y, float& z) {
const int32_t* m = config.mount_matrix;
const float rx = m[0] * x + m[1] * y + m[2] * z;
const float ry = m[3] * x + m[4] * y + m[5] * z;
const float rz = m[6] * x + m[7] * y + m[8] * z;
x = rx;
y = ry;
z = rz;
}
static error_t start(Device* device) { static error_t start(Device* device) {
auto* i2c_controller = device_get_parent(device); auto* i2c_controller = device_get_parent(device);
if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) {
@ -171,13 +181,7 @@ error_t bmi270_read_accel(Device* device, ImuAccelData* data) {
toI16(buffer[2], buffer[3]) * ACCEL_SCALE, toI16(buffer[2], buffer[3]) * ACCEL_SCALE,
toI16(buffer[4], buffer[5]) * ACCEL_SCALE, toI16(buffer[4], buffer[5]) * ACCEL_SCALE,
}; };
const auto& config = *GET_CONFIG(device); applyMountMatrix(*GET_CONFIG(device), axes[0], axes[1], axes[2]);
if (config.swap_xy) std::swap(axes[0], axes[1]);
if (config.swap_xz) std::swap(axes[0], axes[2]);
if (config.swap_yz) std::swap(axes[1], axes[2]);
if (config.invert_x) axes[0] = -axes[0];
if (config.invert_y) axes[1] = -axes[1];
if (config.invert_z) axes[2] = -axes[2];
data->ax = axes[0]; data->ax = axes[0];
data->ay = axes[1]; data->ay = axes[1];
data->az = axes[2]; data->az = axes[2];
@ -197,9 +201,13 @@ error_t bmi270_read_gyro(Device* device, ImuGyroData* data) {
return static_cast<int16_t>(static_cast<uint16_t>(hi) << 8 | lo); return static_cast<int16_t>(static_cast<uint16_t>(hi) << 8 | lo);
}; };
data->gx = toI16(buffer[0], buffer[1]) * GYRO_SCALE; float gx = toI16(buffer[0], buffer[1]) * GYRO_SCALE;
data->gy = toI16(buffer[2], buffer[3]) * GYRO_SCALE; float gy = toI16(buffer[2], buffer[3]) * GYRO_SCALE;
data->gz = toI16(buffer[4], buffer[5]) * GYRO_SCALE; float gz = toI16(buffer[4], buffer[5]) * GYRO_SCALE;
applyMountMatrix(*GET_CONFIG(device), gx, gy, gz);
data->gx = gx;
data->gy = gy;
data->gz = gz;
return ERROR_NONE; return ERROR_NONE;
} }

View File

@ -5,27 +5,11 @@ include: [ "i2c-device.yaml" ]
compatible: "invensense,mpu6886" compatible: "invensense,mpu6886"
properties: properties:
swap-xy: mount-matrix:
type: boolean type: values
default: false default: [1, 0, 0, 0, 1, 0, 0, 0, 1]
description: Swap the X and Y axes description: >
swap-xz: Row-major 3x3 rotation matrix that maps the sensor frame onto the device frame, following the
type: boolean Linux IIO "mount-matrix" convention (x' = m[0]*x + m[1]*y + m[2]*z, and so on). It is applied
default: false to the accelerometer and gyroscope readings so consumers always see the device frame,
description: Swap the X and Z axes regardless of how the IMU is physically oriented on the PCB. Defaults to the identity matrix.
swap-yz:
type: boolean
default: false
description: Swap the Y and Z axes
invert-x:
type: boolean
default: false
description: Invert the X axis
invert-y:
type: boolean
default: false
description: Invert the Y axis
invert-z:
type: boolean
default: false
description: Invert the Z axis

View File

@ -14,12 +14,12 @@ extern "C" {
struct Mpu6886Config { struct Mpu6886Config {
/** Address on bus */ /** Address on bus */
uint8_t address; uint8_t address;
bool swap_xy; /**
bool swap_xz; * Row-major 3x3 rotation matrix mapping the sensor frame onto the device frame (Linux IIO
bool swap_yz; * "mount-matrix" convention: `out[i] = m[i * 3] * x + m[i * 3 + 1] * y + m[i * 3 + 2] * z`).
bool invert_x; * Identity means the sensor frame already matches the device frame.
bool invert_y; */
bool invert_z; int32_t mount_matrix[9];
}; };
/** /**

View File

@ -4,8 +4,6 @@
#include <tactility/drivers/i2c_controller.h> #include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <algorithm>
#define TAG "MPU6886" #define TAG "MPU6886"
// Register map // Register map
@ -48,6 +46,18 @@ static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10);
// region Driver lifecycle // region Driver lifecycle
// Applies the devicetree mount matrix (sensor frame -> device frame) to a 3-axis reading.
// Flat row-major [9]: x' = m0*x + m1*y + m2*z, y' = m3*x + m4*y + m5*z, z' = m6*x + m7*y + m8*z.
static void applyMountMatrix(const Mpu6886Config& config, float& x, float& y, float& z) {
const int32_t* m = config.mount_matrix;
const float rx = m[0] * x + m[1] * y + m[2] * z;
const float ry = m[3] * x + m[4] * y + m[5] * z;
const float rz = m[6] * x + m[7] * y + m[8] * z;
x = rx;
y = ry;
z = rz;
}
static error_t start(Device* device) { static error_t start(Device* device) {
auto* i2c_controller = device_get_parent(device); auto* i2c_controller = device_get_parent(device);
if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) {
@ -145,13 +155,7 @@ error_t mpu6886_read_accel(Device* device, ImuAccelData* data) {
toI16(buf[2], buf[3]) * ACCEL_SCALE, toI16(buf[2], buf[3]) * ACCEL_SCALE,
toI16(buf[4], buf[5]) * ACCEL_SCALE, toI16(buf[4], buf[5]) * ACCEL_SCALE,
}; };
const auto& config = *GET_CONFIG(device); applyMountMatrix(*GET_CONFIG(device), axes[0], axes[1], axes[2]);
if (config.swap_xy) std::swap(axes[0], axes[1]);
if (config.swap_xz) std::swap(axes[0], axes[2]);
if (config.swap_yz) std::swap(axes[1], axes[2]);
if (config.invert_x) axes[0] = -axes[0];
if (config.invert_y) axes[1] = -axes[1];
if (config.invert_z) axes[2] = -axes[2];
data->ax = axes[0]; data->ax = axes[0];
data->ay = axes[1]; data->ay = axes[1];
data->az = axes[2]; data->az = axes[2];
@ -171,9 +175,13 @@ error_t mpu6886_read_gyro(Device* device, ImuGyroData* data) {
error_t error = i2c_controller_read_register(i2c_controller, address, REG_GYRO_XOUT_H, buf, sizeof(buf), I2C_TIMEOUT_TICKS); error_t error = i2c_controller_read_register(i2c_controller, address, REG_GYRO_XOUT_H, buf, sizeof(buf), I2C_TIMEOUT_TICKS);
if (error != ERROR_NONE) return error; if (error != ERROR_NONE) return error;
data->gx = toI16(buf[0], buf[1]) * GYRO_SCALE; float gx = toI16(buf[0], buf[1]) * GYRO_SCALE;
data->gy = toI16(buf[2], buf[3]) * GYRO_SCALE; float gy = toI16(buf[2], buf[3]) * GYRO_SCALE;
data->gz = toI16(buf[4], buf[5]) * GYRO_SCALE; float gz = toI16(buf[4], buf[5]) * GYRO_SCALE;
applyMountMatrix(*GET_CONFIG(device), gx, gy, gz);
data->gx = gx;
data->gy = gy;
data->gz = gz;
return ERROR_NONE; return ERROR_NONE;
} }

View File

@ -343,6 +343,10 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
DEFINE_MODULE_SYMBOL(lv_obj_set_style_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_height),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_clip_corner), DEFINE_MODULE_SYMBOL(lv_obj_set_style_clip_corner),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_shadow_color), DEFINE_MODULE_SYMBOL(lv_obj_set_style_shadow_color),
DEFINE_MODULE_SYMBOL(lv_obj_set_parent),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_bg_image_tiled),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_image_recolor),
DEFINE_MODULE_SYMBOL(lv_obj_set_style_image_opa),
// lv_font // lv_font
DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt), DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt),
DEFINE_MODULE_SYMBOL(lv_font_get_default), DEFINE_MODULE_SYMBOL(lv_font_get_default),

View File

@ -84,7 +84,7 @@ struct Context {
lv_obj_t* timeoutValueLabel = nullptr; lv_obj_t* timeoutValueLabel = nullptr;
lv_obj_t* screensaverValueLabel = nullptr; lv_obj_t* screensaverValueLabel = nullptr;
// Shown when auto-rotate is off or unavailable: the orientation label and chips // Shown when auto-rotate is off or unavailable: the orientation label and chips
lv_obj_t* orientationGroup = nullptr; lv_obj_t* orientationCard = nullptr;
lv_obj_t* orientationChips = nullptr; lv_obj_t* orientationChips = nullptr;
lv_obj_t* autoRotateSwitch = nullptr; lv_obj_t* autoRotateSwitch = nullptr;
}; };
@ -151,10 +151,10 @@ void setCheckedChip(lv_obj_t* chips, settings::display::Orientation orientation)
}); });
} }
/** Shows or hides the manual orientation chips, depending on whether auto-rotate is on */ /** Shows or hides the manual orientation card, depending on whether auto-rotate is on */
void updateOrientationVisibility(Context* ctx) { void updateOrientationVisibility(Context* ctx) {
const bool auto_rotate = ctx->autoRotateSwitch != nullptr && ctx->displaySettings.autoRotateEnabled; const bool auto_rotate = ctx->autoRotateSwitch != nullptr && ctx->displaySettings.autoRotateEnabled;
lv_obj_set_hidden(ctx->orientationGroup, auto_rotate); lv_obj_set_hidden(ctx->orientationCard, auto_rotate);
} }
void onOrientationChipPressed(lv_event_t* event) { void onOrientationChipPressed(lv_event_t* event) {
@ -396,12 +396,12 @@ void createWidgets(lv_obj_t* parent, void* userData) {
} }
// The card shows the manual orientation; auto-rotate hides it while it drives the rotation // The card shows the manual orientation; auto-rotate hides it while it drives the rotation
auto* orientation_card = lvgl_card_create(main_wrapper); ctx->orientationCard = lvgl_card_create(main_wrapper);
lv_obj_set_size(orientation_card, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(ctx->orientationCard, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_flex_flow(orientation_card, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(ctx->orientationCard, LV_FLEX_FLOW_COLUMN);
ctx->orientationGroup = createCardGroup(orientation_card, "Orientation"); auto* orientation_group = createCardGroup(ctx->orientationCard, "Orientation");
ctx->orientationChips = createOrientationChips(ctx->orientationGroup, ctx->displaySettings.orientation, onOrientationChipPressed, ctx); ctx->orientationChips = createOrientationChips(orientation_group, ctx->displaySettings.orientation, onOrientationChipPressed, ctx);
updateOrientationVisibility(ctx); updateOrientationVisibility(ctx);
@ -447,7 +447,7 @@ void destroyWidgets(void* userData) {
ctx->timeoutCard = nullptr; ctx->timeoutCard = nullptr;
ctx->timeoutValueLabel = nullptr; ctx->timeoutValueLabel = nullptr;
ctx->screensaverValueLabel = nullptr; ctx->screensaverValueLabel = nullptr;
ctx->orientationGroup = nullptr; ctx->orientationCard = nullptr;
ctx->orientationChips = nullptr; ctx->orientationChips = nullptr;
ctx->autoRotateSwitch = nullptr; ctx->autoRotateSwitch = nullptr;
} }

View File

@ -7,7 +7,10 @@ namespace tt::service::autorotate {
constexpr float FLAT_THRESHOLD = 0.45F; // gravity in the screen plane below this = lying flat constexpr float FLAT_THRESHOLD = 0.45F; // gravity in the screen plane below this = lying flat
constexpr int DEBOUNCE = 5; // consecutive readings before switching constexpr int DEBOUNCE = 5; // consecutive readings before switching
// gx toward +Y -> rotation 0; each +90 degrees of gx/gy angle is one more quarter-turn. // ax/ay is the accelerometer support-force vector (points up, opposite gravity).
// The UI top should point along (ax,ay): +Y -> rotation 0.
// LVGL ROT90 is 90 deg counter-clockwise (UI top -> device left, -X), ROT270 is
// 90 deg clockwise (UI top -> device right, +X), so +X maps to 270, not 90.
static lv_display_rotation_t gravityToRotation(float gx, float gy, lv_display_rotation_t fallback) { static lv_display_rotation_t gravityToRotation(float gx, float gy, lv_display_rotation_t fallback) {
float plane = sqrtf((gx * gx) + (gy * gy)); float plane = sqrtf((gx * gx) + (gy * gy));
if (plane < FLAT_THRESHOLD) { if (plane < FLAT_THRESHOLD) {
@ -22,9 +25,9 @@ static lv_display_rotation_t gravityToRotation(float gx, float gy, lv_display_ro
int idx = (int)((ang + 45.0F) / 90.0F) % 4; int idx = (int)((ang + 45.0F) / 90.0F) % 4;
static constexpr lv_display_rotation_t map[4] = { static constexpr lv_display_rotation_t map[4] = {
LV_DISPLAY_ROTATION_0, LV_DISPLAY_ROTATION_0,
LV_DISPLAY_ROTATION_90,
LV_DISPLAY_ROTATION_180,
LV_DISPLAY_ROTATION_270, LV_DISPLAY_ROTATION_270,
LV_DISPLAY_ROTATION_180,
LV_DISPLAY_ROTATION_90,
}; };
return map[idx]; return map[idx];
} }

View File

@ -9,7 +9,7 @@ TEST_CASE("a flat reading below the threshold keeps the current rotation") {
CHECK_EQ(math.update(0.1F, 0.1F), LV_DISPLAY_ROTATION_0); CHECK_EQ(math.update(0.1F, 0.1F), LV_DISPLAY_ROTATION_0);
} }
TEST_CASE("gravity toward +X classifies as rotation 90 when no debounce is needed yet") { TEST_CASE("accel toward +X classifies as rotation 270 when no debounce is needed yet") {
OrientationMath math; OrientationMath math;
math.reset(LV_DISPLAY_ROTATION_0); math.reset(LV_DISPLAY_ROTATION_0);
// A single reading only sets the debounce target - the previous rotation is still returned. // A single reading only sets the debounce target - the previous rotation is still returned.
@ -27,32 +27,34 @@ TEST_CASE("a new rotation is only applied after enough consecutive stable readin
CHECK_EQ(last, LV_DISPLAY_ROTATION_0); // not yet debounced CHECK_EQ(last, LV_DISPLAY_ROTATION_0); // not yet debounced
} }
last = math.update(1.0F, 0.0F); last = math.update(1.0F, 0.0F);
CHECK_EQ(last, LV_DISPLAY_ROTATION_90); // 6th call (5th consecutive stable reading) switches CHECK_EQ(last, LV_DISPLAY_ROTATION_270); // 6th call (5th consecutive stable reading) switches
} }
TEST_CASE("all four gravity directions map to the expected rotation") { TEST_CASE("all four accel directions map to the expected rotation") {
OrientationMath math; OrientationMath math;
math.reset(LV_DISPLAY_ROTATION_0); math.reset(LV_DISPLAY_ROTATION_0);
auto settle = [&](float gx, float gy) { auto settle = [&](float ax, float ay) {
lv_display_rotation_t result = LV_DISPLAY_ROTATION_0; lv_display_rotation_t result = LV_DISPLAY_ROTATION_0;
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
result = math.update(gx, gy); result = math.update(ax, ay);
} }
return result; return result;
}; };
// Accel points up (opposite gravity); UI top follows it.
// LVGL ROT90 is CCW (top -> -X), ROT270 is CW (top -> +X).
CHECK_EQ(settle(0.0F, 1.0F), LV_DISPLAY_ROTATION_0); CHECK_EQ(settle(0.0F, 1.0F), LV_DISPLAY_ROTATION_0);
CHECK_EQ(settle(1.0F, 0.0F), LV_DISPLAY_ROTATION_90); CHECK_EQ(settle(1.0F, 0.0F), LV_DISPLAY_ROTATION_270);
CHECK_EQ(settle(0.0F, -1.0F), LV_DISPLAY_ROTATION_180); CHECK_EQ(settle(0.0F, -1.0F), LV_DISPLAY_ROTATION_180);
CHECK_EQ(settle(-1.0F, 0.0F), LV_DISPLAY_ROTATION_270); CHECK_EQ(settle(-1.0F, 0.0F), LV_DISPLAY_ROTATION_90);
} }
TEST_CASE("an unstable target resets the debounce counter") { TEST_CASE("an unstable target resets the debounce counter") {
OrientationMath math; OrientationMath math;
math.reset(LV_DISPLAY_ROTATION_0); math.reset(LV_DISPLAY_ROTATION_0);
// Get close to switching to rotation 90... // Get close to switching to rotation 270...
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
math.update(1.0F, 0.0F); math.update(1.0F, 0.0F);
} }