# Flip Display Clock

This is the official build log and walk-through for my recently completed Flip Display Clock! You can see a video of me explaining this clock here as well as a link to the associated reddit post here.

So, why design and build a Flip Display Clock? Simple – I thought it was a cool thing to make, it presents an interesting electronics challenge, and I love retro displays of all types (I’ve already built several Nixie Clocks). I was originally inspired by a reddit post of someone trying to use the same style flip digits to make their own clock; I couldn’t miss out on the fun so I purchased 6 7-segment 1″ Ferranti-Packard electromechanical displays from eBay.

These displays are electrically interesting because each segment has it’s own electromagnet which must be given a 19V 1msec ~300mA pulse in order to flip the little piece of plastic on the front from black to white. Furthermore, the coils do not have a center tap meaning that the polarity across the coils must be truly reversed to change the piece of plastic again from white to black. As each digit has 7 segments (so 7 electromagnets), and we want a 4 digit clock, the dumbest way to design the control circuitry would be to build 28 independent H-bridges. Now, a H-bridge will take a minimum of 4 transistors; all the sudden we are up to 112 individual transistors needing a ton of I/O on our MCU just to drive a 4 digit clock!

After a fair amount of thought and some recommendations from other engineers, I figured out how to multiplex the digits efficiently. They are arranged in a classic matrix configuration so I can select one of 4 digits to flip and then one of 7 segments within that digit to flip. The control electronics, only needing 11 sets now, are half-bridges (half an H-bridge). Check out schematics below.

I built this clock without the diodes at first and it didn’t work. Only after some intense debugging did I realize that the diodes were needed to prevent power from energizing other segments in completely different digits than the one I was trying to control. Also, if you’re wondering why 2N2222 transistors were needed on the high side fets – this is necessary to correctly turn them on and off because they’re not ground referenced and we are making them switch 19V which is much higher than our I/O residing at 3.3V. Look up H-bridge design for more details on why this is so.

After figuring out all that needed to be done, I designed the entire circuit in Eagle PCB. The rest of the clock system is fairly straight forward. An Atmega328P micro-controller (Read Arduino) is the brains of the operation. I used a MCP23017 I/O expander to gain the necessary number of I/O needed to control all 11 half bridges described earlier (22 I/O in total for the half-bridges); this part connects to the 328P via I2C. Also on the same I2C bus is a DS3231 Real Time Clock chip with a rechargeable lithium backup battery. The only other large system on board is the 12V to 19V boost converter based on the TPS61175 chip from TI and designed with TI’s AMAZING WEBENCH software. Seriously, why would you ever design a buck or boost converter without WEBENCH in 2018?

Assembly was done with my trusty DIY reflow toaster oven (I may write another post about this oven later) and a solder stencil I ordered along with the PCB from PCBway. 10 PCB’s and the solder stencil were roughly \$70 including DHL shipping and they arrived in about 1 week. The pictures below show the application of solderpaste to the PCB using the stencil. After that step, each component (ordered from DigiKey) is placed with tweezers onto the PCB before the whole thing goes in the oven. I highly recommend this technique for soldering PCBs – I’ve had fantastic success with it so far. In fact, I just soldered a 6 pin BGA chip for a different project with these methods and it worked perfectly!

After soldering, the PCB’s look close to professional IMHO. I only had to do a couple touch ups here and there with the hot air gun. Once you’ve assembled a board like this, make sure you power it up slowly with a current limited PSU and check each voltage rail on the board as you go in case there’s a short or other problem you missed.

The next step was building a case. I designed an acrylic enclosure with the help of MakerCase and fine tuned the details by editing the file in AutoCAD. Once designed, I used my university’s laser cutter to cut each piece from Acrylic.

The final hardware:

Next up is software! I wrote the entirety of embedded code for this project in C/C++ with the help of the Arduino ecosystem and libraries. All development was done in the Visual Studio Code IDE with the PlatformIO plugin. If you haven’t used PlatformIO, you’re missing out! My code is attached at the bottom of this post as a zip file (too lazy to make a git repo atm) and the main program is presented here directly as well. I won’t do a very detailed walk through of my code because it’s pretty self explanatory and I tried to comment it well. At a high level, the main loop continually checks if the digits need to be updated by polling the RTC for the time and comparing it with the previously displayed time. You can also see I just used giant case statements to map out the digit I/O as it was simple and fast. Only one segment can ever be flipped at once with this clock design, but they flip very fast so it’s not a problem. The code is dependent on a few other libraries, so if you try it, make sure to get those.

