Arduino Potentiometer Compass Display Project - Complete Wokwi Tutorial

Arduino OLED Compass Simulation | Potentiometer + SSD1306 Display | MakeMindz

Arduino + OLED + Potentiometer

Compass Simulation
on SSD1306 OLED Display

Rotate a potentiometer to simulate compass headings 0°–360°. Watch smooth-scrolling direction labels animate live on a 128×32 OLED screen.

Arduino UNO R3 SSD1306 OLED U8g2 Library I2C analogRead Wokwi Sim
Run Free Simulation

Project Overview

This project demonstrates how to create a digital compass simulation using the Arduino UNO R3, a rotary potentiometer, and an OLED display. By rotating the potentiometer, you simulate compass headings (0°–360°) and display cardinal and intercardinal directions — N, NE, E, SE, S, SW, W, NW — on a 128×32 OLED screen with a smooth-scrolling, animated compass scale.

This is an excellent beginner-to-expert bridge project for learning analog input mapping, I2C display control, PROGMEM bitmap storage, and UI animation techniques in embedded systems.

No real magnetometer is needed — the potentiometer simulates compass heading, making this perfect for learning display concepts before adding real sensors.

Components Required

🖥️
Arduino UNO R3
Main microcontroller
📟
OLED Display
SSD1306, 128×64, I2C
🎛️
Potentiometer
10kΩ Rotary type
🔌
Jumper Wires
Male-to-male assorted
🔋
USB Cable
Type-A to Type-B

Circuit Connections

OLED Display (I2C – SSD1306)

OLED PinArduino PinWire Color
GNDGNDBlack
VCC5VRed
SCLA5Green
SDAA4Blue

Potentiometer (10kΩ)

Pot PinArduino PinWire Color
Left PinGNDBlack
Middle Pin (Wiper)A0Green
Right Pin5VRed
ARDUINO UNO 5V GND A0 A4 A5 SSD1306 OLED 128×64 I2C N NE E SE 180° GND VCC SCL SDA 10kΩ POT GND SIG VCC GND 5V Signal Data Dashed = physical wire connections

How It Works

The potentiometer acts as a variable resistor forming a voltage divider. The Arduino reads this as an analog value from 0 to 1023 on pin A0, which is then mapped to compass degrees 0–360° using the map() function.

Step 01
Analog Read
analogRead(A0) returns a value between 0 and 1023 depending on the potentiometer rotation.
Step 02
Map to Degrees
map(val, 0, 1023, 0, 360) scales the raw reading to compass heading in degrees.
Step 03
Calculate X Offset
The offset for all tick marks is computed: xpos_offset = round((360 - degrees) / 360.0 * 240.0). Each compass cycle spans 240 pixels (24 ticks × 10px each).
Step 04
Power Function Easing
A pow(x, 2.5) easing function is applied so direction labels appear to scale up as they reach the center of the screen — creating a perspective/depth effect.
Step 05
Draw with U8g2
The U8g2 library renders tick marks, bitmap labels (stored in PROGMEM), a bubble overlay with current degrees, and a center indicator line on every frame.

Wokwi Simulation Setup

You can run this project in your browser for free using Wokwi — no hardware needed.

Step 01
Open Wokwi
Go to wokwi.com and sign in or continue as a guest.
Step 02
Create New Project
Click "New Project" and select Arduino UNO as the board.
Step 03
Add Components
From the parts panel, add an SSD1306 OLED (I2C) and a Potentiometer.
Step 04
Paste diagram.json
Open the diagram.json tab and replace its contents with the JSON below — this wires everything automatically.
Step 05
Paste Code & Run
Paste the full Arduino sketch into the sketch.ino tab, then click ▶ Run. Drag the potentiometer knob to see the compass move!

🚀 Jump Straight In

Click below to open the fully pre-wired simulation — no setup needed.

Open Free Simulation on Wokwi

Install Required Libraries

In Arduino IDE: go to Sketch → Include Library → Manage Libraries and search for:

📦U8g2 by oliver — install the latest version
🔌Wire — built-in, no install needed
The code uses the 128×32 OLED initialization by default. If your display is 128×64, uncomment the 128×64 line and comment out the 128×32 line in the code.

