Field Notes · Nº 001 · Maio 2026
Field Notes · 001 — Brushless / SimpleFOC

Waking a Dead Motor

Open-loop bring-up of a salvaged floppy-spindle BLDC on an MKS Dual FOC v3.2 Plus. SimpleFOC and the GPIO map that took an evening to find.

by · edgy13 min read/SimpleFOCBLDCESP32MKS Dual FOCEG2133open-loopFOC
Date
2026 · 05 · 28
Bench
MKS Dual FOC v3.2 · EG2133
Sensor
AS5600 · I²C
Status
Jerky
§01

The motor in the box

The board arrived first. The motors did not. I had ordered a MKS Dual FOC v3.2 Plus off AliExpress with a couple of small brushless rotors, because I wanted to start playing with field-oriented control properly and the controller is half the fun. The board landed in about a week. The motors were going to take three more.

I was impatient. I wanted to flash the ESP32, light up the driver, watch something commutate. For a while I sketched out ways to fake a load: resistor banks across the phases, a salvaged stepper coil, anything that would prove the gate driver was alive without a real rotor on it.1 Then I remembered the box.

Somewhere in the cupboard I had a stash of things kept from when I was younger and obsessed with how machines worked. Among them, I was almost sure, was a brushless motor pulled out of a floppy disk drive: an open-frame spindle outrunner, big magnet, visible coils, no plastic shroud hiding anything. I'd taken it out as a teenager because brushless motors were fascinating and I wanted to use it for something. Never figured out what. Never threw it away either.

I went and looked. It was still there.

So I soldered three wires onto the stator pads, put the multimeter across a pair, and read 15 Ω between any two phases. That's a wye-connected motor with about 7.5 Ω per phase (a forgiving, low-current rotor that would be hard to abuse at the voltages a small bench supply can produce). The plan crystallised right there at the desk: plug it into the controller, flash a minimal SimpleFOC sketch, and see what happened.

§02

Field-oriented control, briefly

A brushless motor is just three coils and a spinning magnet. The hard part is timing: you have to push current through the right coils at the right instant, relative to where the rotor actually is. Get it wrong and the motor cogs, heats up, and goes nowhere.

Field-oriented control (FOC) solves this elegantly. It transforms the three messy phase currents into two clean, rotor-aligned axes (d for flux, q for torque), runs simple proportional-integral (PI) controllers there, and transforms the result back into three phase voltages. You command torque directly and the math handles the commutation.

The loop runs thousands of times a second. Conceptually it looks like this:

FIG. 1 — Closed-loop FOC, one iteration. This post runs the bottom half only (no sensor yet).

phase i

feedback

θ

θ

ω* target

PI velocity

Inv. Park · dq→αβ

SVPWM

3φ driver

BLDC

Clarke · abc→αβ

Park · αβ→dq

AS5600 angle

"You stop thinking about coils, and start thinking about torque."

For closed-loop FOC you need a sensor: the AS5600 magnetic encoder on the shaft tells the controller where the rotor actually is, so the math can put current on the right axis. I had one on order, and it hadn't arrived either. So this first session was open-loop only: no sensor, just ramp an electrical angle at a chosen rate and apply voltage at that angle. The rough-and-ready half of FOC. It either spins or it doesn't.2

§03

The board is not what the forums say it is

The first surprise was the toolchain. SimpleFOC 2.3.4's ESP32 backend uses the new motor-control PWM (MCPWM) API (driver/mcpwm_prelude.h), which only exists in ESP-IDF 5, but PlatformIO's official espressif32 platform pins the Arduino core at 2.0.17, on ESP-IDF 4. The build failed on a missing header: really a "your platform is too old" problem in disguise. Switching to the pioarduino fork3, which tracks arduino-esp32 3.x and ESP-IDF 5, was one line in platformio.ini.