```// ****************************************
// Flip Clock Code V1.0
// Started: 10/26/17
// Coyt Barringer
// ****************************************

// ****************************************
// dependencies
// ****************************************
#include <Arduino.h>

//library for I2C
#include <Wire.h>

#include <DS3231.h>

//library for MCP23017 I/O expander chip

// ****************************************
// PIN definitions clock
// ****************************************
#define BACK_LED 13
#define WHITE_LEDS 10

//#define FRONT_TOP_LED
//#define FRONT_BOTTOM_LED
#define BOOST_ENABLE A0
#define BUTTON_A A3
#define BUTTON_B A2

#define ONET 9
#define ONEB 2
#define TWOT 7
#define TWOB 8
#define THREET 5
#define THREEB 6
#define FOURT 3
#define FOURB 4

#define AT 0
#define AB 8
#define BT 1
#define BB 9
#define CT 2
#define CB 10
#define DT 3
#define DB 11
#define ET 4
#define EB 12
#define FT 5
#define FB 13
#define GT 6
#define GB 14

#define night 22 //defines hour out of 24 hours where clock switches to night mode
#define morning 8 //defines hour out of 24 hours where clock switches to day mode

// ****************************************
// Global Variables
// ****************************************

// ****************************************
// Objects
// ****************************************

DS3231 clock;
RTCDateTime dt;

int lasthour = 0;
int lastminute = 0;
long lastMillis = 0;
int hour = 0;
int minute = 0;
int ledMem;
bool nightFlag = false;

// ****************************************
// ****************************************
//digitalWrite(BOOST_ENABLE,HIGH);
delay(1);
mcp.digitalWrite(AB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
mcp.digitalWrite(AB,LOW);
digitalWrite(TWOT,LOW);
delay(1);
//digitalWrite(BOOST_ENABLE,LOW);
}

// ****************************************
// flipDigitTwoSegmentAUp();
// ****************************************
void flipDigitTwoSegmentAUp(){
//digitalWrite(BOOST_ENABLE,HIGH);
delay(1);
mcp.digitalWrite(AT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
mcp.digitalWrite(AT,LOW);
digitalWrite(TWOB,LOW);
delay(1);
//digitalWrite(BOOST_ENABLE,LOW);
}

// ****************************************
// Function to flip any segment of any digit to white or black
// white = bool true
// black = bool false
// ****************************************
void flip(int digit, int segment, bool dir){

if(dir){
//flip to white
switch(digit){
case 1:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(AB,LOW);
break;
case 2:
mcp.digitalWrite(BB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(BB,LOW);
break;
case 3:
mcp.digitalWrite(CB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(CB,LOW);
break;
case 4:
mcp.digitalWrite(DB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(DB,LOW);
break;
case 5:
mcp.digitalWrite(EB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(EB,LOW);
break;
case 6:
mcp.digitalWrite(FB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(FB,LOW);
break;
case 7:
mcp.digitalWrite(GB,HIGH);
digitalWrite(ONET,HIGH);
delay(1);
digitalWrite(ONET,LOW);
mcp.digitalWrite(GB,LOW);
break;
default:
break;
}

break;
case 2:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(AB,LOW);
break;
case 2:
mcp.digitalWrite(BB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(BB,LOW);
break;
case 3:
mcp.digitalWrite(CB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(CB,LOW);
break;
case 4:
mcp.digitalWrite(DB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(DB,LOW);
break;
case 5:
mcp.digitalWrite(EB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(EB,LOW);
break;
case 6:
mcp.digitalWrite(FB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(FB,LOW);
break;
case 7:
mcp.digitalWrite(GB,HIGH);
digitalWrite(TWOT,HIGH);
delay(1);
digitalWrite(TWOT,LOW);
mcp.digitalWrite(GB,LOW);
break;
default:
break;
}
break;
case 3:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(AB,LOW);
break;
case 2:
mcp.digitalWrite(BB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(BB,LOW);
break;
case 3:
mcp.digitalWrite(CB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(CB,LOW);
break;
case 4:
mcp.digitalWrite(DB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(DB,LOW);
break;
case 5:
mcp.digitalWrite(EB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(EB,LOW);
break;
case 6:
mcp.digitalWrite(FB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(FB,LOW);
break;
case 7:
mcp.digitalWrite(GB,HIGH);
digitalWrite(THREET,HIGH);
delay(1);
digitalWrite(THREET,LOW);
mcp.digitalWrite(GB,LOW);
break;
default:
break;
}
break;
case 4:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(AB,LOW);
break;
case 2:
mcp.digitalWrite(BB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(BB,LOW);
break;
case 3:
mcp.digitalWrite(CB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(CB,LOW);
break;
case 4:
mcp.digitalWrite(DB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(DB,LOW);
break;
case 5:
mcp.digitalWrite(EB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(EB,LOW);
break;
case 6:
mcp.digitalWrite(FB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(FB,LOW);
break;
case 7:
mcp.digitalWrite(GB,HIGH);
digitalWrite(FOURT,HIGH);
delay(1);
digitalWrite(FOURT,LOW);
mcp.digitalWrite(GB,LOW);
break;
default:
break;
}
break;
default:
//do nothing
break;
}
}

else{
//flip to black
switch(digit){
case 1:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(AT,LOW);
break;
case 2:
mcp.digitalWrite(BT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(BT,LOW);
break;
case 3:
mcp.digitalWrite(CT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(CT,LOW);
break;
case 4:
mcp.digitalWrite(DT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(DT,LOW);
break;
case 5:
mcp.digitalWrite(ET,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(ET,LOW);
break;
case 6:
mcp.digitalWrite(FT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(FT,LOW);
break;
case 7:
mcp.digitalWrite(GT,HIGH);
digitalWrite(ONEB,HIGH);
delay(1);
digitalWrite(ONEB,LOW);
mcp.digitalWrite(GT,LOW);
break;
default:
break;
}
break;
case 2:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(AT,LOW);
break;
case 2:
mcp.digitalWrite(BT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(BT,LOW);
break;
case 3:
mcp.digitalWrite(CT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(CT,LOW);
break;
case 4:
mcp.digitalWrite(DT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(DT,LOW);
break;
case 5:
mcp.digitalWrite(ET,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(ET,LOW);
break;
case 6:
mcp.digitalWrite(FT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(FT,LOW);
break;
case 7:
mcp.digitalWrite(GT,HIGH);
digitalWrite(TWOB,HIGH);
delay(1);
digitalWrite(TWOB,LOW);
mcp.digitalWrite(GT,LOW);
break;
default:
break;
}
break;
case 3:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(AT,LOW);
break;
case 2:
mcp.digitalWrite(BT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(BT,LOW);
break;
case 3:
mcp.digitalWrite(CT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(CT,LOW);
break;
case 4:
mcp.digitalWrite(DT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(DT,LOW);
break;
case 5:
mcp.digitalWrite(ET,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(ET,LOW);
break;
case 6:
mcp.digitalWrite(FT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(FT,LOW);
break;
case 7:
mcp.digitalWrite(GT,HIGH);
digitalWrite(THREEB,HIGH);
delay(1);
digitalWrite(THREEB,LOW);
mcp.digitalWrite(GT,LOW);
break;
default:
break;
}
break;
case 4:
//flip certain segment
switch(segment){
case 1:
mcp.digitalWrite(AT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(AT,LOW);
break;
case 2:
mcp.digitalWrite(BT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(BT,LOW);
break;
case 3:
mcp.digitalWrite(CT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(CT,LOW);
break;
case 4:
mcp.digitalWrite(DT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(DT,LOW);
break;
case 5:
mcp.digitalWrite(ET,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(ET,LOW);
break;
case 6:
mcp.digitalWrite(FT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(FT,LOW);
break;
case 7:
mcp.digitalWrite(GT,HIGH);
digitalWrite(FOURB,HIGH);
delay(1);
digitalWrite(FOURB,LOW);
mcp.digitalWrite(GT,LOW);
break;
default:
break;
}
break;

default:
break;
//do nothing
}
}

}

// ****************************************
// Function to flip all segments to white
// ****************************************
void allWhite(){
delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(1,i,true);
}
delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(2,i,true);
}
delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(3,i,true);
}
delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(4,i,true);
}
}

// ****************************************
// Function to flip all segments to black
// ****************************************
void allBlack(){
delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(1,i,false);
}

delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(2,i,false);
}

delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(3,i,false);
}

delay(1);
for(int i = 1; i <= 7; i++){
delay(1);
flip(4,i,false);
}
}

// ****************************************
// Function to make a cool transition - ONE
// ****************************************
void coolSequenceOne(){
int delayOne = 50;

//allBlack();

delay(delayOne);
flip(1,6,true);
delay(delayOne);
flip(1,1,true);
delay(delayOne);
flip(2,1,true);
delay(delayOne);
flip(3,1,true);
delay(delayOne);
flip(4,1,true);
delay(delayOne);
flip(4,2,true);
delay(delayOne);
flip(4,3,true);
delay(delayOne);
flip(4,4,true);
delay(delayOne);
flip(3,4,true);
delay(delayOne);
flip(2,4,true);
delay(delayOne);
flip(1,4,true);
delay(delayOne);
flip(1,5,true);

delay(delayOne);

flip(1,6,false);
delay(delayOne);
flip(1,1,false);
delay(delayOne);
flip(2,1,false);
delay(delayOne);
flip(3,1,false);
delay(delayOne);
flip(4,1,false);
delay(delayOne);
flip(4,2,false);
delay(delayOne);
flip(4,3,false);
delay(delayOne);
flip(4,4,false);
delay(delayOne);
flip(3,4,false);
delay(delayOne);
flip(2,4,false);
delay(delayOne);
flip(1,4,false);
delay(delayOne);
flip(1,5,false);

delay(delayOne);
}

// ****************************************
// Function to write a number from 0 to 9 on any of the digits!
// ****************************************

void displayNumber(int digit, int number){

//makes selected digit all black before we can change the actual number on it
for(int i = 1; i <= 7 ;i++){
flip(digit,i,false);
}

switch(number){
case 0:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
break;
case 1:
flip(digit,2,true);
flip(digit,3,true);
break;
case 2:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,7,true);
break;
case 3:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,7,true);
break;
case 4:
flip(digit,2,true);
flip(digit,3,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 5:
flip(digit,1,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 6:
flip(digit,1,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 7:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
break;
case 8:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 9:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,6,true);
flip(digit,7,true);
break;
}

}

// ****************************************
// Function to write a number from 0 to 9 on any of the digits!
// ****************************************

void displayNumberStyle2(int digit, int number){

switch(number){
case 0:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
flip(digit,7,false);
break;
case 1:
flip(digit,1,false);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,false);
flip(digit,5,false);
flip(digit,6,false);
flip(digit,7,false);
break;
case 2:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,false);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,false);
flip(digit,7,true);
break;
case 3:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,false);
flip(digit,6,false);
flip(digit,7,true);
break;
case 4:
flip(digit,1,false);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,false);
flip(digit,5,false);
flip(digit,6,true);
flip(digit,7,true);
break;
case 5:
flip(digit,1,true);
flip(digit,2,false);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,false);
flip(digit,6,true);
flip(digit,7,true);
break;
case 6:
flip(digit,1,true);
flip(digit,2,false);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 7:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,false);
flip(digit,5,false);
flip(digit,6,false);
flip(digit,7,false);
break;
case 8:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,true);
flip(digit,6,true);
flip(digit,7,true);
break;
case 9:
flip(digit,1,true);
flip(digit,2,true);
flip(digit,3,true);
flip(digit,4,true);
flip(digit,5,false);
flip(digit,6,true);
flip(digit,7,true);
break;
}

}

// ****************************************
// Setup
// ****************************************
void setup() {

pinMode(BOOST_ENABLE, OUTPUT);
pinMode(BACK_LED, OUTPUT);
pinMode(BUTTON_A, INPUT);
pinMode(BUTTON_B, INPUT);
pinMode(WHITE_LEDS, OUTPUT);

//keep boost converter off
digitalWrite(BOOST_ENABLE, LOW);
digitalWrite(WHITE_LEDS, HIGH);

//set all DIGIT pins to outputs
pinMode(ONET, OUTPUT);
pinMode(ONEB, OUTPUT);
pinMode(TWOT, OUTPUT);
pinMode(TWOB, OUTPUT);
pinMode(THREET, OUTPUT);
pinMode(THREEB, OUTPUT);
pinMode(FOURT, OUTPUT);
pinMode(FOURB, OUTPUT);

//set initial matrix pin outputs so everything is OFF!
digitalWrite(ONET, LOW);
digitalWrite(ONEB, LOW);
digitalWrite(TWOT, LOW);
digitalWrite(TWOB, LOW);
digitalWrite(THREET, LOW);
digitalWrite(THREEB, LOW);
digitalWrite(FOURT, LOW);
digitalWrite(FOURB, LOW);

Serial.begin(115200);
delay(10);

// Initialize DS3231
Serial.println("Initialize DS3231");;
clock.begin();

// Set clock time on first reprogram
// make sure to comment the below out so we don't reset time every time the clock restarts!!!
//clock.setDateTime(2017,11,23,10,11,00);

//flash LEDs a few times to indicate startup sequence
for(int i = 0; i <= 10; i++){ digitalWrite(BACK_LED, LOW); delay(200); digitalWrite(BACK_LED, HIGH); delay(200); } //enable I/O expander mcp.begin(); //default address 0 delay(100); //set all I/O expander pins to outputs! mcp.pinMode(0, OUTPUT); //GPA0 mcp.pinMode(1, OUTPUT); //GPA1 mcp.pinMode(2, OUTPUT); //GPA2 mcp.pinMode(3, OUTPUT); //GPA3 mcp.pinMode(4, OUTPUT); //GPA4 mcp.pinMode(5, OUTPUT); //GPA5 mcp.pinMode(6, OUTPUT); //GPA6 mcp.pinMode(7, OUTPUT); //GPA7 //LED1 //BOTTOM LED mcp.pinMode(8, OUTPUT); //GPB0 mcp.pinMode(9, OUTPUT); //GPB1 mcp.pinMode(10, OUTPUT); //GPB2 mcp.pinMode(11, OUTPUT); //GPB3 mcp.pinMode(12, OUTPUT); //GPB4 mcp.pinMode(13, OUTPUT); //GPB5 mcp.pinMode(14, OUTPUT); //GPB6 mcp.pinMode(15, OUTPUT); //GPB7 //LED2 //TOP LED //turn all segment transistors OFF mcp.digitalWrite(AT, LOW); mcp.digitalWrite(AB, LOW); mcp.digitalWrite(BT, LOW); mcp.digitalWrite(BB, LOW); mcp.digitalWrite(CT, LOW); mcp.digitalWrite(CB, LOW); mcp.digitalWrite(DT, LOW); mcp.digitalWrite(DB, LOW); mcp.digitalWrite(ET, LOW); mcp.digitalWrite(EB, LOW); mcp.digitalWrite(FT, LOW); mcp.digitalWrite(FB, LOW); mcp.digitalWrite(GT, LOW); mcp.digitalWrite(GB, LOW); mcp.digitalWrite(7, HIGH); mcp.digitalWrite(15, HIGH); delay(50); digitalWrite(BOOST_ENABLE, HIGH); delay(50); lasthour = dt.hour; lastminute = dt.minute; lastMillis = millis(); //do two extra cool sequences at startup allBlack(); coolSequenceOne(); coolSequenceOne(); } // **************************************** // Forever Loop // **************************************** void loop() { // ********** //this loop will happen every second or whatever is specified in the if statement // ********** if((millis() - lastMillis) >= 1000){

//update time from the RTC chip
dt = clock.getDateTime();

hour = dt.hour;

//24 hour to 12 hour conversion
if(hour > 12){
hour = hour - 12;
}

minute = dt.minute;

//will not blink if we are in night mode
if(ledMem == HIGH || nightFlag == true){
mcp.digitalWrite(7, LOW);
mcp.digitalWrite(15,LOW);
ledMem = LOW;
}

else{
mcp.digitalWrite(7, HIGH);
mcp.digitalWrite(15,HIGH);
ledMem = HIGH;
}

lastMillis = millis();
}

// **********
// set time: if button is pressed increment minute val or rollover and update time in RTC
// **********
//coolSequenceOne();

int tempMinute = 0;

//update time from the RTC chip
dt = clock.getDateTime();

tempMinute = dt.minute;

//this statement handles rollover of the time setting
if((tempMinute + 1) > 60){
tempMinute = 1;
}
else{
tempMinute++;
}

clock.setDateTime(dt.year,dt.month,dt.day,dt.hour,tempMinute,dt.second);

//update time from the RTC chip
dt = clock.getDateTime();

minute = dt.minute;

delay(100);

}

// **********
// set time: if button is pressed increment hour val or rollover and update time in RTC
// **********

int tempHour = 0;

//update time from the RTC chip
dt = clock.getDateTime();

tempHour = dt.hour;

//backled will be ON if we are in PM
if(tempHour > 12){
digitalWrite(BACK_LED, HIGH);
}
else{
digitalWrite(BACK_LED, LOW);
}

//this statement handles rollover of the time setting
if((tempHour + 1) > 24){
tempHour = 1;
}

else{
tempHour++;
}

clock.setDateTime(dt.year,dt.month,dt.day,tempHour,dt.minute,dt.second);

//update time from the RTC chip
dt = clock.getDateTime();

hour = dt.hour;

//24 hour to 12 hour conversion
if(hour > 12){
hour = hour - 12;
}

delay(100);

}

// **********
//update of hour digits
// **********
if(hour != lasthour){
lasthour = hour;

//run cool routine to make display more interesting and transition on each hour!

if(nightFlag == false){
allBlack();
coolSequenceOne();
allBlack();
}

//update minutes after coolSequenceOne or else it'll take one minute for any numbers to show on minute digits
displayNumberStyle2(3,(minute/10));
displayNumberStyle2(4,(minute%10));

if(dt.hour == 0){
displayNumberStyle2(1,1);
displayNumberStyle2(2,2);
}
else{
displayNumberStyle2(1,(hour/10));
displayNumberStyle2(2,(hour%10));
}

//red backled will be ON if we are in PM
if(dt.hour > 12){
digitalWrite(BACK_LED, HIGH);
}
else{
digitalWrite(BACK_LED, LOW);
}

//test for night mode and set back leds accordingly
if(dt.hour >= night || dt.hour <= morning){
digitalWrite(WHITE_LEDS, LOW);
nightFlag = true;
}
else{
digitalWrite(WHITE_LEDS, HIGH);
nightFlag = false;
}

}

// **********
//update of minute digits
// **********
if(minute != lastminute){
lastminute = minute;

displayNumberStyle2(3,(minute/10));
displayNumberStyle2(4,(minute%10));
}

}

//asdf

```

