Building a Haptic Glove with SimpleFOCMini and STM32

Jan 20, 2026 min

I’m building a haptic glove for my master’s thesis on VR robot teleoperation. The core idea is surprisingly simple: use brushless gimbal motors with field-oriented control to both track finger position and provide force feedback. One motor per finger does double duty.

The hardware stack I’m using

Each finger gets its own control unit:

STM32G431CBU6 microcontroller (ARM Cortex-M4 with hardware FPU)
SimpleFOCMini three-phase gate driver
DFRobot 2208 BLDC motor (80KV gimbal motor)
AS5600 magnetic encoder (12-bit absolute)

Plus a TCA9548A I²C multiplexer per STM32 to handle encoder address conflicts, and an ESP32-C3 Super Mini for WiFi to the Apple Vision Pro.

Total cost per hand works out to around €150-200. Commercial haptic gloves cost €8,000 and up.

Why gimbal motors

Gimbal motors are designed for smooth camera positioning. The 80KV rating means high torque at low speeds, which is exactly what you want for haptic feedback. The key realization is that the same motor positioning a gimbal can sense your finger position through the encoder, then resist your movement with controlled torque.

This is kinesthetic feedback. You feel actual forces opposing your motion, like touching a real object. Not vibration, not squeezing. Actual resistance.

SimpleFOCMini setup

SimpleFOCMini is a compact driver board based on the DRV8313 chip. It takes PWM signals from the STM32 and drives the motor phases.

Specs: 2.5A continuous, 3A peak, 6-45V supply, onboard 3.3V regulator, three half-bridge drivers.

Pin connections to STM32:

SimpleFOCMini -> STM32G431
IN1 (Phase A) -> PA8  (TIM1_CH1)
IN2 (Phase B) -> PA9  (TIM1_CH2) 
IN3 (Phase C) -> PA10 (TIM1_CH3)
EN (Enable)   -> PB12 (GPIO)

The STM32 timer outputs six complementary PWM signals but SimpleFOCMini only needs three. It handles the low-side switching internally.

AS5600 encoder

The AS5600 is a 12-bit contactless magnetic encoder. A magnetized disc on the motor shaft, the chip measures field angle. 4096 positions per revolution, which gives 0.088° precision. Good enough that motion feels smooth to your finger.

It’s absolute positioning so no homing sequence needed. The chip knows where it is on power-up. Updates at 500Hz+, so latency stays low.

The problem: all AS5600 chips use address 0x36. With five fingers that’s five address conflicts. Solution is the TCA9548A I²C multiplexer. It’s an 8-channel switch. The STM32 selects which encoder to talk to.

// Select encoder channel
void select_encoder_channel(uint8_t channel) {
    uint8_t control = 1 << channel;
    HAL_I2C_Master_Transmit(&hi2c1, TCA9548A_ADDR << 1, &control, 1, 100);
}

// Read angle
uint16_t read_encoder_angle(void) {
    uint8_t data[2];
    HAL_I2C_Mem_Read(&hi2c1, AS5600_ADDR << 1, 0x0C, 1, data, 2, 100);
    return (data[0] << 8) | data[1];
}

Field-oriented control basics

BLDC motors have three phases. Naive control just energizes them in sequence. Works, but it’s jerky and inefficient.

FOC is better. Instead of thinking in three phases, you transform to a 2D coordinate system that rotates with the motor. You control torque and flux independently, like a DC motor, but get AC efficiency and smoothness.

The math in simplified form:

  1. Read rotor angle from encoder
  2. Clarke transform: three-phase currents to two-axis stationary frame
  3. Park transform: rotate by rotor angle to get direct/quadrature currents
  4. PI controllers adjust Id (flux) and Iq (torque)
  5. Inverse Park and Clarke: back to three-phase voltages
  6. Space vector PWM: generate duty cycles

SimpleFOC library handles all this. You just set target position or torque.

SimpleFOC library code

SimpleFOC is Arduino-compatible. For STM32 I use STM32duino core.

Basic setup:

#include <SimpleFOC.h>

