When software engineers talk about robotic process automation (RPA), they mean bots clicking through spreadsheets. But on the electronics bench, physical robotic process automation projects mean hardware: pick-and-place jigs, automated sorting arms, and testing fixtures that move real objects. Building a reliable physical RPA system requires solving three hard problems simultaneously: precise motion control, robust power delivery, and network telemetry. This guide walks through building a 3-axis automated component sorting arm. We will size the power supply for stall currents, map the I2C bus for a PWM driver, and write firmware that handles network drops without triggering a hardware watchdog reset.
Difficulty: Intermediate | Time: 4-6 Hours | Cost: ~$55 USD

Choosing the Brain: Decision Tree for Physical RPA Boards

Standard 8-bit microcontrollers lack the clock speed to handle inverse kinematics alongside WiFi telemetry. Even the original ESP32-WROOM-32 has quirks with hardware PWM channel conflicts when driving multiple servos. Use this decision path to select your controller:

If your project requires...Then choose...Why?
Simple 1-axis motion, no networkArduino Nano (ATmega328P)5V logic, simple Servo.h library, low cost ($4).
Multi-axis motion + local SD loggingTeensy 4.1600MHz clock, massive hardware PWM, native SDIO ($35).
Multi-axis + WiFi/MQTT telemetry + future visionESP32-S3-DevKitC-1 (N16R8)Dedicated hardware PWM, dual-core (separate motion/network), vector instructions for AI ($9.50).

The Verdict: For networked physical RPA, the ESP32-S3-DevKitC-1 (N16R8 variant) is the definitive pick. The N16R8 gives you 16MB of Quad SPI flash (essential for OTA updates) and 8MB of PSRAM. More importantly, the S3 variant resolves the touch-pin and PWM conflicts present in the original ESP32, and its native USB-Serial-JTAG makes debugging kernel panics vastly easier than the CP2102 bridges on older boards (Espressif ESP32-S3 Datasheet).

Hardware BOM and Pin Mapping for the Sorting Arm

Do not attempt to drive high-torque servos directly from a microcontroller's GPIO pins. You need a dedicated PWM driver and an isolated power bus.

Parts List (2026 Pricing)

  • MCU: ESP32-S3-DevKitC-1 (N16R8) - $9.50
  • PWM Driver: Adafruit 16-Channel 12-bit PWM/Servo Driver (PCA9685) - $7.50
  • Actuators: 3x MG996R Metal Gear Servos (180-degree) - $18.00
  • Power Supply: Mean Well LRS-50-5 (5V, 10A Enclosed Switching) - $16.00
  • Logic Level Shifters: 4-channel I2C bi-directional (if using 5V PCA9685 module with 3.3V ESP32) - $2.00
Power Budget Math: An MG996R servo draws ~500mA under normal load, but the stall current is 2.5A at 5V. Three servos stalling simultaneously = 7.5A. A standard USB-C 5V/2A adapter will brownout instantly. The 10A Mean Well LRS-50-5 provides the necessary 50W headroom.

Pin Mapping Table

ESP32-S3 PinPCA9685 ModuleFunction
GPIO 8SDAI2C Data (via Logic Level Shifter)
GPIO 9SCLI2C Clock (via Logic Level Shifter)
3V3VCCPCA9685 Logic Power
GNDGNDCommon Ground (Must tie to PSU GND)
N/AV+Mean Well 5V (Servo Power)

Assembly Sequence and Power Isolation

  1. Prepare the Power Supply: Wire the Mean Well LRS-50-5 AC inputs through a fused IEC inlet. Connect the DC V+ and V- to a heavy-gauge (16 AWG) terminal block.
  2. Establish Common Ground: Run a 16 AWG jumper from the Mean Well V- terminal to the GND rail on your breadboard or PCB. If the ESP32 and the PCA9685 do not share a common ground reference, the I2C bus will float and crash.
  3. Wire the I2C Bus: Connect ESP32 GPIO 8 and 9 to the low-voltage side of your logic level shifter. Connect the high-voltage side to the PCA9685 SDA and SCL. Pull-up resistors (4.7kΩ) should be on the 5V side of the shifter.
  4. Connect Servo Power: Wire the Mean Well 5V (V+) directly to the PCA9685 green screw terminal (V+). Do not connect this to the ESP32's 5V pin.
  5. Mount Servos: Plug the MG996R servos into channels 0, 1, and 2 on the PCA9685. Ensure the brown wire (GND) faces the edge of the board, red (V+) in the middle, and orange (Signal) on the inside.

Complete ESP32-S3 Firmware with Error Handling

This code targets the ESP32S3 Dev Module board in Arduino IDE 2.x. It includes I2C bus recovery, WiFi reconnection logic, and a hardware watchdog feed to prevent kernel panics during blocking network calls.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <WiFi.h>
#include <esp_task_wdt.h>

// --- PIN DEFINITIONS ---
#define I2C_SDA 8
#define I2C_SCL 9

// --- SERVO PARAMETERS ---
#define SERVO_FREQ 50
#define MIN_PULSE 150  // ~0 degrees
#define MAX_PULSE 600  // ~180 degrees

// --- NETWORK ---
const char* ssid = "YourNetwork";
const char* password = "YourPassword";

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