Flip Digit Clock Code & Libraries (Direct .ZIP)

Flip Digit Clock Complete Schematics (PDF)

Flip Digit Clock Board Layout (PDF)

Flip Digit Clock Eagle PCB Files & BOM (.ZIP)

Flip Digit Clock pcb GERBERS for PCBWay (.ZIP)

AudoCad Drawing for Laser Cut Case (.ZIP)

Feel free to post any questions regarding this project!

# Programming STM32F103 Blue Pill using USB Bootloader and PlatformIO

This is the infamous Blue Pill board – a \$2 ARM STM32F103 development board with all the capabilities of a Teensy 3.x at a fraction of the price of an Arduino. So what’s the catch?

I’ll tell you – software support.

A couple weeks ago I decided to invest some time learning this platform because I was sick of paying 20+ dollars for a Teensy. While the PJRC platforms are fantastic, they are expensive and need a proprietary boot loader in order to work. I want a small and powerful arm chip which I can integrate INTO my own PCBs and the Teensy does not easily or cheaply allow this. The Blue Pill and it’s derivatives appear to be just the thing I need!

But how do you use one of these things? It should be simple! Just buy one, connect it to your computer over USB, and upload code from the Arduino IDE or PlatformIO just like you would with an Arduino!… WRONG! It appears the Blue Pills are not quite there yet. The main problem is lack of a USB boot loader installed at the factory as well as poor documentation and poor support in PlatformIO.

