Split Flap Reverse Engineering – Part 1
As of late I have been trying to focus on the development of art related projects that are of interest to me. I have always had a soft spot for clocks and art related displays of time. I was recently on Ebay and found a rare set of Solar Split Flap Displays.
The listing for the auction read something like this:
You are looking at a Solari Split Flap Display Board. These parts are from the board you see in train/subway stations that tell you destinations and status of the trains incoming and outgoing. This auction is for one “Destination Flap”. These parts were recently decommissioned from service in March of 2015 from Penn Station Newark, NJ. They were originally installed in 1992 and worked all the way up to this year. These are VERY RARE and were hard to come by. They are being replaced with LED displays across the country and soon there will not be any in operation. I’ve spoken with people that travel a lot by train and they say these are very iconic and a joy to see work. They will miss the sound of the units clicking by while they look for their update on their train!
The cost for 8 of these lettered displays equals to about $112.50 a piece. They are certainly pricey, but based on the construction of displays from scratch that reach into the $400-$500 range I thought this was pretty reasonable. Efforts by Unknown Domain and others are still very impressive though. I figure that with the original Solar Displays in hand it would be pretty trivial to create my own displays at a later point in time.
About a week after purchase the split flap displays arrived in good condition. You can see some of the closeup details of the displays in the following images. Overall, they are a very simple piece of machinery that has proven to be very reliable. These displays use a traditional impulse system to advance the letters.
A traditional centralized clock system uses a specific device called master clock to send electrical impulses to receiver clocks which are equipped with a step movement. With each received impulse, the clock motor advances a step, which usually coincides with a minute; the clock movement is therefore generated by a step motor that in analog clocks moves a hand or turns a flap.
It is a simple system, connected using an open architecture with only two wires which allows for low-cost movements. This system was popular well before clocks with microprocessors arrived and is today normally used when expanding pre-existing systems, and is recommended for new installations when using analog clocks.
Quoted from http://www.solaritime.com/system-evolution-en.html
In the case of our displays this impulse can be provided by half of an h-bridge that applies a 24VDC impulse for 75 ms to initiate an index out of the motor. The next index of the motor is initiated by another impulse of reverse polarity from the previous. Each impulsed rotation equates to the single indexing of a flap at the front of the display. It is really a very simple movement and produces a nice “kerchunk” sound at each impulse. This impulse is applied using one side of a L293D H-Bridge Motor Driver
Feedback is provided in the form of 2 hall effect sensors. One sensor equates to a single impulse of the motor and the other is tied to the location of the blank output of the split flaps. These 3091 Hall Effect Sensors are still readily available from Mouser. Each of the hall effect sensors is easily read using a 10K pullup up to 5V.
The feedback for the motor impulses is buried a bit deeper inside the display.
An initial schematic of the motor driver and feedback system is provided in the image below. I have implemented a simple control using a Pic Microchip for the production version of these little control boards. This setup is nearly identical for my bread boarded proof of concept.
The Arduino code below is provided to show how this control setup was implemented. I used the serial terminal to provide input to the arduino to allow for indexed display. Stay tuned for my next post on how my final control boards were implemented to control multiples of these displays.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
/* Split Flap Display Adding Hall Effect detect on zero point Home Point (white wire) needs 1-K pullup to 5V. Home Point detected when voltage goes low *Use ! to invert state so that HIGH = Home Low = Other * This version adds indexing for character strings and handles Lower Case letters */ int in1Pin = 9; int in2Pin = 10; int enablePin = 11; int homePin = 12; int ledPin = 13; byte latchTime = 75; byte flipState = LOW; byte homeState; String flipChars = " 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ-./abcdefghijklmnopqrstuvwxyz"; String inputString = ""; // a string to hold incoming data boolean stringComplete = false; // whether the string is complete int flipToIdx = 0; int flipAtIdx = 0; int charToIdx = 0; //Lookup table to account for Caps/NoCaps const int charLookup[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, }; void setup() { pinMode(in1Pin, OUTPUT); pinMode(in2Pin, OUTPUT); pinMode(enablePin, OUTPUT); pinMode(homePin, INPUT); pinMode(ledPin, OUTPUT); Serial.begin(9600); // open the serial port at 9600 bps: Serial.println("Setup"); inputString.reserve(10); Serial.println("Going Home"); goHome(); //Set Flapper to Home Position Serial.println("Home Complete"); } void loop() { // Wait for input strings to do something if (stringComplete) { Serial.println(inputString); // Find Index of Chars in SplitFlap Array charToIdx = flipChars.indexOf(inputString[0]); // Lookup location in Table to determine flip equivalent flipToIdx = charLookup[charToIdx]; // Test Current Location to new Location Serial.print(charToIdx); Serial.print(" "); Serial.print(flipToIdx); Serial.print(" "); Serial.println(flipAtIdx); // Only Go Home if Needed if (flipToIdx < flipAtIdx) { goHome(); flipFlaps(flipToIdx); } else { flipFlaps(flipToIdx-flipAtIdx); } // Set IDX to Position Commanded flipAtIdx=flipToIdx; // Clear the inputString: inputString = ""; stringComplete = false; } } void goHome() { for (int i=0; i <= 255; i++){ //Get Home Position homeState = !digitalRead(homePin); // Set Led to Indicate Home digitalWrite(ledPin,homeState); if (homeState == LOW){ // Advance the SplitFlap flipFlap(); }else{ Serial.println("Home Reached"); break; } delay(50); } } void flipFlaps(byte flapsToDo) { for (int i=0; i < flapsToDo; i++){ // Set Flipper State for Control if (flipState == LOW){ flipState = HIGH; } else { flipState = LOW; } // Write Data to the L293 digitalWrite(enablePin, HIGH); digitalWrite(in1Pin, flipState); digitalWrite(in2Pin, !flipState); delay(latchTime); digitalWrite(enablePin, LOW); delay(50); } } void flipFlap() { // Set Flipper State for Control if (flipState == LOW){ flipState = HIGH; } else { flipState = LOW; } // Write Data to the L293 digitalWrite(enablePin, HIGH); digitalWrite(in1Pin, flipState); digitalWrite(in2Pin, !flipState); delay(latchTime); digitalWrite(enablePin, LOW); } /* SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available. */ void serialEvent() { while (Serial.available()) { // get the new byte: char inChar = (char)Serial.read(); // add it to the inputString: inputString += inChar; // if the incoming character is a newline, set a flag // so the main loop can do something about it: if (inChar == '\n') { stringComplete = true; } } } |