feat: Add touch coordinate mapping and RTOS task yielding (#2481)
Co-authored-by: Julia Nguyen <julia@uxj.io>
This commit is contained in:
co-authored by
Julia Nguyen
parent
c9188a7347
commit
f42fab1c66
@@ -1506,6 +1506,20 @@ void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const
|
||||
display.displayBuffer(refreshMode, fadingFix);
|
||||
}
|
||||
|
||||
void GfxRenderer::displayBufferAsync(const HalDisplay::RefreshMode refreshMode) const {
|
||||
// The async path has no turn-off-screen hook, which the sunlight fading fix
|
||||
// relies on; keep those users on the blocking path.
|
||||
if (fadingFix) {
|
||||
display.displayBuffer(refreshMode, fadingFix);
|
||||
return;
|
||||
}
|
||||
display.displayBufferAsync(refreshMode);
|
||||
}
|
||||
|
||||
void GfxRenderer::waitRefreshComplete() const { display.waitRefreshComplete(); }
|
||||
|
||||
bool GfxRenderer::supportsAsyncRefresh() const { return !fadingFix && display.supportsAsyncRefresh(); }
|
||||
|
||||
size_t GfxRenderer::readFramebufferRegion(int x, int y, int w, int h, uint8_t* dst, size_t dstCapacity) const {
|
||||
if (dst == nullptr || w <= 0 || h <= 0) return 0;
|
||||
|
||||
@@ -1652,6 +1666,35 @@ int GfxRenderer::getScreenHeight() const {
|
||||
return panelWidth;
|
||||
}
|
||||
|
||||
void GfxRenderer::tapToLogical(float nx, float ny, int& outX, int& outY) const {
|
||||
int phyX = static_cast<int>(nx * panelWidth);
|
||||
int phyY = static_cast<int>(ny * panelHeight);
|
||||
if (phyX < 0) phyX = 0;
|
||||
if (phyX > panelWidth - 1) phyX = panelWidth - 1;
|
||||
if (phyY < 0) phyY = 0;
|
||||
if (phyY > panelHeight - 1) phyY = panelHeight - 1;
|
||||
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
outX = panelHeight - 1 - phyY;
|
||||
outY = phyX;
|
||||
break;
|
||||
case PortraitInverted:
|
||||
outX = phyY;
|
||||
outY = panelWidth - 1 - phyX;
|
||||
break;
|
||||
case LandscapeClockwise:
|
||||
outX = panelWidth - 1 - phyX;
|
||||
outY = panelHeight - 1 - phyY;
|
||||
break;
|
||||
case LandscapeCounterClockwise:
|
||||
default:
|
||||
outX = phyX;
|
||||
outY = phyY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Translate a logical rect through rotateCoordinates and take the bounding
|
||||
// box of its four corners on the physical panel. Output coords are inclusive
|
||||
// and clamped. Returns false if the rect ends up fully off-panel.
|
||||
|
||||
@@ -134,7 +134,19 @@ class GfxRenderer {
|
||||
// Screen ops
|
||||
int getScreenWidth() const;
|
||||
int getScreenHeight() const;
|
||||
void tapToLogical(float nx, float ny, int& outX, int& outY) const;
|
||||
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
||||
// Non-blocking refresh: starts the waveform and returns so CPU work (e.g.
|
||||
// grayscale strip rendering) can overlap the panel's refresh time. The
|
||||
// framebuffer must stay untouched until waitRefreshComplete(). Falls back to
|
||||
// a blocking refresh when fadingFix is enabled or the panel lacks deferral
|
||||
// support. See HalDisplay::displayBufferAsync for the baseline contract.
|
||||
void displayBufferAsync(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
||||
void waitRefreshComplete() const;
|
||||
// True when displayBufferAsync() genuinely overlaps: panel defers and
|
||||
// fadingFix isn't forcing the blocking path. Callers can skip overlap
|
||||
// scaffolding (e.g. whole-plane grayscale buffers) when false.
|
||||
bool supportsAsyncRefresh() const;
|
||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||
// void displayWindow(int x, int y, int width, int height) const;
|
||||
void invertScreen() const;
|
||||
|
||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Згладжванне тэксту"
|
||||
STR_SHORT_PWR_BTN: "Кароткае націсканне PWR"
|
||||
STR_ORIENTATION: "Арыентацыя чытання"
|
||||
STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі"
|
||||
STR_TOUCH_READER_CONTROLS: "Сэнсарнае кіраванне чытаннем"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі"
|
||||
STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела"
|
||||
STR_FONT_PREVIEW_TEXT: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
|
||||
|
||||
@@ -78,6 +78,7 @@ STR_EOB_CONTINUE_WITH: "Continua amb"
|
||||
STR_SHORT_PWR_BTN: "Pulsació curta del botó d'engegada"
|
||||
STR_ORIENTATION: "Orientació de lectura"
|
||||
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
||||
STR_TOUCH_READER_CONTROLS: "Controls tàctils del lector"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Acció en mantenir premut un botó"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||
|
||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Vyhlazování textu"
|
||||
STR_SHORT_PWR_BTN: "Krátké stisknutí tlačítka napájení"
|
||||
STR_ORIENTATION: "Orientace čtení"
|
||||
STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)"
|
||||
STR_TOUCH_READER_CONTROLS: "Dotykové ovládání čtečky"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientovat přední tlačítka"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Chování při dlouhém stisknutí tlačítka"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Skjul"
|
||||
STR_SHORT_PWR_BTN: "Kort tryk på tænd/sluk-knap"
|
||||
STR_ORIENTATION: "Læseretning"
|
||||
STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touchkontroller i læser"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientér forreste knapper"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Verbergen"
|
||||
STR_SHORT_PWR_BTN: "Korte klik aan/uit-knop"
|
||||
STR_ORIENTATION: "Leesstand"
|
||||
STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)"
|
||||
STR_TOUCH_READER_CONTROLS: "Aanraakbediening lezer"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Richt voorste knoppen"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -88,6 +88,7 @@ STR_EOB_CONTINUE_WITH: "Continue with"
|
||||
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
||||
STR_ORIENTATION: "Reading Orientation"
|
||||
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touch Reader Controls"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orient front buttons"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
@@ -230,6 +231,7 @@ STR_CONNECT: "Connect"
|
||||
STR_OPEN: "Open"
|
||||
STR_DOWNLOAD: "Download"
|
||||
STR_RETRY: "Retry"
|
||||
STR_TAP_TO_RETRY: "Tap to retry"
|
||||
STR_YES: "Yes"
|
||||
STR_NO: "No"
|
||||
STR_SHOW: "Show"
|
||||
@@ -242,6 +244,9 @@ STR_DIR_RIGHT: "Right"
|
||||
STR_DIR_UP: "Up"
|
||||
STR_DIR_DOWN: "Down"
|
||||
STR_OK_BUTTON: "OK"
|
||||
STR_KEY_SHIFT: "Shift"
|
||||
STR_KEY_MODE_SYMBOLS: "?123"
|
||||
STR_KEY_MODE_ABC: "abc"
|
||||
STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter"
|
||||
STR_FILTER_CONTRAST: "Contrast"
|
||||
STR_CUSTOMISE_STATUS_BAR: "Customise Status Bar"
|
||||
|
||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Tekstin reunanpehmennys"
|
||||
STR_SHORT_PWR_BTN: "Lyhyt virtapainikkeen painallus"
|
||||
STR_ORIENTATION: "Lukusuunta"
|
||||
STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)"
|
||||
STR_TOUCH_READER_CONTROLS: "Lukijan kosketusohjaus"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Suuntaa etupainikkeet"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Masquer"
|
||||
STR_SHORT_PWR_BTN: "Appui court alim."
|
||||
STR_ORIENTATION: "Orientation de lecture"
|
||||
STR_SIDE_BTN_LAYOUT: "Boutons latéraux"
|
||||
STR_TOUCH_READER_CONTROLS: "Commandes tactiles lecteur"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienter boutons avant"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Comportement lors d'un appui long"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Désactivé"
|
||||
|
||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Schriftglättung"
|
||||
STR_SHORT_PWR_BTN: "An-Taste kurz drücken"
|
||||
STR_ORIENTATION: "Leseausrichtung"
|
||||
STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touch-Steuerung beim Lesen"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Vordere Tasten ausrichten"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Aus"
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "הסתר תמונות"
|
||||
STR_SHORT_PWR_BTN: "לחיצה קצרה על כפתור ההפעלה"
|
||||
STR_ORIENTATION: "כיוון קריאה (מסך)"
|
||||
STR_SIDE_BTN_LAYOUT: "פריסת כפתורי צד (בקריאה)"
|
||||
STR_TOUCH_READER_CONTROLS: "שליטה במגע (קורא)"
|
||||
STR_LONG_PRESS_BEHAVIOR: "פעולת לחיצה ארוכה"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "כבוי"
|
||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "דלג פרק"
|
||||
|
||||
@@ -74,6 +74,7 @@ STR_IMAGES_SUPPRESS: "Elnyomás"
|
||||
STR_SHORT_PWR_BTN: "Rövid bekapcsológomb nyomás"
|
||||
STR_ORIENTATION: "Olvasási irány"
|
||||
STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)"
|
||||
STR_TOUCH_READER_CONTROLS: "Érintős olvasóvezérlés"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Elülső gombok tájolása"
|
||||
STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás"
|
||||
STR_FONT_PREVIEW_TEXT: "Egy hűtlen vejét fülöncsípő, dühös mexikói úr Wesselényinél mázol Quitóban"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Nascondi"
|
||||
STR_SHORT_PWR_BTN: "Press. breve pul. accensione"
|
||||
STR_ORIENTATION: "Orientamento lettura"
|
||||
STR_SIDE_BTN_LAYOUT: "Pul. laterali (lettore)"
|
||||
STR_TOUCH_READER_CONTROLS: "Controlli touch lettore"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienta pul. frontali"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Press. lunga pul. laterali"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -67,6 +67,7 @@ STR_TEXT_AA: "Мәтін сырғытпасы"
|
||||
STR_SHORT_PWR_BTN: "Қуат түймесін қысқа басу"
|
||||
STR_ORIENTATION: "Оқу бағдары"
|
||||
STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)"
|
||||
STR_TOUCH_READER_CONTROLS: "Оқырманның сенсорлық басқаруы"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Алдыңғы түймелерді бағдарлау"
|
||||
STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу"
|
||||
STR_FONT_PREVIEW_TEXT: "Канагаттандырылмагандыктарыныздан"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Slėpti"
|
||||
STR_SHORT_PWR_BTN: "Trumpas įjungimo pasp."
|
||||
STR_ORIENTATION: "Orientacija"
|
||||
STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai"
|
||||
STR_TOUCH_READER_CONTROLS: "Lietimo valdikliai skaityklėje"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuoti priekinius mygtukus"
|
||||
STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)"
|
||||
STR_FONT_PREVIEW_TEXT: "Įlinkdama fechtuotojo špaga sublykčiojusi pragręžė apvalų arbūzą"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Pomijaj"
|
||||
STR_SHORT_PWR_BTN: "Krótkie naciśnięcie zasilania"
|
||||
STR_ORIENTATION: "Układ czytania"
|
||||
STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych"
|
||||
STR_TOUCH_READER_CONTROLS: "Sterowanie dotykowe czytnika"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuj przednie przyciski"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Funkcja długiego przyciśnięcia"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Wył."
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Ocultar"
|
||||
STR_SHORT_PWR_BTN: "Clique curto botão ligar"
|
||||
STR_ORIENTATION: "Orientação de leitura"
|
||||
STR_SIDE_BTN_LAYOUT: "Disposição botões laterais"
|
||||
STR_TOUCH_READER_CONTROLS: "Controles táteis do leitor"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botões frontais"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Comportamento de Pressionar e segurar"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "DESL."
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Suprimare"
|
||||
STR_SHORT_PWR_BTN: "Apăsare scurtă întrerupător"
|
||||
STR_ORIENTATION: "Orientare lectură"
|
||||
STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)"
|
||||
STR_TOUCH_READER_CONTROLS: "Comenzi tactile cititor"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientare butoane frontale"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Comportament buton apăsat lung"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Dezactivat"
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Скрыть"
|
||||
STR_SHORT_PWR_BTN: "Короткое нажатие PWR"
|
||||
STR_ORIENTATION: "Ориентация чтения"
|
||||
STR_SIDE_BTN_LAYOUT: "Боковые кнопки"
|
||||
STR_TOUCH_READER_CONTROLS: "Сенсорное управление чтением"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ориентировать передние кнопки"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Долгое нажатие"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Ничего"
|
||||
|
||||
@@ -72,7 +72,8 @@ STR_IMAGES_PLACEHOLDER: "Rezervované miesto"
|
||||
STR_IMAGES_SUPPRESS: "Potlačiť"
|
||||
STR_SHORT_PWR_BTN: "Krátke stlačenie tlačidla napájania"
|
||||
STR_ORIENTATION: "Orientácia čítania"
|
||||
STR_SIDE_BTN_LAYOUT: "Rozloženie bočných tlačidiel (čítačka)"
|
||||
STR_SIDE_BTN_LAYOUT: "Rozloženie bočných tlačidiel (čítačka)"
|
||||
STR_TOUCH_READER_CONTROLS: "Dotykové ovládanie čítačky"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Prispôsobiť predné tlačidlá orientácii"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Správanie pri dlhom stlačení tlačidla"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Zatdi"
|
||||
STR_SHORT_PWR_BTN: "Kratek pritisk na gumb za vklop"
|
||||
STR_ORIENTATION: "Orientacija branja"
|
||||
STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov"
|
||||
STR_TOUCH_READER_CONTROLS: "Upravljanje bralnika na dotik"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Usmeri sprednje gumbe"
|
||||
STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja"
|
||||
STR_FONT_PREVIEW_TEXT: "V kožuščku hudobnega fanta stopiclja mizar"
|
||||
|
||||
@@ -78,6 +78,7 @@ STR_EOB_CONTINUE_WITH: "Continuar con"
|
||||
STR_SHORT_PWR_BTN: "Toque corto del encendido"
|
||||
STR_ORIENTATION: "Orientación"
|
||||
STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)"
|
||||
STR_TOUCH_READER_CONTROLS: "Controles táctiles del lector"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botones frontales"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Al mantener pulsado un botón"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada"
|
||||
|
||||
@@ -88,6 +88,7 @@ STR_EOB_CONTINUE_WITH: "Fortsätt med"
|
||||
STR_SHORT_PWR_BTN: "Kort strömknappsklick"
|
||||
STR_ORIENTATION: "Läsrikting"
|
||||
STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)"
|
||||
STR_TOUCH_READER_CONTROLS: "Pekkontroller i läsaren"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Rikta främre knappar"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Beteende vid lång knapptryckning"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "AV"
|
||||
|
||||
@@ -67,6 +67,7 @@ STR_TEXT_AA: "Metin Yumuşatma (AA)"
|
||||
STR_SHORT_PWR_BTN: "Kısa Güç Tuşu Tıklaması"
|
||||
STR_ORIENTATION: "Okuma Yönü"
|
||||
STR_SIDE_BTN_LAYOUT: "Yan Tuş Dizilimi (okuyucu)"
|
||||
STR_TOUCH_READER_CONTROLS: "Dokunmatik okuyucu kontrolleri"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ön düğmeleri yönlendir"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Uzun basma tuş davranışı"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "KAPALI"
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Приховати"
|
||||
STR_SHORT_PWR_BTN: "Короткий натиск кн. живл."
|
||||
STR_ORIENTATION: "Орієнтація читання"
|
||||
STR_SIDE_BTN_LAYOUT: "Схема бічних кнопок"
|
||||
STR_TOUCH_READER_CONTROLS: "Сенсорне керування читанням"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Орієнтувати передні кнопки"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Поведінка при довгому настику"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Немає"
|
||||
|
||||
@@ -79,6 +79,7 @@ STR_EOB_CONTINUE_WITH: "Continua amb"
|
||||
STR_SHORT_PWR_BTN: "Pulsació curta del botó d'engegada"
|
||||
STR_ORIENTATION: "Orientació de lectura"
|
||||
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
||||
STR_TOUCH_READER_CONTROLS: "Controls tàctils del lector"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Acció en mantindre premut un botó"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Ẩn đi"
|
||||
STR_SHORT_PWR_BTN: "Nhấn nhanh nút nguồn"
|
||||
STR_ORIENTATION: "Hướng đọc"
|
||||
STR_SIDE_BTN_LAYOUT: "Bố trí nút bên (trình đọc)"
|
||||
STR_TOUCH_READER_CONTROLS: "Điều khiển đọc bằng cảm ứng"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Xoay nút trước theo hướng"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Hành vi nhấn giữ nút"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "TẮT"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <JPEGDEC.h>
|
||||
#include <Logging.h>
|
||||
#include <Memory.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -169,11 +171,22 @@ constexpr uint32_t FP_ONE = 1UL << 16;
|
||||
// Static file pointer for JPEGDEC open callback.
|
||||
// Safe in single-threaded embedded context; never accessed concurrently.
|
||||
static HalFile* s_jpegFile = nullptr;
|
||||
static uint8_t s_jpegIoSinceYield = 0;
|
||||
|
||||
static void yieldToIdle() { vTaskDelay(1); }
|
||||
|
||||
static void yieldDuringJpegIo() {
|
||||
if (++s_jpegIoSinceYield < 4) return;
|
||||
s_jpegIoSinceYield = 0;
|
||||
yieldToIdle();
|
||||
}
|
||||
|
||||
void* bmpJpegOpen(const char* /*filename*/, int32_t* size) {
|
||||
if (!s_jpegFile || !*s_jpegFile) return nullptr;
|
||||
s_jpegIoSinceYield = 0;
|
||||
s_jpegFile->seek(0);
|
||||
*size = static_cast<int32_t>(s_jpegFile->size());
|
||||
yieldDuringJpegIo();
|
||||
return s_jpegFile;
|
||||
}
|
||||
|
||||
@@ -187,6 +200,7 @@ int32_t bmpJpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
||||
int32_t n = f->read(pBuf, len);
|
||||
if (n < 0) n = 0;
|
||||
pFile->iPos += n;
|
||||
yieldDuringJpegIo();
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -194,6 +208,7 @@ int32_t bmpJpegSeek(JPEGFILE* pFile, int32_t pos) {
|
||||
auto* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
||||
if (!f || !f->seek(pos)) return -1;
|
||||
pFile->iPos = pos;
|
||||
yieldDuringJpegIo();
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -236,9 +251,23 @@ struct BmpConvertCtx {
|
||||
std::unique_ptr<FloydSteinbergDitherer> fsDitherer;
|
||||
std::unique_ptr<Atkinson1BitDitherer> atkinson1BitDitherer;
|
||||
|
||||
uint8_t rowsSinceYield;
|
||||
uint8_t blocksSinceYield;
|
||||
bool error;
|
||||
};
|
||||
|
||||
static void yieldDuringDecode(BmpConvertCtx* ctx) {
|
||||
if (++ctx->rowsSinceYield < 8) return;
|
||||
ctx->rowsSinceYield = 0;
|
||||
yieldToIdle();
|
||||
}
|
||||
|
||||
static void yieldDuringDecodeBlock(BmpConvertCtx* ctx) {
|
||||
if (++ctx->blocksSinceYield < 16) return;
|
||||
ctx->blocksSinceYield = 0;
|
||||
yieldToIdle();
|
||||
}
|
||||
|
||||
// Write a fully-assembled output row (grayscale bytes, length outWidth) to BMP
|
||||
static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) {
|
||||
memset(ctx->bmpRow.get(), 0, ctx->bytesPerRow);
|
||||
@@ -274,6 +303,7 @@ static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY)
|
||||
}
|
||||
|
||||
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
||||
yieldDuringDecode(ctx);
|
||||
}
|
||||
|
||||
// Matches the progressive-JPEG smoothing used by JpegToFramebufferConverter, but stays
|
||||
@@ -396,6 +426,7 @@ static void flushScaledRow(BmpConvertCtx* ctx) {
|
||||
|
||||
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
||||
ctx->currentOutY++;
|
||||
yieldDuringDecode(ctx);
|
||||
}
|
||||
|
||||
// JPEGDEC draw callback — receives one MCU-width × MCU-height block at a time,
|
||||
@@ -405,6 +436,7 @@ static void flushScaledRow(BmpConvertCtx* ctx) {
|
||||
int bmpDrawCallback(JPEGDRAW* pDraw) {
|
||||
auto* ctx = reinterpret_cast<BmpConvertCtx*>(pDraw->pUser);
|
||||
if (!ctx || ctx->error) return 0;
|
||||
yieldDuringDecodeBlock(ctx);
|
||||
|
||||
const uint8_t* pixels = reinterpret_cast<uint8_t*>(pDraw->pPixels);
|
||||
const int stride = pDraw->iWidth;
|
||||
@@ -599,6 +631,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(HalFile& jpegFile, Print& b
|
||||
ctx.smoothScaleY_fp = interpolationStep(ctx.srcHeight, outHeight);
|
||||
ctx.smoothNextOutY = 0;
|
||||
ctx.smoothPrevY = -1;
|
||||
ctx.rowsSinceYield = 0;
|
||||
ctx.blocksSinceYield = 0;
|
||||
ctx.error = false;
|
||||
|
||||
// MCU row buffer: MAX_MCU_HEIGHT rows × decoded srcWidth columns of grayscale
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "Logging.h"
|
||||
|
||||
#include <BoardConfig.h>
|
||||
#include <esp_rom_sys.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define MAX_ENTRY_LEN 256
|
||||
@@ -59,9 +62,16 @@ void logPrintf(const char* level, const char* origin, const char* format, ...) {
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
#if FREEINK_LOG_TRANSPORT == FREEINK_LOG_TRANSPORT_ROM_PRINTF
|
||||
// IDF/ROM console path for boards monitored over USB-Serial-JTAG, where the
|
||||
// HWCDC `operator bool` reads false under `pio device monitor` and logs would
|
||||
// otherwise be silently dropped (e.g. Sticky).
|
||||
esp_rom_printf("%s", buf);
|
||||
#else
|
||||
if (logSerial) {
|
||||
logSerial.print(buf);
|
||||
}
|
||||
#endif
|
||||
addToLogRingBuffer(buf);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <HardwareSerial.h>
|
||||
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||
#include <HWCDC.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -27,7 +31,13 @@ won't trigger deprecation warnings.
|
||||
#define LOG_LEVEL 0
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||
static HWCDC& logSerial = Serial;
|
||||
#define LOG_SERIAL_HAS_TX_TIMEOUT 1
|
||||
#else
|
||||
static HardwareSerial& logSerial = Serial;
|
||||
#define LOG_SERIAL_HAS_TX_TIMEOUT 0
|
||||
#endif
|
||||
|
||||
void logPrintf(const char* level, const char* origin, const char* format, ...);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <HalStorage.h>
|
||||
#include <InflateStream.h>
|
||||
#include <Logging.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -72,6 +74,12 @@ enum PngFilter : uint8_t {
|
||||
PNG_FILTER_PAETH = 4,
|
||||
};
|
||||
|
||||
void yieldDuringDecode(uint8_t& rowsSinceYield) {
|
||||
if (++rowsSinceYield < 8) return;
|
||||
rowsSinceYield = 0;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
// Read a big-endian 32-bit value from file
|
||||
bool readBE32(HalFile& file, uint32_t& value) {
|
||||
uint8_t buf[4];
|
||||
@@ -659,6 +667,7 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(HalFile& pngFile, Print& bmpO
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
uint8_t rowsSinceYield = 0;
|
||||
|
||||
// Process each scanline
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
@@ -710,6 +719,7 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(HalFile& pngFile, Print& bmpO
|
||||
fsDitherer->nextRow();
|
||||
}
|
||||
bmpOut.write(rowBuffer, bytesPerRow);
|
||||
yieldDuringDecode(rowsSinceYield);
|
||||
} else {
|
||||
// Area-averaging scaling (same as JpegToBmpConverter)
|
||||
for (int outX = 0; outX < outWidth; outX++) {
|
||||
@@ -778,6 +788,7 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(HalFile& pngFile, Print& bmpO
|
||||
|
||||
bmpOut.write(rowBuffer, bytesPerRow);
|
||||
currentOutY++;
|
||||
yieldDuringDecode(rowsSinceYield);
|
||||
|
||||
nextOutY_srcStart = static_cast<uint32_t>(currentOutY + 1) * scaleY_fp;
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
#include <Bitmap.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
namespace {
|
||||
void yieldDuringThumbnail(uint8_t& rowsSinceYield) {
|
||||
if (++rowsSinceYield < 8) return;
|
||||
rowsSinceYield = 0;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool Xtc::load() {
|
||||
LOG_DBG("XTC", "Loading XTC: %s", filepath.c_str());
|
||||
@@ -380,6 +390,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
||||
const uint8_t* plane2 = (bitDepth == 2) ? pageBuffer + planeSize : nullptr;
|
||||
const size_t colBytes = (bitDepth == 2) ? ((pageInfo.height + 7) / 8) : 0;
|
||||
const size_t srcRowBytes = (bitDepth == 1) ? ((pageInfo.width + 7) / 8) : 0;
|
||||
uint8_t rowsSinceYield = 0;
|
||||
|
||||
for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) {
|
||||
memset(rowBuffer, 0xFF, rowSize); // Start with all white (bit 1)
|
||||
@@ -471,6 +482,7 @@ bool Xtc::generateThumbBmp(int height) const {
|
||||
|
||||
// Write row (already padded to 4-byte boundary by rowSize)
|
||||
thumbBmp.write(rowBuffer, rowSize);
|
||||
yieldDuringThumbnail(rowsSinceYield);
|
||||
}
|
||||
|
||||
free(rowBuffer);
|
||||
|
||||
+21
-91
@@ -5,46 +5,11 @@
|
||||
#include <esp_sntp.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
HalClock halClock; // Singleton instance
|
||||
|
||||
// DS3231 register layout (BCD encoded):
|
||||
// 0x00: Seconds (bits 6-4 = tens, bits 3-0 = ones)
|
||||
// 0x01: Minutes (bits 6-4 = tens, bits 3-0 = ones)
|
||||
// 0x02: Hours (bit 6 = 12/24 mode, bits 5-4 = tens, bits 3-0 = ones)
|
||||
|
||||
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); }
|
||||
|
||||
void HalClock::begin() {
|
||||
if (!gpio.deviceIsX3()) {
|
||||
_available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// I2C is already initialised by HalPowerManager::begin() for X3.
|
||||
// Probe the DS3231 by reading the seconds register.
|
||||
Wire.beginTransmission(I2C_ADDR_DS3231);
|
||||
Wire.write(DS3231_SEC_REG);
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
LOG_INF("CLK", "DS3231 RTC not found");
|
||||
_available = false;
|
||||
return;
|
||||
}
|
||||
Wire.requestFrom(I2C_ADDR_DS3231, (uint8_t)1);
|
||||
if (Wire.available() < 1) {
|
||||
_available = false;
|
||||
return;
|
||||
}
|
||||
Wire.read(); // discard — just testing connectivity
|
||||
|
||||
_available = true;
|
||||
LOG_INF("CLK", "DS3231 RTC found");
|
||||
|
||||
// Prime the cache with an initial read
|
||||
uint8_t h, m;
|
||||
getTime(h, m);
|
||||
_available = _sdkRtc.begin();
|
||||
LOG_INF("CLK", _available ? "SDK RTC found" : "RTC not found");
|
||||
}
|
||||
|
||||
bool HalClock::getTime(uint8_t& hour, uint8_t& minute) const {
|
||||
@@ -57,44 +22,18 @@ bool HalClock::getTime(uint8_t& hour, uint8_t& minute) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read 3 bytes starting at register 0x00: seconds, minutes, hours
|
||||
Wire.beginTransmission(I2C_ADDR_DS3231);
|
||||
Wire.write(DS3231_SEC_REG);
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
Rtc::DateTime dt;
|
||||
if (!_sdkRtc.now(dt)) {
|
||||
if (!_hasCachedTime) return false;
|
||||
_lastPollMs = now;
|
||||
hour = _cachedHour;
|
||||
minute = _cachedMinute;
|
||||
return true;
|
||||
}
|
||||
Wire.requestFrom(I2C_ADDR_DS3231, (uint8_t)3);
|
||||
if (Wire.available() < 3) {
|
||||
if (!_hasCachedTime) return false;
|
||||
_lastPollMs = now;
|
||||
hour = _cachedHour;
|
||||
minute = _cachedMinute;
|
||||
return true;
|
||||
}
|
||||
|
||||
Wire.read(); // seconds — not needed
|
||||
const uint8_t rawMin = Wire.read();
|
||||
const uint8_t rawHour = Wire.read();
|
||||
|
||||
_cachedMinute = bcdToDec(rawMin & 0x7F);
|
||||
// Handle 12/24h mode: bit 6 high = 12h mode
|
||||
if (rawHour & 0x40) {
|
||||
// 12h mode: bit 5 = PM, bits 4-0 = hours (1-12)
|
||||
uint8_t h12 = bcdToDec(rawHour & 0x1F);
|
||||
bool pm = rawHour & 0x20;
|
||||
if (h12 == 12) h12 = 0;
|
||||
_cachedHour = pm ? (h12 + 12) : h12;
|
||||
} else {
|
||||
// 24h mode: bits 5-0 = hours (0-23)
|
||||
_cachedHour = bcdToDec(rawHour & 0x3F);
|
||||
}
|
||||
_cachedHour = dt.hour;
|
||||
_cachedMinute = dt.minute;
|
||||
_lastPollMs = now;
|
||||
_hasCachedTime = true;
|
||||
|
||||
hour = _cachedHour;
|
||||
minute = _cachedMinute;
|
||||
return true;
|
||||
@@ -127,28 +66,6 @@ bool HalClock::formatTime(char* buf, size_t bufSize, uint8_t utcOffsetQuarterHou
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HalClock::writeTimeToRTC(uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
assert(hour < 24);
|
||||
assert(minute < 60);
|
||||
assert(second < 60);
|
||||
Wire.beginTransmission(I2C_ADDR_DS3231);
|
||||
Wire.write(DS3231_SEC_REG); // Start at register 0x00
|
||||
Wire.write(decToBcd(second)); // 0x00: Seconds
|
||||
Wire.write(decToBcd(minute)); // 0x01: Minutes
|
||||
Wire.write(decToBcd(hour)); // 0x02: Hours (24h mode, bit 6 = 0)
|
||||
if (Wire.endTransmission() != 0) {
|
||||
LOG_ERR("CLK", "Failed to write time to DS3231");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invalidate cache so next read fetches fresh data
|
||||
_lastPollMs = 0;
|
||||
_cachedHour = hour;
|
||||
_cachedMinute = minute;
|
||||
_hasCachedTime = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HalClock::syncFromNTP() {
|
||||
if (!_available) return false;
|
||||
|
||||
@@ -168,8 +85,21 @@ bool HalClock::syncFromNTP() {
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
+5
-9
@@ -1,15 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "HalGPIO.h"
|
||||
#include <Rtc.h>
|
||||
|
||||
class HalClock;
|
||||
extern HalClock halClock; // Singleton
|
||||
|
||||
class HalClock {
|
||||
bool _available = false;
|
||||
mutable Rtc _sdkRtc;
|
||||
mutable uint8_t _cachedHour = 0;
|
||||
mutable uint8_t _cachedMinute = 0;
|
||||
mutable bool _hasCachedTime = false;
|
||||
@@ -18,10 +17,10 @@ class HalClock {
|
||||
static constexpr unsigned long CLOCK_POLL_MS = 10000; // 10 seconds
|
||||
|
||||
public:
|
||||
// Call after gpio.begin() and powerManager.begin() (I2C already initialised for X3)
|
||||
// Call after BoardConfig has selected the active device.
|
||||
void begin();
|
||||
|
||||
// True if the DS3231 RTC is present on this device
|
||||
// True if an RTC is present on this device
|
||||
bool isAvailable() const { return _available; }
|
||||
|
||||
// Get current hour (0-23) and minute (0-59).
|
||||
@@ -35,14 +34,11 @@ class HalClock {
|
||||
// Returns false if RTC is not available.
|
||||
bool formatTime(char* buf, size_t bufSize, uint8_t utcOffsetQuarterHoursBiased = 48, bool use12Hour = false) const;
|
||||
|
||||
// Sync the DS3231 RTC from an NTP server. Requires WiFi to be connected.
|
||||
// Sync the RTC from an NTP server. Requires WiFi to be connected.
|
||||
// Blocks for up to ~5s while waiting for SNTP response.
|
||||
// Returns true if the RTC was successfully updated.
|
||||
//
|
||||
// Debouncing (skip if already synced once) is enforced by the caller, not here,
|
||||
// so the HAL stays free of any app-layer settings dependency.
|
||||
bool syncFromNTP();
|
||||
|
||||
private:
|
||||
bool writeTimeToRTC(uint8_t hour, uint8_t minute, uint8_t second);
|
||||
};
|
||||
|
||||
@@ -65,6 +65,18 @@ void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen)
|
||||
einkDisplay.displayBuffer(convertRefreshMode(mode), turnOffScreen);
|
||||
}
|
||||
|
||||
void HalDisplay::displayBufferAsync(HalDisplay::RefreshMode mode) {
|
||||
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
|
||||
einkDisplay.requestResync(1);
|
||||
}
|
||||
|
||||
einkDisplay.displayBufferAsyncNoShadow(convertRefreshMode(mode));
|
||||
}
|
||||
|
||||
void HalDisplay::waitRefreshComplete() { einkDisplay.waitRefreshComplete(); }
|
||||
|
||||
bool HalDisplay::supportsAsyncRefresh() const { return einkDisplay.supportsAsyncRefresh(); }
|
||||
|
||||
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
||||
if (gpio.deviceIsX3() && mode == RefreshMode::HALF_REFRESH) {
|
||||
einkDisplay.requestResync(1);
|
||||
|
||||
@@ -39,6 +39,17 @@ class HalDisplay {
|
||||
bool fromProgmem = false) const;
|
||||
|
||||
void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
||||
// Non-blocking refresh (shadow-free): starts the panel waveform and returns
|
||||
// while the panel refreshes on its own. The framebuffer must stay untouched
|
||||
// until waitRefreshComplete(), and the caller must rebuild the differential
|
||||
// baseline before the next differential update (the tiled grayscale cleanup
|
||||
// does). Panels without deferral fall back to a blocking refresh.
|
||||
void displayBufferAsync(RefreshMode mode = RefreshMode::FAST_REFRESH);
|
||||
// Block until a pending deferred refresh completes (no-op when none is).
|
||||
void waitRefreshComplete();
|
||||
// True when displayBufferAsync() genuinely overlaps (panel driver defers);
|
||||
// false where it falls back to a blocking refresh.
|
||||
bool supportsAsyncRefresh() const;
|
||||
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
||||
|
||||
// Power management
|
||||
|
||||
+53
-5
@@ -1,5 +1,6 @@
|
||||
#include <HalGPIO.h>
|
||||
#include <Logging.h>
|
||||
#include <PowerManager.h>
|
||||
#include <Preferences.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
@@ -191,15 +192,20 @@ HalGPIO::DeviceType detectDeviceTypeWithFingerprint() {
|
||||
} // namespace
|
||||
|
||||
void HalGPIO::begin() {
|
||||
inputMgr.begin();
|
||||
#if FREEINK_MCU_C3
|
||||
SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS);
|
||||
|
||||
_deviceType = detectDeviceTypeWithFingerprint();
|
||||
BoardConfig::selectDevice(deviceIsX3() ? BoardConfig::Board::XteinkX3 : BoardConfig::Board::XteinkX4);
|
||||
|
||||
if (deviceIsX4()) {
|
||||
pinMode(BAT_GPIO0, INPUT);
|
||||
pinMode(UART0_RXD, INPUT);
|
||||
}
|
||||
#else
|
||||
_deviceType = DeviceType::X4;
|
||||
#endif
|
||||
inputMgr.begin();
|
||||
}
|
||||
|
||||
void HalGPIO::update() {
|
||||
@@ -225,7 +231,44 @@ unsigned long HalGPIO::getHeldTime() const { return inputMgr.getHeldTime(); }
|
||||
|
||||
unsigned long HalGPIO::getPowerButtonHeldTime() const { return inputMgr.getPowerButtonHeldTime(); }
|
||||
|
||||
bool HalGPIO::hasTouch() const { return inputMgr.hasTouch(); }
|
||||
|
||||
bool HalGPIO::wasTouchTap(float& nx, float& ny) const { return inputMgr.wasTouchTap(nx, ny); }
|
||||
|
||||
bool HalGPIO::wasTouchDown(float& nx, float& ny) const { return inputMgr.wasTouchPressedAt(nx, ny); }
|
||||
|
||||
bool HalGPIO::isTouchTapCandidate(float& nx, float& ny, unsigned long& heldMs) const {
|
||||
return inputMgr.isTouchTapCandidate(nx, ny, heldMs);
|
||||
}
|
||||
|
||||
bool HalGPIO::isTouchHeldAt(float& nx, float& ny) const { return inputMgr.isTouchHeldAt(nx, ny); }
|
||||
|
||||
unsigned long HalGPIO::lastTouchHeldMs() const { return inputMgr.lastTouchHeldMs(); }
|
||||
|
||||
bool HalGPIO::wasSwipe(float& nxStart, float& nyStart, float& nxEnd, float& nyEnd) const {
|
||||
return inputMgr.wasSwipe(nxStart, nyStart, nxEnd, nyEnd);
|
||||
}
|
||||
|
||||
bool HalGPIO::wasTouchActivity() const { return inputMgr.wasTouchActivity(); }
|
||||
|
||||
void HalGPIO::setSharedConfirmPowerShortPressEmitsPower(const bool enabled) {
|
||||
InputManager::setSharedConfirmPowerShortPressEmitsPower(enabled);
|
||||
}
|
||||
|
||||
bool HalGPIO::isXteinkDevice() const {
|
||||
return BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX3 ||
|
||||
BoardConfig::ACTIVE.board == BoardConfig::Board::XteinkX4;
|
||||
}
|
||||
|
||||
bool HalGPIO::verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed) {
|
||||
// Boards without a power button (or M5Paper's latch circuit) cannot verify a
|
||||
// hold; treat the wake as valid.
|
||||
if (BoardConfig::ACTIVE.input.power < 0) {
|
||||
return true;
|
||||
}
|
||||
#if defined(FREEINK_DEVICE_M5PAPER) && FREEINK_DEVICE_M5PAPER
|
||||
return true;
|
||||
#endif
|
||||
if (shortPressAllowed) {
|
||||
// Fast path - no duration check needed
|
||||
return true;
|
||||
@@ -271,8 +314,10 @@ bool HalGPIO::isUsbConnected() const {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// U0RXD/GPIO20 reads HIGH when USB is connected
|
||||
return digitalRead(UART0_RXD) == HIGH;
|
||||
if (BoardConfig::ACTIVE.usbDetect < 0) {
|
||||
return false;
|
||||
}
|
||||
return digitalRead(BoardConfig::ACTIVE.usbDetect) == HIGH;
|
||||
}
|
||||
|
||||
HalGPIO::WakeupReason HalGPIO::getWakeupReason() const {
|
||||
@@ -281,8 +326,11 @@ HalGPIO::WakeupReason HalGPIO::getWakeupReason() const {
|
||||
|
||||
const bool usbConnected = isUsbConnected();
|
||||
|
||||
if ((wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED && resetReason == ESP_RST_POWERON && !usbConnected) ||
|
||||
(wakeupCause == ESP_SLEEP_WAKEUP_GPIO && resetReason == ESP_RST_DEEPSLEEP && usbConnected)) {
|
||||
if (resetReason == ESP_RST_DEEPSLEEP &&
|
||||
(wakeupCause == ESP_SLEEP_WAKEUP_GPIO || wakeupCause == ESP_SLEEP_WAKEUP_EXT1)) {
|
||||
return WakeupReason::PowerButton;
|
||||
}
|
||||
if (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED && resetReason == ESP_RST_POWERON && !usbConnected) {
|
||||
return WakeupReason::PowerButton;
|
||||
}
|
||||
if (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED && resetReason == ESP_RST_UNKNOWN && usbConnected) {
|
||||
|
||||
@@ -58,6 +58,7 @@ class HalGPIO {
|
||||
// Inline device type helpers for cleaner downstream checks
|
||||
inline bool deviceIsX3() const { return _deviceType == DeviceType::X3; }
|
||||
inline bool deviceIsX4() const { return _deviceType == DeviceType::X4; }
|
||||
bool isXteinkDevice() const;
|
||||
|
||||
// Start button GPIO and setup SPI for screen and SD card
|
||||
void begin();
|
||||
@@ -71,6 +72,15 @@ class HalGPIO {
|
||||
bool wasAnyReleased() const;
|
||||
unsigned long getHeldTime() const;
|
||||
unsigned long getPowerButtonHeldTime() const;
|
||||
bool hasTouch() const;
|
||||
bool wasTouchTap(float& nx, float& ny) const;
|
||||
bool wasTouchDown(float& nx, float& ny) const;
|
||||
bool isTouchTapCandidate(float& nx, float& ny, unsigned long& heldMs) const;
|
||||
bool isTouchHeldAt(float& nx, float& ny) const;
|
||||
unsigned long lastTouchHeldMs() const;
|
||||
bool wasSwipe(float& nxStart, float& nyStart, float& nxEnd, float& nyEnd) const;
|
||||
bool wasTouchActivity() const;
|
||||
void setSharedConfirmPowerShortPressEmitsPower(bool enabled);
|
||||
|
||||
// Verify power button was held long enough after wakeup.
|
||||
// Returns true if verification succeeded, false if device should return to sleep.
|
||||
|
||||
+35
-50
@@ -1,8 +1,11 @@
|
||||
#include "HalPowerManager.h"
|
||||
|
||||
#include <BoardConfig.h>
|
||||
#include <Logging.h>
|
||||
#include <PowerManager.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <soc/soc_caps.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -11,14 +14,8 @@
|
||||
HalPowerManager powerManager; // Singleton instance
|
||||
|
||||
void HalPowerManager::begin() {
|
||||
if (gpio.deviceIsX3()) {
|
||||
// X3 uses an I2C fuel gauge for battery monitoring.
|
||||
// I2C init must come AFTER gpio.begin() so early hardware detection/probes are finished.
|
||||
Wire.begin(X3_I2C_SDA, X3_I2C_SCL, X3_I2C_FREQ);
|
||||
Wire.setTimeOut(4);
|
||||
_batteryUseI2C = true;
|
||||
} else {
|
||||
pinMode(BAT_GPIO0, INPUT);
|
||||
if (BoardConfig::ACTIVE.batteryAdc >= 0) {
|
||||
pinMode(BoardConfig::ACTIVE.batteryAdc, INPUT);
|
||||
}
|
||||
normalFreq = getCpuFrequencyMhz();
|
||||
modeMutex = xSemaphoreCreateMutex();
|
||||
@@ -61,12 +58,6 @@ void HalPowerManager::setPowerSaving(bool enabled) {
|
||||
}
|
||||
|
||||
void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
|
||||
// Ensure that the power button has been released to avoid immediately turning back on if you're holding it
|
||||
while (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||
delay(50);
|
||||
gpio.update();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SERIAL_LOG
|
||||
// Tear down HWCDC so the host sees a clean disconnect and the peripheral
|
||||
// doesn't hold power domains that interfere with USB-powered GPIO wake.
|
||||
@@ -75,53 +66,47 @@ void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
|
||||
logSerial.end();
|
||||
#endif
|
||||
|
||||
// Pre-sleep routines from the original firmware
|
||||
// GPIO13 is connected to battery latch MOSFET, we need to make sure it's low during sleep
|
||||
// Note that this means the MCU will be completely powered off during sleep, including RTC
|
||||
constexpr gpio_num_t GPIO_SPIWP = GPIO_NUM_13;
|
||||
gpio_set_direction(GPIO_SPIWP, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(GPIO_SPIWP, 0);
|
||||
esp_sleep_config_gpio_isolate();
|
||||
gpio_deep_sleep_hold_en();
|
||||
gpio_hold_en(GPIO_SPIWP);
|
||||
pinMode(InputManager::POWER_BUTTON_PIN, INPUT_PULLUP);
|
||||
// Arm the wakeup trigger *after* the button is released
|
||||
// Note: this is only useful for waking up on USB power. On battery, the MCU will be completely powered off, so the
|
||||
// power button is hard-wired to briefly provide power to the MCU, waking it up regardless of the wakeup source
|
||||
// configuration
|
||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||
// Enter Deep Sleep
|
||||
esp_deep_sleep_start();
|
||||
#if !SOC_PM_SUPPORT_EXT1_WAKEUP
|
||||
if (gpio.isXteinkDevice() && !gpio.deviceIsX3()) {
|
||||
// X4 GPIO13 is connected to the battery latch MOSFET. Keeping it low powers
|
||||
// the MCU off on battery, while the SDK wake source still handles USB power.
|
||||
constexpr gpio_num_t GPIO_SPIWP = GPIO_NUM_13;
|
||||
gpio_set_direction(GPIO_SPIWP, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(GPIO_SPIWP, 0);
|
||||
gpio_hold_en(GPIO_SPIWP);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cut the gated peripheral rails (touch/SD/EPD on boards like the Sticky) and
|
||||
// hold the enables off through deep sleep — otherwise the GT911 and SD card
|
||||
// stay powered all through "off" and drain the battery. No-op on boards with
|
||||
// no switched rails (X4/X3). Trade-off: no touch-to-wake; wake is the power
|
||||
// button. Must run after display.deepSleep() so the panel controller gets its
|
||||
// deep-sleep command while its rail is still up (enterDeepSleep() in main.cpp
|
||||
// guarantees that ordering).
|
||||
freeink::PowerManager::powerDownRailsForSleep();
|
||||
|
||||
// Waits for the power button to be physically released (so holding it doesn't
|
||||
// immediately wake the device again), then arms the wake source and sleeps.
|
||||
freeink::PowerManager::deepSleepUntilPowerButton();
|
||||
}
|
||||
|
||||
uint16_t HalPowerManager::getBatteryPercentage() const {
|
||||
if (_batteryUseI2C) {
|
||||
static const BatteryMonitor battery;
|
||||
if (BoardConfig::ACTIVE.batteryGauge.gaugeAddr != 0) {
|
||||
const unsigned long now = millis();
|
||||
if (_batteryLastPollMs != 0 && (now - _batteryLastPollMs) < BATTERY_POLL_MS) {
|
||||
return _batteryCachedPercent;
|
||||
}
|
||||
|
||||
// Read SOC directly from I2C fuel gauge (16-bit LE register).
|
||||
// On I2C error, keep last known value to avoid UI jitter/slowdowns.
|
||||
Wire.beginTransmission(I2C_ADDR_BQ27220);
|
||||
Wire.write(BQ27220_SOC_REG);
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
_batteryLastPollMs = now;
|
||||
return _batteryCachedPercent;
|
||||
}
|
||||
Wire.requestFrom(I2C_ADDR_BQ27220, (uint8_t)2);
|
||||
if (Wire.available() < 2) {
|
||||
_batteryLastPollMs = now;
|
||||
return _batteryCachedPercent;
|
||||
}
|
||||
const uint8_t lo = Wire.read();
|
||||
const uint8_t hi = Wire.read();
|
||||
const uint16_t soc = (hi << 8) | lo;
|
||||
_batteryCachedPercent = soc > 100 ? 100 : soc;
|
||||
_batteryLastPollMs = now;
|
||||
uint16_t percent = 0;
|
||||
if (!battery.readPercentageChecked(percent)) {
|
||||
return _batteryCachedPercent;
|
||||
}
|
||||
_batteryCachedPercent = percent;
|
||||
return _batteryCachedPercent;
|
||||
}
|
||||
static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0);
|
||||
|
||||
// smooth the battery %.
|
||||
if (_batteryCachedPercent == 0) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <BatteryMonitor.h>
|
||||
#include <InputManager.h>
|
||||
#include <Logging.h>
|
||||
#include <Wire.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <cassert>
|
||||
@@ -18,8 +17,6 @@ class HalPowerManager {
|
||||
int normalFreq = 0; // MHz
|
||||
bool isLowPower = false;
|
||||
|
||||
// I2C fuel gauge configuration for X3 battery monitoring
|
||||
bool _batteryUseI2C = false; // True if using I2C fuel gauge (X3), false for ADC (X4)
|
||||
mutable int _batteryCachedPercent = 0; // Last read battery percentage (0-100)
|
||||
mutable unsigned long _batteryLastPollMs = 0; // Timestamp of last battery read in milliseconds
|
||||
|
||||
@@ -28,7 +25,11 @@ class HalPowerManager {
|
||||
SemaphoreHandle_t modeMutex = nullptr; // Protect access to currentLockMode
|
||||
|
||||
public:
|
||||
static constexpr int LOW_POWER_FREQ = 10; // MHz
|
||||
#if BOARD_HAS_PSRAM
|
||||
static constexpr int LOW_POWER_FREQ = 80; // MHz
|
||||
#else
|
||||
static constexpr int LOW_POWER_FREQ = 10; // MHz
|
||||
#endif
|
||||
static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // ms
|
||||
static constexpr unsigned long BATTERY_POLL_MS = 1500; // ms
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) {
|
||||
__real_panic_print_backtrace(frame, core);
|
||||
return;
|
||||
}
|
||||
|
||||
#if !__riscv
|
||||
__real_panic_print_backtrace(frame, core);
|
||||
return;
|
||||
#else
|
||||
for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) {
|
||||
panicStack[i].sp = 0;
|
||||
}
|
||||
@@ -65,6 +70,7 @@ void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) {
|
||||
}
|
||||
|
||||
__real_panic_print_backtrace(frame, core);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-93
@@ -4,84 +4,29 @@
|
||||
|
||||
HalTiltSensor halTiltSensor; // Singleton instance
|
||||
|
||||
bool HalTiltSensor::writeReg(uint8_t reg, uint8_t val) const {
|
||||
Wire.beginTransmission(_i2cAddr);
|
||||
Wire.write(reg);
|
||||
Wire.write(val);
|
||||
return Wire.endTransmission() == 0;
|
||||
}
|
||||
|
||||
bool HalTiltSensor::readReg(uint8_t reg, uint8_t* val) const {
|
||||
Wire.beginTransmission(_i2cAddr);
|
||||
Wire.write(reg);
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
return false;
|
||||
}
|
||||
Wire.requestFrom(_i2cAddr, (uint8_t)1);
|
||||
if (Wire.available() < 1) {
|
||||
return false;
|
||||
}
|
||||
*val = Wire.read();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HalTiltSensor::readGyro(float& gx, float& gy, float& gz) const {
|
||||
Wire.beginTransmission(_i2cAddr);
|
||||
Wire.write(REG_GX_L); // Start reading at Gyro X Low
|
||||
if (Wire.endTransmission(false) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Wire.requestFrom(_i2cAddr, (uint8_t)6);
|
||||
if (Wire.available() < 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto readInt16 = [&]() -> int16_t {
|
||||
const uint8_t lo = Wire.read();
|
||||
const uint8_t hi = Wire.read();
|
||||
return static_cast<int16_t>((hi << 8) | lo);
|
||||
};
|
||||
|
||||
// If Full Scale is ±512 dps, the scale factor is 32768 / 512 = 64 LSB/dps
|
||||
constexpr float SCALE = 1.0f / 64.0f;
|
||||
gx = readInt16() * SCALE;
|
||||
gy = readInt16() * SCALE;
|
||||
gz = readInt16() * SCALE;
|
||||
Imu::Sample sample;
|
||||
if (!_sdkImu.read(sample)) return false;
|
||||
gx = sample.gx;
|
||||
gy = sample.gy;
|
||||
gz = sample.gz;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HalTiltSensor::begin() {
|
||||
if (!gpio.deviceIsX3()) {
|
||||
_available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try primary address, then alternate
|
||||
uint8_t whoami = 0;
|
||||
_i2cAddr = I2C_ADDR_QMI8658;
|
||||
if (!readReg(QMI8658_WHO_AM_I_REG, &whoami) || whoami != QMI8658_WHO_AM_I_VALUE) {
|
||||
_i2cAddr = I2C_ADDR_QMI8658_ALT;
|
||||
if (!readReg(QMI8658_WHO_AM_I_REG, &whoami) || whoami != QMI8658_WHO_AM_I_VALUE) {
|
||||
LOG_ERR("GYR", "QMI8658 IMU not found");
|
||||
_available = false;
|
||||
return;
|
||||
_available = _sdkImu.begin();
|
||||
if (_available) {
|
||||
_initMs = millis();
|
||||
_lastPollMs = millis();
|
||||
// begin() leaves the sensors sampling; stand them by until tilt page turn
|
||||
// actually wakes them, so a disabled IMU doesn't drain the battery.
|
||||
if (!_sdkImu.sleep()) {
|
||||
LOG_ERR("GYR", "IMU standby failed");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INF("GYR", "QMI8658 IMU found at 0x%02X", _i2cAddr);
|
||||
|
||||
if (!writeReg(REG_CTRL7, CTRL7_DISABLE_ALL) || !writeReg(REG_CTRL3, CTRL3_FS_512DPS | CTRL3_ODR_28HZ) ||
|
||||
!writeReg(REG_CTRL1, CTRL1_BASE | CTRL1_SENSOR_DISABLE)) {
|
||||
LOG_ERR("GYR", "QMI8658 register configuration failed");
|
||||
_available = false;
|
||||
LOG_INF("GYR", "SDK IMU initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
_available = true;
|
||||
_initMs = millis();
|
||||
_lastPollMs = millis();
|
||||
LOG_INF("GYR", "QMI8658 gyro initialized and put to sleep");
|
||||
LOG_ERR("GYR", "SDK IMU not found");
|
||||
}
|
||||
|
||||
bool HalTiltSensor::wake() {
|
||||
@@ -89,21 +34,16 @@ bool HalTiltSensor::wake() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for init to complete before waking
|
||||
if ((millis() - _initMs) < SLEEP_STABILIZE_MS) {
|
||||
if (!_sdkImu.wake()) {
|
||||
LOG_ERR("GYR", "IMU wake failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeReg(REG_CTRL1, CTRL1_BASE) && writeReg(REG_CTRL7, CTRL7_GYRO_ENABLE)) {
|
||||
_lastPollMs = millis();
|
||||
_lastTiltMs = millis();
|
||||
_wakeMs = millis();
|
||||
LOG_INF("GYR", "QMI8658 woke up");
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERR("GYR", "Failed to wake QMI8658");
|
||||
return false;
|
||||
}
|
||||
_lastPollMs = millis();
|
||||
_lastTiltMs = millis();
|
||||
_wakeMs = millis();
|
||||
_isAwake = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HalTiltSensor::deepSleep() {
|
||||
@@ -111,20 +51,15 @@ bool HalTiltSensor::deepSleep() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((millis() - _wakeMs) < SLEEP_STABILIZE_MS) {
|
||||
if (!_sdkImu.sleep()) {
|
||||
LOG_ERR("GYR", "IMU sleep failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeReg(REG_CTRL7, CTRL7_DISABLE_ALL) && writeReg(REG_CTRL1, CTRL1_BASE | CTRL1_SENSOR_DISABLE)) {
|
||||
// Clear any residual state so it doesn't immediately trigger upon waking
|
||||
clearPendingEvents();
|
||||
_inTilt = false;
|
||||
LOG_INF("GYR", "QMI8658 entered sleep mode");
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERR("GYR", "Failed to put QMI8658 to sleep");
|
||||
return false;
|
||||
}
|
||||
clearPendingEvents();
|
||||
_inTilt = false;
|
||||
_isAwake = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HalTiltSensor::update(const uint8_t mode, const uint8_t orientation, const bool inReader) {
|
||||
|
||||
+6
-33
@@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "HalGPIO.h"
|
||||
#include <Imu.h>
|
||||
|
||||
// TODO: Move enums into new header and share with CrossPointSettings.h
|
||||
namespace CrossPointOrientation {
|
||||
@@ -19,7 +17,7 @@ extern HalTiltSensor halTiltSensor; // Singleton
|
||||
|
||||
class HalTiltSensor {
|
||||
bool _available = false;
|
||||
uint8_t _i2cAddr = 0;
|
||||
mutable Imu _sdkImu;
|
||||
|
||||
// Tilt gesture state machine
|
||||
bool _tiltForwardEvent = false; // Consumed by wasTiltedForward()
|
||||
@@ -37,47 +35,22 @@ class HalTiltSensor {
|
||||
static constexpr unsigned long COOLDOWN_MS = 600; // Minimum ms between triggers
|
||||
static constexpr unsigned long POLL_INTERVAL_MS = 50; // 20 Hz polling
|
||||
static constexpr unsigned long WAKE_STABILIZE_MS = 300; // Ignore readings after wake
|
||||
static constexpr unsigned long SLEEP_STABILIZE_MS = 15; // Sleep turn on/off delay
|
||||
|
||||
mutable unsigned long _lastPollMs = 0;
|
||||
|
||||
// --- QMI8658 registers ---
|
||||
static constexpr uint8_t REG_CTRL1 = 0x02;
|
||||
static constexpr uint8_t REG_CTRL3 = 0x04;
|
||||
static constexpr uint8_t REG_CTRL7 = 0x08;
|
||||
static constexpr uint8_t REG_GX_L = 0x3B;
|
||||
|
||||
// --- Register Bit Flags ---
|
||||
|
||||
// REG_CTRL1 (0x02)
|
||||
static constexpr uint8_t CTRL1_BIG_ENDIAN = (1 << 5); // 0x20: Default state (1 = Big Endian)
|
||||
static constexpr uint8_t CTRL1_AUTO_INC = (1 << 6); // 0x40: Enable address auto-increment
|
||||
static constexpr uint8_t CTRL1_SENSOR_DISABLE = (1 << 0); // 0x01: Power down sensor engine
|
||||
static constexpr uint8_t CTRL1_BASE = CTRL1_AUTO_INC | CTRL1_BIG_ENDIAN; // 0x60
|
||||
|
||||
// REG_CTRL3 (0x04) - Gyro Config
|
||||
static constexpr uint8_t CTRL3_FS_512DPS = (0b101 << 4); // Bits 6:4 = 101
|
||||
static constexpr uint8_t CTRL3_ODR_28HZ = 0b1000; // Bits 3:0 = 1000 (28.025 Hz)
|
||||
|
||||
// REG_CTRL7 (0x08) - Enable
|
||||
static constexpr uint8_t CTRL7_DISABLE_ALL = 0x00;
|
||||
static constexpr uint8_t CTRL7_GYRO_ENABLE = (1 << 1); // Bit 1 = 1
|
||||
|
||||
bool writeReg(uint8_t reg, uint8_t val) const;
|
||||
bool readReg(uint8_t reg, uint8_t* val) const;
|
||||
bool readGyro(float& gx, float& gy, float& gz) const;
|
||||
|
||||
public:
|
||||
// Call after gpio.begin() and powerManager.begin() (I2C already initialised for X3)
|
||||
// Call after BoardConfig has selected the active device.
|
||||
void begin();
|
||||
|
||||
// Enables the QMI8658 internal sensor engine
|
||||
// Enables tilt polling state
|
||||
bool wake();
|
||||
|
||||
// Puts the QMI8658 into a low-power standby state
|
||||
// Puts tilt polling state to sleep
|
||||
bool deepSleep();
|
||||
|
||||
// True if the QMI8658 IMU is present on this device
|
||||
// True if an IMU is present on this device
|
||||
bool isAvailable() const { return _available; }
|
||||
|
||||
// Poll the accelerometer and update tilt gesture state.
|
||||
|
||||
Reference in New Issue
Block a user