As a side note, if you are not using the PlatformIO framework with either ATOM or Visual Studio Code as your prototype embedded development environment then you need to get with the program! Just download VS Code and install the PlatformIO plugin – it’s fantastic! Way better than Arduino IDE!

Now, I’ll tell you exactly how to get your blue pill working like an Arduino:

1. Get a Blue Pill STM32F103C8T6 board AND EITHER a 3.3V USB/UART adapter, a knockoff ST-LINK programmer, or a REAL ARM programmer such as a J-Link. You only need one of those three things. I’ve verified this tutorial works fine with all 3.
2. Change resistor R10 from 10k Ohms to 1.5k Ohms on the Blue pill board. This is an 0603 size surface mount resistor on the bottom side of the Blue Pill. Read up elsewhere on this problem and make sure you need to do it for your specific board. I know it’s a PITA, but just do it – it’s not that hard and will make USB work properly.
1. You need to get the latest STM32duino bootloader from rogerclarkmelbourne’s GITHUB here: https://github.com/rogerclarkmelbourne/STM32duino-bootloader Go into the “binaries” folder and download the bootloader.bin file that matches the LED pin on your actual Blue Pill board! My Blue Pill boards have the LED on pin PC13 so I selected the file “generic_boot20_PC13.bin”.
2. OPTION 1: Use J-Flash Lite program with your J-LINK ARM programmer to flash the bootloader file to the Blue Pill
1. Wire up the Programmer as I did in my picture below, or look up how to connect your specific ARM programmer to an ARM processor over SWD pins.
2. In J-Flash lite, select the “STM32F103CB” processor from the dropdown, select the bootloader.bin file from earlier, and hit write. J-Flash Lite should say success. Plug in your Blue Pill to PC via USB and it should register as a com port or something and the led should blink a few times.
3. OPTION 2: The Blue Pill does come from factory with a UART bootloader pre-programmed to operate on a couple of the pins. This allows you to upload the USB bootloader from earlier with just a USB to UART adapter. This is more annoying because you must use a weird Python Program. See this dude’s tutorial for info.
4. OPTION 3: Use the knockoff STLINK V2 programmer you purchased to burn the bootloader. Honestly I lied and haven’t done this method yet, but you should just need to download some STLINK flasher app and upload our USB bootloader to the Blue Pill just as we did with J-Flash in option 1.
5.  You’re USB bootloader should now be on the Blue Pill STM32F103 – make sure both jumpers on Blue Pill are set to default of 0 and the LED should blink fast after pressing reset and then continue blinking slowly to indicate no user program is found yet.
4. Install DFU drivers to allow Windows to program the Blue Pill
1. Go to rogerclarkmelbourne’s github repo for Arduino_STM32 here: https://github.com/rogerclarkmelbourne/Arduino_STM32
2. Download the repo, extract all the stuff, and go into “/drivers/win”, and run the “install_drivers.bat” as administrator. This seems super sketchy but it actually worked for me every time.
5. Make sure you have GIT and JAVA installed on your PC – trust me – PlatformIO needs both to work with the Blue Pill apparently
6. Make sure you have the PlatformIO plugin installed and updated in either ATOM or Visual Studio Code
7. Make a NEW project using PlatformIO and selecting board: bluepill & framework:Arduino
8. Edit platformio.ino file with a couple custom settings
1. Change the text after platform to be “platform = https://github.com/platformio/platform-ststm32.git” This is why we needed GIT – it will allow PlatformIO to pull the most recent framework and board info directly from GITHUB.
4. See my screenshot of completed PlatformIO.ini
9. You should now be able to upload a sketch/program from PlatformIO onto the Blue Pill Board with ONLY a USB cable!
1. If you’re testing with Arduino “Blink” style program, make sure to change the LED pin from 13 or whatever to PB13 for the Blue Pill!
2. You’ll likely need to press the reset button on the Blue Pill to get the program to upload

