fix(t5s3-epaper): 40MHz octal PSRAM; guard EInk init; esp32s3 sw-atomic shim (#11062)
- t5s3-epaper: the board's 3.3V octal (AP_3v3) PSRAM is unreliable at the qio_opi
default of 80MHz (Total PSRAM reads 0, display dead / boot-loop); clock it at 40MHz.
- EInkParallelDisplay: skip EInk init and no-op display ops when the PSRAM framebuffer
is unavailable, so the node boots headless instead of hard-faulting on a NULL buffer.
- src/platform/esp32: weak software __atomic_*_{1,2,4} so esp32s3 links on toolchains
(e.g. macOS) that lack the sized libcalls GCC emits under -mdisable-hardware-atomics.
This commit is contained in:
@@ -77,12 +77,13 @@ EInkParallelDisplay::~EInkParallelDisplay()
|
|||||||
bool EInkParallelDisplay::connect()
|
bool EInkParallelDisplay::connect()
|
||||||
{
|
{
|
||||||
LOG_INFO("Do EPD init");
|
LOG_INFO("Do EPD init");
|
||||||
|
int initRc = BBEP_SUCCESS;
|
||||||
if (!epaper) {
|
if (!epaper) {
|
||||||
epaper = new FASTEPD;
|
epaper = new FASTEPD;
|
||||||
#if defined(T5_S3_EPAPER_PRO_V1)
|
#if defined(T5_S3_EPAPER_PRO_V1)
|
||||||
epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
|
initRc = epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
|
||||||
#elif defined(T5_S3_EPAPER_PRO_V2)
|
#elif defined(T5_S3_EPAPER_PRO_V2)
|
||||||
epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
|
initRc = epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
|
||||||
// initialize all port 0 pins (0-7) as outputs / HIGH
|
// initialize all port 0 pins (0-7) as outputs / HIGH
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
epaper->ioPinMode(i, OUTPUT);
|
epaper->ioPinMode(i, OUTPUT);
|
||||||
@@ -93,6 +94,16 @@ bool EInkParallelDisplay::connect()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FastEPD allocates its framebuffer only from PSRAM; if PSRAM init failed the alloc returns
|
||||||
|
// NULL and initPanel() returns an error, so clearWhite() below would memset(NULL). Skip EInk
|
||||||
|
// bring-up but return true so OLEDDisplay still allocates its base buffer (base draw ops stay
|
||||||
|
// safe); displayReady stays false so the FastEPD push paths no-op -> node runs headless.
|
||||||
|
if (initRc != BBEP_SUCCESS || epaper->currentBuffer() == nullptr) {
|
||||||
|
LOG_ERROR("EPD framebuffer unavailable (initPanel rc=%d, PSRAM=%u); running headless", initRc,
|
||||||
|
(unsigned)ESP.getPsramSize());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// epaper->setRotation(rotation); // does not work, messes up width/height
|
// epaper->setRotation(rotation); // does not work, messes up width/height
|
||||||
epaper->setMode(BB_MODE_1BPP);
|
epaper->setMode(BB_MODE_1BPP);
|
||||||
epaper->clearWhite();
|
epaper->clearWhite();
|
||||||
@@ -103,6 +114,7 @@ bool EInkParallelDisplay::connect()
|
|||||||
resetGhostPixelTracking();
|
resetGhostPixelTracking();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
displayReady = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +199,9 @@ void EInkParallelDisplay::asyncFullUpdateTask(void *pvParameters)
|
|||||||
*/
|
*/
|
||||||
void EInkParallelDisplay::display(void)
|
void EInkParallelDisplay::display(void)
|
||||||
{
|
{
|
||||||
|
if (!displayReady) // no framebuffer (PSRAM absent / init failed) -> nothing to push
|
||||||
|
return;
|
||||||
|
|
||||||
const uint16_t w = this->displayWidth;
|
const uint16_t w = this->displayWidth;
|
||||||
const uint16_t h = this->displayHeight;
|
const uint16_t h = this->displayHeight;
|
||||||
|
|
||||||
@@ -400,6 +415,9 @@ void EInkParallelDisplay::resetGhostPixelTracking()
|
|||||||
*/
|
*/
|
||||||
bool EInkParallelDisplay::forceDisplay(uint32_t msecLimit)
|
bool EInkParallelDisplay::forceDisplay(uint32_t msecLimit)
|
||||||
{
|
{
|
||||||
|
if (!displayReady)
|
||||||
|
return false;
|
||||||
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (lastDrawMsec == 0 || (now - lastDrawMsec) > msecLimit) {
|
if (lastDrawMsec == 0 || (now - lastDrawMsec) > msecLimit) {
|
||||||
display();
|
display();
|
||||||
@@ -410,6 +428,8 @@ bool EInkParallelDisplay::forceDisplay(uint32_t msecLimit)
|
|||||||
|
|
||||||
void EInkParallelDisplay::endUpdate()
|
void EInkParallelDisplay::endUpdate()
|
||||||
{
|
{
|
||||||
|
if (!displayReady)
|
||||||
|
return;
|
||||||
{
|
{
|
||||||
// ensure any async full update is started/completed
|
// ensure any async full update is started/completed
|
||||||
if (asyncFullRunning.load()) {
|
if (asyncFullRunning.load()) {
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class EInkParallelDisplay : public OLEDDisplay
|
|||||||
uint32_t lastDrawMsec = 0;
|
uint32_t lastDrawMsec = 0;
|
||||||
FASTEPD *epaper;
|
FASTEPD *epaper;
|
||||||
|
|
||||||
|
// Set only when connect() fully succeeds; framebuffer-touching methods no-op while false.
|
||||||
|
bool displayReady = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Async full-refresh support
|
// Async full-refresh support
|
||||||
std::atomic<bool> asyncFullRunning{false};
|
std::atomic<bool> asyncFullRunning{false};
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// Weak software __atomic_*_{1,2,4} for ESP32-S2/S3. -mdisable-hardware-atomics makes
|
||||||
|
// GCC emit these libcalls, but the precompiled libnewlib ships only the _8 variants and
|
||||||
|
// some toolchains' libgcc ships none, so S3 links fail on macOS. Weak = a real libgcc
|
||||||
|
// definition wins with no clash. Spinlock/critical-section like IDF's stdatomic.c.
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
// These names are GCC builtins; declaring them trips -Wbuiltin-declaration-mismatch
|
||||||
|
// (uint32_t vs GCC's unsigned int, same ABI). Suppressed as IDF's stdatomic.c does.
|
||||||
|
#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"
|
||||||
|
|
||||||
|
static portMUX_TYPE s_swatomic_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
#define SWATOMIC_ENTER() portENTER_CRITICAL_SAFE(&s_swatomic_mux)
|
||||||
|
#define SWATOMIC_EXIT() portEXIT_CRITICAL_SAFE(&s_swatomic_mux)
|
||||||
|
|
||||||
|
// load / store / exchange / compare_exchange for one width.
|
||||||
|
#define GEN_SWATOMIC_CORE(N, TYPE) \
|
||||||
|
__attribute__((weak, used)) TYPE __atomic_load_##N(const volatile void *ptr, int memorder) \
|
||||||
|
{ \
|
||||||
|
(void)memorder; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
TYPE ret = *(const volatile TYPE *)ptr; \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
__attribute__((weak, used)) void __atomic_store_##N(volatile void *ptr, TYPE val, int memorder) \
|
||||||
|
{ \
|
||||||
|
(void)memorder; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
*(volatile TYPE *)ptr = val; \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
} \
|
||||||
|
__attribute__((weak, used)) TYPE __atomic_exchange_##N(volatile void *ptr, TYPE val, int memorder) \
|
||||||
|
{ \
|
||||||
|
(void)memorder; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
TYPE old = *(volatile TYPE *)ptr; \
|
||||||
|
*(volatile TYPE *)ptr = val; \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
return old; \
|
||||||
|
} \
|
||||||
|
__attribute__((weak, used)) bool __atomic_compare_exchange_##N(volatile void *ptr, void *expected, TYPE desired, \
|
||||||
|
bool is_weak, int success, int failure) \
|
||||||
|
{ \
|
||||||
|
(void)is_weak; \
|
||||||
|
(void)success; \
|
||||||
|
(void)failure; \
|
||||||
|
bool ok; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
TYPE cur = *(volatile TYPE *)ptr; \
|
||||||
|
if (cur == *(TYPE *)expected) { \
|
||||||
|
*(volatile TYPE *)ptr = desired; \
|
||||||
|
ok = true; \
|
||||||
|
} else { \
|
||||||
|
*(TYPE *)expected = cur; \
|
||||||
|
ok = false; \
|
||||||
|
} \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
return ok; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// A read-modify-write pair: fetch_<name> returns the old value, <name>_fetch the
|
||||||
|
// new. EXPR is evaluated over `old` and `val`.
|
||||||
|
#define GEN_SWATOMIC_RMW(N, TYPE, NAME, EXPR) \
|
||||||
|
__attribute__((weak, used)) TYPE __atomic_fetch_##NAME##_##N(volatile void *ptr, TYPE val, int memorder) \
|
||||||
|
{ \
|
||||||
|
(void)memorder; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
TYPE old = *(volatile TYPE *)ptr; \
|
||||||
|
*(volatile TYPE *)ptr = (TYPE)(EXPR); \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
return old; \
|
||||||
|
} \
|
||||||
|
__attribute__((weak, used)) TYPE __atomic_##NAME##_fetch_##N(volatile void *ptr, TYPE val, int memorder) \
|
||||||
|
{ \
|
||||||
|
(void)memorder; \
|
||||||
|
SWATOMIC_ENTER(); \
|
||||||
|
TYPE old = *(volatile TYPE *)ptr; \
|
||||||
|
TYPE nv = (TYPE)(EXPR); \
|
||||||
|
*(volatile TYPE *)ptr = nv; \
|
||||||
|
SWATOMIC_EXIT(); \
|
||||||
|
return nv; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GEN_SWATOMIC_ALL(N, TYPE) \
|
||||||
|
GEN_SWATOMIC_CORE(N, TYPE) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, add, old + val) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, sub, old - val) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, and, old &val) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, or, old | val) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, xor, old ^ val) \
|
||||||
|
GEN_SWATOMIC_RMW(N, TYPE, nand, ~(old & val))
|
||||||
|
|
||||||
|
GEN_SWATOMIC_ALL(1, uint8_t)
|
||||||
|
GEN_SWATOMIC_ALL(2, uint16_t)
|
||||||
|
GEN_SWATOMIC_ALL(4, uint32_t)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Keep this a non-empty translation unit on targets that inline hardware atomics.
|
||||||
|
typedef int swatomic_translation_unit_not_empty;
|
||||||
|
|
||||||
|
#endif // CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
@@ -28,6 +28,14 @@ lib_deps =
|
|||||||
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
|
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
|
||||||
https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
|
https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
|
||||||
|
|
||||||
|
; This board's 3.3V octal (AP_3v3) 8MB PSRAM is unreliable at the qio_opi default 80MHz
|
||||||
|
; (Total PSRAM reads 0); 40MHz makes it enumerate. Forces a from-source build as no
|
||||||
|
; precompiled 40MHz-octal libs exist; bandwidth is immaterial for an e-paper node.
|
||||||
|
custom_sdkconfig =
|
||||||
|
${esp32s3_base.custom_sdkconfig}
|
||||||
|
CONFIG_SPIRAM_SPEED_40M=y
|
||||||
|
CONFIG_SPIRAM_SPEED=40
|
||||||
|
|
||||||
[env:t5s3_epaper_inkhud]
|
[env:t5s3_epaper_inkhud]
|
||||||
extends = t5s3_epaper_base, inkhud
|
extends = t5s3_epaper_base, inkhud
|
||||||
board_level = extra
|
board_level = extra
|
||||||
|
|||||||
Reference in New Issue
Block a user