Diagram JSON

Paste this into the diagram.json tab in Wokwi. It automatically places and wires all components.
diagram.json
{
  "version": 1,
  "author": "https://www.youtube.com/upir_upir",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-uno", "id": "uno", "top": 0, "left": 0, "attrs": {} },
    {
      "type": "board-ssd1306",
      "id": "oled1",
      "top": 233.54,
      "left": 153.83,
      "attrs": { "i2cAddress": "0x3c" }
    },
    { "type": "wokwi-potentiometer", "id": "pot1", "top": 190.7, "left": 297.4, "attrs": {} }
  ],
  "connections": [
    [ "oled1:GND", "uno:GND.3", "black",  ["v-21.22", "h-17.76", "v-3.43"] ],
    [ "oled1:VCC", "uno:5V",    "red",    ["v-13.71", "h-43.95", "v-4.94"] ],
    [ "oled1:SCL", "uno:A5",   "green",  ["v-18.22", "h42.53",  "v-1.5"]  ],
    [ "oled1:SDA", "uno:A4",   "blue",   ["v-25.94", "h21.97",  "v-4.72"] ],
    [ "pot1:SIG",  "uno:A0",   "green",  ["v19.2",   "h-58",    "v-115.2","h-67.2","v9.6"] ],
    [ "pot1:GND",  "uno:GND.3","black",  ["v9.6",    "h-38.4",  "v-124.8","h-96"]  ],
    [ "pot1:VCC",  "uno:5V",   "red",    ["v19.2",   "h37.6",   "v-144",  "h-220.8","v9.6"] ]
  ],
  "dependencies": {}
}

Full Arduino Code

The sketch uses the U8g2 library to render the animated compass. Bitmap arrays for each direction label are stored in PROGMEM to save RAM.

sketch.ino — Arduino C++
#include <Arduino.h>
#include <U8g2lib.h>   // u8g2 library for OLED graphics
#include <Wire.h>      // required for I2C communication

// ── DISPLAY INIT ──────────────────────────────────────────────
// Uncomment the line that matches your display size:
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // 128x64
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // 128x32

// ── BITMAP IMAGES (stored in PROGMEM to save RAM) ─────────────

// Bubble overlay images (fill + outline)
const unsigned char epd_bitmap_img_bubble_fill[] PROGMEM = {
  0xfc,0xff,0xff,0x00, 0xfe,0xff,0xff,0x01, 0xfe,0xff,0xff,0x01, 0xfe,0xff,0xff,0x01,
  0xfe,0xff,0xff,0x01, 0xfe,0xff,0xff,0x01, 0xfe,0xff,0xff,0x01, 0xfe,0xff,0xff,0x01,
  0xfc,0xff,0xff,0x00, 0x00,0xfc,0x00,0x00, 0x00,0x78,0x00,0x00, 0x00,0x30,0x00,0x00,
  0x00,0x00,0x00,0x00
};
const unsigned char epd_bitmap_img_bubble_outline[] PROGMEM = {
  0xfe,0xff,0xff,0x01, 0xff,0xff,0xff,0x03, 0xff,0xff,0xff,0x03, 0xff,0xff,0xff,0x03,
  0xff,0xff,0xff,0x03, 0xff,0xff,0xff,0x03, 0xff,0xff,0xff,0x03, 0xff,0xff,0xff,0x03,
  0xfe,0xff,0xff,0x01, 0xfc,0xff,0xff,0x00, 0x00,0xfc,0x00,0x00, 0x00,0x78,0x00,0x00,
  0x00,0x30,0x00,0x00
};

// ── Direction label bitmaps (4 size variants each, for scaling effect) ──
// N (0°) - 4 variants
const unsigned char epd_bitmap_000_letter_n_0[] PROGMEM = {
  0x00,0x48,0x00,0x00, 0x00,0x48,0x00,0x00, 0x00,0x58,0x00,0x00, 0x00,0x58,0x00,0x00,
  0x00,0x58,0x00,0x00, 0x00,0x58,0x00,0x00, 0x00,0x68,0x00,0x00, 0x00,0x68,0x00,0x00,
  0x00,0x68,0x00,0x00, 0x00,0x68,0x00,0x00, 0x00,0x48,0x00,0x00, 0x00,0x48,0x00,0x00};
