28BYJ-48 test sketch

Had some 28BYJ-48 stepper motors with ULN driver boards on the bench, and nothing better to do, so I wrote a useless test sketch that just spins the motor at max speed (~15RPM). Single coil drive, so low torque and low current draw (<200mA). All in a single line in loop(). Increase the >> 1 to something like >> 8 for a lower speed.
Leo..

const byte pins[] {4, 5, 6, 7}; // IN 1-4 to pin 4-7
const byte coils[] {0b1, 0b10, 0b100, 0b1000};

void setup() {
  for (byte i = 0; i < 4; i++) pinMode(pins[i], OUTPUT);
}

void loop() {
  for (byte i = 0; i < 4; i++) digitalWrite(pins[i], bitRead(coils[(millis() >> 1 & 3)], i));
}
1 Like

bringing the discussion from this thread

This looks cool, though it'd help to add in a comment the sequence it generates.

For my lib I'll provide several strategiers for sequencing, definitely adding this one. But I have a couple of doubts:

  • Wouldn't it be best to set all out pins at the exact same time? in RPi this method lets you do so (testes with a scope and rise time is the same for 2 pins) GPIO.output([pin1, pin2, pin3, pin4], [val1, val2, val3, val4]) .
  • In my stepping API I handle it as stepping jobs with n steps, currently all jobs start at step zero which bring up:
    # Todo: Not sure if starting from zero on every stepping job is the right call.
    # What happens when a job ends at a different phase and then the next starts at phase 0?

Given that I use simultaneous pin setting this are my sequences:

        STEP_SEQUENCE = {
            FULL_2PIN: [(GPIO.LOW, GPIO.HIGH),   # 01
                        (GPIO.HIGH, GPIO.HIGH),  # 11
                        (GPIO.HIGH, GPIO.LOW)],  # 10

            FULL:  [(GPIO.HIGH, GPIO.LOW, GPIO.HIGH, GPIO.LOW),  # 1010
                    (GPIO.LOW, GPIO.HIGH, GPIO.HIGH, GPIO.LOW),  # 0110
                    (GPIO.LOW, GPIO.HIGH, GPIO.LOW, GPIO.HIGH),  # 0101
                    (GPIO.HIGH, GPIO.LOW, GPIO.LOW, GPIO.HIGH)], # 1001

            HALF: [(GPIO.HIGH, GPIO.LOW, GPIO.LOW, GPIO.LOW),     # 1000
                   (GPIO.HIGH, GPIO.LOW, GPIO.HIGH, GPIO.LOW),   # 1010
                   (GPIO.LOW, GPIO.LOW, GPIO.HIGH, GPIO.LOW),    # 0010
                   (GPIO.LOW, GPIO.HIGH, GPIO.HIGH, GPIO.LOW),   # 0110
                   (GPIO.LOW, GPIO.HIGH, GPIO.LOW, GPIO.LOW),    # 0100
                   (GPIO.LOW, GPIO.HIGH, GPIO.LOW, GPIO.HIGH),   # 0101
                   (GPIO.LOW, GPIO.LOW, GPIO.LOW, GPIO.HIGH),    # 0001
                   (GPIO.HIGH, GPIO.LOW, GPIO.LOW, GPIO.HIGH)],  # 1001

            FULL_3PIN: [(GPIO.HIGH, GPIO.LOW, GPIO.LOW),   # 100
                        (GPIO.LOW, GPIO.LOW, GPIO.HIGH),   # 001
                        (GPIO.LOW, GPIO.HIGH, GPIO.LOW)],  # 010

            HALF_3PIN: [(GPIO.HIGH, GPIO.LOW, GPIO.LOW),    # 100
                        (GPIO.HIGH, GPIO.LOW, GPIO.HIGH),   # 101
                        (GPIO.LOW, GPIO.LOW, GPIO.HIGH),    # 001
                        (GPIO.LOW, GPIO.HIGH, GPIO.HIGH),   # 011
                        (GPIO.LOW, GPIO.HIGH, GPIO.LOW),    # 010
                        (GPIO.HIGH, GPIO.HIGH, GPIO.LOW)]}  # 110

const byte coils[] {0b1, 0b10, 0b100, 0b1000}; // removed leading zeroes
const byte coils[] {B0001, B0010, B0100, B1000}; // one coil at the time
const byte coils[] {1, 2, 4, 8}; // same

0001
0010
0100
1000

Leo..

Also...
Do you double Pulse frequency on half step, quadruple on 1/4 stepping and so on...?
I'm doing that right now but above 1/16 it gets tight.

I only do a 4-sequence full step with these 28BYJ-48 motors, because the backlash of the plastic gear train is so bad that half step is just silly. I think the 28BYJ-48 needs it's own simple library, without the fancy additions of 200-step motors. Here is the code I wrote a few years ago for 48 motors, 24 TPIC6B595 and a classic Nano. Maybe it's useful.
Leo..

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR)
   D10 | 12 (RCK)
   D11 | 3 (SER IN) of the first chip
   D13 | 13 (SRCK)
       | 9 to GND
       | 10, 11,19 to GND
       | 18 (SER OUT) to 3 of the next chip