I really hope this helps some people out. This took more hours than I’d care to admit to figure out. Please let me know if anything needs to be changed or updated. I’ll try to keep it current – everything here worked as of 12/16/17.

# My worst FAIL or how to destroy a \$500 radio

A year or two have passed so I figured I’m finally ready to share one of the worst FAILs of my young EE life.

It all started with a dream… A dream to tap into the Intermediate Frequency line inside my beloved FT-817. If it worked, I would have been able to use the FT-817 as a HF upconverter for a RTL SDR dongle. In conjunction with my tablet, I would have had a super cool portable “panadapter” setup with my FT-817. A waterfall display on the tablet would show me every signal inside the FT-817’s receive bandwidth at the same time!

For this to work, I needed to install a buffer amp and filter between the IF line in the FT-817 and an SMA connector I install in the radio case. The RTL SDR will reside just outside the FT-817. I bought the amp and filter kit from w1ghz and was all ready to go. 1st things 1st… drill a hole in the FT-817 case to install the SMA connector.

Stupidly, I didn’t remove the main PCB inside the FT-817 as I drilled through the back side of it’s aluminum case. In a split second the drill bit propelled through the case completeing the hole and taking out several SMD components as it screeched across the FT-817 main PCB! All in all, the drill completely took out the CXA1611 FM/AM radio chip and it’s traces on the PCB.