// Watchdog timeout in seconds
#define WDT_TIMEOUT 5

void setup() {
  Serial.begin(115200);
  delay(500);
  
  // Initialize Task Watchdog Timer
  esp_task_wdt_init(WDT_TIMEOUT, true);
  esp_task_wdt_add(NULL);

  // Initialize I2C with explicit pins and 100kHz clock
  Wire.begin(I2C_SDA, I2C_SCL, 100000);
  
  // I2C Bus Verification
  if (!verifyI2C(0x40)) {
    Serial.println("[FATAL] PCA9685 not found at 0x40. Check wiring and logic level shifters.");
    while(1) { esp_task_wdt_reset(); delay(1000); } // Safe halt
  }

  pwm.begin();
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);
  delay(10);

  // Home all axes
  homeAxes();
  
  // Connect to WiFi
  connectWiFi();
}

void loop() {
  // Feed the watchdog to prevent Core 1 panic
  esp_task_wdt_reset();
  
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  // Example Physical RPA Sequence: Pick and Place
  executeSortSequence();
  
  delay(500);
}

bool verifyI2C(uint8_t addr) {
  Wire.beginTransmission(addr);
  return (Wire.endTransmission() == 0);
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    esp_task_wdt_reset(); // Keep feeding WDT during blocking delay
    attempts++;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected!");
  } else {
    Serial.println("\nFailed. Will retry in loop.");
  }
}

void homeAxes() {
  for (uint8_t i = 0; i < 3; i++) {
    pwm.setPWM(i, 0, MIN_PULSE);
  }
  delay(1000);
}

void moveServo(uint8_t channel, uint16_t pulse) {
  pulse = constrain(pulse, MIN_PULSE, MAX_PULSE);
  pwm.setPWM(channel, 0, pulse);
  delay(20); // Allow mechanical settling
}

void executeSortSequence() {
  // Move base to bin A
  moveServo(0, 250);
  // Lower arm
  moveServo(1, 400);
  // Close gripper
  moveServo(2, 300);
  delay(500);
  // Raise arm
  moveServo(1, 150);
  // Move base to bin B
  moveServo(0, 500);
  // Open gripper
  moveServo(2, 150);
}

Debugging: First Three Checks and Common Error Strings

When physical RPA hardware fails, it usually fails violently or silently. If your arm twitches, drops connection, or halts, run through these first three checks:

  1. Check I2C Pull-ups and Logic Levels: The PCA9685 is a 5V chip. The ESP32-S3 is strictly 3.3V. Feeding 5V into GPIO 8 will eventually fry the pin. Ensure your bi-directional logic level shifter has 4.7kΩ pull-ups on both sides.
  2. Check for Ground Loops: Measure the voltage between the ESP32 GND pin and the PCA9685 GND terminal with a multimeter. It must read < 0.05V. If it reads higher, your ground wire is too thin or loose.
  3. Check Power Supply Sag: Hook an oscilloscope or a fast multimeter to the 5V servo rail. When the MG996R servos start moving, the voltage must not drop below 4.8V. If it does, your PSU is undersized or your wires are too long.

Resolving the Dreaded Watchdog Panic

If your serial monitor spits out this exact error string:

Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)

This means the FreeRTOS operating system on the ESP32 detected that your code blocked the CPU for too long without yielding to background tasks (like WiFi and I2C interrupts).

Ranked CauseThe Fix
1. Blocking delay() in a tight loop without WDT resetAdd esp_task_wdt_reset(); inside your loop, or replace delay() with non-blocking millis() timers.
2. I2C Bus LockupThe SDA line is stuck low. Implement a bus recovery routine that toggles SCL manually 9 times to release the slave device.
3. WiFi connection hangingAlways use a timeout counter in your while(WiFi.status() != WL_CONNECTED) loop, as shown in the firmware above.

Scaling the Project: Extend or Simplify

Once the baseline sorting arm is operational, you will need to adapt it to your specific bench workflow. Here is how to modify the build based on your constraints.

How to Extend (Adding Machine Vision)

If you need the arm to sort components by value or color, swap the ESP32-S3 DevKit for an ESP32-S3-CAM (XIAO or AI-Thinker variant). Mount an OV2640 camera above the pick-up zone. Use the ESP32's vector instructions to run a lightweight TensorFlow Lite Micro model that classifies resistor color bands. You will need to offload the servo PWM to the PCA9685 via I2C, as the camera will consume the native PWM pins.

How to Simplify (Manual Teach Mode)

If WiFi telemetry and MQTT are overkill for your application, strip the network code entirely. Add three 10kΩ linear potentiometers to the ESP32's ADC pins (GPIO 1, 2, and 3). Map the analog reads (0-4095) directly to the servo pulse widths. This creates a manual "teach pendant" allowing you to physically dial in the pick-and-place coordinates without rewriting C++ code (Adafruit PCA9685 Guide).

For 90% of benchtop physical RPA tasks, the ESP32-S3 paired with a PCA9685 and a Mean Well enclosed power supply is the optimal, most reliable configuration. Do not compromise on the power supply; a $16 Mean Well unit will outlast a $3 generic USB brick and save your microcontroller from voltage spike damage.