OLED Screens & Touch Sensors
OLED Screens & Touch Sensors
What I wanted to learn, and why it interested me:
I wanted to try my hand at using an OLED screen as it is a versatile display that I could utilize for a number of projects. I initially came up with the idea to make a small, portable pomodoro timer (25-minute timer w/ 5-minute breaks) that I can gift to a friend of mine. A touch sensor seemed like the best fit for this kind of device as I wanted every interaction with the timer to be intentional whereas a button could sometimes be pressed by accident. The interesting thing about this project is that it will challenge me to experiment with state machines, as I need to be able to reliably switch between lots of screens. I hope that this will make me more comfortbale with writing logically sound code for the future.
Final outcome:
To nobody's surprise, a project is always easier said than done. I ran into lots of problems with trying to make the screens work the way I imagined them to work, all trivial but taking me a long time to figure out. I was able to get to a point where I can switch between screens based on pre-set interactions with my touch sensor, setting the foundation for my timer idea. I am excited to extend this further and be able to make this into a tangible product that I don't have to have connected to a clunky USB cable.
Images of final creative/exploratory output
Closeup image of the device, with the OLED screen at its default screen of displaying "hello"s of varying sizes that scroll.
Video of working device. I go through all the transitions that there could exist between the default "hello" scrolling screen, the timer screen, and the hamster bitmap screen.
Process images from development towards creative/exploratory output
I used an LCD image converter to turn an image of my choice (a hamster meme that I found online) that is 128x64 pixels into C code. The resizing of the image was done on Windows Paint.
Previous code logic for switching between the default screen and the timer screen. I thought that having a timer just counting upwards wouldn't require more than two variables or much code, but I was wrong. I have a greater appreciation for simpler projects now.
Process and reflection:
My process started with first figuring out how the OLED display worked. It was my first time working with an I2C device and I was surprised to learn each piece of hardware had a unique "address" that signals would be delivered to from the Arduino. From looking up tutorials online, I found a sketch that would let me know the I2C address of my 128x64 OLED. Instead of the 0x3D address that the part was supposed to have, I had 0x3C. Without the right address, my sketch would not be able to execute properly. After going through some faulty hardware pieces (loose pins, etc), I simply wired up the touch sensor to the Arduino which acts the same way as a push button does. After confirming the example code ran as expected, I decided to then understand how to go back and forth between two, then three screens using various inputs. Loops in Arduino confused me greatly as I was trying to wrap my head around the fact that if I did not place the right conditionals in the right spot, a code section would be running over and over, tens of hundreds of times a second. After guidance from the professor, I figured out how to apply this smooth transition to multiple screens. A surprise I had was that if I did not set a text color of my display, it would run as black text on a black screen. This made me think that my OLED screen had suddenly stopped working, when in reality I simply just hadn't made my outputs visible. All in all, I'm happy with how I finished my project. I used an LCD image converter, thought deeper about my code, and developed a sketch that I can expand on for future projects.
Technical details
Electrical schematic. The setup for this device is very straightforward as there are no resistors, transistors, or the like needed.
/*
60-223 Intro to Physical Computing, Spring 2026
Domain-specific Skill Building exercise: OLED Screen Experimenting
This sketch lets you display scrolling text, a bitmap, and a timer on an OLED screen utilizing inputs from a touch sensor. The OLED first displays a default screen of scrolling text. Two quick taps in succession will turn the screen to a bitmap of a hamster drawing, and two taps on that screen will revert the display back to default. A two-second hold to the sensor will activate a timer that counts up how many seconds you've held the sensor for. Letting go will return the screen to default.
Pin mapping:
Arduino pin | role | details
------------------------------
A4 bidirectional SDA I2C Pin
A5 bidirectional SCL I2C Pin
4 input Touch Sensor
Released to the public domain by the author, January 2024
Robert Zacharias, rzachari@andrew.cmu.edu
*/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
static const unsigned char PROGMEM image_data_hamster[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x01, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x41, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x89, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x17, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfb, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
0xff, 0xff, 0xfe, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xe7, 0xff,
0xff, 0xff, 0xef, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xfb, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x7b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef,
0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7,
0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb,
0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb,
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb,
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xfb,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7
};
int touchPin = 4;
// -------- Timing Variables --------
unsigned long holdStartTime = 0;
unsigned long lastSecondMark = 0;
unsigned long lastTapTime = 0;
bool isHolding = false;
bool countingStarted = false;
bool showingScrollScreen = false;
bool tappedTwice = false;
bool showingHamster = false;
bool lastTouchState = LOW;
const unsigned long doubleTapWindow = 400;
int secondsHeld = 0;
int tapsRegistered = 0;
void scrollScreen() {
display.clearDisplay();
// SMALL
display.setTextSize(1);
display.setCursor(0, 0);
display.println("hello");
// MEDIUM
display.setTextSize(2);
display.setCursor(0, 12);
display.println("hello");
// LARGE
display.setTextSize(3);
display.setCursor(0, 32);
display.println("HELLO");
display.display();
display.startscrollright(0x00, 0x0F); // THEN start scrolling
}
void setup() {
Serial.begin(9600);
delay(500);
pinMode(touchPin, INPUT);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
scrollScreen();
}
void displayHamster() {
display.clearDisplay();
display.drawBitmap(0, 0, image_data_hamster, 128, 64, 1);
display.display();
}
void loop() {
bool touchState = digitalRead(touchPin);
unsigned long currentTime = millis();
// =========================
// DOUBLE TAP DETECTION
// =========================
if (touchState == HIGH && lastTouchState == LOW) {
if (currentTime - lastTapTime <= doubleTapWindow) {
if (showingHamster){
showingHamster = false;
showingScrollScreen = true;
scrollScreen();
}
else {
display.stopscroll();
displayHamster();
showingHamster = true;
showingScrollScreen = false;
}
}
lastTapTime = currentTime;
}
// =========================
// HOLD DETECTION
// =========================
if (touchState == HIGH) {
if (!isHolding) {
isHolding = true;
holdStartTime = currentTime;
lastSecondMark = currentTime;
}
if (!countingStarted && currentTime - holdStartTime >= 2000) {
countingStarted = true;
secondsHeld = 0;
lastSecondMark = currentTime;
}
if (countingStarted && currentTime - lastSecondMark >= 1000) {
lastSecondMark += 1000;
secondsHeld++;
}
if (countingStarted) {
showingScrollScreen = false;
showingHamster = false;
display.clearDisplay();
display.stopscroll();
display.setTextSize(2);
display.setCursor(0, 15);
display.print(secondsHeld);
display.print("s");
if (secondsHeld == 5){
display.setTextSize(1);
display.setCursor(0,30);
display.println("You've held the button for five seconds!");
}
display.display();
}
}
// =========================
// RELEASE
// =========================
if (touchState == LOW && lastTouchState == HIGH) {
isHolding = false;
countingStarted = false;
secondsHeld = 0;
if (!showingScrollScreen && !showingHamster) {
showingScrollScreen = true;
scrollScreen();
}
}
lastTouchState = touchState;
}