From 68ee8c210b0911ed0003ef08d438d22b162d71fd Mon Sep 17 00:00:00 2001 From: Jarno Date: Tue, 26 May 2026 22:07:04 +0300 Subject: [PATCH] Add U8X8 carousel screens --- include/carousel.h | 31 ++++++++++++ include/display_config.h | 22 +++++++++ include/screens.h | 16 ++++++ src/carousel.cpp | 104 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 62 ++++++++++++++++++++--- src/screens.cpp | 34 +++++++++++++ 6 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 include/carousel.h create mode 100644 include/display_config.h create mode 100644 include/screens.h create mode 100644 src/carousel.cpp create mode 100644 src/screens.cpp diff --git a/include/carousel.h b/include/carousel.h new file mode 100644 index 0000000..cedc095 --- /dev/null +++ b/include/carousel.h @@ -0,0 +1,31 @@ +#ifndef CAROUSEL_H +#define CAROUSEL_H + +#include +#include + +#include "screens.h" + +class Carousel { + public: + Carousel(Screen *screens, size_t screenCount); + + void begin(unsigned long now); + void update(unsigned long now); + void next(unsigned long now); + void render(U8X8 &display) const; + bool consumeDirty(); + + private: + Screen *screens_; + size_t screenCount_; + size_t currentIndex_; + unsigned long nextAutoAdvanceAt_; + unsigned long manualModeUntil_; + bool dirty_; + + void scheduleNextAdvance(unsigned long now); + void drawIndicators(U8X8 &display) const; +}; + +#endif diff --git a/include/display_config.h b/include/display_config.h new file mode 100644 index 0000000..aa53158 --- /dev/null +++ b/include/display_config.h @@ -0,0 +1,22 @@ +#ifndef DISPLAY_CONFIG_H +#define DISPLAY_CONFIG_H + +#include + +namespace display_config { + +constexpr uint8_t kNextButtonPin = A4; + +constexpr unsigned long kAutoAdvanceIntervalMs = 10000; +constexpr unsigned long kManualHoldIntervalMs = 60000; +constexpr unsigned long kButtonDebounceMs = 150; +constexpr unsigned long kDebugLogIntervalMs = 500; + +constexpr uint8_t kDisplayColumns = 16; +constexpr uint8_t kDisplayRows = 8; +constexpr uint8_t kIndicatorRow = 7; +constexpr uint8_t kIndicatorSpacing = 2; + +} // namespace display_config + +#endif diff --git a/include/screens.h b/include/screens.h new file mode 100644 index 0000000..7472848 --- /dev/null +++ b/include/screens.h @@ -0,0 +1,16 @@ +#ifndef SCREENS_H +#define SCREENS_H + +#include + +struct Screen { + const char *title; + void (*render)(U8X8 &display); +}; + +void renderTimeScreen(U8X8 &display); +void renderWeatherScreen(U8X8 &display); +void renderTemperatureScreen(U8X8 &display); +void renderHumidityScreen(U8X8 &display); + +#endif diff --git a/src/carousel.cpp b/src/carousel.cpp new file mode 100644 index 0000000..941ebf5 --- /dev/null +++ b/src/carousel.cpp @@ -0,0 +1,104 @@ +#include "carousel.h" + +#include "display_config.h" + +Carousel::Carousel(Screen *screens, size_t screenCount) + : screens_(screens), + screenCount_(screenCount), + currentIndex_(0), + nextAutoAdvanceAt_(0), + manualModeUntil_(0), + dirty_(true) {} + +void Carousel::begin(unsigned long now) { + currentIndex_ = 0; + manualModeUntil_ = 0; + dirty_ = true; + scheduleNextAdvance(now); + Serial.print("carousel: begin index="); + Serial.print(currentIndex_); + Serial.print(" nextAutoAdvanceAt="); + Serial.println(nextAutoAdvanceAt_); +} + +void Carousel::update(unsigned long now) { + if (screenCount_ == 0) { + return; + } + + if (manualModeUntil_ != 0 && now < manualModeUntil_) { + return; + } + + if (now >= nextAutoAdvanceAt_) { + currentIndex_ = (currentIndex_ + 1) % screenCount_; + dirty_ = true; + scheduleNextAdvance(now); + Serial.print("carousel: auto advance index="); + Serial.print(currentIndex_); + Serial.print(" now="); + Serial.print(now); + Serial.print(" nextAutoAdvanceAt="); + Serial.println(nextAutoAdvanceAt_); + } +} + +void Carousel::next(unsigned long now) { + if (screenCount_ == 0) { + return; + } + + currentIndex_ = (currentIndex_ + 1) % screenCount_; + manualModeUntil_ = now + display_config::kManualHoldIntervalMs; + dirty_ = true; + scheduleNextAdvance(manualModeUntil_); + Serial.print("carousel: manual advance index="); + Serial.print(currentIndex_); + Serial.print(" now="); + Serial.print(now); + Serial.print(" manualModeUntil="); + Serial.print(manualModeUntil_); + Serial.print(" nextAutoAdvanceAt="); + Serial.println(nextAutoAdvanceAt_); +} + +void Carousel::render(U8X8 &display) const { + if (screenCount_ == 0) { + return; + } + + screens_[currentIndex_].render(display); + drawIndicators(display); +} + +bool Carousel::consumeDirty() { + const bool wasDirty = dirty_; + dirty_ = false; + return wasDirty; +} + +void Carousel::scheduleNextAdvance(unsigned long now) { + nextAutoAdvanceAt_ = now + display_config::kAutoAdvanceIntervalMs; +} + +void Carousel::drawIndicators(U8X8 &display) const { + char indicatorLine[display_config::kDisplayColumns + 1]; + + for (uint8_t i = 0; i < display_config::kDisplayColumns; ++i) { + indicatorLine[i] = ' '; + } + indicatorLine[display_config::kDisplayColumns] = '\0'; + + const int totalWidth = + static_cast((screenCount_ - 1) * display_config::kIndicatorSpacing) + 1; + const int startX = (display_config::kDisplayColumns - totalWidth) / 2; + + for (size_t i = 0; i < screenCount_; ++i) { + const int x = startX + static_cast(i * display_config::kIndicatorSpacing); + if (x >= 0 && x < display_config::kDisplayColumns) { + indicatorLine[x] = (i == currentIndex_) ? '*' : 'o'; + } + } + + display.drawString(0, display_config::kIndicatorRow, indicatorLine); +} diff --git a/src/main.cpp b/src/main.cpp index 8817214..0137425 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,17 +2,67 @@ #include #include +#include "carousel.h" +#include "display_config.h" +#include "screens.h" + // SH1106 128x64 OLED over hardware I2C, rotated 180 degrees for flipped mounting. -U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R2, U8X8_PIN_NONE); +U8X8_SH1106_128X64_NONAME_HW_I2C display(U8X8_PIN_NONE, SCL, SDA); + +Screen screens[] = { + {"Local Time", renderTimeScreen}, + {"Forecast", renderWeatherScreen}, + {"Temperature", renderTemperatureScreen}, + {"Humidity", renderHumidityScreen}, +}; + +Carousel carousel(screens, sizeof(screens) / sizeof(screens[0])); + +unsigned long lastButtonPressAt = 0; +int lastNextButtonState = LOW; void setup() { + Serial.begin(115200); + pinMode(display_config::kNextButtonPin, INPUT); + display.begin(); - display.clearBuffer(); - display.setFont(u8g2_font_ncenB08_tr); - display.drawStr(18, 32, "Hello, world!"); - display.sendBuffer(); + display.setFlipMode(1); + display.setFont(u8x8_font_chroma48medium8_r); + display.clearDisplay(); + carousel.begin(millis()); + + Serial.println("setup: display initialized"); + Serial.print("setup: next button pin="); + Serial.println(display_config::kNextButtonPin); } void loop() { - delay(1000); + const unsigned long now = millis(); + const int nextButtonState = digitalRead(display_config::kNextButtonPin); + + if (nextButtonState != lastNextButtonState) { + Serial.print("button: state changed to "); + Serial.print(nextButtonState); + Serial.print(" at "); + Serial.println(now); + } + + if (lastNextButtonState == HIGH && nextButtonState == LOW && + now - lastButtonPressAt >= display_config::kButtonDebounceMs) { + Serial.print("button: press detected at "); + Serial.println(now); + carousel.next(now); + lastButtonPressAt = now; + } + + lastNextButtonState = nextButtonState; + + carousel.update(now); + if (carousel.consumeDirty()) { + Serial.print("display: redraw at "); + Serial.println(now); + carousel.render(display); + } + + delay(100); } diff --git a/src/screens.cpp b/src/screens.cpp new file mode 100644 index 0000000..ef47881 --- /dev/null +++ b/src/screens.cpp @@ -0,0 +1,34 @@ +#include "screens.h" + +namespace { + +void clearContentRows(U8X8 &display) { + for (uint8_t row = 0; row < 7; ++row) { + display.clearLine(row); + } +} + +void drawScreenFrame(U8X8 &display, const char *title, const char *value) { + clearContentRows(display); + display.drawString(0, 0, title); + display.drawString(0, 2, "--------------"); + display.drawString(0, 4, value); +} + +} // namespace + +void renderTimeScreen(U8X8 &display) { + drawScreenFrame(display, "Local Time", "12:34"); +} + +void renderWeatherScreen(U8X8 &display) { + drawScreenFrame(display, "Forecast", "Sunny"); +} + +void renderTemperatureScreen(U8X8 &display) { + drawScreenFrame(display, "Temperature", "21.5 C"); +} + +void renderHumidityScreen(U8X8 &display) { + drawScreenFrame(display, "Humidity", "45 %"); +}