Add SDK RTC and IMU hardware abstraction support

Integrate SDK-based RTC and IMU backends as alternatives to direct hardware access. HalClock now attempts SDK RTC initialization and caches time values for reliability. HalTiltSensor adds SDK IMU backend with fallback logic. ClockOffsetActivity gains touch and swipe gesture support for field navigation.
This commit is contained in:
Justin Mitchell
2026-06-19 14:25:51 -04:00
parent 526cf1b6f9
commit 06ff5aa44a
8 changed files with 177 additions and 3 deletions
+62 -1
View File
@@ -18,8 +18,11 @@ static uint8_t bcdToDec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F);
static uint8_t decToBcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } static uint8_t decToBcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); }
void HalClock::begin() { void HalClock::begin() {
_usesSdkRtc = false;
if (!gpio.deviceIsX3()) { if (!gpio.deviceIsX3()) {
_available = false; _available = _sdkRtc.begin();
_usesSdkRtc = _available;
LOG_INF("CLK", _available ? "SDK RTC found" : "RTC not found");
return; return;
} }
@@ -57,6 +60,24 @@ bool HalClock::getTime(uint8_t& hour, uint8_t& minute) const {
return true; return true;
} }
if (_usesSdkRtc) {
Rtc::DateTime dt;
if (!_sdkRtc.now(dt)) {
if (!_hasCachedTime) return false;
_lastPollMs = now;
hour = _cachedHour;
minute = _cachedMinute;
return true;
}
_cachedHour = dt.hour;
_cachedMinute = dt.minute;
_lastPollMs = now;
_hasCachedTime = true;
hour = _cachedHour;
minute = _cachedMinute;
return true;
}
// Read 3 bytes starting at register 0x00: seconds, minutes, hours // Read 3 bytes starting at register 0x00: seconds, minutes, hours
Wire.beginTransmission(I2C_ADDR_DS3231); Wire.beginTransmission(I2C_ADDR_DS3231);
Wire.write(DS3231_SEC_REG); Wire.write(DS3231_SEC_REG);
@@ -131,6 +152,25 @@ bool HalClock::writeTimeToRTC(uint8_t hour, uint8_t minute, uint8_t second) {
assert(hour < 24); assert(hour < 24);
assert(minute < 60); assert(minute < 60);
assert(second < 60); assert(second < 60);
if (_usesSdkRtc) {
Rtc::DateTime dt;
dt.hour = hour;
dt.minute = minute;
dt.second = second;
dt.year = 2000;
dt.month = 1;
dt.day = 1;
if (!_sdkRtc.set(dt)) {
LOG_ERR("CLK", "Failed to write time to SDK RTC");
return false;
}
_lastPollMs = 0;
_cachedHour = hour;
_cachedMinute = minute;
_hasCachedTime = true;
return true;
}
Wire.beginTransmission(I2C_ADDR_DS3231); Wire.beginTransmission(I2C_ADDR_DS3231);
Wire.write(DS3231_SEC_REG); // Start at register 0x00 Wire.write(DS3231_SEC_REG); // Start at register 0x00
Wire.write(decToBcd(second)); // 0x00: Seconds Wire.write(decToBcd(second)); // 0x00: Seconds
@@ -168,6 +208,27 @@ bool HalClock::syncFromNTP() {
struct tm timeinfo; struct tm timeinfo;
gmtime_r(&now, &timeinfo); gmtime_r(&now, &timeinfo);
if (_usesSdkRtc) {
Rtc::DateTime dt;
dt.year = static_cast<uint16_t>(timeinfo.tm_year + 1900);
dt.month = static_cast<uint8_t>(timeinfo.tm_mon + 1);
dt.day = static_cast<uint8_t>(timeinfo.tm_mday);
dt.hour = static_cast<uint8_t>(timeinfo.tm_hour);
dt.minute = static_cast<uint8_t>(timeinfo.tm_min);
dt.second = static_cast<uint8_t>(timeinfo.tm_sec);
dt.weekday = static_cast<uint8_t>(timeinfo.tm_wday);
if (_sdkRtc.set(dt)) {
_lastPollMs = 0;
_cachedHour = dt.hour;
_cachedMinute = dt.minute;
_hasCachedTime = true;
LOG_INF("CLK", "RTC set to %04u-%02u-%02u %02u:%02u:%02u UTC", dt.year, dt.month, dt.day, dt.hour,
dt.minute, dt.second);
return true;
}
return false;
}
if (writeTimeToRTC(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)) { if (writeTimeToRTC(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)) {
LOG_INF("CLK", "RTC set to %02d:%02d:%02d UTC", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); LOG_INF("CLK", "RTC set to %02d:%02d:%02d UTC", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
return true; return true;
+3
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <Rtc.h>
#include <Wire.h> #include <Wire.h>
#include "HalGPIO.h" #include "HalGPIO.h"
@@ -10,6 +11,8 @@ extern HalClock halClock; // Singleton
class HalClock { class HalClock {
bool _available = false; bool _available = false;
bool _usesSdkRtc = false;
mutable Rtc _sdkRtc;
mutable uint8_t _cachedHour = 0; mutable uint8_t _cachedHour = 0;
mutable uint8_t _cachedMinute = 0; mutable uint8_t _cachedMinute = 0;
mutable bool _hasCachedTime = false; mutable bool _hasCachedTime = false;
+36 -1
View File
@@ -26,6 +26,15 @@ bool HalTiltSensor::readReg(uint8_t reg, uint8_t* val) const {
} }
bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const { bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const {
if (_backend == Backend::SdkImu) {
Imu::Sample sample;
if (!_sdkImu.read(sample)) return false;
gx = sample.gx;
gy = sample.gy;
gz = sample.gz;
return true;
}
Wire.beginTransmission(_i2cAddr); Wire.beginTransmission(_i2cAddr);
Wire.write(REG_GX_L); // Start reading at Gyro X Low Wire.write(REG_GX_L); // Start reading at Gyro X Low
if (Wire.endTransmission(false) != 0) { if (Wire.endTransmission(false) != 0) {
@@ -52,11 +61,22 @@ bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const {
} }
void HalTiltSensor::begin() { void HalTiltSensor::begin() {
_backend = Backend::None;
if (!gpio.deviceIsX3()) { if (!gpio.deviceIsX3()) {
_available = false; _available = _sdkImu.begin();
if (!_available) {
LOG_ERR("GYR", "SDK IMU not found");
return;
}
_backend = Backend::SdkImu;
_initMs = millis();
_lastPollMs = millis();
LOG_INF("GYR", "SDK IMU initialized");
return; return;
} }
_backend = Backend::Qmi8658;
// Try primary address, then alternate // Try primary address, then alternate
uint8_t whoami = 0; uint8_t whoami = 0;
_i2cAddr = I2C_ADDR_QMI8658; _i2cAddr = I2C_ADDR_QMI8658;
@@ -89,6 +109,14 @@ bool HalTiltSensor::wake() {
return false; return false;
} }
if (_backend == Backend::SdkImu) {
_lastPollMs = millis();
_lastTiltMs = millis();
_wakeMs = millis();
_isAwake = true;
return true;
}
// Wait for init to complete before waking // Wait for init to complete before waking
if ((millis() - _initMs) < SLEEP_STABILIZE_MS) { if ((millis() - _initMs) < SLEEP_STABILIZE_MS) {
return false; return false;
@@ -111,6 +139,13 @@ bool HalTiltSensor::deepSleep() {
return false; return false;
} }
if (_backend == Backend::SdkImu) {
clearPendingEvents();
_inTilt = false;
_isAwake = false;
return true;
}
if ((millis() - _wakeMs) < SLEEP_STABILIZE_MS) { if ((millis() - _wakeMs) < SLEEP_STABILIZE_MS) {
return false; return false;
} }
+5
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <Imu.h>
#include <Wire.h> #include <Wire.h>
#include "HalGPIO.h" #include "HalGPIO.h"
@@ -18,8 +19,12 @@ class HalTiltSensor;
extern HalTiltSensor halTiltSensor; // Singleton extern HalTiltSensor halTiltSensor; // Singleton
class HalTiltSensor { class HalTiltSensor {
enum class Backend : uint8_t { None, Qmi8658, SdkImu };
bool _available = false; bool _available = false;
Backend _backend = Backend::None;
uint8_t _i2cAddr = 0; uint8_t _i2cAddr = 0;
mutable Imu _sdkImu;
// Tilt gesture state machine // Tilt gesture state machine
bool _tiltForwardEvent = false; // Consumed by wasTiltedForward() bool _tiltForwardEvent = false; // Consumed by wasTiltedForward()
+2
View File
@@ -66,6 +66,8 @@ lib_deps =
SDCardManager=symlink://freeink-sdk/libs/hardware/SDCardManager SDCardManager=symlink://freeink-sdk/libs/hardware/SDCardManager
BoardConfig=symlink://freeink-sdk/libs/hardware/BoardConfig BoardConfig=symlink://freeink-sdk/libs/hardware/BoardConfig
PowerManager=symlink://freeink-sdk/libs/hardware/PowerManager PowerManager=symlink://freeink-sdk/libs/hardware/PowerManager
Rtc=symlink://freeink-sdk/libs/hardware/Rtc
Imu=symlink://freeink-sdk/libs/hardware/Imu
Icons=symlink://freeink-sdk/libs/assets/Icons Icons=symlink://freeink-sdk/libs/assets/Icons
bblanchon/ArduinoJson @ 7.4.2 bblanchon/ArduinoJson @ 7.4.2
ricmoo/QRCode @ 0.0.1 ricmoo/QRCode @ 0.0.1
@@ -9,6 +9,7 @@
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "components/TouchRegistry.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
@@ -108,14 +109,57 @@ void ClockOffsetActivity::adjustActiveField(int delta) {
} }
} }
void ClockOffsetActivity::selectRelativeField(const int delta) {
activeField = static_cast<Field>((activeField + delta + FIELD_COUNT) % FIELD_COUNT);
}
void ClockOffsetActivity::loop() { void ClockOffsetActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
finish(); finish();
return; return;
} }
switch (mappedInput.wasSwipe()) {
case MappedInputManager::SwipeDir::Up:
adjustActiveField(+1);
requestUpdate();
return;
case MappedInputManager::SwipeDir::Down:
adjustActiveField(-1);
requestUpdate();
return;
case MappedInputManager::SwipeDir::Left:
selectRelativeField(+1);
requestUpdate();
return;
case MappedInputManager::SwipeDir::Right:
selectRelativeField(-1);
requestUpdate();
return;
case MappedInputManager::SwipeDir::None:
break;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < FIELD_COUNT) {
activeField = static_cast<Field>(downId);
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < FIELD_COUNT) {
const Field tappedField = static_cast<Field>(tappedId);
if (activeField == tappedField) {
adjustActiveField(+1);
} else {
activeField = tappedField;
}
requestUpdate();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
activeField = static_cast<Field>((activeField + 1) % FIELD_COUNT); selectRelativeField(+1);
requestUpdate(); requestUpdate();
return; return;
} }
@@ -183,6 +227,8 @@ void ClockOffsetActivity::render(RenderLock&&) {
} }
const int textX = boxX + (boxWidth - widthOf(text)) / 2; const int textX = boxX + (boxWidth - widthOf(text)) / 2;
renderer.drawText(UI_12_FONT_ID, textX, centreY, text, true, EpdFontFamily::BOLD); renderer.drawText(UI_12_FONT_ID, textX, centreY, text, true, EpdFontFamily::BOLD);
TouchRegistry::getInstance().add(Rect{boxX - 4, centreY - 8, boxWidth + 8, fieldHeight + 16},
static_cast<int>(field), TouchRegistry::Item);
}; };
drawField(signStr, x, signBoxW, FIELD_SIGN); drawField(signStr, x, signBoxW, FIELD_SIGN);
@@ -34,4 +34,5 @@ class ClockOffsetActivity final : public Activity {
void saveToSettings() const; void saveToSettings() const;
void adjustActiveField(int delta); void adjustActiveField(int delta);
void clampForSign(); void clampForSign();
void selectRelativeField(int delta);
}; };
@@ -123,6 +123,27 @@ void StatusBarSettingsActivity::loop() {
return; return;
} }
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
const bool swiped = mappedInput.wasListScroll(selectedIndex, visibleItemCount, pageItems);
if (swiped) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < visibleItemCount) {
selectedIndex = downId;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < visibleItemCount) {
selectedIndex = tappedId;
handleSelection();
requestUpdate();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
handleSelection(); handleSelection();
requestUpdate(); requestUpdate();