DEV Community

Cover image for Taming Tedious Tasks with a Tiny Titan: The ESP32 Auto-Controller
Luis F. Patrocinio
Luis F. Patrocinio

Posted on

Taming Tedious Tasks with a Tiny Titan: The ESP32 Auto-Controller

Hey, dev community! Patrocinio here. I'm back after a productive few months of focused learning.

I've got a lot of new projects and insights ready to go, and I can't wait to share them. Let's jump right in. As someone who lives and breathes the gamedev universe, and while studying the fascinating world of embedded systems, I've always asked myself: how can I merge these two worlds?

The other day, while playing the incredible Pokémon Red, I stumbled upon a wild challenge: you need 9,999 coins to buy Porygon! And the biggest jackpot in the casino, if you're incredibly lucky, is just 300 coins. I realized that to beat this challenge, the process could be boiled down to a single action: mashing the 'A' button forever. Eventually, the coins would come. My inner engineer immediately screamed, "Why not build a joystick to do that for me?".

Porygon is insanely expensive on Game Corner

(Just a side note: this mindset is mentioned in the book "A Theory of Fun for Game Design", which talks about how players, by nature, tend to optimize the fun out of games, often looking for the easiest path to victory. It's like we're programmed to "cheat", dominating a pattern. Our brain wants to solve the problem!)

And so, the uncontrollable urge to create the PatroAutoController was born. I want to share the key lessons from this quest with you.


The Armory: Our Forging Tools

For this endeavor, our "loadout" was carefully chosen:

  • 🧠 The Brain (ESP32): A powerful microcontroller with built-in Wi-Fi and Bluetooth. It's our brainy, brawny hero.
  • 🕸️ The Nervous System (FreeRTOS): Instead of a giant, confusing loop(), we used a real-time operating system. This lets us have "mini-brains" (tasks) running in parallel.
  • 🛠️ The Workbench (PlatformIO): Using PlatformIO in VS Code is like having a perfectly organized workshop. It manages our libraries, compilation, and uploads.
  • 📜 The Magic Scroll (ESP32-BLE-Gamepad Library): This incredible library does all the heavy lifting to turn our ESP32 into a Human Interface Device (HID).

PatroAutoController Assembled


The Build, Level by Level

Building robust software is like forging a sword: each layer adds strength and functionality. But as challenges grow, you don't just need a sword. You start building an arm cannon, a blaster that can fire powerful shots to take down the toughest bosses.

Ice beam to defeat it!

💠Level 1: The Modular and Scalable Architecture

From the very beginning, the goal was clear: the project had to be modular and scalable. I knew this small idea could grow, so I built a solid foundation that would allow for easy expansion and modification in the future. Instead of one giant main.cpp, we separated the responsibilities:

  • config.h: Where all the "magic numbers" (pins, delays) live.
  • HardwareIO.cpp: An abstraction layer for the hardware, with functions like io_read_potentiometer().
  • JoystickController.cpp: Where all the Bluetooth logic resides.

A good architecture is what separates a prototype from a product. It saves us from the spaghetti code monster! It's worth the effort!

💠Level 2: Mastering the Analog World

This project was also an excuse to explore the potentiometer better. As an analog component, it offers incredibly fine-grained control. All I had to do was adjust the range in the code to set the speed limits. It was a great opportunity to understand how it works in practice: it acts as a variable resistor, controlling the resistance applied to the current. By turning it, we change the voltage that the ESP32's analog pin reads, giving us a continuous spectrum of values to work with.

How potentiometer works

// Inside our task_read_potentiometer...
void task_read_potentiometer(void *pvParameters) {
  for (;;) {
    int rawValue = io_read_potentiometer_raw();
    // We map the ADC value (0-4095) to our desired delay
    joystick_delay = map(rawValue, 0, 4095, MIN_DELAY, MAX_DELAY);
    vTaskDelay(pdMS_TO_TICKS(100)); // Pause without blocking!
  }
}
Enter fullscreen mode Exit fullscreen mode

A quick note: Ever wonder why the ADC value goes from 0 to 4095 specifically? It's because the ESP32's ADC Resolution is 12-bit. That means the number of distinct values it can produce occupies 12 bits. 2¹² = 4096 possible levels.

💠Level 2.5: The Unexpected (and Happy) Detour