const unsigned char epd_bitmap_000_letter_n_1[] PROGMEM = {
  0x00,0x88,0x00,0x00, 0x00,0x98,0x00,0x00, 0x00,0x98,0x00,0x00, 0x00,0x98,0x00,0x00,
  0x00,0x98,0x00,0x00, 0x00,0xa8,0x00,0x00, 0x00,0xa8,0x00,0x00, 0x00,0xa8,0x00,0x00,
  0x00,0xc8,0x00,0x00, 0x00,0xc8,0x00,0x00, 0x00,0xc8,0x00,0x00, 0x00,0x88,0x00,0x00};
const unsigned char epd_bitmap_000_letter_n_2[] PROGMEM = {
  0x00,0x8c,0x01,0x00, 0x00,0x9c,0x01,0x00, 0x00,0x9c,0x01,0x00, 0x00,0xbc,0x01,0x00,
  0x00,0xbc,0x01,0x00, 0x00,0xbc,0x01,0x00, 0x00,0xec,0x01,0x00, 0x00,0xec,0x01,0x00,
  0x00,0xec,0x01,0x00, 0x00,0xcc,0x01,0x00, 0x00,0xcc,0x01,0x00, 0x00,0x8c,0x01,0x00};
const unsigned char epd_bitmap_000_letter_n_3[] PROGMEM = {
  0x00,0x03,0x03,0x00, 0x00,0x07,0x03,0x00, 0x00,0x0f,0x03,0x00, 0x00,0x0f,0x03,0x00,
  0x00,0x1b,0x03,0x00, 0x00,0x33,0x03,0x00, 0x00,0x33,0x03,0x00, 0x00,0x63,0x03,0x00,
  0x00,0xc3,0x03,0x00, 0x00,0xc3,0x03,0x00, 0x00,0x83,0x03,0x00, 0x00,0x03,0x03,0x00};

// (NE, E, SE, S, SW, W, NW bitmaps follow same pattern — see full source)
// For brevity, remaining bitmaps are defined identically to the full code above.
// Copy the complete bitmap arrays from the full code listing.

// ── BITMAP INDEX ARRAY ────────────────────────────────────────
const unsigned char* character_bitmaps[32] = {
  epd_bitmap_000_letter_n_0, epd_bitmap_000_letter_n_1,
  epd_bitmap_000_letter_n_2, epd_bitmap_000_letter_n_3,
  /* NE_0..3, E_0..3, SE_0..3, S_0..3, SW_0..3, W_0..3, NW_0..3 */
};

// ── GLOBAL VARIABLES ──────────────────────────────────────────
int   compass_degrees;
char  buffer[20];
int   xpos_offset, xpos_with_offset;
float xpos_final;
int   str_width;

int   labels_count     = 3;   // 4 size variants per label (0..3)
int   label_display    = 0;
int   SHOW_SCALED_LABEL = 1;  // 1 = bitmap labels, 0 = font labels

char *compass_labels[] = {"N","NE","E","SE","S","SW","W","NW","N"};

// ── SETUP ─────────────────────────────────────────────────────
void setup() {
  u8g2.begin();
  pinMode(A0, INPUT);
}

