Add U8X8 carousel screens

This commit is contained in:
2026-05-26 22:07:04 +03:00
parent db5ae6eb6b
commit 68ee8c210b
6 changed files with 263 additions and 6 deletions
+31
View File
@@ -0,0 +1,31 @@
#ifndef CAROUSEL_H
#define CAROUSEL_H
#include <Arduino.h>
#include <U8g2lib.h>
#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
+22
View File
@@ -0,0 +1,22 @@
#ifndef DISPLAY_CONFIG_H
#define DISPLAY_CONFIG_H
#include <Arduino.h>
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
+16
View File
@@ -0,0 +1,16 @@
#ifndef SCREENS_H
#define SCREENS_H
#include <U8g2lib.h>
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
+104
View File
@@ -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<int>((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<int>(i * display_config::kIndicatorSpacing);
if (x >= 0 && x < display_config::kDisplayColumns) {
indicatorLine[x] = (i == currentIndex_) ? '*' : 'o';
}
}
display.drawString(0, display_config::kIndicatorRow, indicatorLine);
}
+56 -6
View File
@@ -2,17 +2,67 @@
#include <U8g2lib.h> #include <U8g2lib.h>
#include <Wire.h> #include <Wire.h>
#include "carousel.h"
#include "display_config.h"
#include "screens.h"
// SH1106 128x64 OLED over hardware I2C, rotated 180 degrees for flipped mounting. // 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() { void setup() {
Serial.begin(115200);
pinMode(display_config::kNextButtonPin, INPUT);
display.begin(); display.begin();
display.clearBuffer(); display.setFlipMode(1);
display.setFont(u8g2_font_ncenB08_tr); display.setFont(u8x8_font_chroma48medium8_r);
display.drawStr(18, 32, "Hello, world!"); display.clearDisplay();
display.sendBuffer(); carousel.begin(millis());
Serial.println("setup: display initialized");
Serial.print("setup: next button pin=");
Serial.println(display_config::kNextButtonPin);
} }
void loop() { 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);
} }
+34
View File
@@ -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 %");
}