*/

#include <SPI.h>
const byte motors = 48; // sets of four motors
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
const byte rampSteps = 7; // accel/decel steps
byte rampDelay[motors]; // accel/decel delay between steps
byte lag[motors]; // speed reduction
int velocity[motors]; // signed, for direction
int nowPos[motors], newPos[motors]; // int is 16 rotations max (2.5m string with a 50mm dia wheel)
unsigned long prevMillis, prevMicros, interval; // timing
byte val[4]; // TPIC write bytes
bool torque; // reduced during homing
byte sequence, index; // patterns, motor index

void setup() {
  SPI.begin();
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  for (byte i = 0; i < motors; i++) nowPos[i] = 1024; // homing all motors 1/2 turn
}

void loop() {
  switch (sequence) {
    case 0: // break free from the top, all motors
      if (nowPos[0] == newPos[0]) { // finished homing
        for (byte i = 0; i < motors; i++) nowPos[i] = -128; // 1/16 turn
        torque = true; // was low power during homing
        sequence = 1; // case 0 is not called anymore
        prevMillis = millis(); // so mark here
      }
      break;
    case 1: // 4.5 turns down, all motors
      if (millis() - prevMillis > 8000) {
        for (byte i = 0; i < motors; i++) newPos[i] = 9216; // 4.5 * 2048
        sequence = 2;
      }
      break;
    case 2: // all return to zero, one after the other
      if (millis() - prevMillis > 30000 + interval) {
        if (index < motors) {
          newPos[index] = 0;
          interval += 500;
          index += 1;
        } else { // when done
          index = 0;
          interval = 0;
          sequence = 3;
        }
      }
      break;
    case 3: // run sequence(s) again
      if (millis() - prevMillis > 75000) {
        prevMillis = millis();
        sequence = 1;
      }
      break;
  }
  goPos(); // let's step, one step each loop
}

void goPos() {
  while (micros() - prevMicros < 1953); // step interval, motor RPM limitation, 1/(15rpm/60sec*2048steps)= ~1953ms, wait if arrived here too early
  prevMicros = micros(); // update, for next interval
  for (int i = 0; i < motors; i++) {
    if (rampDelay[i]) rampDelay[i]--; // subtract one, do nothing
    else { // step
      if (newPos[i] > nowPos[i]) { // request forwards
        if (velocity[i] >= 0) { // if standing still, or going forwards
          nowPos[i]++; // one step forward
          if (newPos[i] - nowPos[i] < rampSteps) velocity[i]--; // reduce speed if almost there
          else if (velocity[i] < min(rampSteps, rampSteps - lag[i])) velocity[i]++; // increase speed if not there yet
          rampDelay[i] = max(lag[i], rampSteps - velocity[i]); // insert ramp delay
        } else { // if wrong direction
          velocity[i]++; // reduce reverse speed
          if (velocity[i] < 0) nowPos[i]--; // if still reverse, keep on going the wrong way
          rampDelay[i] = rampSteps - abs(velocity[i]); // insert ramp delay
        }
      }
      if (newPos[i] < nowPos[i]) { // request reverse
        if (velocity[i] <= 0) { // if standing still, or going reverse
          nowPos[i]--; // one step reverse
          if (nowPos[i] - newPos[i] < rampSteps) velocity[i]++; // reduce speed if almost there
          else if (abs(velocity[i]) < min(rampSteps, rampSteps - lag[i])) velocity[i]--; // increase speed if not there yet
          rampDelay[i] = max(lag[i], rampSteps + velocity[i]); // insert ramp delay
        } else { // wrong direction
          velocity[i]--; // reduce forward speed
          if (velocity[i] > 0) nowPos[i]++; // if still reverse, keep on going the wrong way
          rampDelay[i] = rampSteps - velocity[i]; // insert ramp delay
        }
      }
    }
    if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there
    else {
      if (torque) { // this block for full torque
        switch (nowPos[i] & 3) { // the two LSB translated to motor coils (full step)
          case 0: val[i & 3] = B00000011; break; // two coils at the time
          case 1: val[i & 3] = B00000110; break;
          case 2: val[i & 3] = B00001100; break;
          case 3: val[i & 3] = B00001001; break;
        }
      } else { // this block for low power
        switch (nowPos[i] & 3) {
          case 0: val[i & 3] = B00000001; break; // one coil at the time
          case 1: val[i & 3] = B00000010; break;
          case 2: val[i & 3] = B00000100; break;
          case 3: val[i & 3] = B00001000; break;
        }
      }
    }
    if ((i & 3) == 3) { // process set of four motors (faster)
      unsigned int chain = val[0] << 12 | val[1] << 8 | val[2] << 4 | val[3]; // concat
      SPI.transfer16(chain); // transfer 4-motor int
    }
  }
  digitalWrite(latchPin, HIGH); // transfer to TPIC driver outputs
  digitalWrite(latchPin, LOW); // latch time ~8us
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.