perf: unused TinyUSB classes off on nRF52, SSD1306 sends only changed blocks, ESP32 loop yields when idle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-12 19:30:46 +02:00
co-authored by Claude Opus 5.5
parent 2b9e013071
commit ec42a74ab6
5 changed files with 85 additions and 2 deletions
+5
View File
@@ -381,6 +381,11 @@ void loop() {
if (!the_mesh.hasPendingWork()) { if (!the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
#elif defined(ESP32) && !defined(UI_LOOP_IDLES)
// Arduino's loop task never yields: without this it spins the CPU flat
// out. One tick lets the idle task halt the core (WAITI) until the next
// interrupt or tick -- radio, buttons and serial still get a pass a ms.
vTaskDelay(1);
#endif #endif
} }
@@ -18,6 +18,10 @@
#include "../ui-core/UiCoreHost.h" #include "../ui-core/UiCoreHost.h"
#include "../ui-core/ScreenLock.h" #include "../ui-core/ScreenLock.h"
// loop() waits on its own until something is due (lvport::idle, woken by a
// frame from the app): main.cpp adds no wait of its own.
#define UI_LOOP_IDLES 1
class UiCore; class UiCore;
class NearbyModel; class NearbyModel;
struct UiEvent; struct UiEvent;
+8
View File
@@ -93,6 +93,14 @@ build_flags = ${arduino_base.build_flags}
-D LFS_NO_ASSERT=1 -D LFS_NO_ASSERT=1
-D EXTRAFS=1 -D EXTRAFS=1
-D USE_CC310_HW_CRYPTO=1 -D USE_CC310_HW_CRYPTO=1
; USB is the serial port only: TinyUSB's other device classes (on by default
; in the core's tusb_config_nrf.h) cost ~2.4 KB of RAM for nothing.
-D CFG_TUD_MSC=0
-D CFG_TUD_HID=0
-D CFG_TUD_MIDI=0
-D CFG_TUD_VENDOR=0
-D CFG_TUD_VIDEO=0
-D CFG_TUD_VIDEO_STREAMING=0
lib_deps = lib_deps =
${arduino_base.lib_deps} ${arduino_base.lib_deps}
https://github.com/oltaco/CustomLFS#0.2.3 https://github.com/oltaco/CustomLFS#0.2.3
+47 -1
View File
@@ -17,6 +17,7 @@ bool SSD1306Display::begin() {
#ifdef DISPLAY_ROTATION #ifdef DISPLAY_ROTATION
display.setRotation(DISPLAY_ROTATION); display.setRotation(DISPLAY_ROTATION);
#endif #endif
_force_redraw = true;
return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false) && i2c_probe(Wire, DISPLAY_ADDRESS); return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false) && i2c_probe(Wire, DISPLAY_ADDRESS);
} }
@@ -27,6 +28,7 @@ void SSD1306Display::turnOn() {
if (_peripher_power) begin(); // re-init display after power was cut if (_peripher_power) begin(); // re-init display after power was cut
} }
display.ssd1306_command(SSD1306_DISPLAYON); display.ssd1306_command(SSD1306_DISPLAYON);
_force_redraw = true; // panel was off -- guarantee the next endFrame() flushes
} }
void SSD1306Display::turnOff() { void SSD1306Display::turnOff() {
@@ -45,6 +47,7 @@ void SSD1306Display::turnOff() {
void SSD1306Display::clear() { void SSD1306Display::clear() {
display.clearDisplay(); display.clearDisplay();
display.display(); display.display();
_force_redraw = true; // next endFrame() must flush even if its hash matches a pre-clear frame
} }
void SSD1306Display::startFrame(Color bkg) { void SSD1306Display::startFrame(Color bkg) {
@@ -107,6 +110,49 @@ uint16_t SSD1306Display::getTextWidth(const char* str) {
#endif #endif
} }
void SSD1306Paged::sendBlock(uint8_t page, uint8_t col, uint8_t len) {
const uint8_t cmd[] = { SSD1306_PAGEADDR, page, page, SSD1306_COLUMNADDR, col, (uint8_t)(col + len - 1) };
const uint8_t* ptr = buffer + (uint16_t)page * WIDTH + col;
ssd1306_commandList(cmd, sizeof(cmd));
while (len) { // 32 data bytes a transfer fit every core's Wire buffer
uint8_t n = len < 32 ? len : 32;
wire->beginTransmission(i2caddr);
wire->write((uint8_t)0x40);
wire->write(ptr, n);
wire->endTransmission();
ptr += n; len -= n;
}
}
void SSD1306Display::endFrame() { void SSD1306Display::endFrame() {
display.display(); const uint8_t* buf = display.getBuffer();
const uint8_t per_page = 128 / BLOCK_W;
bool any = false;
bool dirty[BLOCKS];
for (uint8_t i = 0; i < BLOCKS; i++) {
const uint8_t* q = buf + (uint16_t)i * BLOCK_W; // blocks run along each page in buffer order
uint32_t h = 2166136261u;
for (uint8_t j = 0; j < BLOCK_W; j++) { h ^= q[j]; h *= 16777619u; }
dirty[i] = _force_redraw || h != _block_hash[i];
_block_hash[i] = h;
any |= dirty[i];
}
if (!any) return;
if (_force_redraw || !display.canSendBlocks()) {
_force_redraw = false;
display.display(); // whole frame; also resets the panel's address window
return;
}
display.blocksBegin();
for (uint8_t page = 0; page < 64 / 8; page++) {
// runs of neighbouring dirty blocks go out as one transfer
for (uint8_t b = 0; b < per_page; ) {
if (!dirty[page * per_page + b]) { b++; continue; }
uint8_t e = b;
while (e + 1 < per_page && dirty[page * per_page + e + 1]) e++;
display.sendBlock(page, b * BLOCK_W, (e - b + 1) * BLOCK_W);
b = e + 1;
}
}
display.blocksEnd();
} }
+21 -1
View File
@@ -15,12 +15,32 @@
#define DISPLAY_ADDRESS 0x3C #define DISPLAY_ADDRESS 0x3C
#endif #endif
// Adafruit's display() always sends the whole frame (1 KB, ~25 ms at 400 kHz),
// and startFrame() clears the whole buffer, so every frame went out in full.
// As on the SH1106 (SH1106Display.h): a sender for single 32-column blocks.
class SSD1306Paged : public Adafruit_SSD1306 {
public:
using Adafruit_SSD1306::Adafruit_SSD1306;
bool canSendBlocks() const { return wire != nullptr; }
// A frame's blocks go out between these: the bus clock is set once.
void blocksBegin() { wire->setClock(wireClk); }
void blocksEnd() { wire->setClock(restoreClk); }
void sendBlock(uint8_t page, uint8_t col, uint8_t len);
};
class SSD1306Display : public DisplayDriver { class SSD1306Display : public DisplayDriver {
Adafruit_SSD1306 display; SSD1306Paged display;
bool _isOn; bool _isOn;
uint8_t _color; uint8_t _color;
int _text_sz = 1; int _text_sz = 1;
RefCountedDigitalPin* _peripher_power; RefCountedDigitalPin* _peripher_power;
// Frame-skip: endFrame() hashes each 32-column block of each 8-px page and
// sends only the blocks whose hash changed; _force_redraw sends the frame
// after begin()/turnOn()/clear() whole.
static const uint8_t BLOCK_W = 32;
static const uint8_t BLOCKS = (128 / BLOCK_W) * (64 / 8);
uint32_t _block_hash[BLOCKS] = {};
bool _force_redraw = true;
bool i2c_probe(TwoWire& wire, uint8_t addr); bool i2c_probe(TwoWire& wire, uint8_t addr);
#ifdef OLED_MISC_FIXED_FONT #ifdef OLED_MISC_FIXED_FONT