Silvia's Day Cycle Tracker
Silvia's Day Cycle Tracker
What I wanted to learn, and why it interested me: For week 2 of domain-specific skill building, I wanted to explore pixel displays because I was inspired by projects in week 1 that explored them. I was initially intrigued by the quality of the display and liked the pixelly charm to it. I wanted to be able to display bitmaps and images, and also learn about how different inputs could connect to the display in creative ways. I think I gravitate towards learning about components that allow for lots of customization when it comes to visuals and effects.
Final outcome: I was overall satisfied with the outcome and was able to really see the potential of the pixel displays when it comes to customization. I paired the pixel display with a photoresistor so that, depending on the brightness of light that the photoresistor measured, the pixel display would display five different images of various phases of the day, such as sunny day, day, sunset, dusk, and night. The light brightness would correlate to which image was displayed, with sunny day being the brightest and night being the darkest.
Images of final creative/exploratory output
Final Image of creative/exploratory output
An image of a medium white breadboard with a 64 x 128 pixel display, wires, photoresistor, resistor, and Arduino Uno, all set up.
Final video of creative/exploratory output (the pixel display only flickers on video, not in real life)
An video of a medium white breadboard with a 64 x 128 pixel display, wires, photoresistor, resistor, and Arduino Uno, all set up. A finger is hovering above the photoresistor, changing the brightness input. Depending on whether the finger is lifted or against the photoresistor, the pixel display flashes different images representing phases of the day (sunny day, day, sunset, dusk, night).
Process images from development towards creative/exploratory output
Wiring up the photoresistor
A close-up image of a white breadboard with wires and a pixel display, with the focus being on a photoresistor and 5.6 ohm resistor setup.
Soldering pins for the pixel display
A close-up image of a hand holding a pixel display showing it's left profile, with the focus being on pins that are soldered into the pixel display.
Process and reflection:
After completing the technical sequence, the first step I took in my process was wiring up the photoresistor. I wired it to pin A0 and added a 5.6k ohm resistor. After that, the wiring was already all complete. I then took it to programming the Arduino with the help of ChatGPT. The photoresistor would measure the light brightness, and depending on the lowest light value vs the highest light value, the level of the five phases would be set, and the display would change depending on those values. A lot of the process was getting the images to look how I wanted. It was fun to play around with different bitmaps and images, and also to play with text. Eventually, I landed on a theme I liked, which was very simple and analog-looking. I wanted each phase to be accurately represented in the image, and I also wanted each phase to be able to be distinguished, from covering the photoresistor with my finger to shining a flashlight on it. Overall, I learned a lot from this experience and was surprised again by the level of customization the pixel display had to offer. I would love to combine my new knowledge of pixel displays and LEDs from last week to create a project that is visually stunning. Using the small pins to solder on the pixel display was new to me, and it was a valuable insight I gained, moving forward to connect other components to my breadboard easily. I feel good about where I ended up and am excited to take my learnings forward.
Technical details
Electrical schematic
/*
60-223 Intro to Physical Computing, fall 2025
Domain-specific Skill Building exercise: Silvia's Day Cycle Tracker
Depending on the brightness of light that the photoresistor measured,
the pixel display will display five different images of various phases of the day,
such as sunny day, day, sunset, dusk, and night.
The light brightness will correlate to which image was displayed,
with sunny day being the brightest and night being the darkest.
Pin mapping:
Arduino pin | role | details
------------------------------
A0 input photoresistor
SCL output pixel display
SDA output pixel display
Silvia Shin, seshin@andrew.cmu.edu
Script with the assistance of ChatGPT!
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define I2C_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const int PHOTO_PIN = A0;
#define INVERT_READINGS 1 // flip if bright gives low readings
float ema = -1.0f;
const float EMA_ALPHA = 0.2;
unsigned long lastUpdate = 0;
const unsigned long UPDATE_MS = 200;
int T1 = 90, T2 = 180, T3 = 350, T4 = 700, HYST = 20;
int calLo = 0, calHi = 1023;
enum Phase { PH_NIGHT,
PH_DUSK,
PH_SUNSET,
PH_DAY,
PH_SUNNYDAY };
Phase currentPhase;
bool hasPhase = false;
// ---------------- Sensor read ----------------
int readBrightnessRaw() {
int v = analogRead(PHOTO_PIN);
#if INVERT_READINGS
v = 1023 - v;
#endif
return v;
}
// ---------------- Basic shapes ----------------
void drawLabel(const char* txt) {
int16_t x1, y1;
uint16_t w, h;
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);
display.setCursor(SCREEN_WIDTH - w - 2, SCREEN_HEIGHT - h - 1);
display.print(txt);
}
void drawHorizon() {
display.drawFastHLine(0, SCREEN_HEIGHT - 16, SCREEN_WIDTH, SSD1306_WHITE);
}
void drawSunRays(int cx, int cy, int r, int len, int count) {
for (int i = 0; i < count; i++) {
float a = i * (2 * 3.14159 / count);
int x1 = cx + (int)(r * cos(a));
int y1 = cy + (int)(r * sin(a));
int x2 = cx + (int)((r + len) * cos(a));
int y2 = cy + (int)((r + len) * sin(a));
display.drawLine(x1, y1, x2, y2, SSD1306_WHITE);
}
}
void drawMoonCrescentFilled(int cx, int cy, int r) {
// filled crescent moon
display.fillCircle(cx, cy, r, SSD1306_WHITE);
display.fillCircle(cx + 6, cy, r, SSD1306_BLACK);
}
// ---------------- Scenes ----------------
void sceneSunnyDay() {
display.clearDisplay();
drawHorizon();
int cx = SCREEN_WIDTH / 2, cy = SCREEN_HEIGHT / 2 - 10;
display.fillCircle(cx, cy, 15, SSD1306_WHITE);
drawSunRays(cx, cy, 15, 9, 16);
drawLabel("SUNNY DAY");
display.display();
}
void sceneDay() {
display.clearDisplay();
drawHorizon();
int cx = SCREEN_WIDTH / 2, cy = SCREEN_HEIGHT / 2 - 8;
display.fillCircle(cx, cy, 10, SSD1306_WHITE);
drawLabel("DAY");
display.display();
}
void sceneSunset() {
display.clearDisplay();
drawHorizon();
int x = SCREEN_WIDTH / 2, y = SCREEN_HEIGHT - 16, r = 12;
for (int yy = 0; yy <= r; yy++) {
int w = (int)sqrt((float)(r * r - yy * yy));
display.drawFastHLine(x - w, y - yy, 2 * w, SSD1306_WHITE);
}
drawLabel("SUNSET");
display.display();
}
void sceneDusk() {
display.clearDisplay();
drawHorizon();
int x = SCREEN_WIDTH / 2, y = SCREEN_HEIGHT - 16, r = 12;
// outlined (not filled) sunset arc
for (int yy = 0; yy <= r; yy++) {
int w = (int)sqrt((float)(r * r - yy * yy));
display.drawPixel(x - w, y - yy, SSD1306_WHITE);
display.drawPixel(x + w, y - yy, SSD1306_WHITE);
}
// filled crescent moon visible above
drawMoonCrescentFilled(SCREEN_WIDTH / 2 + 25, SCREEN_HEIGHT / 2 - 16, 8);
drawLabel("DUSK");
display.display();
}
void sceneNight() {
display.clearDisplay();
drawHorizon();
drawMoonCrescentFilled(SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 6, 11);
const uint8_t stars[][2] = { { 6, 6 }, { 16, 18 }, { 28, 9 }, { 42, 5 }, { 58, 15 }, { 72, 7 }, { 86, 20 }, { 100, 10 }, { 114, 4 }, { 120, 16 } };
for (uint8_t i = 0; i < sizeof(stars) / sizeof(stars[0]); i++)
display.drawPixel(stars[i][0], stars[i][1], SSD1306_WHITE);
drawLabel("NIGHT");
display.display();
}
// ---------------- Phase logic ----------------
Phase classifyPhase(int v) {
if (!hasPhase) {
hasPhase = true;
if (v < T1) return PH_NIGHT;
else if (v < T2) return PH_DUSK;
else if (v < T3) return PH_SUNSET;
else if (v < T4) return PH_DAY;
else return PH_SUNNYDAY;
}
switch (currentPhase) {
case PH_NIGHT:
if (v > T1 + HYST) return PH_DUSK;
return PH_NIGHT;
case PH_DUSK:
if (v > T2 + HYST) return PH_SUNSET;
if (v < T1 - HYST) return PH_NIGHT;
return PH_DUSK;
case PH_SUNSET:
if (v > T3 + HYST) return PH_DAY;
if (v < T2 - HYST) return PH_DUSK;
return PH_SUNSET;
case PH_DAY:
if (v > T4 + HYST) return PH_SUNNYDAY;
if (v < T3 - HYST) return PH_SUNSET;
return PH_DAY;
case PH_SUNNYDAY:
if (v < T4 - HYST) return PH_DAY;
return PH_SUNNYDAY;
}
return PH_DAY;
}
// ---------------- Auto calibration ----------------
void autoCalibrate() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Calibrating... (~3s)"));
display.println(F("Wave hand + light"));
display.display();
int lo = 1023, hi = 0;
unsigned long t0 = millis();
while (millis() - t0 < 3000) {
int r = readBrightnessRaw();
lo = min(lo, r);
hi = max(hi, r);
delay(5);
}
if (hi - lo < 50) {
lo = max(0, lo - 25);
hi = min(1023, hi + 25);
}
int pad = max(10, (hi - lo) / 12);
calLo = max(0, lo - pad);
calHi = min(1023, hi + pad);
int span = max(1, calHi - calLo);
T1 = calLo + span * 0.20;
T2 = calLo + span * 0.40;
T3 = calLo + span * 0.60;
T4 = calLo + span * 0.80;
display.clearDisplay();
display.setCursor(0, 0);
display.println(F("Calibrated!"));
display.display();
delay(800);
}
// ---------------- Setup / Loop ----------------
void setup() {
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDRESS);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Day/Night OLED"));
display.display();
delay(700);
autoCalibrate();
}
void loop() {
int raw = readBrightnessRaw();
if (ema < 0) ema = raw;
else ema = EMA_ALPHA * raw + (1 - EMA_ALPHA) * ema;
int val = (int)ema;
Phase newPhase = classifyPhase(val);
unsigned long now = millis();
if (newPhase != currentPhase || (now - lastUpdate) > UPDATE_MS) {
currentPhase = newPhase;
lastUpdate = now;
switch (currentPhase) {
case PH_SUNNYDAY: sceneSunnyDay(); break;
case PH_DAY: sceneDay(); break;
case PH_SUNSET: sceneSunset(); break;
case PH_DUSK: sceneDusk(); break;
case PH_NIGHT: sceneNight(); break;
}
}
delay(10);
}