Introduction: Split Flap Display - Antique Remix

I've always wanted to play around with a split flap display but haven't until now been able to get one working satisfactorily, The other is that many builds also use laser cut items to which I dont have access. So when this one came along from Jonas Bonjeanand which I discovered on https://hackaday.io/project/163725-split-flap-disp...

I built up one of these, but also wanted to build self-contained electronics into it and also had this idea to make it look antique.

Step 1: Step 1 - the Build Files

Starting with the base models I went ahead and remixed nearly all of the parts, apart from the flaps. As well as building in an enclosure for the Wemos board I also added somewhere to mount the ULN2003 drive board.

So what you see here are some test builds based on a mix of the original parts and some modified components. The bearings used here are 625ZZ which are 16mm OD and 5mm thick with an inner bore diameter of 5mm. Nice little alternatives to the normal go to fidget/skateboard bearing. You can get them cheap as well in 10 packs.

The end plate was remodelled to mirror the other side and the gears were reworked to give a bit more industrial feel to them. Likewise a faux petrol tank was added to cover the mechanical end-stop board and a lifting eye added to provide some detail for the back. There was some internal work also done for the drum to mate easier onto the shaft and a 2mm bolt added to allow the drum to be securely clamped when the flaps were in .

All the latest files can be found on https://www.thingiverse.com/thing:3449562

Step 2: Step 2 : Building the Machine

Putting it all together is pretty simple, which is one of things that attracted me to this design. There are a couple of parts where you need to assemble them correctly. The main one of course is the drum. You'll need to add the spacer inside so that it keeps the nub of the drum aligned with the mechanical endstop. The other is to get all the flaps installed without them falling on the floor. I did try and build an assembly jig, but couldnt wait in the end and just used a lot of blu-tak to hold the flaps in place. With them all aligned the same way, the top goes on and a 2mm bolt holds it all together.

Shortening the leads on the stepper motor and on the switch is also needed to keep things nice and neat. Pieces of heatshrink on every lead for this stops anything shorting out and keep all the leads short for the Wemos as well.

Step 3: Step 3: Software

The software is for the Wemos D1 Mini R2 and works on an arduino if you remove the wifi parts. The principle behind this is to place the sensing of the end stop switch on an interupt which zeros the stepper position. You can then move the drum each time to the position you want knowing that zero is always the same. To work out the steps required for each flap considering the number of steps the standard motors have and the gear train I just did it backwards. So I put it all together and just ran the stepper counting the steps for several revolutions to get a sort of average and then divided this by 12. It sort of worked out about right at 225 steps per flap. Then to guarantee that if you wanted to go to the home position, you drive the stepper past this to 2800 steps knowing that it will hit the home switch before that and get zeroed. The only other major issue is that i discovered two different wiring setups for these stepper motors. Older ones I had would go the other way and on inspection you can see that the 5 wires to the motor were in a different order. However all the recent ones I have work with this code.

I also use the Adafruit IO service with this which in turn is fed data from IFTTT. You can use the core and just feed anything in you want, or you can even control it manually through the serial programming port by typing the number of steps you want, or use m1,m2 etc to move directly to a flap position for testing.

Code:-

// Split flap controller using Wemos D1 R1 with 28BYJ-48 stepper motor
/************************** Configuration ***********************************/
// Adafruit IO connection details
#define IO_USERNAME  "io-username-here"
#define IO_KEY       "io-key-here"

// leave blank as we get these through the Wifi Manager when it runs
#define WIFI_SSID       ""
#define WIFI_PASS       ""

// These set up all the includes to run the wifi component and setup manager
#include <ESP8266WiFi.h>            //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include "WiFiManager.h"            //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>

// Adafruit queue service, where we send the IFTT data to and then can download it
#include "AdafruitIO_WiFi.h"
AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS);

// Stepper driver library
#include <AccelStepper.h>
#define HALFSTEP 8#define HALFSTEP 8

// Wemos values to make addressing the pins easy
static const uint8_t D0   = 16;
static const uint8_t D1   = 5;
static const uint8_t D2   = 4;
static const uint8_t D3   = 0;
static const uint8_t D4   = 2;
static const uint8_t D5   = 14;
static const uint8_t D6   = 12;
static const uint8_t D7   = 13;
static const uint8_t D8   = 15;

// Connection pins for stepper controller
#define motorPin1  D5     // Driver board IN1
#define motorPin2  D6     // Driver board IN2
#define motorPin3  D7     // Driver board IN3
#define motorPin4  D8     // Driver board IN4

