Add U8X8 carousel screens
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 %");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user