feat: implement sunlight fading fix (PR #603)

Add user-toggleable setting to turn off display between refreshes,
which helps mitigate the sunlight fading issue on e-ink displays.

Changes:
- Add turnOffScreen parameter to HalDisplay methods
- Add fadingFix member and setFadingFix() to GfxRenderer
- Add fadingFix setting to CrossPointSettings with persistence
- Add "Sunlight Fading Fix" toggle in Display settings
- Update SDK submodule with turnOffScreen support
This commit is contained in:
cottongin 2026-01-30 23:02:29 -05:00
parent be8b02efd6
commit 520a0cb124
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
9 changed files with 27 additions and 9 deletions

View File

@ -531,7 +531,9 @@ void GfxRenderer::invertScreen() const {
} }
} }
void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { display.displayBuffer(refreshMode); } void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const {
display.displayBuffer(refreshMode, fadingFix);
}
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth, std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
const EpdFontFamily::Style style) const { const EpdFontFamily::Style style) const {
@ -910,7 +912,7 @@ void GfxRenderer::copyGrayscaleLsbBuffers() const { display.copyGrayscaleLsbBuff
void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(display.getFrameBuffer()); } void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(display.getFrameBuffer()); }
void GfxRenderer::displayGrayBuffer() const { display.displayGrayBuffer(); } void GfxRenderer::displayGrayBuffer() const { display.displayGrayBuffer(fadingFix); }
void GfxRenderer::freeBwBufferChunks() { void GfxRenderer::freeBwBufferChunks() {
for (auto& bwBufferChunk : bwBufferChunks) { for (auto& bwBufferChunk : bwBufferChunks) {

View File

@ -37,6 +37,7 @@ class GfxRenderer {
HalDisplay& display; HalDisplay& display;
RenderMode renderMode; RenderMode renderMode;
Orientation orientation; Orientation orientation;
bool fadingFix = false; // Sunlight fading fix - turn off screen after refresh
int bezelCompensation = 0; // Pixels to add for bezel defect compensation int bezelCompensation = 0; // Pixels to add for bezel defect compensation
int bezelEdge = 0; // Which physical edge (0=bottom, 1=top, 2=left, 3=right in portrait) int bezelEdge = 0; // Which physical edge (0=bottom, 1=top, 2=left, 3=right in portrait)
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
@ -76,6 +77,9 @@ class GfxRenderer {
void setOrientation(const Orientation o) { orientation = o; } void setOrientation(const Orientation o) { orientation = o; }
Orientation getOrientation() const { return orientation; } Orientation getOrientation() const { return orientation; }
// Fading fix control
void setFadingFix(const bool enabled) { fadingFix = enabled; }
// Screen ops // Screen ops
int getScreenWidth() const; int getScreenWidth() const;
int getScreenHeight() const; int getScreenHeight() const;

View File

@ -28,7 +28,9 @@ EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) {
} }
} }
void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode) { einkDisplay.displayBuffer(convertRefreshMode(mode)); } void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen) {
einkDisplay.displayBuffer(convertRefreshMode(mode), turnOffScreen);
}
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) { void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen); einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
@ -48,4 +50,4 @@ void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); } void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
void HalDisplay::displayGrayBuffer() { einkDisplay.displayGrayBuffer(); } void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }

View File

@ -31,7 +31,7 @@ class HalDisplay {
void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
bool fromProgmem = false) const; bool fromProgmem = false) const;
void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH); void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false); void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
// Power management // Power management
@ -45,7 +45,7 @@ class HalDisplay {
void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer); void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer);
void cleanupGrayscaleBuffers(const uint8_t* bwBuffer); void cleanupGrayscaleBuffers(const uint8_t* bwBuffer);
void displayGrayBuffer(); void displayGrayBuffer(bool turnOffScreen = false);
private: private:
EInkDisplay einkDisplay; EInkDisplay einkDisplay;

@ -1 +1 @@
Subproject commit dede09001c4c7bc96bd3616716cdf80913f57658 Subproject commit be6ba1b62b1262929cded6ccdae774a098d33010

