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 defined(NRF52_PLATFORM)
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
}
@@ -18,6 +18,10 @@
#include "../ui-core/UiCoreHost.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 NearbyModel;
struct UiEvent;
+8
View File
@@ -93,6 +93,14 @@ build_flags = ${arduino_base.build_flags}
-D LFS_NO_ASSERT=1
-D EXTRAFS=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 =
${arduino_base.lib_deps}
https://github.com/oltaco/CustomLFS#0.2.3
+47 -1
View File
@@ -17,6 +17,7 @@ bool SSD1306Display::begin() {
#ifdef DISPLAY_ROTATION
display.setRotation(DISPLAY_ROTATION);
#endif
_force_redraw = true;
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
}
display.ssd1306_command(SSD1306_DISPLAYON);
_force_redraw = true; // panel was off -- guarantee the next endFrame() flushes
}
void SSD1306Display::turnOff() {
@@ -45,6 +47,7 @@ void SSD1306Display::turnOff() {
void SSD1306Display::clear() {
display.clearDisplay();
display.display();
_force_redraw = true; // next endFrame() must flush even if its hash matches a pre-clear frame
}
void SSD1306Display::startFrame(Color bkg) {
@@ -107,6 +110,49 @@ uint16_t SSD1306Display::getTextWidth(const char* str) {
#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() {
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
#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 {
Adafruit_SSD1306 display;
SSD1306Paged display;
bool _isOn;
uint8_t _color;
int _text_sz = 1;
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);
#ifdef OLED_MISC_FIXED_FONT