Stepper dial on the analyzer, on an Arduino Uno
A 28BYJ-48 following a knob, with a logic analyzer on its four coil lines: the half-step sequence walking, and the capture as CSV. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $34. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino Stepper Motor: 28BYJ-48 and ULN2003
What you'll learn
- A stepper goes to a position, not a speed
- Why a 28BYJ-48 has 4096 half-steps per turn
- Reading coil signals on a logic analyzer
- Using a dead band to stop the motor humming
- Why counting steps does not prove where the shaft is
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
Turn the knob on A0 and the 28BYJ-48 stepper shaft goes to the matching position and stays there. The knob's full sweep covers half a turn of the output shaft, 2048 half-steps out of 4096 for a full turn.
A logic analyzer is hooked to the four coil lines on pins 8 to 11, so you can watch the half-step sequence walk across the channels as the motor moves. When the shaft arrives, the sketch releases the coils so nothing holds current.
Parts list
4 parts, plus the jumper wires and a bench instrument. The parts retail for about $34; on Mokxi they're free, and so are the instruments.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Stepper motor, 28BYJ-48 with ULN2003
- 1 × PotentiometerIn Halloween Prop Kit, Starter Projects Kit.
- 1 × Logic analyzer, four channels
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Stepper motor, 28BYJ-48 with ULN2003 pin IN4 | Arduino Uno R3 pin 11 |
| Logic analyzer, four channels pin D3 | Arduino Uno R3 pin 11 |
| Stepper motor, 28BYJ-48 with ULN2003 pin IN3 | Arduino Uno R3 pin 10 |
| Logic analyzer, four channels pin D2 | Arduino Uno R3 pin 10 |
| Stepper motor, 28BYJ-48 with ULN2003 pin IN2 | Arduino Uno R3 pin 9 |
| Logic analyzer, four channels pin D1 | Arduino Uno R3 pin 9 |
| Stepper motor, 28BYJ-48 with ULN2003 pin IN1 | Arduino Uno R3 pin 8 |
| Logic analyzer, four channels pin D0 | Arduino Uno R3 pin 8 |
| Stepper motor, 28BYJ-48 with ULN2003 pin VCC | Arduino Uno R3 pin 5V |
| Potentiometer pin 3 | Arduino Uno R3 pin 5V |
| Stepper motor, 28BYJ-48 with ULN2003 pin GND | Arduino Uno R3 pin GND |
| Potentiometer pin 1 | Arduino Uno R3 pin GND |
| Logic analyzer, four channels pin GND | Arduino Uno R3 pin GND |
| Potentiometer pin 2 | Arduino Uno R3 pin A0 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Stepper dial: a knob on A0, the shaft follows it.
//
// A0 the potentiometer's wiper; its ends to 5 V and GND
// pins 8..11 IN1 to IN4 on the ULN2003 board
//
// A stepper does not go at a speed, it goes to a *position*, and this is the
// shortest circuit that makes that obvious: turn the knob and the shaft goes
// there and stays there.
//
// # What a driver actually is
//
// A table and a delay. The motor is four coils; switching them in order pulls
// the rotor round one point of a magnetic circle at a time. Nothing in
// firmware/lib/mokxi_stepper.h is cleverer than that, and nothing in the part
// is either; Mokxi's model works out where the field is pointing and moves
// the rotor there, so half-step, wave drive and two-phase-on all fall out of
// one table rather than being three special cases.
//
// # 4096
//
// The rotor turns 5.625 degrees per half-step and the gearbox divides by 64, so
// the output shaft moves 5.625/64 of a degree at a time and a whole turn is
// 4096 half-steps. That number is in every 28BYJ-48 sketch ever written and
// this is where it comes from. Half a turn (2048) is the sweep here, which
// is about as far as a dial wants to go.
//
// # The rate ceiling is real
//
// The datasheet's idle out-traction frequency is over 1000 Hz, so steps closer
// together than a millisecond are steps the rotor cannot follow. Mokxi's part
// misses them exactly as a real motor does. Turn the knob quickly and the
// shaft follows at its own top speed rather than teleporting, which is what a
// real one does too.
//
// # It does not know where it is
//
// Nothing here reads the shaft. The sketch counts the steps it has sent and
// assumes the motor made them, which is true until it is not (a stall, a
// jam, or a rate past the ceiling), and is why real positioning systems have
// a limit switch to home against. Press nothing and watch: if the count and
// the shaft ever disagree, the sketch will never find out.
#include "mokxi_stepper.h"
const int KNOB = A0;
const int IN1 = 8;
const int IN2 = 9;
const int IN3 = 10;
const int IN4 = 11;
// Half a turn of the output shaft across the knob's whole sweep.
const long SWEEP_STEPS = STEPS_PER_TURN_HALF / 2;
// The knob has to move this far before the motor is asked to, so the last bit
// of the ADC does not make it hum.
const int DEADBAND = 8;
Stepper28byj motor(IN1, IN2, IN3, IN4);
long at = 0; // where the sketch believes the shaft is, in half-steps
int lastKnob = -1000;
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Uno: 28BYJ-48 following a knob on A0");
motor.begin();
}
void loop() {
int knob = analogRead(KNOB);
if (knob > lastKnob - DEADBAND && knob < lastKnob + DEADBAND) {
return;
}
lastKnob = knob;
// Where the knob says the shaft should be, in half-steps from the start.
long want = ((long)knob * SWEEP_STEPS) / 1023;
long move = want - at;
if (move == 0) {
return;
}
motor.stepHalf(move, 1500);
at = want;
// Tenths of a degree, so there is no floating point anywhere:
// degrees = half-steps * 5.625 / 64, and ten times that in tenths.
long tenths = (at * 5625L) / 6400L;
Serial.print("knob ");
Serial.print(knob);
Serial.print(" -> ");
Serial.print(at);
Serial.print(" half-steps, ");
Serial.print(tenths / 10);
Serial.print(".");
Serial.print(tenths % 10);
Serial.println(" degrees");
// Let go once it is there: nothing holds the shaft, nothing draws current,
// and a real one stops getting warm.
motor.release();
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Starts the Serial Monitor at 115200 and prints a hello line. It calls motor.begin() to set the four coil pins as outputs.
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Uno: 28BYJ-48 following a knob on A0");
motor.begin();
}loop sketch.ino
Reads the knob and ignores it unless it moved by DEADBAND or more. It converts the knob to a wanted position in half-steps, moves the difference with stepHalf, and updates at. Then it prints the position in degrees and calls release.
void loop() {
int knob = analogRead(KNOB);
if (knob > lastKnob - DEADBAND && knob < lastKnob + DEADBAND) {
return;
}
lastKnob = knob;
// Where the knob says the shaft should be, in half-steps from the start.
long want = ((long)knob * SWEEP_STEPS) / 1023;
long move = want - at;
if (move == 0) {
return;
}
motor.stepHalf(move, 1500);
at = want;
// Tenths of a degree, so there is no floating point anywhere:
// degrees = half-steps * 5.625 / 64, and ten times that in tenths.
long tenths = (at * 5625L) / 6400L;
Serial.print("knob ");
Serial.print(knob);
Serial.print(" -> ");
Serial.print(at);
Serial.print(" half-steps, ");
Serial.print(tenths / 10);
Serial.print(".");
Serial.print(tenths % 10);
// ... 6 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The motor does not turn or twitches This project
Check that IN1 to IN4 go to pins 8, 9, 10 and 11 in order. A wrong order breaks the coil sequence. - The shaft slowly drifts from where the sketch thinks it is This project
The sketch only counts steps it sent. If the motor stalls or is moved too fast it loses steps, and the sketch cannot tell. - The motor buzzes or shakes and does not turn. The coil pins IN1 to IN4 are in the wrong order for the library. Stepper motor
For Arduino's Stepper library on a 28BYJ-48, create it with pins 8, 10, 9, 11 (IN1, IN3, IN2, IN4). Or use AccelStepper with HALF4WIRE and the same pin order. - It stalls or skips when spun fast or powered from the Arduino 5 V pin. It also draws more than a USB port may give. Stepper motor
Power the ULN2003 board from a separate 5 V supply and join the grounds. Run it slowly, about 10 to 15 RPM, and ramp up speed gradually. - The reading jumps around or only covers part of the range. Potentiometer
Wire the two outer legs to 5 V and ground and the middle leg (the wiper) to an analog pin. Using the wrong two legs gives a dead or partial range. - Turning the knob does nothing, or the value is stuck at 0 or 1023. Potentiometer
Check that the wiper goes to an analog input (A0 to A5 on an Uno), not a digital pin. Make sure both ends are connected, since a floating end gives noise. - The traces show random noise or nothing useful. Logic analyzer
Connect the analyzer's ground to the circuit's ground. Without a shared reference the probes float. Also check each channel is on the right wire and the logic level matches (3.3 V or 5 V). - Fast signals look wrong or edges are missing. Logic analyzer
The sample rate must be well above the signal rate, at least 4 times and better 10 times. Slow it down or sample faster, and set a trigger on the edge you want to catch.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.