// ── MAIN LOOP ─────────────────────────────────────────────────
void loop() {

  // Read potentiometer → map to 0–360°
  compass_degrees = map(analogRead(A0), 0, 1023, 0, 360);

  // Calculate pixel offset for tick mark scroll
  xpos_offset = round((360 - compass_degrees) / 360.0 * 240.0);

  u8g2.clearBuffer();
  u8g2.setDrawColor(1);
  u8g2.setBitmapMode(1);

  // Draw 24 tick marks
  for (int i = 0; i < 24; i++) {

    xpos_with_offset = (64 + (i * 10) + xpos_offset) % 240;

    if (xpos_with_offset > 2 && xpos_with_offset < 128) {

      // Apply power-function easing for perspective effect
      if (xpos_with_offset < 64) {
        xpos_final = xpos_with_offset / 64.0;
        xpos_final = pow(xpos_final, 2.5);
        label_display = round(xpos_final * labels_count);
        xpos_final = xpos_final * 64.0;
      } else {
        xpos_final = (128 - xpos_with_offset) / 64.0;
        xpos_final = pow(xpos_final, 2.5);
        label_display = round(xpos_final * labels_count);
        xpos_final = (64 - (xpos_final * 64.0)) + 64;
      }

      xpos_final = round(xpos_final);

      if (i % 3 == 0) { // Major tick (cardinal/intercardinal)
        u8g2.drawLine(xpos_final, 7, xpos_final, 15);

        if (SHOW_SCALED_LABEL == 0) {
          str_width = u8g2.getStrWidth(compass_labels[i/3]);
          u8g2.drawStr(xpos_final - str_width/2, 24, compass_labels[i/3]);
        } else {
          int bitmap_index = ((i/3) * (labels_count+1)) + label_display;
          int label_xpos   = xpos_final - (26/2);
          if (xpos_final > 3 && xpos_final < 125) {
            u8g2.drawXBMP(label_xpos, 17, 26, 12, character_bitmaps[bitmap_index]);
          }
        }

        if (label_display == labels_count) // Widen center tick
          u8g2.drawLine(xpos_final-1, 7, xpos_final-1, 15);

      } else { // Minor tick
        u8g2.drawLine(xpos_final, 7, xpos_final, 13);
      }
    }
  }

  // Horizontal rule
  u8g2.drawLine(0, 5, 127, 5);

  // Draw degree bubble overlay
  u8g2.setDrawColor(0);
  u8g2.drawXBMP(51, 0, 26, 13, epd_bitmap_img_bubble_outline);
  u8g2.setDrawColor(1);
  u8g2.drawXBMP(51, 0, 26, 13, epd_bitmap_img_bubble_fill);

  // Draw degree value inside bubble
  u8g2.setDrawColor(0);
  u8g2.setFont(u8g2_font_squeezed_b7_tr);
  sprintf(buffer, "%d'", compass_degrees);
  str_width = u8g2.getStrWidth(buffer);
  u8g2.drawStr(64 - str_width/2, 8, buffer);

  u8g2.sendBuffer(); // Push frame to display
}
The full code with all 32 bitmap arrays is available in the Wokwi simulation. Use the link above to copy the complete working sketch.

Key Features

🔄Smooth scrolling compass scale
🔤Animated scalable direction labels
🧭Full 0°–360° heading simulation
📡Real-time analog input mapping
🖥️I2C OLED communication (SDA/SCL)
💾PROGMEM bitmap storage
📐Power function perspective easing
🔘Degree bubble overlay indicator

Learning Outcomes

analogRead() — Analog Input
Reading variable voltage (0–5V) from a potentiometer and converting it to a 10-bit integer (0–1023).
map() — Value Scaling
Using the Arduino map() function to re-range analog readings into any desired scale, such as 0–360°.
I2C Communication
Understanding the SDA/SCL two-wire protocol used by the OLED display and thousands of other sensors.
U8g2 Graphics Library
Rendering lines, strings, and bitmap images on OLED displays using the powerful U8g2 library.
PROGMEM Bitmap Storage
Storing constant bitmap data in flash memory instead of RAM to work within the Arduino UNO's 2KB RAM limit.
UI Animation in Embedded Systems
Applying easing functions (pow()) to create smooth, perspective-like animations on a tiny microcontroller display.

Upgrade Ideas

  • Replace the potentiometer with a HMC5883L or QMC5883L magnetometer for a real working compass using actual magnetic north.
  • Add Bluetooth output (HC-05 module) to mirror the compass heading on a smartphone app.
  • Integrate with an autonomous rover to give it directional awareness and goal-oriented navigation.
  • Build a GPS + compass navigation unit using a NEO-6M GPS module alongside this display.
  • Add a tilt-compensation sensor (MPU-6050) to correct compass readings when the device is not held flat.

© 2026 MakeMindz · Arduino tutorials for everyone · Built with ❤️ in Tamil Nadu

Comments

try for free