Merge pull request #3180 from fdlamotte/thinknode_m8
thinknode_m8: initial support adds some functionalities to ui_task and epddisplay: epd display, - supports screen off, and clear screen - supports forcing full refresh every N partial refreshes (off by default, configured to 60 for M8) ui_task - UI_SHOW_CLOCK flag to display clock on first screen - rotary button can act like joystick (with regards to Enter key), defaults to old config for existing nodes - screen off on long press when using rotary button (very handy for the watch)
This commit is contained in:
15 files changed
+709
-13
No files matched your search
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
virtual bool isOn() = 0;
|
||||
virtual bool isEink() { return false; } // default to non-eink, override in eink drivers
|
||||
virtual void forceFullRefresh() {} // next refresh will be full for eink
|
||||
virtual void turnOn() = 0;
|
||||
virtual void turnOff() = 0;
|
||||
virtual void clear() = 0;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
SPIClass SPI1 = SPIClass(FSPI);
|
||||
#endif
|
||||
|
||||
#ifndef EPD_WASHING_MACHINE_CYCLES
|
||||
#define EPD_WASHING_MACHINE_CYCLES 0
|
||||
#endif
|
||||
|
||||
// Color scheme
|
||||
ColorVal UIColor::window_bkg = GxEPD_WHITE;
|
||||
ColorVal UIColor::title_bkg = GxEPD_WHITE;
|
||||
@@ -25,7 +29,6 @@ ColorVal UIColor::popup_bkg = GxEPD_WHITE;
|
||||
ColorVal UIColor::popup_txt = GxEPD_BLACK;
|
||||
ColorVal UIColor::corp_blue = GxEPD_BLACK;
|
||||
|
||||
|
||||
bool GxEPDDisplay::begin() {
|
||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
#ifdef ESP32
|
||||
@@ -36,15 +39,27 @@ bool GxEPDDisplay::begin() {
|
||||
display.init(115200, true, 2, false);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
setTextSize(1); // Default to size 1
|
||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.display(true);
|
||||
display.setFullWindow();
|
||||
|
||||
for (int i = 0; i < EPD_WASHING_MACHINE_CYCLES; i++) {
|
||||
display.fillScreen(GxEPD_BLACK);
|
||||
display.display(false);
|
||||
delay(2000);
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.display(false);
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||
resetPartialRefreshCounter();
|
||||
|
||||
#if DISP_BACKLIGHT
|
||||
digitalWrite(DISP_BACKLIGHT, LOW);
|
||||
pinMode(DISP_BACKLIGHT, OUTPUT);
|
||||
#endif
|
||||
_init = true;
|
||||
_isOn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,7 +70,9 @@ void GxEPDDisplay::turnOn() {
|
||||
#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN)
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH);
|
||||
#endif
|
||||
_isOn = true;
|
||||
if (!_isOn) {
|
||||
_isOn = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::turnOff() {
|
||||
@@ -65,6 +82,13 @@ void GxEPDDisplay::turnOff() {
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW);
|
||||
#endif
|
||||
_isOn = false;
|
||||
// do full refresh before powering off to clear screen
|
||||
// no full refresh needed at wakeup
|
||||
display.clearScreen(0xFF); // Clears microcontroller side RAM
|
||||
display.writeScreenBuffer(0xFF); // Forces 0xFF (White) into the display controller's history registers
|
||||
resetPartialRefreshCounter();
|
||||
last_display_crc_value=0;
|
||||
display.hibernate();
|
||||
}
|
||||
|
||||
void GxEPDDisplay::clear() {
|
||||
@@ -77,6 +101,16 @@ void GxEPDDisplay::startFrame(ColorVal bkg) {
|
||||
display.fillScreen(bkg);
|
||||
display.setTextColor(_curr_color = UIColor::primary_txt);
|
||||
display_crc.reset();
|
||||
if (_cycles_before_full_refresh != 0) {
|
||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||
} else {
|
||||
// forces a full wipe of the screen ...
|
||||
display.clearScreen(0xFF); // Clears microcontroller side RAM
|
||||
display.writeScreenBuffer(0xFF); // Forces 0xFF (White) into the display controller's history registers
|
||||
// we'll need a partial refresh after that (whatever crc value is)
|
||||
last_display_crc_value = 0;
|
||||
resetPartialRefreshCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setTextSize(int sz) {
|
||||
@@ -178,9 +212,13 @@ uint16_t GxEPDDisplay::getTextWidth(const char* str) {
|
||||
}
|
||||
|
||||
void GxEPDDisplay::endFrame() {
|
||||
if (_isOn == false) return;
|
||||
uint32_t crc = display_crc.finalize();
|
||||
if (crc != last_display_crc_value) {
|
||||
display.display(true);
|
||||
last_display_crc_value = crc;
|
||||
if (_cycles_before_full_refresh > 0) {
|
||||
_cycles_before_full_refresh--;
|
||||
}
|
||||
}
|
||||
last_display_crc_value = crc;
|
||||
}
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
#ifndef EINK_MAX_PARTIAL_REFRESH
|
||||
// 60 to prevent ghosting when refreshing every minute ...
|
||||
// set to -1 to disable the counter
|
||||
#define EINK_MAX_PARTIAL_REFRESH -1
|
||||
#endif
|
||||
|
||||
class GxEPDDisplay : public DisplayDriver {
|
||||
|
||||
#if defined(EINK_DISPLAY_MODEL)
|
||||
@@ -36,6 +42,8 @@ class GxEPDDisplay : public DisplayDriver {
|
||||
uint16_t _curr_color;
|
||||
CRC32 display_crc;
|
||||
int last_display_crc_value = 0;
|
||||
int _cycles_before_full_refresh;
|
||||
int max_partial_refresh = EINK_MAX_PARTIAL_REFRESH; // so this can be changed by an option after
|
||||
|
||||
public:
|
||||
#if defined(EINK_DISPLAY_MODEL)
|
||||
@@ -48,6 +56,8 @@ public:
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
bool isEink() override { return true; }
|
||||
void forceFullRefresh() override { _cycles_before_full_refresh = 0; };
|
||||
void resetPartialRefreshCounter() { _cycles_before_full_refresh = max_partial_refresh; };
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
|
||||
@@ -25,8 +25,8 @@ class MomentaryButton {
|
||||
public:
|
||||
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true);
|
||||
MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold);
|
||||
void begin();
|
||||
int check(bool repeat_click=false); // returns one of BUTTON_EVENT_*
|
||||
virtual void begin();
|
||||
virtual int check(bool repeat_click=false); // returns one of BUTTON_EVENT_*
|
||||
void cancelClick(); // suppress next BUTTON_EVENT_CLICK (if already in DOWN state)
|
||||
uint8_t getPin() { return _pin; }
|
||||
bool isPressed() const;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "RotaryInputGPIO.h"
|
||||
|
||||
bool RotaryInputGPIO::begin() {
|
||||
|
||||
if (_pin_a >= 0) {
|
||||
pinMode(_pin_a, _pull_a ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
if (_pin_b >= 0) {
|
||||
pinMode(_pin_b, _pull_b ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
|
||||
b_prec = digitalRead(_pin_b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RotaryInputEvent RotaryInputGPIO::poll() {
|
||||
RotaryInputEvent ev = RotaryInputEvent::None;
|
||||
bool a = digitalRead(_pin_a);
|
||||
bool b = digitalRead(_pin_b);
|
||||
|
||||
// this is the simplest scheme and it works well
|
||||
// for thinknode M8, just read A when B rises ;)
|
||||
if (!b_prec && b) { // rising edge of A
|
||||
if (a) {
|
||||
ev = RotaryInputEvent::Next;
|
||||
} else {
|
||||
ev = RotaryInputEvent::Prev;
|
||||
}
|
||||
}
|
||||
b_prec = b;
|
||||
|
||||
return ev;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "RotaryInput.h"
|
||||
|
||||
#define BUTTON_EVENT_UP 5
|
||||
#define BUTTON_EVENT_DOWN 6
|
||||
|
||||
class RotaryInputGPIO: public RotaryInput {
|
||||
int8_t _pin_a;
|
||||
bool _pull_a;
|
||||
int8_t _pin_b;
|
||||
bool _pull_b;
|
||||
bool b_prec;
|
||||
public:
|
||||
RotaryInputGPIO(int8_t pin_a, int8_t pin_b, bool pull_a=false, bool pull_b=false): _pin_a(pin_a), _pin_b(pin_b), _pull_a(pull_a), _pull_b(pull_b) {}
|
||||
|
||||
bool begin() override;
|
||||
RotaryInputEvent poll() override;
|
||||
bool isReady() const override { return true;}
|
||||
};
|
||||
Reference in new issue
Block a user