View File

@ -23,7 +23,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields // Increment this when adding new persisted settings fields
constexpr uint8_t SETTINGS_COUNT = 29; // 28 + bezelCompensationEdge constexpr uint8_t SETTINGS_COUNT = 30; // 29 + fadingFix
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace } // namespace
@ -72,6 +72,8 @@ bool CrossPointSettings::saveToFile() const {
serialization::writePod(outputFile, bezelCompensation); serialization::writePod(outputFile, bezelCompensation);
// Which physical edge needs bezel compensation // Which physical edge needs bezel compensation
serialization::writePod(outputFile, bezelCompensationEdge); serialization::writePod(outputFile, bezelCompensationEdge);
// Sunlight fading fix
serialization::writePod(outputFile, fadingFix);
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
outputFile.close(); outputFile.close();
@ -182,6 +184,9 @@ bool CrossPointSettings::loadFromFile() {
// Which physical edge needs bezel compensation // Which physical edge needs bezel compensation
readAndValidate(inputFile, bezelCompensationEdge, BEZEL_EDGE_COUNT); readAndValidate(inputFile, bezelCompensationEdge, BEZEL_EDGE_COUNT);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
// Sunlight fading fix
serialization::readPod(inputFile, fadingFix);
if (++settingsRead >= fileSettingsCount) break;
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
} while (false); } while (false);

View File

@ -144,6 +144,8 @@ class CrossPointSettings {
uint8_t hideBatteryPercentage = HIDE_NEVER; uint8_t hideBatteryPercentage = HIDE_NEVER;
// Long-press chapter skip on side buttons // Long-press chapter skip on side buttons
uint8_t longPressChapterSkip = 1; uint8_t longPressChapterSkip = 1;
// Sunlight fading compensation (0 = off, 1 = on)
uint8_t fadingFix = 0;
// System-wide display contrast (0 = normal, 1 = high) // System-wide display contrast (0 = normal, 1 = high)
uint8_t displayContrast = 0; uint8_t displayContrast = 0;
// Bezel compensation - extra margin for physical screen edge defects (0-10px) // Bezel compensation - extra margin for physical screen edge defects (0-10px)

View File

@ -15,7 +15,7 @@ namespace {
// Visibility condition for bezel edge setting (only show when compensation > 0) // Visibility condition for bezel edge setting (only show when compensation > 0)
bool isBezelCompensationEnabled() { return SETTINGS.bezelCompensation > 0; } bool isBezelCompensationEnabled() { return SETTINGS.bezelCompensation > 0; }
constexpr int displaySettingsCount = 9; constexpr int displaySettingsCount = 10;
const SettingInfo displaySettings[displaySettingsCount] = { const SettingInfo displaySettings[displaySettingsCount] = {
// Should match with SLEEP_SCREEN_MODE // Should match with SLEEP_SCREEN_MODE
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
@ -28,6 +28,7 @@ const SettingInfo displaySettings[displaySettingsCount] = {
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
SettingInfo::Enum("Sunlight Fading Fix", &CrossPointSettings::fadingFix, {"OFF", "ON"}),
SettingInfo::Value("Bezel Compensation", &CrossPointSettings::bezelCompensation, {0, 10, 1}), SettingInfo::Value("Bezel Compensation", &CrossPointSettings::bezelCompensation, {0, 10, 1}),
SettingInfo::Enum("Bezel Edge", &CrossPointSettings::bezelCompensationEdge, {"Bottom", "Top", "Left", "Right"}, SettingInfo::Enum("Bezel Edge", &CrossPointSettings::bezelCompensationEdge, {"Bottom", "Top", "Left", "Right"},
isBezelCompensationEnabled)}; isBezelCompensationEnabled)};

View File

@ -524,6 +524,8 @@ void loop() {
gpio.update(); gpio.update();
renderer.setFadingFix(SETTINGS.fadingFix);
if (Serial && millis() - lastMemPrint >= 10000) { if (Serial && millis() - lastMemPrint >= 10000) {
// Basic heap info // Basic heap info
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(), Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),