Over the following year, I attempted two seperate repairs of the chip and traces. Unfortunately, the radio has never functioned like it once did, and I hardly use it anymore due to this accident.

Lessons learned: think before you drill holes in expensive radios!

# Score of the Year! Free Multi-kW HF Linear Amplifier!

Ahhhh how I love attending a college with active research labs decades older than me!

Where else could I score a free Multi-kW HF Linear Amplifier!?!?!?!

Anyway, this find is most definitely my find of the year. This SERVER RACK SIZED amp was a CW (continuous wave) RF source for a Sputtering System in one of the materials labs at my university. Every once in a while these old research labs get some money and decide to renovate. When they do this they discard old equipment in certain areas for recycling. Many times this equipment is in perfect shape and very valuable if you happen to know what your looking for… and when and where to find it.

This is where I come in. Over the past 10 or so years that I’ve been hanging around my college I’ve become an expert in dumpster diving this electronic junk. This amplifier is simply my most recent find and possibly my most valuable one to date.

1. It’s a MASSIVE AMPLIFIER! It can boost an RF signal 15 to 17 times with an output of several kW!
2. It can be easily modified to work on Ham Radio Frequencies! (I’m a Ham!)
3. It’s worth A LOT in parts alone! (similar Ham Amps are several \$1000’s)
4. Nearly everything to make it run is present and intact

