Arduino Firmware Breakdown

This documentation explains the complete firmware architecture used in the Digital Contour Gauge communication prototype. The RP2040 firmware simultaneously acts as a sensing node, display renderer, embedded UI controller, memory manager, and UART communication bridge.

Embedded System Responsibilities

Subsystem Responsibility
Analog Acquisition Node Reads potentiometer positions
Display Rendering Engine Draws contour graphics onto the OLED
Embedded UI Controller Handles menus and button interactions
Memory Buffer Stores contour scans in SRAM
UART Communication Node Streams coordinate packets to the laptop

Imported Libraries & Hardware Drivers

The firmware begins by importing multiple communication and graphics libraries. These libraries provide hardware abstraction layers between the RP2040 and its peripherals.

libraries.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Wire.h — I2C Communication Bus

The Wire library initializes and manages the synchronous I2C protocol. This protocol uses:

  • SDA → Serial Data Line
  • SCL → Serial Clock Line

The OLED exists as a separate hardware node connected through this shared communication bus.

Adafruit_GFX.h — Graphics Engine

The graphics library provides primitive rendering instructions such as:

  • Lines
  • Circles
  • Pixels
  • Text
  • Rectangles

Adafruit_SSD1306.h — OLED Driver

This driver translates graphics instructions into SSD1306-compatible display commands. Without this driver the RP2040 cannot understand the OLED memory architecture.


OLED Display Configuration

The firmware next defines the physical geometry of the display.

display_config.ino
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define VIS_ZONE 44
Constant Purpose
SCREEN_WIDTH OLED horizontal resolution
SCREEN_HEIGHT OLED vertical resolution
VIS_ZONE Reserved contour rendering zone
Embedded UI Principle

The visualization area is intentionally restricted so the lower region of the display remains available for menus, labels, and instructions.

display_object.ino
Adafruit_SSD1306 display(
    SCREEN_WIDTH,
    SCREEN_HEIGHT,
    &Wire,
    -1
);

This line constructs the OLED display object. All future graphics commands are routed through this object.


Global Memory Architecture

global_memory.ino
const int btnPin = 26;

struct Scan {
  int y1;
  int y2;
};

Scan library[5];

int savedTotal = 0;
int currentIdx = 0;

Push Button Pin

The push button is connected to GPIO pin 26. The firmware later configures this pin as an INPUT_PULLUP configuration.

Struct-Based Scan Storage

The firmware introduces a custom structure called Scan. This behaves as a lightweight embedded database record.

Each stored scan contains:

  • First contour coordinate
  • Second contour coordinate

The array:

Scan library[5];

allocates memory for five contour profiles directly inside SRAM.

Why Use Structs?

Structs allow multiple related variables to remain grouped together as a single logical object, making memory handling safer and easier to scale.


Finite State Machine Architecture

state_machine.ino
int currentState = 0;
int menuOption = 0;
State Meaning
0 Live View
1 Library Browser
2 Action Menu

This design prevents conflicting interface behaviors. Only the logic relevant to the active state becomes executable.


Hardware Initialization — setup()

The setup function executes once during system startup. Its responsibility is to initialize every hardware subsystem.

setup.ino
void setup() {

  Serial.begin(115200);

  Wire.setSDA(6);
  Wire.setSCL(7);
  Wire.begin();

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  pinMode(btnPin, INPUT_PULLUP);

  display.setTextColor(WHITE);
}

Serial.begin(115200)

Initializes UART communication between the RP2040 and the laptop. The baud rate must exactly match the Python listener configuration.

Wire.begin()

Activates the RP2040 internal I2C controller hardware. Without this call the OLED receives no communication frames.

display.begin(..., 0x3C)

Initializes the OLED using I2C address 0x3C. This behaves like the display's network address on the local bus.


Main Runtime Loop

The loop function behaves as the embedded runtime engine. It continuously:

  • Samples analog inputs
  • Processes button events
  • Updates the display
  • Handles menu states
analog_sampling.ino
int v1 = map(analogRead(A2), 0, 1023, VIS_ZONE, 8);
int v2 = map(analogRead(A1), 0, 1023, VIS_ZONE, 8);

The potentiometers generate raw ADC values from 0–1023. The map function rescales those values into OLED pixel coordinates.

Signal Translation

The firmware continuously translates physical mechanical movement into graphical screen coordinates in real time.


Multi-Function Button System

A single push button controls:

  • Saving scans
  • Opening the library
  • Entering menus
  • Scrolling menu options
  • Confirming actions
button_detection.ino
if (digitalRead(btnPin) == LOW)

The firmware constantly checks whether the button transitions LOW. If pressed, the system begins timing the interaction duration.

long_press.ino
if (duration > 800)

Any press longer than 800 milliseconds becomes a long press event.

double_click.ino
if (millis() - lastClick < 300)

The firmware compares timestamps to determine whether the interaction was:

  • Single click
  • Double click

UART Communication & Coordinate Streaming

This section performs the primary networking operation of the firmware. The RP2040 converts graphical contour data into structured coordinate packets.

uart_start.ino
Serial.println("START_DATA");

This line acts as a transmission handshake key. The Python application listens for this exact phrase before beginning extraction.

coordinate_conversion.ino
float mmY1 = map(library[i].y1, VIS_ZONE, 8, 0, MAX_TRAVEL);
float mmY2 = map(library[i].y2, VIS_ZONE, 8, 0, MAX_TRAVEL);

The firmware converts OLED pixel coordinates back into real mechanical distances measured in millimeters.

packet_structure.ino
Serial.print("SCAN:");
Serial.print(i);
Serial.print(",");
Serial.print(x1);
Serial.print(",");
Serial.print(mmY1);
Serial.print(",");
Serial.print(x2);
Serial.print(",");
Serial.println(mmY2);

Each scan becomes a structured UART packet formatted as:

SCAN:0,0.0,12.5,3.0,15.2
Packet Field Meaning
SCAN:0 Scan Identifier
0.0 First X Coordinate
12.5 First Y Coordinate
3.0 Second X Coordinate
15.2 Second Y Coordinate

Embedded Graphics Rendering

graphics_rendering.ino
display.drawLine(15, y1, 113, y2, WHITE);
display.fillCircle(15, y1, 2, WHITE);
display.fillCircle(113, y2, 2, WHITE);
Function Purpose
drawLine() Creates contour segment
fillCircle() Draws contour endpoints
setCursor() Positions text cursor
print() Renders text labels

The OLED therefore behaves as a live vector visualization interface.


Complete Firmware Architecture Summary

Subsystem Main Functions Purpose
Analog Input analogRead() Reads potentiometer positions
Scaling Engine map() Converts voltages into screen coordinates
I2C Network Wire.begin() Activates OLED communication
Graphics Engine drawLine() Renders contour geometry
UI State Machine currentState Controls embedded interface flow
Memory Buffer Scan library[5] Stores contour snapshots
UART Network Serial.print() Streams coordinate packets
Data Packaging CSV Formatting Structures geometry frames
Final Embedded Systems Takeaway

This firmware demonstrates how a small RP2040 microcontroller can simultaneously behave as a sensing node, graphics processor, memory manager, embedded UI controller, and distributed communication endpoint.