Initially, I planned to build a USB controller. However, I ran into a crucial hardware limitation: the standard ESP32 I was using doesn't have a native USB controller (OTG). Its USB port is only for programming and serial communication. For a USB controller, I'd need an ESP32-S2 or S3.

However, this closed door opened another path. This "obstacle" forced me to look at the ESP32's other great feature: Bluetooth. And what a great call that was! The implementation was surprisingly smooth, and the final result was a wireless controller, which, let's be honest, is infinitely cooler.

It Looks Like Magic, but It's Technology: How the ESP32 Becomes a Joystick

Okay, but how does this "transformation" happen? How does a simple microcontroller convince a computer or phone that it's a joystick? It's not magic. It's a very clever protocol.

Spongebob and Patrick seeing Techonology

When a Bluetooth device connects to a computer, it's not a casual meeting. It's more like a job interview. The device has to introduce itself and state exactly what its skills are.

This is where an incredible feature of Bluetooth (and USB) comes in: the HID Profile (Human Interface Device). Think of profiles as job titles: a headset uses the audio profile, and a controller uses the HID profile. It's a universal language for mice, keyboards, and, of course, joysticks.

What the ESP32-BLE-Gamepad library does behind the scenes is handle this entire "negotiation":

  1. The Advertisement: The ESP32 starts broadcasting via Bluetooth: "Hello! I'm not just any device, I'm an HID Gamepad!" Your phone or PC sees this ad and already knows how to talk to it.
  2. The Contract (Report Descriptor): Once the connection starts, the ESP32 sends a detailed "map" to the computer. This map says, in a standardized format, "I have 16 buttons, 2 analog sticks (X/Y axes), a D-pad, etc". The operating system reads this map and creates the virtual joystick device.

ESP32 robot interview

From there, our code just needs to send tiny data packets like "Button 2 was pressed". The crucial point is that all this communication must be assembled bit by bit, following strict protocol specifications. This all needs to be written in low-level, almost machine-level programming. And thankfully, the ESP32-BLE-Gamepad library handles all of this for us.

It does all the complex byte manipulation behind the scenes, giving us clean, simple functions like joystick_press_button(2). In the end, our "magic" as programmers is knowing how to choose and use the right tools that let us focus on our game's logic, not the bureaucracy of protocols.

💠Level 3: Unleashing the Bluetooth Magic

With the decision to use BLE, the next FreeRTOS task became the heart of the project. This is the code for the task responsible for pressing the button "infinitely". It's a loop that will run in parallel.

// Inside our task_joystick_press...
void task_joystick_press(void *pvParameters) {
  for (;;) {
    if (joystick_is_connected()) {
      io_blink_led(); // Visual Feedback
      joystick_press_button(2); // Press Button 2
      joystick_release_button(2); // Release immediately
      vTaskDelay(pdMS_TO_TICKS(joystick_delay)); // Wait for the time set by the potentiometer
    } else {
      vTaskDelay(pdMS_TO_TICKS(1000)); // Waiting for connection
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The beauty of FreeRTOS shines here: while this task is "sleeping" in vTaskDelay, the potentiometer task is free to run and update the speed. No blocking, maximum responsiveness!


Victory! The Final Result

After defeating a few mini-bosses (like blocked pins and library dependencies), the moment of truth arrived. I powered on the ESP32. On my phone, a new Bluetooth device appeared: "PatroAutoController". I connected. I opened an emulator. And there it was, my character in the Game Corner, automatically racking up coins. I turned the potentiometer, and the speed changed instantly.

Pokemon Red - Mastering the Game Corner

The feeling of solving a real in-game problem with hardware you built and programmed yourself... it's simply indescribable. And this freedom is achieved through a lot of study and practice!

You can check out all the code, the structure, and the full documentation in the GitHub repository!

PatroAutoController GitHub Repository


Next Level: What's to Come?

This project is proof that a simple idea can become a delightful learning journey. And now, with this modular foundation, the possibilities are endless. An accelerometer-based controller? A web interface for configuration? Who knows?

Porygon finally obtained

What we do know is that the knowledge we've gained will let us move on to bigger and better things. This is purely the Mega Man Method™️! I hope this story has inspired you to cross the borders between your own passions. We can grab a microcontroller, have a crazy idea, and start building!

Porygon ultra mega happy

Until next time, and let's keep leveling up our skills!

Top comments (0)