How to Use a 1.54 Inch 128x64 OLED with a Joystick
To get a 1.54 inch 128x64 oled display working with a joystick, you need to wire the SPI interface and read analog inputs from the joystick’s X and Y axes, then map those values to control what’s shown on the screen. The OLED uses a 128x64 pixel matrix with a monochrome driver like the SSD1306 or SH1106, which communicates over SPI at speeds up to 10 MHz. The joystick typically outputs two analog voltages (0–3.3V or 0–5V) and a digital switch signal. A common setup involves an Arduino Uno or ESP32, where the OLED’s CS, DC, MOSI, SCK, and RESET pins connect to specific digital pins, and the joystick’s VRx, VRy, and SW pins connect to analog inputs and a digital pin with a pull-up resistor. The display’s resolution is 128 columns by 64 rows, so each pixel is addressable individually, and the joystick’s movement can be used to draw a cursor, navigate menus, or select items. For example, when you push the joystick left, the X-axis voltage drops below 1.65V (on a 3.3V system), and your code can shift a cursor left by one pixel or one menu item. The joystick’s button, when pressed, sends a LOW signal to the digital pin, which can trigger an action like selecting an option or clearing the screen. The OLED’s SPI interface requires four data lines plus a reset line, and the joystick’s analog outputs need to be read with an ADC (analog-to-digital converter) that has at least 10-bit resolution for smooth movement. The 1.54 inch 128x64 oled display typically draws about 20 mA during operation, so it’s fine to power it from the microcontroller’s 3.3V pin. The joystick module itself draws negligible current, around 5 mA, so the whole setup can run off a USB port or a small battery pack. The key is to initialize the OLED with the correct SPI settings, read the joystick values in a loop, and update the display buffer only when the joystick position changes to avoid flicker. The OLED’s refresh rate can be set to 60 Hz, but for a joystick-driven interface, 30 Hz is often enough to keep the response crisp without wasting CPU cycles.
The hardware wiring is straightforward but requires attention to pin assignments. The OLED’s SPI pins are labeled: CS (chip select), DC (data/command), MOSI (master out slave in), SCK (serial clock), and RESET. The joystick module has five pins: GND, VCC, VRx, VRy, and SW. For an Arduino Uno, connect the OLED’s CS to digital pin 10, DC to pin 9, MOSI to pin 11 (hardware SPI), SCK to pin 13, and RESET to pin 8. The VCC goes to 3.3V, and GND to ground. The joystick’s VCC goes to 5V (or 3.3V if your module supports it), GND to ground, VRx to analog pin A0, VRy to analog pin A1, and SW to digital pin 2 with an internal pull-up resistor enabled. On an ESP32, the SPI pins are different: you can use VSPI with MOSI on GPIO 23, SCK on GPIO 18, CS on GPIO 5, DC on GPIO 17, and RESET on GPIO 16. The joystick’s analog pins connect to GPIO 32 and 33, which are ADC-capable pins. The ESP32’s ADC has 12-bit resolution, so joystick readings range from 0 to 4095, compared to the Arduino’s 0 to 1023. This higher resolution allows finer control, but you’ll need to map the values to 128x64 pixel coordinates. The OLED’s SPI speed should be set to 4 MHz or lower to avoid signal integrity issues, especially if you’re using long jumper wires. The joystick’s analog outputs are noisy, so adding a 100 nF capacitor between VRx and ground and between VRy and ground can smooth out the readings. The joystick’s switch is normally open and pulls to ground when pressed, so you need a pull-up resistor (10 kΩ is standard) or use the microcontroller’s internal pull-up, which is typically 20–50 kΩ. The internal pull-up is fine for most applications, but if you experience false triggers, add an external 10 kΩ resistor to VCC.
The software side involves initializing the OLED library, reading the joystick, and mapping the values to screen coordinates. The most common library for the SSD1306 OLED is the Adafruit SSD1306 library, which works with the Adafruit GFX library for drawing shapes, text, and bitmaps. For the SH1106 driver, you’ll need the Adafruit SH1106 library or a custom one. The initialization code sets up the SPI interface, defines the display dimensions (128x64), and clears the buffer. The joystick reading is done with analogRead() on Arduino or analogRead() on ESP32, which returns a value proportional to the voltage. The center position of the joystick is typically around 512 for Arduino (2.5V with 5V reference) or 2048 for ESP32 (1.65V with 3.3V reference). You need to add a dead zone around the center to prevent the cursor from drifting when the joystick is idle. A dead zone of ±50 for Arduino or ±200 for ESP32 works well. For example, if the X-axis reading is between 462 and 562, treat it as centered. If it’s below 462, move the cursor left; if above 562, move it right. The speed of movement depends on how far the joystick is pushed. You can implement a simple threshold system: if the reading is below 200, move fast; if between 200 and 462, move slow. The same logic applies to the Y-axis. The joystick’s button is read with digitalRead() on pin 2. When the button is pressed (LOW), you can toggle a menu selection, draw a pixel, or switch modes. The OLED’s buffer is updated with display.display() after each change, but you should avoid calling this too frequently because it takes about 1–2 ms per update. A better approach is to update the buffer only when the joystick position or button state changes, and use a timer to throttle updates to 30 Hz. This reduces CPU load and prevents screen flicker.
Practical applications for this setup include a menu system, a drawing pad, or a simple game. For a menu system, you can display a list of options like “Start,” “Settings,” and “About” on the OLED. The joystick moves a highlight bar up and down, and the button selects an option. The menu items are drawn using the GFX library’s drawRect() and setCursor() functions. The highlight bar is a filled rectangle that changes position based on the Y-axis reading. The number of items is limited by the screen’s height: with 64 pixels and a font height of 8 pixels (for a 5x7 font), you can fit 8 items, but you need some padding. A practical limit is 6 items with 10 pixels of spacing. The joystick’s Y-axis is mapped to an index from 0 to 5, and the highlight bar moves to the corresponding item. The button press triggers the selected action. For a drawing pad, the joystick controls a cursor that leaves a trail of pixels on the screen. The cursor position is stored as (x, y) coordinates, and each time the joystick moves, a pixel is set at the new position using drawPixel(). The button can be used to toggle drawing on and off, or to clear the screen. The cursor size can be changed by drawing a filled circle instead of a single pixel. For a game like Snake, the joystick controls the direction of the snake, and the OLED shows the game board. The snake’s body is a series of rectangles, and the food is a single pixel. The game loop reads the joystick, updates the snake’s position, and redraws the screen. The button can be used to pause or restart the game. The OLED’s 128x64 resolution is small but sufficient for simple games with low graphical demands.
Data and performance metrics are important for optimizing the setup. The OLED’s SPI interface has a maximum clock speed of 10 MHz, but practical tests show that 4 MHz is reliable with standard jumper wires. The display’s refresh rate is limited by the buffer update time. With the Adafruit library, a full buffer update takes about 1.5 ms at 4 MHz SPI clock. This means you can achieve a theoretical refresh rate of 666 Hz, but the joystick’s analog readings are much slower. The Arduino’s analogRead() takes about 100 µs per reading, so reading both axes and the button takes 300 µs. The total loop time is around 2 ms, giving a practical update rate of 500 Hz. However, the human eye can’t perceive changes faster than 60 Hz, so you can add a delay of 16 ms to cap the update rate at 60 Hz. The joystick’s analog output has a resolution of 10 bits on Arduino and 12 bits on ESP32, so the cursor movement can be as fine as 1/128th of the screen width per step. The dead zone reduces jitter, but you can also implement a moving average filter to smooth the readings. For example, take 10 samples and average them before mapping to coordinates. This adds latency but improves stability. The joystick’s switch has a debounce time of about 10 ms, so you need to add a debounce routine in software to avoid multiple triggers from a single press. A simple debounce waits 50 ms after the first press and ignores any changes during that time.
Common issues and fixes are worth covering. One problem is that the OLED doesn’t display anything after wiring. This is often due to incorrect SPI pin assignments or the display not being reset properly. The reset pin should be held low for at least 10 µs after power-up, then released. The Adafruit library handles this automatically if you specify the reset pin in the constructor. Another issue is that the joystick readings are erratic, especially when using long wires. This is caused by noise from the power supply or electromagnetic interference. Adding a 100 nF capacitor on each analog pin helps. Also, make sure the joystick’s VCC is connected to the same voltage as the microcontroller’s ADC reference. If the microcontroller uses 3.3V logic and the joystick is powered from 5V, the analog readings will be scaled incorrectly. Use a voltage divider or power the joystick from 3.3V if possible. The OLED’s contrast can be adjusted with the setContrast() function, which takes a value from 0 to 255. A value of 128 is typical, but you may need to increase it to 200 for better visibility in bright light. The OLED’s lifetime is rated at 50,000 hours for the organic material, but the glass substrate can break if the display is bent. Mount it on a breadboard or PCB with standoffs to avoid stress. The joystick module has a mechanical life of 100,000 cycles, so it’s durable for most projects.
Advanced techniques include using the OLED’s page addressing mode to update only parts of the screen, which reduces SPI traffic. The SSD1306 supports horizontal, vertical, and page addressing modes. In page mode, the screen is divided into 8 pages of 8 pixels each (64 rows / 8 = 8 pages). You can update a single page instead of the whole buffer, which is useful for a joystick-driven cursor that only changes a small area. For example, if the cursor moves one pixel, you only need to update the page containing that pixel. The library’s drawPixel() function already handles this efficiently, but you can optimize further by using the setPageAddress() and setColumnAddress() commands. Another technique is to use the OLED’s built-in charge pump to generate the negative voltage for the display. The charge pump can be enabled or disabled in software, and it affects power consumption. Enabling it increases current draw by about 5 mA but improves contrast. For battery-powered projects, you can disable the charge pump and use an external voltage regulator to save power. The joystick can also be used to control the OLED’s brightness by adjusting the contrast based on the joystick’s position. For example, map the X-axis to contrast values from 0 to 255, and the Y-axis to something else. This creates a dynamic interface where the user can adjust the display in real time.
Real-world examples show that this setup is used in DIY electronics projects like a mini oscilloscope, a text-based RPG game, or a weather station display. In a mini oscilloscope, the joystick controls the time base and voltage scale, while the OLED shows the waveform. The joystick’s button freezes the display for analysis. The OLED’s 128x64 resolution is enough to show a single waveform with decent detail. The sampling rate is limited by the microcontroller’s ADC, which can reach 10 kHz on Arduino with a fast ADC prescaler. The joystick’s analog inputs are used to set the trigger level and position. In a text-based RPG, the joystick moves a character through a dungeon, and the OLED shows the map. The map is a 2D array of tiles, and the joystick updates the player’s position. The button interacts with objects like doors or chests. The OLED’s small size means the map is limited to 16x8 tiles if each tile is 8x8 pixels, but you can scroll the view to explore larger areas. The joystick’s dead zone prevents accidental movement, and the debounce routine stops the button from double-triggering. In a weather station, the OLED shows temperature, humidity, and pressure, and the joystick cycles through different screens. The button switches between screens, and the joystick’s X-axis adjusts the brightness. The OLED’s SPI interface allows fast updates, so the weather data can refresh every second without flicker.
Power consumption data is useful for battery-powered designs. The OLED draws 20 mA at 3.3V with the charge pump enabled. The joystick draws 5 mA from the 5V rail. The Arduino Uno itself draws about 50 mA in idle mode. Total current is around 75 mA, which gives a runtime of 13 hours with a 1000 mAh battery. The ESP32 draws more, around 80 mA, so total current is 105 mA, giving 9.5 hours with the same battery. You can reduce power by using the OLED’s sleep mode, which drops current to 1 µA. The joystick can be powered down by disconnecting its VCC via a MOSFET, but this adds complexity. A simpler approach is to use a deep sleep mode on the microcontroller and wake up on a joystick interrupt. The ESP32 supports deep sleep with wake-up from GPIO pins, so you can use the joystick’s button to wake the system. This reduces average power to microamps when idle. The OLED’s display buffer can be stored in RAM, so it retains the last image when waking up. The joystick’s analog pins can be read after wake-up, but you need to wait for the ADC to stabilize, which takes about 1 ms.
The wiring diagram for this setup is simple: connect the OLED’s SPI pins to the microcontroller’s hardware SPI pins, and the joystick’s analog pins to ADC inputs. The button goes to a digital pin with a pull-up. The OLED’s CS pin is critical because multiple SPI devices can share the same bus. If you have other SPI devices, make sure they have different CS pins. The OLED’s DC pin distinguishes between commands and data, so it must be set correctly. The RESET pin is optional but recommended because it ensures the display initializes properly. The joystick’s GND and VCC should be connected to the same ground and power rails as the microcontroller to avoid ground loops. The OLED’s VCC should be 3.3V, but some modules accept 5V on the logic pins. Check the datasheet for your specific module. The 1.54 inch 128x64 oled display typically has a 3.3V logic level, but the SPI pins are 5V tolerant on many modules. If you’re using a 5V microcontroller, you can connect the OLED directly, but a level shifter is safer. The joystick module is usually 5V, but it works with 3.3V logic if the output voltage is within the ADC’s range. The ESP32’s ADC range is 0–3.3V, so a 5V joystick will saturate the ADC. Use a voltage divider (two resistors, e.g., 10 kΩ and 20 kΩ) to scale the joystick output to 3.3V. The button pin on the joystick is a digital signal, so it’s fine with 3.3V logic if the pull-up resistor is connected to 3.3V.
Testing the setup involves writing a simple sketch that reads the joystick and displays the coordinates on the OLED. The sketch initializes the OLED, clears the screen, and prints the X and Y values as text. The joystick’s button prints a message when pressed. This confirms that the wiring and libraries are working. Then, you can add the cursor movement logic. The cursor is a small crosshair drawn with drawLine() and drawCircle(). The crosshair moves with the joystick, and the button toggles a pixel on the screen. This is a good test for the dead zone and mapping functions. The cursor’s position should be clamped to the screen boundaries (0–127 for X, 0–63 for Y). If the cursor moves off-screen, the joystick readings are probably mapped incorrectly. Check the ADC range and the mapping function. The mapping function should be: x = map(analogRead(VRx), 0, 1023, 0, 127) for Arduino,