// Motor: 2208 has 14 pole pairs
BLDCMotor motor = BLDCMotor(14);

// Driver: SimpleFOCMini on 3PWM mode
BLDCDriver3PWM driver = BLDCDriver3PWM(PA8, PA9, PA10, PB12);

// Sensor: AS5600 on I2C
MagneticSensorI2C sensor = MagneticSensorI2C(AS5600_I2C);

void setup() {
    Wire.begin();
    sensor.init();
    motor.linkSensor(&sensor);
    
    driver.voltage_power_supply = 12;
    driver.init();
    motor.linkDriver(&driver);
    
    motor.controller = MotionControlType::angle;
    
    // PID tuning
    motor.PID_velocity.P = 0.2;
    motor.PID_velocity.I = 20;
    motor.PID_velocity.D = 0.001;
    motor.P_angle.P = 20;
    
    motor.init();
    motor.initFOC();
}

void loop() {
    motor.loopFOC();
    motor.move();
}

The library supports three control modes:

Torque control: set desired torque directly. For haptics this resists motion with proportional force.
Velocity control: maintain constant speed. Match user’s finger speed.
Position control: hold or move to target angle. Track finger joint angle.

Haptic feedback strategy

The motor does two things:

Tracking mode (default):

motor.controller = MotionControlType::angle;
float finger_angle = sensor.getAngle();
motor.target = finger_angle;
motor.voltage_limit = 0.5;  // Very low resistance

Haptic feedback mode (when touching virtual object):

motor.controller = MotionControlType::torque;
float penetration = virtual_object_position - finger_angle;
if (penetration > 0) {
    motor.target = penetration * stiffness_constant;
} else {
    motor.target = 0;
}

The impedance approach means motor speed matches finger speed, creating the feeling of touching a real object.

System integration

Each STM32 board runs independently, controlling two motors. The ESP32 aggregates data:

STM32 reads encoder angles at 1kHz
STM32 sends angle data to ESP32 via UART
ESP32 buffers all finger positions
ESP32 sends full hand state to Vision Pro via UDP WiFi
Vision Pro computes contact forces
Vision Pro sends force commands back
ESP32 distributes force targets to each STM32
STM32 sets motor torque

Target latency is 20-40ms end-to-end. Vision Pro handles arm and shoulder tracking. The glove handles finger dexterity.

Things I learned

Current sensing would be better. SimpleFOCMini doesn’t have built-in current sensors so I’m running voltage mode FOC. It works but limits torque accuracy. Adding inline current shunts on each phase would enable current-mode FOC with better force control.

I2C can be flaky. The AS5600 locks up occasionally, especially during motor startup cogging. Added pullup resistors (2.2kΩ) and implemented bus reset on timeout. Fixed most issues.

Calibration matters. Each motor has different resistance and inductance. Running motor.initFOC() measures electrical parameters. I do this once per motor, save to EEPROM, skip on subsequent boots.

Thermal management is important. DRV8313 gets hot at 2A continuous. Small heatsink or a tiny fan helps if running aggressive haptics for extended periods.

Why this approach works

Commercial haptic gloves use voice coils, pneumatics, cable systems. All expensive and complex. Gimbal motors are €3 each, widely available, designed for exactly this kind of smooth precise control.

A 2023 PMC paper validated this exact hardware stack for kinesthetic feedback. Same motors, same SimpleFOC approach. Their results: users could distinguish virtual object stiffness, texture simulation worked, force rendering was convincing.

For €150 per hand you get five-finger tracking and force feedback. Control loop runs at 1kHz. STM32G431 at 170MHz handles FOC on two motors without breaking a sweat.

Next steps

Single-finger proof-of-concept is ordered (€16). Once that works I’ll scale to the full five-finger system. The architecture is modular so each finger is identical. Debug one, debug them all.

SimpleFOC lets you prototype fast. Flash code, motors spin. Tune PID gains in real-time over serial. Swap control modes on the fly. No black-box drivers, no vendor lock-in. Just open-source FOC on commodity hardware.

~Ajit George