mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-04 03:18:10 +10:00
* logging: gate LOG_TRACE behind MESHTASTIC_TRACE_LOGGING, drop redundant reclock logs LOG_TRACE now compiles out by default so trace-level diagnostics cost no flash; enable with -DMESHTASTIC_TRACE_LOGGING. Portduino keeps it on for the traceFilename packet-trace feature. Remove the 66 caller-side I2C reclock/restore log lines in the telemetry sensors: ReClockI2C::setClock/restoreClock already log both frequencies internally (now at trace level, since they fire every sensor read). Also unify near-duplicate literals (colon/case/punctuation variants) so linker string dedup applies, and drop an information-free bare 'done'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: demote chatty per-packet/per-poll DEBUG lines to trace level With LOG_TRACE compiled out by default, per-iteration chatter (packet bookkeeping, sensor poll values, e-ink refresh reasons, GPS pin states, UI runState traces) now costs no flash on device builds while remaining one -DMESHTASTIC_TRACE_LOGGING away. 108 lines demoted, 4 information- free lines removed; failure paths, drop reasons, and one-time init logs all stay at debug level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: address CodeRabbit review on trace-gate PR - GPS: pass serial-derived buffers as %s args, never as format strings (untrusted bytes could contain % directives) - 0x%08x for packet id / NodeNum per convention (Router, CannedMessage, NeighborInfo); unsigned casts for size_t args; %u for uint32_t delta - EInk: async full-refresh begin/complete back to DEBUG (rare state transitions); per-frame SKIPPED lines stay trace Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: gate trace on the flag's value, not its presence -DMESHTASTIC_TRACE_LOGGING=0 previously *enabled* trace logging because the gate tested definedness. The flag now defaults per-platform (portduino 1, else 0) and both backends test the value, so =0 disables, =1 or a bare -D enables. Also cast tx_after-millis() to uint32_t for %u (millis() is unsigned long on native). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: clang-format rewrap after specifier widening Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * Even fewer bytes! * logging: keep compile-gated debug lines at debug level; fix native-suite-count Lines already inside default-off #ifdef blocks (GPS_DEBUG, DEBUG_LOOP_TIMING) cost no flash and should stay visible at debug level when their gate is enabled, rather than also requiring MESHTASTIC_TRACE_LOGGING. test/native-suite-count lags the two test_event_channel_* suites added by #11045 (develop's Native Suite Count check has the same mismatch); bump 46 -> 47. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * gps: route GPS_DEBUG diagnostics through a LOG_DEBUG_GPS() macro (#11414) Replaces 27 log-only #ifdef GPS_DEBUG blocks across GPS.cpp, PositionModule, MeshService, and GPSStatus.h with a single-line LOG_DEBUG_GPS() call (src/gps/GPSLog.h, modeled on LOG_MIGRATION: value-gated, ((void)0) when off). Blocks containing declarations, control flow, hexDump, or nested conditionals keep an explicit '#if GPS_DEBUG' guard. RTC.cpp's per-reading raw time dumps and per-candidate rejection chatter fold under the same gate; quality transitions and boot-time seeding stay at debug. Also fixes the '// define GPS_DEBUG' missing-# typo in two variant headers and updates all seven commented examples to the value form ('#define GPS_DEBUG 1') required by the value-based gate. Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 Co-authored-by: Claude <noreply@anthropic.com> * gps: declare RTC gmtime result as pointer to const (cppcheck) With the setTime debug dump gated behind GPS_DEBUG, all remaining uses of t are reads; cppcheck (constVariablePointer) now flags it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 --------- Co-authored-by: Claude <noreply@anthropic.com>
315 lines
12 KiB
C++
315 lines
12 KiB
C++
#include "configuration.h"
|
|
#include "graphics/Backlight.h"
|
|
|
|
#if defined(USE_EINK) && !defined(USE_EINK_PARALLELDISPLAY)
|
|
#include "EInkDisplay2.h"
|
|
#include "FSCommon.h"
|
|
#include "SPILock.h"
|
|
#include "main.h"
|
|
#include <SPI.h>
|
|
|
|
#ifdef GXEPD2_DRIVER_0
|
|
#include "einkDetect.h"
|
|
#endif
|
|
|
|
/*
|
|
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
|
|
Previously, these macros were defined at the top of this file.
|
|
|
|
For archival reasons, note that the following configurations had also been tested during this period:
|
|
* ifdef RAK4631
|
|
- 4.2 inch
|
|
EINK_DISPLAY_MODEL: GxEPD2_420_M01
|
|
EINK_WIDTH: 300
|
|
EINK_WIDTH: 400
|
|
|
|
- 2.9 inch
|
|
EINK_DISPLAY_MODEL: GxEPD2_290_T5D
|
|
EINK_WIDTH: 296
|
|
EINK_HEIGHT: 128
|
|
|
|
- 1.54 inch
|
|
EINK_DISPLAY_MODEL: GxEPD2_154_M09
|
|
EINK_WIDTH: 200
|
|
EINK_HEIGHT: 200
|
|
*/
|
|
|
|
// Constructor
|
|
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
|
{
|
|
// Set dimensions in OLEDDisplay base class
|
|
this->geometry = GEOMETRY_RAWMODE;
|
|
this->displayWidth = EINK_WIDTH;
|
|
this->displayHeight = EINK_HEIGHT;
|
|
|
|
// Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
|
|
uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
|
|
uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
|
|
if (shortSide % 8 != 0)
|
|
shortSide = (shortSide | 7) + 1;
|
|
|
|
this->displayBufferSize = longSide * (shortSide / 8);
|
|
}
|
|
|
|
/**
|
|
* Force a display update if we haven't drawn within the specified msecLimit
|
|
*/
|
|
bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
|
{
|
|
// No need to grab this lock because we are on our own SPI bus
|
|
// concurrency::LockGuard g(spiLock);
|
|
|
|
uint32_t now = millis();
|
|
uint32_t sinceLast = now - lastDrawMsec;
|
|
|
|
if (adafruitDisplay && (sinceLast > msecLimit || lastDrawMsec == 0))
|
|
lastDrawMsec = now;
|
|
else
|
|
return false;
|
|
|
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
|
const bool flipped = config.display.flip_screen;
|
|
// HACK for L1 EInk
|
|
#if defined(SEEED_WIO_TRACKER_L1_EINK)
|
|
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
|
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
|
auto b = buffer[x + (y / 8) * displayWidth];
|
|
auto isset = b & (1 << (y & 7));
|
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
|
}
|
|
}
|
|
#else
|
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
|
auto b = buffer[x + (y / 8) * displayWidth];
|
|
auto isset = b & (1 << (y & 7));
|
|
if (flipped)
|
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
|
else
|
|
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Trigger the refresh in GxEPD2
|
|
LOG_DEBUG("Update E-Paper");
|
|
adafruitDisplay->nextPage();
|
|
|
|
// End the update process
|
|
endUpdate();
|
|
|
|
return true;
|
|
}
|
|
|
|
// End the update process - virtual method, overridden in derived class
|
|
void EInkDisplay::endUpdate()
|
|
{
|
|
#ifndef EINK_NOT_HIBERNATE
|
|
// By default, power off the E-Ink display hardware and enter hibernate().
|
|
// Boards/panels that define EINK_NOT_HIBERNATE intentionally skip this step.
|
|
// Skipping hibernate() can help avoid panel-specific wake/refresh or ghosting issues,
|
|
// but it typically trades lower power savings for that compatibility.
|
|
adafruitDisplay->hibernate();
|
|
#endif
|
|
}
|
|
|
|
// Write the buffer to the display memory
|
|
void EInkDisplay::display(void)
|
|
{
|
|
// We don't allow regular 'dumb' display() calls to draw on eink until we've shown
|
|
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
|
|
// bootscreen (that we want to look nice)
|
|
|
|
if (lastDrawMsec) {
|
|
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
|
|
}
|
|
}
|
|
|
|
// Send a command to the display (low level function)
|
|
void EInkDisplay::sendCommand(uint8_t com)
|
|
{
|
|
(void)com;
|
|
// Drop all commands to device (we just update the buffer)
|
|
}
|
|
|
|
void EInkDisplay::setDetected(uint8_t detected)
|
|
{
|
|
(void)detected;
|
|
}
|
|
|
|
// Connect to the display - variant specific
|
|
bool EInkDisplay::connect()
|
|
{
|
|
LOG_INFO("Do EInk init");
|
|
|
|
#ifdef PIN_EINK_EN
|
|
// backlight power, HIGH is backlight on, LOW is off
|
|
pinMode(PIN_EINK_EN, OUTPUT);
|
|
#ifdef ELECROW_ThinkNode_M1
|
|
// ThinkNode M1 has a hardware dimmable backlight. Start enabled
|
|
digitalWrite(PIN_EINK_EN, HIGH);
|
|
#elif defined(MINI_EPAPER_S3)
|
|
// T-Mini Epaper S3 requires panel power rail enabled before SPI transfer.
|
|
digitalWrite(PIN_EINK_EN, HIGH);
|
|
delay(10);
|
|
#else
|
|
digitalWrite(PIN_EINK_EN, LOW);
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS) || \
|
|
defined(ELECROW_ThinkNode_M8)
|
|
{
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init();
|
|
#if defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M8)
|
|
adafruitDisplay->setRotation(4);
|
|
#else
|
|
adafruitDisplay->setRotation(3);
|
|
#endif
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
|
}
|
|
#elif defined(ELECROW_ThinkNode_M5)
|
|
{
|
|
// Start HSPI
|
|
hspi = new SPIClass(HSPI);
|
|
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init();
|
|
|
|
adafruitDisplay->setRotation(4);
|
|
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
|
}
|
|
#elif defined(MESHLINK)
|
|
{
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init();
|
|
adafruitDisplay->setRotation(3);
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
|
}
|
|
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
|
{
|
|
if (eink_found) {
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
// RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
|
|
adafruitDisplay->setRotation(3);
|
|
// Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
|
|
// adafruitDisplay->setRotation(1);
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
|
} else {
|
|
(void)adafruitDisplay;
|
|
}
|
|
}
|
|
|
|
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
|
|
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || \
|
|
defined(MINI_EPAPER_S3)
|
|
{
|
|
#if defined(TLORA_T3S3_EPAPER)
|
|
// T3-S3 shares HSPI with the SD card; preconfigure the panel control pins.
|
|
hspi = &SPI_HSPI;
|
|
pinMode(PIN_EINK_CS, OUTPUT);
|
|
pinMode(PIN_EINK_DC, OUTPUT);
|
|
pinMode(PIN_EINK_BUSY, INPUT);
|
|
if (PIN_EINK_RES >= 0) {
|
|
pinMode(PIN_EINK_RES, OUTPUT);
|
|
digitalWrite(PIN_EINK_RES, HIGH);
|
|
}
|
|
digitalWrite(PIN_EINK_CS, HIGH);
|
|
digitalWrite(PIN_EINK_DC, HIGH);
|
|
#else
|
|
// Start HSPI
|
|
hspi = new SPIClass(HSPI);
|
|
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
|
#endif
|
|
// VExt already enabled in setup()
|
|
// RTC GPIO hold disabled in setup()
|
|
|
|
// Create GxEPD2 objects
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
|
|
// Init GxEPD2
|
|
adafruitDisplay->init();
|
|
#if defined(MINI_EPAPER_S3)
|
|
adafruitDisplay->setRotation(3);
|
|
#else
|
|
adafruitDisplay->setRotation(3);
|
|
#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER)
|
|
adafruitDisplay->setRotation(0);
|
|
#endif
|
|
#endif
|
|
}
|
|
#elif defined(PCA10059) || defined(ME25LS01)
|
|
{
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init(115200, true, 40, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
adafruitDisplay->setRotation(0);
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
|
}
|
|
#elif defined(M5_COREINK) || defined(T_DECK_PRO)
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
adafruitDisplay->setRotation(0);
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
|
#elif defined(my) || defined(ESP32_S3_PICO)
|
|
{
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
adafruitDisplay->setRotation(1);
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
|
}
|
|
#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
|
|
{
|
|
spi1 = &SPI1;
|
|
spi1->begin();
|
|
// VExt already enabled in setup()
|
|
// RTC GPIO hold disabled in setup()
|
|
|
|
// Create GxEPD2 objects
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
|
|
|
// Init GxEPD2
|
|
adafruitDisplay->init();
|
|
adafruitDisplay->setRotation(3);
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
|
}
|
|
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
|
|
|
// Detect display model, before starting SPI
|
|
EInkDetectionResult displayModel = detectEInk();
|
|
|
|
// Start HSPI
|
|
hspi = new SPIClass(HSPI);
|
|
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
|
|
|
// Create GxEPD2 object
|
|
adafruitDisplay = new GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1>((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
|
|
PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
|
|
|
// Init GxEPD2
|
|
adafruitDisplay->init();
|
|
adafruitDisplay->setRotation(3);
|
|
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|