Pin manipulation and shift registers

Hi all,

I'm new to programming the arduino so I'm looking for a bit of guidance.

I'm attempting to use the arduino to drive stepper motors via a shift register. Components are Arduino Nano, Shift register 74HC595, and a 28bjy-48 stepper with ULN2003 driver.

I'm attempting to input bits using port manipulation because I believe it will enable a few daisy chained shift registers to turn the motors faster, and I can easily turn the motors in unison but to different degrees (θ).

My code is

void setup()
{
pinMode (11, OUTPUT); // Connected to Serial input on shift register
pinMode (8, OUTPUT); // Clock pin
pinMode (9, OUTPUT); // Latch pin
}

void loop()
{
for(i = 0; i < 512; i++)
{
PORTB = B00000001 // Send 0 on pin 11 and clock high on pin 8
PORTB = B00000001 // Send 0 on pin 11 clock high on pin 8
PORTB = B00000001 // Send 0 on pin 11
PORTB = B00001011 // Send 1 on pin 11 and latch pin 9 (output to stepper)
PORTB = B00000011 // Send 0 on pin 11 and latch pin 9 (output to stepper)
PORTB = B00000011 // Send 0 on pin 11 and latch pin 9 (output to stepper)
PORTB = B00000011 // Send 0 on pin 11 and latch pin 9 (output to stepper)
}
}

In my mind the shift register would look like this

The sequence of [1000] > [0001] causes the motor to do 1/512 of a rotation. However the motor doesn't turn using my code.

If anyone can point me in the direction of what I need to learn / include in my code to make this work it would be very appreciated. Thanks

In the IDE format your code with <ctrl> t. This will show some of your issues.

The clock pin has to be toggled (clocked) once for each bit you want to shift. Shift eight times for each set of stepper outputs so that the control bits end up at the same S.R. outputs each time.

Why not get your feet wet with the shiftOut() - Arduino Reference function and then go to port manipulation?

Take a look at this code that @wawa wrote for a 24 shift register board. Adjust it for a single shift register.

#include <SPI.h>
#define motors 24 // in lots of four motors - On my board
const byte clrPin = 7; // TPIC6B595 SRCLR pin  - Dummy Pin - ON MY BOARD, SRCLR IS HIGH
const byte activityPin = 8; // On my board
const byte oePin = 9; // On my board
const byte latchPin = 10; // TPIC6B595 RCK pin
const byte rampSteps = 4; // 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>long=+220us
unsigned long prevMicros; // timing
byte val[4]; // TPIC write bytes
unsigned long start; // 4 testing
bool T0, T1, T2, T3; // 4 testing
unsigned long prevMillis; // 4 testing
byte x;

void setup() {
  Serial.begin(115200); // 4testing
  SPI.begin();
  //pinMode(clrPin, OUTPUT);
  //digitalWrite(clrPin, HIGH); // clean start
  pinMode (oePin, OUTPUT);
  digitalWrite (oePin, LOW);

  pinMode(latchPin, OUTPUT);
  digitalWrite (latchPin, LOW);
  for (x = 0; x < 12; x = x + 1) {
    SPI.transfer(0); // clear all shift registers
  }
  digitalWrite (latchPin, HIGH);

  pinMode (activityPin, OUTPUT);
  for (x = 0; x < 3; x = x + 1) {
    digitalWrite (activityPin, HIGH); // flash the onboard LED
    delay (250);
    digitalWrite (activityPin, LOW);
    delay (250);
  }

}

void loop() {
  // sumdumtest
  if (!T0 && millis() - prevMillis > 1000) {
    for (byte i = 0; i < motors; i++) { // all motors
      lag[i] = 0;
      newPos[i] = 512; // 1/4 turn
    }
    T0 = true;
  }
  if (!T1 && millis() - prevMillis > 3000) {
    for (byte i = 0; i < motors; i++) { // all motors
      lag[i] = 0;
      newPos[i] = -511; // 1/2 turn
    }
    T1 = true;
  }
  if (!T2 && millis() - prevMillis > 6000) {
    for (byte i = 0; i < motors; i++) { // all motors
      lag[i] = 0;
      newPos[i] = 0; // back to zero
    }
    T2 = true;
  }
  if (millis() - prevMillis > 8000) {
    prevMillis = millis();
    T0 = false;
    T1 = false;
    T2 = false;
  }

  goPos(); // let's step
}