// Define the Pins used for the Home detection 
#define home_switch D3    // Pin 2 connected to Home Switch (MicroSwitch) - not the best pin as it stops program upload if its not open
int incomingByte = 0;     // for incoming serial data
int flacker = 0;          // count for which flap is flacked
int carryover = 0;        // value to carry over

// read position required
const byte numChars = 6;
char receivedChars[numChars]; // an array to store the received data
boolean newData = false;

// Homing startup value
long initial_homing=-1;       // Used to Home Stepper at startup

// Define a stepper and the pins it will use
AccelStepper stepper1(8,motorPin1,motorPin3,motorPin2,motorPin4); 

// set up the 'twitterfan' feed , the feed we connect to so as to get updates
AdafruitIO_Feed *twitter = io.feed("twitterfan");

// Saves the last tweet details and status so we only fire once
String lasttweet  ="";
bool newTweet = false;
int tweeter_number;

// All the setup details here
void setup() {
  Serial.begin(115200);
  Serial.println("split flap drive - 225 steps a flap");
  
  // for testing show the status of the WiFi
  WiFi.printDiag(Serial);
  WiFiManager wifiManager;

  //set a wifi callback that is called if connecting to a previous WiFi fails, this then calls up Access Point setup
  wifiManager.setAPCallback(configModeCallback);

  // reset saved settings , un comment this next line to force the testing of the WiFi Manager so you can connect
  // use your phone or tablet to look for the Split flap network that will appear
  // wifiManager.resetSettings();

  // sets timeout until configuration portal gets turned off
  // useful to make it all retry or go to sleep in 240 seconds
  // this means you have a few minutes to connect 
  wifiManager.setTimeout(240);

  if (!wifiManager.autoConnect("Split Flap")) {
      Serial.println(F("failed to connect and hit timeout"));
      //reset and try again
      delay(3000);
      ESP.reset();
      delay(1000);
  }
  // now we connect to the Adafruiti IO service
  Serial.print(F("Connecting to Adafruit IO"));
  io.connect();
  // set up the message handler for the 'twitterfan' feed.
  twitter->onMessage(twitterMessage);
  // wait for a connection
  while (io.status() < AIO_CONNECTED) {
    Serial.print(F("."));


  Serial.print("The Stepper is now Homing . . . . . . . . . . . ");

  // Set up the home position switch
  pinMode(home_switch, INPUT_PULLUP);
  // Attach an interrupt to the ISR vector for the home switch
  attachInterrupt(digitalPinToInterrupt(home_switch), handleInterrupt, FALLING);

  ESP.wdtDisable();                     // the timeout has to be turned off as the stepper move is blocking
  stepper1.setMaxSpeed(750.0);
  stepper1.move(1);               // seems to like a small move to get things going 
  stepper1.setAcceleration(12000);
  stepper1.setSpeed(50);
  stepper1.move(2000);
  ESP.wdtEnable(1000);                  // so we turn it back on after the movement


  ESP.wdtDisable();                     // the timeout has to be turned off as the stepper move is blocking
  while (digitalRead(home_switch)) {  // Make the Stepper move until the home switch is activated   
    stepper1.moveTo(initial_homing);  // Set the position to move to
    initial_homing++;                 // Decrease by 1 for next move if needed
    stepper1.run();                   // Start moving the stepper
    //// Serial.print(".");
    delay(2);
  }  
  ESP.wdtEnable(1000);                  // so we turn it back on after the movement

  stepper1.setCurrentPosition(0);
  Serial.print(" Stepper is now Homed position=");     Serial.println(stepper1.currentPosition());
  delay(500);
  }

  // now we are connected
  Serial.println();
  Serial.println(io.statusText());
   
}//--(end setup )---

void loop() {
  io.run();
  recvWithEndMarker();  // check the serial port for manual step details
  showNewData();        
  flapToTweeter();     
  // Need to call this every loop
}


// read the serial input and if there is data, store it
void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;
  // if (Serial.available() > 0) {
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    } else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

