i
This project involved building an interactive control interface using a joystick, an OLED display, and LEDs, all managed through the XIAO ESP32-C3 microcontroller. The primary goal was to learn and demonstrate how analog input from a joystick can be interpreted and translated into real-time feedback through both digital (LED) and graphical (OLED) outputs. The project successfully simulates a basic Human-Machine Interface (HMI), where directional movement of a joystick is used to provide multi-channel output response.
The joystick module provides two degrees of analog motion (X and Y) along with an optional button press. These inputs are connected to analog pins of the ESP32-C3 and sampled using analogRead() to detect direction. Based on the tilt or displacement, the microcontroller determines whether the joystick is being pushed up, down, left, or right.
Each direction corresponds to a separate LED output. When the joystick is pushed up, for example, the UP LED lights up. The OLED display complements this by showing the current direction in text form—providing visual feedback for user input. This kind of dual-output design reinforces the concept of multi-modal feedback in embedded systems.
The OLED display operates via the I2C protocol, meaning it requires only two wires (SDA and SCL), making it ideal for compact designs like the XIAO ESP32-C3, which has limited pins. Using the Adafruit_SSD1306 and Adafruit_GFX libraries, the OLED was programmed to dynamically update text whenever the joystick is moved.
One important concept learned during the implementation was signal thresholding. Joystick analog values can fluctuate even when idle, which may result in false direction readings. To address this, center dead-zones were implemented in code to avoid noise-based misreads. If the X-axis is within ±300 of center (2048 on 12-bit ADC), it's considered idle. The same logic is applied to the Y-axis.
Another important lesson was understanding pin mapping and addressing, especially with the GPIO pins of the XIAO ESP32-C3, which can behave differently than traditional ESP32 or Arduino Uno boards. Using a pin mapping table specific to the XIAO ESP32-C3 was crucial for correct setup.
Overall, this project helped me gain practical experience in:
By integrating multiple components into a unified setup, I improved my understanding of embedded systems design and the synergy between input devices, microcontrollers, and output interfaces.
The programming for this joystick-based control system follows a modular structure, beginning with hardware setup and then focusing on logic implementation and output synchronization. The programming process was executed using the Arduino IDE, with the XIAO ESP32-C3 board selected from the board manager and libraries installed for OLED and I2C communication.
The first part of the program involves setting up the necessary libraries:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
These handle I2C communication and graphics rendering on the OLED. After defining the display size and initializing the display object, pin definitions were made for both the joystick analog pins and the digital pins connected to LEDs.
The setup() function initializes serial communication for debugging, the OLED display using display.begin(), and sets the LED pins as outputs using pinMode(). The joystick pins are left as analog inputs by default.
The loop() function is where the bulk of the logic occurs. First, analog readings are taken from the joystick's X and Y axes:
int xVal = analogRead(A0);
int yVal = analogRead(A1);
Using calibrated thresholds, the code determines which direction the joystick is pointing. For example, if xVal > 3000, it's considered "Right". These conditions are used to trigger the corresponding LED and update the OLED display. Before every new update, display.clearDisplay() is called to avoid overlapping frames.
The direction is then printed to the OLED using:
display.setCursor(0, 20);
display.print("Direction: RIGHT");
display.display();
The corresponding LED is also activated using digitalWrite().
To add a sliding effect, a global variable xOffset can be used to shift the cursor position across frames. This adds UI flair and simulates text scrolling across the screen.
One challenge in the programming process was creating a responsive interface without flicker or delay. OLEDs can flicker when content is cleared and redrawn too quickly. To address this, frame updates were optimized, and redraws were done only when the direction changed.
Debouncing logic was added to prevent rapid LED flicker when the joystick was near the center threshold. Flags were used to track direction changes and update outputs only when a transition occurred.
Modularity was emphasized through use of functions like updateDisplay(direction) and setLED(direction), which made debugging and scalability easier.
In summary, the programming involved:
This structured approach made the entire system responsive and reliable.
A critical part of this project was ensuring proper **hardware addressing**, especially because multiple peripherals were used: an **OLED screen** (using I2C) and four **LEDs** (using GPIO). The **XIAO ESP32-C3** has a unique pin mapping structure compared to traditional Arduino boards, and confirming pin assignments and communication addresses was essential to achieving reliable operation.
The OLED display used in this project operates on the **I2C protocol**. I2C devices require a unique 7-bit address to communicate with the microcontroller. The common address for most SSD1306 OLED displays is **0x3C**, but it can vary depending on the module's configuration. To confirm this:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(115200);
while (!Serial); // wait for serial monitor
Serial.println("I2C Scanner");
}
void loop() {
byte error, address;
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at 0x");
Serial.println(address, HEX);
}
}
delay(1000);
}
This verified that my OLED module was correctly wired to the default I2C pins:
These are default hardware I2C pins on the XIAO ESP32-C3. While it's possible to use Wire.begin(SDA, SCL), sticking with default pins ensured library compatibility.
The LEDs were assigned to digital GPIO pins on the microcontroller. The following assignments were used:
Direction | Function | Pin Label | GPIO Number |
---|---|---|---|
UP | UP_LED | D0 | GPIO0 |
DOWN | DOWN_LED | D2 | GPIO2 |
LEFT | LEFT_LED | D3 | GPIO3 |
RIGHT | RIGHT_LED | D9 | GPIO9 |
To avoid any hardware confusion:
The joystick's X and Y axes were connected to analog pins:
These inputs were confirmed using `analogRead()` and `Serial.print()` to verify the response to physical joystick movement. I also ensured the analog resolution matched the ESP32's default 12-bit ADC range (0 to 4095).
By verifying the I2C address using an I2C scanner, confirming GPIO pin mappings via official documentation, and testing each peripheral in isolation, I ensured the full addressing scheme worked correctly. This step was crucial to avoid overlapping resources and debugging conflicts. Proper addressing is foundational in embedded systems, especially when multiple input/output devices share the same communication bus or pin bank.
Every embedded project introduces a set of technical challenges, and this joystick-OLED-LED interface was no exception. Through iterative testing and debugging, I encountered several problems related to communication, flickering displays, unstable readings, and wiring misconfigurations. Here is a breakdown of the major problems and how they were fixed:
These problems taught me the importance of:
By systematically tackling each issue, I was able to build a stable, real-time UI interface driven by joystick input.