void goPos() {
  while (micros() - prevMicros < 2440); // minimum step interval, 12RPM motor limit
  prevMicros = micros(); // update, for next interval
  //start = micros(); // 4testing
  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 {
      switch (nowPos[i] & 3) { // the two LSB translated to motor coils (full step)
        // this block for torque
        //case 0: val[i & 3] = B00000011; break;
        //case 1: val[i & 3] = B00001001; break;
        //case 2: val[i & 3] = B00001100; break;
        //case 3: val[i & 3] = B00000110; break;
        // or this block for low power
        case 0: val[i & 3] = B00000001; break;
        case 1: val[i & 3] = B00001000; break;
        case 2: val[i & 3] = B00000100; break;
        case 3: val[i & 3] = B00000010; break;
      } // order is important for not missing steps when coil power is cut
    }
    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
  //Serial.println(micros() - start); // 4testing, must stay well below step interval
}

TPIC6B595 can drive a motor directly in place of a 74HC595 and ULN2003.
Its TPIC6B595 and 74HC595 are both controlled identically.

What pattern of bits did you want to send? In general you start by setting LATCH to LOW. Then, for each data bit, set the DATA pin and turn the CLOCK on and off. For example, to send the pattern 0100 to the bottom 4 bits:

PORTB = B00000000; // LATCH: LOW

PORTB = B00000001; // CLOCK:HIGH (DATA: 0)
PORTB = B00000000; // CLOCK: LOW (DATA: 0)

PORTB = B00001001; // CLOCK:HIGH (DATA: 1)
PORTB = B00001000; // CLOCK: LOW (DATA: 1)

PORTB = B00000001; // CLOCK:HIGH (DATA: 0)
PORTB = B00000000; // CLOCK: LOW (DATA: 0)

PORTB = B00000001; // CLOCK:HIGH (DATA: 0)
PORTB = B00000000; // CLOCK: LOW (DATA: 0)

PORTB = B00000010; // LATCH: HIGH

Then you would need to repeat such a pattern for the three other steps needed to complete a cycle. Then repeat the four steps 128 times for 512 total steps.

If the pattern is a single moving bit you can move it by doing one clock cycle:

PORTB = B00000000; // LATCH: LOW

PORTB = B00000001; // CLOCK:HIGH (DATA: 0)
PORTB = B00000000; // CLOCK: LOW (DATA: 0)

PORTB = B00000010; // LATCH: HIGH

but remember that that only works in one direction. To step in the other direction you need to send enough bits to shift the old '1' off the top each time.

PS: I don't think your stepper can do anywhere near to a million steps a second so why do you think you need direct port manipulation?

1 Like

Thanks John.

After a bit of tinkering I was able to get it to work.

I'm using 40 stepper motors (each requiring 4 bits to be loaded into the appropriate SReg output pins). The motors are stepped by shifting the 1 down from [1000] to [0100] and so forth every 2 ms. This essentially means the Arduino needs to output (every 4 cycles) 160 bits in 2ms to reinitialise all the steppers to [1000] or 80,000 bits per second.

I'm not sure if digitalWrite would have been able to do this, but there were other benefits to using port manipulation.

For example, if I wanted to turn Stepper A 10 times and the Stepper B 20 times it was very easy to write the program to:

  1. Detect that Stepper A and Stepper B were to be turned a different amount
  2. Start initialising the Stepper A's Shift register pins to [0000] after the 10th cycle.

Anyway the program works now and you knew the problem. Thank you

I was able to control 48 28BYJ-48 steppers to their speed limit (~15RPM) with a classic Nano, without port manipulation. With processor time left.
Leo..

1 Like

Thanks for letting me know. I can certainly use that as a rough benchmark for the digitalWrite function in any future projects.

I suppose I understood what was going on at the pin-shift register level and found it easier to write the software where I had control of that.

Use SPI.transfer. or SPI.transfer16 to write to the shift register chain.
Then 100 steppers at ~12RPM (all at once) is still possible with a basic Nano, even with some acceleration.
Leo..