Battery, EPD, and SD support for m5paper

Also hide button hints
This commit is contained in:
Justin Mitchell
2026-06-08 12:47:43 -04:00
parent 7f01eab62e
commit 38ad4f9036
12 changed files with 112 additions and 21 deletions
+11 -4
View File
@@ -18,6 +18,13 @@
#include "settings/SettingsActivity.h"
#include "util/FullScreenMessageActivity.h"
// taskENTER_CRITICAL needs a real spinlock on dual-core targets (classic ESP32,
// e.g. M5Paper). On the single-core ESP32-C3 a nullptr mux was tolerated, but
// the dual-core port acquires an inter-core spinlock and asserts on a null
// pointer (spinlock_acquire, spinlock.h:84). One shared spinlock guards the
// short waitingTaskHandle critical sections below.
static portMUX_TYPE activityMux = portMUX_INITIALIZER_UNLOCKED;
void ActivityManager::begin() {
xTaskCreate(&renderTaskTrampoline, "ActivityManagerRender",
8192, // Stack size
@@ -45,10 +52,10 @@ void ActivityManager::renderTaskLoop() {
}
// Notify any task blocked in requestUpdateAndWait() that the render is done.
TaskHandle_t waiter = nullptr;
taskENTER_CRITICAL(nullptr);
taskENTER_CRITICAL(&activityMux);
waiter = waitingTaskHandle;
waitingTaskHandle = nullptr;
taskEXIT_CRITICAL(nullptr);
taskEXIT_CRITICAL(&activityMux);
if (waiter) {
xTaskNotify(waiter, 1, eIncrement);
}
@@ -279,7 +286,7 @@ void ActivityManager::requestUpdateAndWait() {
}
// Atomic section to perform checks
taskENTER_CRITICAL(nullptr);
taskENTER_CRITICAL(&activityMux);
auto currTaskHandler = xTaskGetCurrentTaskHandle();
auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex);
bool isRenderTask = (currTaskHandler == renderTaskHandle);
@@ -288,7 +295,7 @@ void ActivityManager::requestUpdateAndWait() {
if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) {
waitingTaskHandle = currTaskHandler;
}
taskEXIT_CRITICAL(nullptr);
taskEXIT_CRITICAL(&activityMux);
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock
assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()");
+20 -1
View File
@@ -132,8 +132,27 @@ void BaseTheme::drawProgressBar(const GfxRenderer& renderer, Rect rect, const si
renderer.drawCenteredText(UI_10_FONT_ID, rect.y + rect.height + 15, percentText.c_str());
}
// CROSSPOINT_SHOW_BUTTON_HINTS: build-flag switch (env). Default on. A device with
// no physical button row (touch + rotary, e.g. M5Paper) builds with =0.
#ifndef CROSSPOINT_SHOW_BUTTON_HINTS
#define CROSSPOINT_SHOW_BUTTON_HINTS 1
#endif
bool BaseTheme::showButtonHints() { return CROSSPOINT_SHOW_BUTTON_HINTS; }
// Non-virtual gates: one check hides hints across every theme. Themes override the
// *Impl, which only ever runs when hints are enabled.
void BaseTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
if (!showButtonHints()) return;
drawButtonHintsImpl(renderer, btn1, btn2, btn3, btn4);
}
void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const {
if (!showButtonHints()) return;
drawSideButtonHintsImpl(renderer, topBtn, bottomBtn);
}
void BaseTheme::drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
const GfxRenderer::Orientation orig_orientation = renderer.getOrientation();
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
@@ -163,7 +182,7 @@ void BaseTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
renderer.setOrientation(orig_orientation);
}
void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const {
void BaseTheme::drawSideButtonHintsImpl(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const {
const int screenWidth = renderer.getScreenWidth();
constexpr int buttonWidth = BaseMetrics::values.sideButtonHintsWidth; // Width on screen (height when rotated)
constexpr int buttonHeight = 80; // Height on screen (width when rotated)
+13 -3
View File
@@ -179,9 +179,19 @@ class BaseTheme {
void drawBatteryRight(const GfxRenderer& renderer, Rect rect,
bool showPercentage = true) const; // Right aligned (UI headers)
virtual void fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const;
virtual void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const;
virtual void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const;
// Single switch for on-screen button hints, set by the CROSSPOINT_SHOW_BUTTON_HINTS
// build flag (env; default on). Devices with no physical button row (touch +
// rotary, e.g. M5Paper) set it to 0 in their env.
static bool showButtonHints();
// Public, NON-virtual gate: returns early when hints are disabled, otherwise
// dispatches to the per-theme *Impl below. Themes override the *Impl, never
// these — so one switch hides hints across every theme.
void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const;
void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const;
virtual void drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const;
virtual void drawSideButtonHintsImpl(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const;
virtual int getListPageItems(int contentHeight, bool hasSubtitle) const;
virtual void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
const std::function<std::string(int index)>& rowTitle,
+3 -3
View File
@@ -317,8 +317,8 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
}
}
void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
void LyraTheme::drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
const GfxRenderer::Orientation orig_orientation = renderer.getOrientation();
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
@@ -356,7 +356,7 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
renderer.setOrientation(orig_orientation);
}
void LyraTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const {
void LyraTheme::drawSideButtonHintsImpl(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const {
const int screenWidth = renderer.getScreenWidth();
constexpr int buttonWidth = LyraMetrics::values.sideButtonHintsWidth; // Width on screen (height when rotated)
constexpr int buttonHeight = 78; // Height on screen (width when rotated)
+3 -3
View File
@@ -84,9 +84,9 @@ class LyraTheme : public BaseTheme {
const std::function<std::string(int index)>& rowSubtitle,
const std::function<UIIcon(int index)>& rowIcon, const std::function<std::string(int index)>& rowValue,
bool highlightValue, const std::function<bool(int index)>& rowDimmed = nullptr) const override;
void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const override;
void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const override;
void drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const override;
void drawSideButtonHintsImpl(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const override;
void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel,
const std::function<UIIcon(int index)>& rowIcon) const override;
@@ -381,8 +381,8 @@ void RoundedRaffTheme::drawList(const GfxRenderer& renderer, Rect rect, int item
drawScrollBar(renderer, rect, itemCount, pageStartIndex, pageItems);
}
void RoundedRaffTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
void RoundedRaffTheme::drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const {
const GfxRenderer::Orientation origOrientation = renderer.getOrientation();
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
@@ -92,7 +92,7 @@ class RoundedRaffTheme : public BaseTheme {
const std::function<UIIcon(int index)>& rowIcon = nullptr,
const std::function<std::string(int index)>& rowValue = nullptr, bool highlightValue = false,
const std::function<bool(int index)>& rowDimmed = nullptr) const override;
void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const override;
void drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const override;
bool homeMenuShowsContinueReading() const { return true; }
};
+12
View File
@@ -305,6 +305,14 @@ void setupDisplayAndFonts(bool seamless = false) {
void setup() {
t1 = millis();
#if defined(FREEINK_DEVICE_M5PAPER) && FREEINK_DEVICE_M5PAPER
// M5Paper v1.1 latches its own power through a MOSFET on GPIO2. Drive it HIGH
// first thing or the board powers off the instant USB is unplugged. (Board
// responsibility, not the SDK — see freeink-sdk platformio.sample.ini.)
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
#endif
#ifdef ENABLE_SERIAL_LOG
// Earliest possible Serial setup. The 250 ms stall before begin() lets the
// USB Serial/JTAG peripheral finish power-on and lets the host complete USB
@@ -313,7 +321,11 @@ void setup() {
// worked without the delay because USB was already enumerated.
delay(250);
Serial.begin(115200);
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
// setTxTimeoutMs is an HWCDC (native USB CDC) method. On the classic ESP32
// (M5Paper) Serial is UART0/HardwareSerial, which has no such method.
logSerial.setTxTimeoutMs(1); // This is a load-bearing 1. Do not modify.
#endif
#endif
HalSystem::begin();