The second surprise was the board, for the boring reason that my revision had no published schematic. I had v3.2 Plus; the MKS-DUALFOC repository4 only documented v3.3. A forum post5 pointed me there anyway ("close enough, the difference is in the current sensor"), so I treated v3.3 as a proxy for my I/O map and left the current-sense differences for later. That gave me the pin map: M0 enable is GPIO 22, M0 PWM phases GPIO 32 / 33 / 25. Both channels live on one board (M0 and M1, separate enable pins); older v2.0 boards shared GPIO 12 for both, probably the source of half the forum confusion.

FIG. 2 — Wiring: brains → buck → driver → motor
ESP32 6× PWM · I²C EN (GPIO 22) BL9342 buck 12V → gate rail PWM ×6 gate Vcc EG2133 3φ gate driver 3φ A · B · C BLDC Motor 30s/20p · 7.5 Ω/φ 12 V PSU shared GND Vmot
FIG. 2The ESP32 does not enable the driver directly. It enables a small buck regulator that makes the gate-drive rail the EG2133 needs. The motor's three phases come off the driver outputs.
§04

First flash, nothing happens

The first upload was uneventful. The toolchain compiled, pio run -t upload flashed cleanly at 1152006, and the serial monitor came up at the same rate. I sent a target velocity over Commander and waited for torque.

Nothing happened.

The motor sat there: no hum, no twitch, no hint of current. I leaned in for the PWM whine. Silence. I checked the supply: my bench was on 5 V, because that's what was already on the desk. The MKS Dual FOC needs at least 12 V to switch its MOSFETs, and a board-level divider on GPIO 13 lets the Makerbase firmware refuse to enable under about 11.1 V. At 5 V the gate-drive supply doesn't even come up. I swapped to a 12 V wall-wart and tried again.

Still nothing. Which channel: M0 or M1? I'd been driving the enable pin on GPIO 12, because an old MKS-ESP32FOC v2.0 example uses it for both motors. On v3.3, GPIO 12 is M1's enable; M0's is GPIO 22. My PWM lines were on M0's gate inputs while my enable drove M1's supply: I was lighting up the wrong channel and leaving M0 dark.

I changed one line in the sketch:

fix.difftext
- BLDCDriver3PWM driver = BLDCDriver3PWM(32, 33, 25, 12);
+ BLDCDriver3PWM driver = BLDCDriver3PWM(32, 33, 25, 22);

Re-flashed. Hit T1 for a 1 rad/s open-loop target. And finally, this:

/dev/ttyUSB0 — first sign of life115200 baud
MOT: Init MOT: Enable driver. MOT: Motor ready. > T1 target = 1.00 rad/s (open-loop)

A faint, regular tick from the motor. Current was flowing in the right coils at the right time. The rotor was twitching, then stepping, then twitching again, but it was clearly doing something on command.

§05

The motor rotates (sort of)

What I had now was the smallest possible SimpleFOC sketch: a driver, an open-loop velocity controller, a Commander binding so I could change the target without re-flashing.

foc_openloop.inocpp
#include <SimpleFOC.h>

// 30-slot / 20-pole floppy-spindle outrunner → 10 pole pairs.
// Pin map from MKS-DUALFOC v3.3 (no schematic published for v3.2 Plus).
BLDCMotor    motor  = BLDCMotor(10);
BLDCDriver3PWM driver = BLDCDriver3PWM(32, 33, 25, 22);

Commander commander = Commander(Serial);
void doTarget(char *cmd) { commander.scalar(&motor.target, cmd); }

void setup() {
  Serial.begin(115200);

  driver.voltage_power_supply = 12;
  driver.voltage_limit        = 3;     // be gentle; ~0.4 A through 7.5 Ω
  driver.init();
  motor.linkDriver(&driver);

  motor.controller     = MotionControlType::velocity_openloop;
  motor.voltage_limit  = 3;
  motor.velocity_limit = 20;

  motor.useMonitoring(Serial);
  motor.init();                        // no initFOC; no sensor yet
  commander.add('T', doTarget, "target velocity [rad/s]");
  Serial.println(F("ready. open loop."));
}

void loop() {
  motor.move();                        // open loop: just ramp the angle
  commander.run();
}

I bumped the voltage limit up gently (1 V, 2 V, 3 V) and watched. At low targets the rotor turned, but not smoothly: move, pause, move, pause. A staccato rotation, always the same direction, never quite continuous.