Now for the nitty gritty…

This Amp consists of 4 main components all housed in a convienient rack with wheels.

1. The power supply (High voltage system, tube filament transformers, etc…)
2. Control system (relays, metering circuitry, etc…)
3. RF exciter (100w Sine Wave Generator @ 13? Mhz ISM)
4. Main RF Deck

The RF Deck is the most important component. This is the heart of the amplifier. It uses a 3cx3000a7 Triode Vacuum Tube with a rated plate dissipation of 4000 watts. The Amp is in a common zero-bias grounded grid configuration with a tuned input network and a Pi-L output network. If the 3cx3000a7 tube is good, it alone is worth almost \$1k.

Here is a hand drawn schematic of the Main RF Deck…because I’m awesome

Getting this Amp up and running is now one of my primary projects.

The Goal: Turn this Amplifier into a legal limit (will easily go above 🙂 Ham Radio amplifier for the HF bands.

Here are some more pictures until I finish writing this post:

# Programmable VFD Display & Ticker

This is a project I’ve been working on for a while now, and it’s finally progressed to the point where I feel comfortable sharing it. The goal was to make a small programmable display so I can quickly check the current Bitcoin or Dogecoin price, see the time, weather, etc as I walk into my room. The display also had to look cool – I tried to make it steampunkish / hipster if you will. I am also contemplating the marketability of a reprogrammable display of this nature. If there is enough interest, I may improve it a bit (laser cut acrylic or wood base, etc…) and try to sell it. Price would likely be between 50\$ & \$100. Of course it would be open source so anyone could build their own or or modify to their liking.

The whole thing is really just a large VFD (Vacuum Fluorescent Display) and a Raspberry Pi, both of which I mounted to a bent metal plate as a stand. There is also a PIR motion sensor which I have setup to put the display to sleep after a set amount of time with no motion in the room. There are green LEDs between the metal plate and the back of the VFD to create a green glow around the edge. The Raspberry Pi is running it’s default debian distro (Raspbian?) and auto connects to wifi on boot, and a Python script controls the VFD through the Raspberry Pi’s serial port.

This project all started when I purchased a large VFD (Vacuum Fluorescent Display) from a surplus electronics store (Skycraft – greatest surplus store in Orlando) a year or two ago. I found out this display actually accepts serial data. The connector was RS232 voltage level, but there was a MAX232 chip on the back converting the +-12v signal to TTL (+5v) for the onboard controller. I spliced into the 5 volt input to the controller chip and the display happily sprung to life as I sent it simple serial print commands from an Arduino.

To power the Raspberry Pi, I rigged up a LM7805 5v voltage regulator on the back of the metal plate. I’ll hopefully be replacing this with a 5v switching regulator though because the LM7805 heats up the whole metal plate. The PIR sensor is connected directly to one of the I/O pins on the Raspberry Pi, and it triggers an interrupt in the Python script when motion is detected. I may add a photoresistor so the display turns off after a certain amount of time without light (when I go to bed). The last major detail is a small transistor controlling the green leds…I can actually use the Raspberry Pi to PWM the leds to vary the green glow brightness, but this isn’t implemented yet.

More pics:

Here’s the Python Script:

I tried to comment as much as I could, but I’m not a good programmer. Let me know if you have questions about anything.

```</span></pre>
#working programmable display code woohoo!

#!/usr/bin/python
import sys, serial, time, requests, json, glob
from time import sleep, strftime
from subprocess import *
from datetime import datetime
global serialport
import RPi.GPIO as GPIO

sensorPin = 7

GPIO.setmode(GPIO.BOARD) #I/O connector layout config
#GPIO.setmode(GPIO.BCM)
GPIO.setup(sensorPin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

global timeTest #this is used for the interrupt
timeTest = 38 #display function takes 16 seconds to run so 38*16 = 608s or about 10m until display goes to sleep after motion stops.

LCD = serial.Serial('/dev/ttyAMA0',19200) #seup serial port
cmd = "ip addr show wlan0 | grep inet | awk '{print \$2}' | cut -d/ -f1" #get IP address

def run_cmd(cmd): #get IP address fnc.
p = Popen(cmd, shell=True, stdout=PIPE)
output = p.communicate()[0]
return output

#def getBitcoinPriceStr(): #Screw Mt. Gox
# URL = 'http://data.mtgox.com/api/2/BTCUSD/money/ticker_fast'
# r = requests.get(URL)
# priceStr = "\$" + "{0:.2f}".format(float(json.loads(r.text)['data']['last']['value'])) + "/Doge"
# return priceStr

def getDogePriceStr():
URL = 'http://api.vaultofsatoshi.com/public/recent_transactions?order_currency=doge&payment_currency=usd&count=1' #fetches data I want
r = requests.get(URL)
priceStr = "\$" + "{0:.6f}".format(float(json.loads(r.text)['data'][0]['price']['value'])) + "/Doge" #uses json to parse text to find price value
return priceStr

def getBitcoinPriceCampBX():
URL = 'http://campbx.com/api/xticker.php'
r = requests.get(URL)
return priceStr

def displayStuff():
try:
LCD.write('\x0E\x0C') #clears display
LCD.write(datetime.now().strftime('%b %d %H:%M:%S\n'))
#time.sleep(3)
LCD.write('\x10\x0D') #2nd line on display
LCD.write('IP %s' % ( ipaddr ))
time.sleep(8) #pause 8 seconds

outStringDoge = getDogePriceStr()
outStringCampBX = getBitcoinPriceCampBX()
LCD.write('\x0E\x0C')
LCD.write(outStringDoge + " VoS")
LCD.write('\x0A\x0D')
LCD.write(outStringCampBX + " CampBX")
sleep(8)

except:
print "Error"
return

def resetTimeTest(self): #function called by interrupt in another thread (requires timeTest to be global to still use here?)
global timeTest
timeTest = 38
#print "hello"
return

LCD.open() #initialization stuff
LCD.write('\x0E\x0C') #clears display \x0C #LCD.write("whatever")
LCD.write("hello & welcome lostengineer")

GPIO.add_event_detect(sensorPin, GPIO.RISING, callback=resetTimeTest, bouncetime=40) #interrupt for PIR sensor

while 1:

if(timeTest > 0):
displayStuff() #display isn't asleep until time runs out. time is reset to 10m (38) or whatever by PIR sensor if motion is detected

else:
LCD.write('\x0E\x0C')
LCD.write("sleeping.....")

timeTest = timeTest - 1
sleep(1) #this main loop runs on a 1s basis with displayStuff() taking 16s during every call.

LCD.close()
```

There are a couple other things you need to do to make everything work.

1. Make Python executable with \$ sudo chmod +x pythonscript.py
2. Run script as root or it can’t access the serial port \$ sudo ./pythonscript.py
3. Disable boot crap output on serial port on startup
4. Setup script to autorun on startup