Skip to content

episode 6 the potentiometer

DanielFLopez1620 edited this page Apr 23, 2026 · 6 revisions

Episode 6: The Potentiometers / Los Potenciómetros

"Give ORION control of its arms — two knobs, two joints, zero complications." "Dale a ORION el control de sus brazos — dos perillas, dos articulaciones, cero complicaciones."

Commit and release version: Release: Episode 6 - The potentiometer

📑 Contents


🎥 Watch the Short

English Version

Vambrace Adventure - Episode 6: The Potentiometers (English)

Versión en Español

Vambrace Adventure - Episode 6: Los Potenciómetros (Español)



▶️ Watch in English | ▶️ Ver en Español


📖 Episode Description

Felipe worked previously on the potentiometer along with ORION and left for a moment, Danna taking advantage of this to explore the updates and ORION encourage her to test it (just no moving the joystick as ORION is on a table and doesn't want to fall). Danna is amazed, and at that moment David and Felipe appear, Felipe seems proud that it works well and David just wanted to ensure he will be the next testing it.


🚀 Usage

Note: Wiring details for all components are in the Connections section below.

1. Configure WiFi credentials

Copy the example template and fill in your values:

cp lib/vambrace_secret/vambrace_secret_ex.hpp lib/vambrace_secret/vambrace_secret.hpp

Then edit vambrace_secret.hpp:

#define WIFI_SSID    "YOUR_NETWORK_NAME"
#define WIFI_PASS    "YOUR_PASSWORD"
#define AGENT_IP_STR "192.168.X.X"   // IP of the machine running the micro-ROS agent
#define AGENT_PORT   8888

To find your current IP, run ip a or ifconfig on your machine.

2. Connect the potentiometers to the ESP32

Wire the left and right arm potentiometers following the Wiring Diagram in the Connections section below.

You may need the help of a protoboard/breadboard or an expansion board to make all connections.

3. Connect the ESP32 and grant port access

Plug the ESP32 into your computer via USB. Then grant read/write access to the serial port:

ls -al /dev | grep USB
sudo chmod 666 /dev/ttyUSB0

If the device appears as /dev/ttyUSB1 or another port, adjust accordingly.

4. Flash the firmware

From the vambrace_micro_ros_app/ directory:

# Build and upload
pio run --target upload

# Monitor serial output to verify calibration and connection
pio device monitor --baud 115200

On a successful boot you should see:

Vambrace ESP32 started!
Connected. IP: 192.168.X.X
NTP synced!
Joystick calibrated: center_x=XXXX, center_y=XXXX
Hardware initialized.

5. Start the µ-ROS agent on ORION

ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888

6. Verify the topics

Check that all publishers are active:

ros2 topic list

You should see at minimum:

/mobile_base_controller/cmd_vel
/simple_left_arm_controller/commands
/simple_right_arm_controller/commands
/emotion/int
/orion_response

Verify arm commands are publishing and changing as you turn the potentiometers:

# Left arm — should change as you turn the left potentiometer
ros2 topic echo /simple_left_arm_controller/commands

# Right arm — should change as you turn the right potentiometer
ros2 topic echo /simple_right_arm_controller/commands

Expected output per message (one position value in radians, range −1.047 to +1.047) of type std_msgs/msg/Float64MultiArray


🔄 Changes from Episode 5

Episode 5 introduced the vambrace_hardware module with joystick support. Episode 6 extends it without touching the existing joystick logic.

New in vambrace_config.hpp

// Potentiometer pins (ADC1 only — ADC2 conflicts with WiFi)
#define POT_LEFT_ARM_PIN    33    // ADC1_CH5
#define POT_RIGHT_ARM_PIN   36    // ADC1_CH0, input-only

// Arm position limits (radians)
#define ARM_LOWER_LIMIT    -1.0472f   // -pi/3
#define ARM_UPPER_LIMIT     1.0472f   //  pi/3
#define POT_ADC_MAX         4095      // 12-bit ADC

// Update interval
#define POT_INTERVAL_MS     50        // 20 Hz

New in vambrace_hardware.cpp

Two functions added to the anonymous namespace:

Function Purpose
read_pot_avg(int pin) Reads 4 ADC samples and returns their average via bit shift
map_pot_to_arm(int raw) Maps a raw ADC value to radians using linear interpolation

One new timer added to vambrace_hardware_update():

// Before (Ep. 5) — only joystick
if (now - last_joy_ms >= JOY_INTERVAL_MS) { ... }

// After (Ep. 6) — joystick + potentiometers
if (now - last_joy_ms >= JOY_INTERVAL_MS) { ... }
if (now - last_pot_ms >= POT_INTERVAL_MS) { ... }

The left arm value is negated to correct for Z-axis orientation of ORION's servos (see Why is the left arm inverted? below).

No changes to

  • vambrace_ros.cpp — arm publishers and buffers were already in place from Episode 4
  • vambrace_app.hppTeleoperationCmd already had setLeftArm() / setRightArm()
  • main.cpp — already calls vambrace_hardware_update() every loop

💡 Lessons & Learnings

Key Concept: From voltage to radians

A potentiometer is essentially a variable voltage divider. Turning it changes the resistance and therefore the voltage at its wiper pin. The ESP32's ADC converts that voltage into a number between 0 and 4095 (12-bit). Our job is to convert that number into a useful angle for ORION's controller.

The mapping is linear: if the ADC reads 0, the arm goes to its lower limit (−π/3 rad). If it reads 4095, it goes to its upper limit (+π/3 rad). Everything in between is interpolated proportionally.

ADC [0 ──────────── 2048 ──────────── 4095]
      ↓                                  ↓
   -1.047 rad (−60°)          +1.047 rad (+60°)

Why is the left arm inverted?

ORION's servos have their Z axes pointing in opposite directions for the left and right arms. This means the same positive position value raises one arm but lowers the other.

To make the experience intuitive, so turning either potentiometer clockwise raises the corresponding arm; therefore, the left arm receives the negated value:

cmd.setLeftArm({ -map_pot_to_arm(read_pot_avg(POT_LEFT_ARM_PIN)) });
cmd.setRightArm({  map_pot_to_arm(read_pot_avg(POT_RIGHT_ARM_PIN)) });

Without this negation, turning both potentiometers clockwise would move one arm up and the other down — disorienting for the operator.

Deep Dive: The potentiometer reading functions

Two functions in vambrace_hardware.cpp do all the heavy lifting for arm control.

read_pot_avg(int pin) — noise reduction via averaging

int read_pot_avg(int pin)
{
    long sum = 0;
    sum += analogRead(pin);
    sum += analogRead(pin);
    sum += analogRead(pin);
    sum += analogRead(pin);
    return (int)(sum >> 2);
}

Four consecutive analogRead() calls accumulate into a long. The result is then right-shifted by 2 (>> 2), which is equivalent to integer division by 4 but avoids the division instruction. The key insight is that random ADC noise cancels out statistically when averaged — noise reduces by a factor of √N, so 4 samples gives √4 = 2× noise reduction. The long type prevents overflow: 4 × 4095 = 16380, which fits comfortably in a 32-bit integer.

map_pot_to_arm(int raw) — linear interpolation to radians

float map_pot_to_arm(int raw)
{
    return ARM_LOWER_LIMIT + ((float)raw / (float)POT_ADC_MAX) * (ARM_UPPER_LIMIT - ARM_LOWER_LIMIT);
}

This is the standard linear interpolation formula: output = min + (input / input_max) * (max - min). Breaking it down:

  1. (float)raw / (float)POT_ADC_MAX — normalizes the raw ADC value to a [0.0, 1.0] range. The explicit casts to float are necessary — without them, integer division would truncate everything to 0 or 1.
  2. * (ARM_UPPER_LIMIT - ARM_LOWER_LIMIT) — scales that normalized value to the full angular range (2 × π/3 ≈ 2.094 rad).
  3. + ARM_LOWER_LIMIT — shifts the result so it starts at −π/3 instead of 0.

The two functions chain together in vambrace_hardware_update():

analogRead() × 4  →  read_pot_avg()  →  map_pot_to_arm()  →  setLeftArm() / setRightArm()
[0..4095] raw        averaged int        radians float         TeleoperationCmd

What We Learned:

  • Linear ADC → radians mapping: the formula lower + (raw / max) * (upper - lower) is the universal pattern for converting any analog sensor to physical units.
  • Axis inversion by robot geometry: when two actuators are physically symmetric but their reference axes point in opposite directions, negating one of the values is the simplest and most readable solution for a teleoperation.
  • ADC sample averaging: the ESP32 has a noisy ADC, especially with WiFi active. Averaging 4 consecutive readings halves the noise with no perceptible latency.
  • Independent publishing per topic: each of ORION's arm controllers listens on its own topic and expects a Float64MultiArray with a single element — the angle in radians.

Technical Highlights:

  • Potentiometers use ADC1 exclusively (GPIOs 33 and 36) — ADC2 is incompatible with active WiFi on the ESP32.
  • GPIO 36 is input-only (no internal pull-up) — not an issue since the potentiometer generates its own voltage.
  • Arm publishing runs at 20 Hz (POT_INTERVAL_MS = 50) — sufficient for smooth joint movement.
  • The averaging filter uses a bit shift (sum >> 2) instead of integer division — equivalent to / 4 but avoids a division instruction.

🔌 Vambrace Connections Up to This Episode

Joystick (Ep. 5)

ESP32 Pin GPIO Signal Description
ADC1_CH6 34 VRx X axis — angular velocity
ADC1_CH7 35 VRy Y axis — linear velocity
GPIO 32 32 SW Sprint button (INPUT_PULLUP)

Potentiometers (Ep. 6)

ESP32 Pin GPIO Signal Description
ADC1_CH5 33 WIPER Left arm potentiometer
ADC1_CH0 36 WIPER Right arm potentiometer (input-only)

Wiring Diagram

Joystick Module          ESP32 DevKit V1.0
┌──────────────┐         ┌──────────────────┐
│ VCC ─────────┼────────→│ 3.3V             │
│ GND ─────────┼────────→│ GND              │
│ VRx ─────────┼────────→│ GPIO 34 (ADC1)   │
│ VRy ─────────┼────────→│ GPIO 35 (ADC1)   │
│ SW  ─────────┼────────→│ GPIO 32          │
└──────────────┘         └──────────────────┘

Left Arm Potentiometer   ESP32 DevKit V1.0
┌──────────────┐         ┌──────────────────┐
│ VCC ─────────┼────────→│ 3.3V             │
│ GND ─────────┼────────→│ GND              │
│ WIPER ───────┼────────→│ GPIO 33 (ADC1)   │
└──────────────┘         └──────────────────┘

Right Arm Potentiometer  ESP32 DevKit V1.0
┌──────────────┐         ┌──────────────────┐
│ VCC ─────────┼────────→│ 3.3V             │
│ GND ─────────┼────────→│ GND              │
│ WIPER ───────┼────────→│ GPIO 36 (ADC1)   │
└──────────────┘         └──────────────────┘

🎯 Challenge Posed

" This is so cool! - Danna " " You can actually move my arms! - ORION "

The Vambrace can now control movement of the base platform and position of the arms. The next step is emotions: the implementation of part of a 4x4 membrane keyboard to control the display of emotion.


⏭️ Next Episode

➡️ Episode 7: The Emotions


Characters: Danna, ORION, David and Felipe | Date: 2026-04-08

⬅️ Previous: The Joystick | 🏠 Home | Next ➡️: The Emotions

Clone this wiki locally