My first instinct was a wrong pole-pair count. SimpleFOC's open-loop ramp builds the electrical angle as electrical_angle = target × pole_pairs × t, so if pole_pairs is off the rotor desyncs: twitch, stutter, refuse to keep up. The classic move-stop-move pattern is exactly what a wrong count looks like.

I tried 8, 11, 14. Same staccato every time, which was confusing until I read more carefully: in open loop, almost any pole-pair count produces smooth rotation at low enough speeds. The rotor is dragged along by a slowly rotating field and sticks to it whether the electrical-to-mechanical ratio is right or not, because mechanical speed comes straight from the commanded target, not from pole_pairs. The move-stop pattern at higher speeds wasn't a pole-pair error either. It was the open-loop slip ceiling: past a critical speed the small fixed voltage can't hold the rotor in sync, so it slips a pole, catches up, slips again.

So I needed a real way to count poles. Eyeballing was out: the rotor is a continuous ferrite ring magnet, smooth all the way around, no visible pole boundaries. Two physical methods, two answers. Rotating the unpowered shaft by hand, I felt the cogging detents click into place roughly every half-slot: thirty slots, two clicks each, ~60 detents per revolution, which for a three-phase 30-slot motor lines up with 20 poles, ten pole pairs. Then, rotor off, I walked a small probe magnet around the inner bell and counted the jumps as it locked from pole to pole: nine. Counted again, still nine.

Nine pairs would mean eighteen poles, contradicting the cogging count and (later) what SimpleFOC's own MOT: PP check: reports once the encoder is in. I went with ten, because the rest of the evidence agreed and the motor behaved better there. But the probe kept nagging: its jumps were noticeably less crisp at one end of the bell than the other, which I'd much later trace to magnet eccentricity in the mount. I wouldn't bet money it isn't nine.

So the motor turned. Not well: a clear hitch every revolution, uneven sound, and any real load would have stalled it instantly. But it turned, on command, in the direction and roughly the speed I asked for. With a pole-pair count I was about 80 % sure of.

§06

What I had not yet earned

This was a milestone, the kind you celebrate while privately suspecting it isn't quite what you think it is.

What I had proved: the toolchain compiled, the gate-drive rail came up, the EG2133 was switching, the three phases reached the motor, and SimpleFOC could ramp an angle and produce torque-at-an-angle. That's the bottom half of FIG. 1: the modulator, the driver, the motor itself.

What I had not proved: any of the top half. The sensor wasn't in the loop, the PI controllers weren't running, and the motor was being dragged around by a synthetic angle counter rather than closing any loop with the real rotor. Any board that can produce three balanced PWM phases passes this test, and almost any pole-pair count lets it look like it's working.

A few days on, I'd learn this the hard way. Open loop is a trap: "spins under open loop" cannot be transferred to "will spin under closed loop", because the two use different equations for the electrical angle and the open-loop side is blind to half of what can break the closed-loop side.7 The pole-pair count would bite too: nine versus ten only matters once the sensor is in the loop, and the closed-loop alignment routine would care even though open-loop hadn't.

That is a story for the next entry. For now the AS5600 and the new motors were both on their way, the board was alive, the sketch was minimal, and a salvaged floppy spindle was turning in stuttering little half-revolutions on the bench, doing what it had been kept in a box for two decades to do.

End of file · 001

References & Links

  1. SimpleFOC — Arduino field-oriented control librarydocs.simplefoc.com
  2. MKS-DUALFOC repository — Makerbase's first-hand pinout and example sketchesgithub.com · makerbase-motor
  3. SimpleFOC community thread — pinout pointers for MKS Dual FOC v3.xcommunity.simplefoc.com
  4. pioarduino fork — arduino-esp32 3.x + ESP-IDF 5 PlatformIO platformgithub.com · pioarduino
  5. EG2133 three-phase gate driver — manufacturer datasheetegmicro.com
  6. AS5600 12-bit magnetic position sensorams-osram.com
  7. A primer on field-oriented control & the Park/Clarke transformswikipedia