// move the stepper if we have incoming serial command
void showNewData() {
  if (newData == true) {
    flacker++;
    Serial.print("Serial cmd  ... rx=");
    String cmd = String(receivedChars);
    Serial.print(cmd); 
    Serial.print(' '); 
    if (receivedChars[0] == 'm') {          // denotes a move command m1,m2,m3 etc
      int flap = atoi(&receivedChars[1]);
      // int flap_pos = flap * 225 * -1;    // use this for reverse wired stepper motors that exist
      int flap_pos = flap * 225;
      Serial.print("  move flaps from position ");
      Serial.print(stepper1.currentPosition());
      Serial.print (" to ");
      Serial.print (flap_pos);
      if (stepper1.currentPosition()<flap_pos) {
        Serial.println(" move straight there as its before the home position ");
        stepper1.moveTo(flap_pos);   
        carryover = 0;                      // 0 value as we can get there in one move
      } else {
        Serial.println(" has to go past home ");
        carryover = flap_pos;
        stepper1.moveTo(2800);              // move to the home position and then the carryover will move it again 
                                            // we do this so that the stepping always goes in one direction 
      }
      ESP.wdtDisable();                     // the timeout has to be turned off as the stepper move is blocking
      stepper1.runToPosition();             // if the watchdog timer is enabled, then the wemos will reset 
      ESP.wdtEnable(1000);                  // so we turn it back on after the movement
      flacker=flap;
    } else {
      //Serial.print(receivedChars);          // show what came in on the serial 
      int pos = atoi(&receivedChars[0]);    // turn it into an integer and just move there

      stepper1.move(pos);
      ESP.wdtDisable();
      stepper1.runToPosition();
      ESP.wdtEnable(1000);
 
      Serial.print(" showing flap ");
      Serial.print(flacker); 
      Serial.print(" at step  position ");
      Serial.println(stepper1.currentPosition());
    }   
   newData = false;
   for( int i = 0; i < sizeof(receivedChars);  ++i )
      receivedChars[i] = (char)0;
  }
}


// movement to a flap from a io number 
void flapToTweeter () {
    if (newTweet == true) {
     //int flap_pos = tweeter_number * 225 * -1;  // use for old reversed steppers
      int flap_pos = tweeter_number * 225;
      Serial.print("IO flap move request from step ");
      Serial.print(stepper1.currentPosition());
      Serial.print (" to step ");
      Serial.print (tweeter_number);
      if (stepper1.currentPosition()<flap_pos) {
        Serial.println(" go straight there");
        stepper1.moveTo(flap_pos);  
        carryover = 0;
      } else {
        Serial.println(" go past home first ");
        carryover = flap_pos;
        stepper1.moveTo(2800);
      }
      ESP.wdtDisable();
      stepper1.runToPosition();
      ESP.wdtEnable(1000);
      flacker=flap_pos;
      newTweet = false;
    }
}


// -----------------------------------------------------------------------------------------
//  the interupt when the home switch has been tripped 
void handleInterrupt() {
    // Serial.print(" home position=");   // Serial.print(stepper1.currentPosition());
    // Serial.print(" carry over=");      // Serial.println(carryover);
    stepper1.setCurrentPosition(0);
    flacker =0;
   stepper1.moveTo(carryover);            // keep moving if a carry over was required
   ESP.wdtDisable();
   stepper1.runToPosition();
   ESP.wdtEnable(1000);
   carryover=0;
}


// -----------------------------------------------------------------------------------------
// this is called whenever a 'twitter' message arrives - get the first integer value
void twitterMessage(AdafruitIO_Data *data) {
    String tweeter =   (data->toString());    
    tweeter.toLowerCase();
    if ((lasttweet != tweeter) && (tweeter != "")) {
        lasttweet = tweeter;
        tweeter_number = (data->toInt());
        newTweet = true;
        Serial.print(F("Tweet: "));   Serial.print(tweeter);
        Serial.println("");
    }
}


// -----------------------------------------------------------------------------------------
// this is called when WiFiManager enters configuration mode,  
void configModeCallback (WiFiManager *myWiFiManager) {
    Serial.println(F("Entered config mode"));
    Serial.println(WiFi.softAPIP());
    //if you used auto generated SSID, print it
    Serial.println(myWiFiManager->getConfigPortalSSID());

}

Step 4: Step 4: Weathering

Not wanting a plain print finish I took the route of trying to give it an antique metal look. You'll also see that I resprayed the motor can as well with some copper airbrush paint from Vallejo. The actual parts themselves were all first painted in Matt black car spray. Then I brushed on a burnt umber and let it dry which right away gives a metalled tone to the colour. This was just brushed on and wiped back, then followed up with some raw umber. These are cheap acrylics from a local art shop and only cost a couple of pounds each. With the petrol tank, this was painted in green and tamiya gold leaf X-12 was painted on. This also got a spray of gloss varnish to give it that oily look that you'd expect with a petrol/oil tank. Last touch was some raw sienna to give a light rusting look and then some silver leaf rub n buff on edges to give the impression of worn metal surfaces.

Step 5: Step 5: Gallery

Here are some pictures I've taken of the final model , I've also added victorian water colour prints of flowers to each flap.

Step 6: Step 6 : Final View

Remix Contest

Participated in the
Remix Contest