Alex Chang
Published © CC BY-NC

Split flap display

My take on the timeless split flap display from old train stations. Controlled using A4988 motor drivers and an Arduino Uno.

AdvancedFull instructions provided7 hours5,223

Things used in this project

Hardware components

A4988 Stepper Motor Driver
×1
28byj-48 Stepper motor
×1
Arduino UNO
Arduino UNO
×1
5V 4000mA Switching Power Supply
Digilent 5V 4000mA Switching Power Supply
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

3D printed parts

Schematics

Fritzing wiring diagram

Code

flipflap.ino

Arduino
#include <AccelStepper.h>

class MotorController {
  private:
    AccelStepper stepper;
    int enablePin;
    int currentPosition;
    int targetPosition;
    float initOffset;

    
  public:
    const String flapsOrder = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!&.:+-=/$%# ";
    const int stepsPerRevolution = 4096;  // Full rotation of the motor
    const float stepsPerFlap = 83.59183673469388;  // Steps to move from one flap to the next
  
    MotorController(int stepPin, int dirPin, int enablePin, float initOffset)
      : stepper(1, stepPin, dirPin), enablePin(enablePin), initOffset(initOffset), currentPosition(0){ 
      stepper.setAcceleration(500);
      stepper.setMaxSpeed(800);
    }

    void homeMotor () {
      digitalWrite(enablePin, LOW); // Enable motor drivers
      moveSteps(-stepsPerRevolution * 1.1);
    }

    void moveToStart () {
      digitalWrite(enablePin, LOW);
      moveSteps(stepsPerRevolution/2 + initOffset * stepsPerFlap);
    }

    void moveSteps (int steps){
      digitalWrite(enablePin, LOW);
      stepper.move(steps);
    }

    int calculateStepsToMove(int targetFlapIndex) {
      int targetStepPosition = round(targetFlapIndex * stepsPerFlap);
      int stepDifference = targetStepPosition - currentPosition;
      if (stepDifference < 0) {
        stepDifference += stepsPerRevolution;  // Wrap-around
      }
      return stepDifference;
    }

    void moveToFlap(int targetFlapIndex) {
      int stepsToMove = calculateStepsToMove(targetFlapIndex);
      targetPosition = currentPosition + stepsToMove;
      if (targetPosition >= stepsPerRevolution) {
        targetPosition -= stepsPerRevolution;
      }
      stepper.move(stepsToMove);
      digitalWrite(enablePin, LOW);
    }

    void moveToChar(char c){
      int targetFlapIndex = charToFlapIndex(c);
      if (targetFlapIndex != -1) {
        moveToFlap(targetFlapIndex);
      }
    }

    bool updateMotor() {
      if (abs(stepper.distanceToGo()) > 0) {
        stepper.run();  // Non-blocking motor movement
        return false;
      } else if (stepper.distanceToGo() == 0) {
        currentPosition = targetPosition;
        return true;
      }
    }

    int charToFlapIndex(char c) {
      // Find the index of the character in flapsOrder
      int index = flapsOrder.indexOf(c);
      return index;
    }
};

const int enablePin = 14;
const int dirPin = 2;
MotorController motors[] = { 
  MotorController(7, dirPin, enablePin, 3.8), // Motor objects MotorController(step pin, direction pin (shared), enable pin (shared), inital offset (flaps))
  MotorController(6, dirPin, enablePin, 3), 
  MotorController(4, dirPin, enablePin, 3.8),
  MotorController(5, dirPin, enablePin, 3.5),
  MotorController(3, dirPin, enablePin, 3),
};
int numMotors = 5;  // Number of motors available

bool allMotorsFinished() {
  for (int i = 0; i < numMotors; i++) {
    if (!motors[i].updateMotor()) {
      return false;  // If any motor has not finished, return false
    }
  }
  return true;  // All motors have completed their movements
}


void setup() {
  Serial.begin(9600);
  // Move all motors in reverse until they hit the home position and get stuck
  for (int i = 0; i < numMotors; i++) {
    motors[i].homeMotor();
  }
  
  // Update all motors until they finish the initial move
  while (!allMotorsFinished()) {
    for (int i = 0; i < numMotors; i++) {
      motors[i].updateMotor();
    }
  }
  
  // Move all motors to start positions, the first flap 'A'.
  for (int i = 0; i < numMotors; i++) {
    motors[i].moveToStart();
  }
  
  // Update all motors until they reach their start positions
  while (!allMotorsFinished()) {
    for (int i = 0; i < numMotors; i++) {
      motors[i].updateMotor();
    }
  }

  digitalWrite(enablePin, HIGH); // Disable motor drivers
} 

void loop() {
  if (Serial.available() > 0) {
    delay(100);  // Allow Serial buffer to fill

    // Read the input string from Serial
    String inputString = "";
    while (Serial.available()) {
      char inputChar = toupper(Serial.read());  // Convert to uppercase
      if (motors[0].flapsOrder.indexOf(inputChar) != -1) {  // Check if input is in avaiable list of symobls
        inputString += inputChar;  // Append valid characters to the string
      }
    }

    // Ensure we have at least one valid character in the string
    if (inputString.length() == 0) {
      Serial.println("No valid input provided.");
      return;
    }

    int numChars = inputString.length();  // Number of characters in the input string

    // Ensure we don't exceed the number of available motors
    if (numChars > numMotors) {
      Serial.println("Input string too long, limited to number of motors.");
      numChars = numMotors;  // Only process as many characters as motors available
    }
    digitalWrite(enablePin, LOW);
    // Assign each character to a motor and move it to the corresponding flap
    Serial.print("Displaying: ");
    for (int i = 0; i < numChars; i++) {
      char inputChar = inputString.charAt(i);
      Serial.print(inputChar);
      motors[i].moveToChar(inputChar);  // Move motor to the target flap
    }
    Serial.println();
  }
  if(allMotorsFinished()){
    // If all motors stopped, disable drivers
    digitalWrite(enablePin, HIGH);
  }

  // Continuously update all motors to allow asynchronous movement
  for (int i = 0; i < numMotors; i++) {
    motors[i].updateMotor();
  }
}

Credits

Alex Chang

Alex Chang

2 projects • 19 followers

Comments