3D Printed Digital Timer
I do speaking gigs via zoom and wanted an easy way to track how long I'd been talking. But I wanted a physical object on my standing desk rather than anything on the computer screen, as I'm always bouncing from one screen to another. So I made this super simple design: single button to start/reset the timer. Big bold LED display. I used a Teensy I had lying around but any Arduino board you can fit into the casing will do.
Supplies
What you'll need:
3D Printer (or access to one)
Arduino, Teensy, or equivalent
MAX7219 8x8 LED display board
Pushbutton switch
10K Resistor
Breadboard & connectors
Hot glue gun
Print the Case
Print the main case by importing the attached .stl file into Blender, making any adjustments, exporting to a slicer, and printing. If none of that sentence made sense, there's a nice tutorial here about how to go from an .stl file to final print.
Downloads
Print the reset button. I found the fit of the two housing pieces very tight and had to do a bit of sanding to make them fit. No biggie, but if you're of a precise nature, you may want to shave a half a millimeter off one of those interfaces.
Downloads
Wire Up Your Arduino
The MAX7219 8x8 LED Matrix unit has five pins: power, ground, DIN, CS, and CLK. On a Teensy, you want to wire up the power to the 5 volt VIN. Wire the data out (DOUT) pin 11 from the Arduino to the Data In (DIN) pin on the Max, The Chip Select or CS pin is 10, and the clock signal comes from pin 13.
The Code
/* Stopwatch project displays time elapsed in minutes on a MAX7219 8x8 LED display. Button to start / reset to zero. Displays two digits, the second dropped down a row, and a dot that pulses to let you know it's working. Code is Creative Commons non commercial share and share alike. */ #include <MaxMatrix.h> #include <avr/pgmspace.h> //Define the array of alphanumeric characters the 8x8 Matrix can display //You actually only use the digits 0-9 //This array maps every LED on the board as on or off depending on //which character you want to display PROGMEM prog_uchar CH[] = { 3, 8, B00000000, B00000000, B00000000, B00000000, B00000000, // space 1, 8, B01011111, B00000000, B00000000, B00000000, B00000000, // ! 3, 8, B00000011, B00000000, B00000011, B00000000, B00000000, // " 5, 8, B00010100, B00111110, B00010100, B00111110, B00010100, // # 4, 8, B00100100, B01101010, B00101011, B00010010, B00000000, // $ 5, 8, B01100011, B00010011, B00001000, B01100100, B01100011, // % 5, 8, B00110110, B01001001, B01010110, B00100000, B01010000, // & 1, 8, B00000011, B00000000, B00000000, B00000000, B00000000, // ' 3, 8, B00011100, B00100010, B01000001, B00000000, B00000000, // ( 3, 8, B01000001, B00100010, B00011100, B00000000, B00000000, // ) 5, 8, B00101000, B00011000, B00001110, B00011000, B00101000, // * 5, 8, B00001000, B00001000, B00111110, B00001000, B00001000, // + 2, 8, B10110000, B01110000, B00000000, B00000000, B00000000, // , 4, 8, B00001000, B00001000, B00001000, B00001000, B00000000, // - 2, 8, B01100000, B01100000, B00000000, B00000000, B00000000, // . 4, 8, B01100000, B00011000, B00000110, B00000001, B00000000, // / 4, 8, B00111110, B01000001, B01000001, B00111110, B00000000, // 0 3, 8, B01000010, B01111111, B01000000, B00000000, B00000000, // 1 4, 8, B01100010, B01010001, B01001001, B01000110, B00000000, // 2 4, 8, B00100010, B01000001, B01001001, B00110110, B00000000, // 3 4, 8, B00011000, B00010100, B00010010, B01111111, B00000000, // 4 4, 8, B00100111, B01000101, B01000101, B00111001, B00000000, // 5 4, 8, B00111110, B01001001, B01001001, B00110000, B00000000, // 6 4, 8, B01100001, B00010001, B00001001, B00000111, B00000000, // 7 4, 8, B00110110, B01001001, B01001001, B00110110, B00000000, // 8 4, 8, B00000110, B01001001, B01001001, B00111110, B00000000, // 9 2, 8, B01010000, B00000000, B00000000, B00000000, B00000000, // : 2, 8, B10000000, B01010000, B00000000, B00000000, B00000000, // ; 3, 8, B00010000, B00101000, B01000100, B00000000, B00000000, // < 3, 8, B00010100, B00010100, B00010100, B00000000, B00000000, // = 3, 8, B01000100, B00101000, B00010000, B00000000, B00000000, // > 4, 8, B00000010, B01011001, B00001001, B00000110, B00000000, // ? 5, 8, B00111110, B01001001, B01010101, B01011101, B00001110, // @ 4, 8, B01111110, B00010001, B00010001, B01111110, B00000000, // A 4, 8, B01111111, B01001001, B01001001, B00110110, B00000000, // B 4, 8, B00111110, B01000001, B01000001, B00100010, B00000000, // C 4, 8, B01111111, B01000001, B01000001, B00111110, B00000000, // D 4, 8, B01111111, B01001001, B01001001, B01000001, B00000000, // E 4, 8, B01111111, B00001001, B00001001, B00000001, B00000000, // F 4, 8, B00111110, B01000001, B01001001, B01111010, B00000000, // G 4, 8, B01111111, B00001000, B00001000, B01111111, B00000000, // H 3, 8, B01000001, B01111111, B01000001, B00000000, B00000000, // I 4, 8, B00110000, B01000000, B01000001, B00111111, B00000000, // J 4, 8, B01111111, B00001000, B00010100, B01100011, B00000000, // K 4, 8, B01111111, B01000000, B01000000, B01000000, B00000000, // L 5, 8, B01111111, B00000010, B00001100, B00000010, B01111111, // M 5, 8, B01111111, B00000100, B00001000, B00010000, B01111111, // N 4, 8, B00111110, B01000001, B01000001, B00111110, B00000000, // O 4, 8, B01111111, B00001001, B00001001, B00000110, B00000000, // P 4, 8, B00111110, B01000001, B01000001, B10111110, B00000000, // Q 4, 8, B01111111, B00001001, B00001001, B01110110, B00000000, // R 4, 8, B01000110, B01001001, B01001001, B00110010, B00000000, // S 5, 8, B00000001, B00000001, B01111111, B00000001, B00000001, // T 4, 8, B00111111, B01000000, B01000000, B00111111, B00000000, // U 5, 8, B00001111, B00110000, B01000000, B00110000, B00001111, // V 5, 8, B00111111, B01000000, B00111000, B01000000, B00111111, // W 5, 8, B01100011, B00010100, B00001000, B00010100, B01100011, // X 5, 8, B00000111, B00001000, B01110000, B00001000, B00000111, // Y 4, 8, B01100001, B01010001, B01001001, B01000111, B00000000, // Z 2, 8, B01111111, B01000001, B00000000, B00000000, B00000000, // [ 4, 8, B00000001, B00000110, B00011000, B01100000, B00000000, // \ backslash 2, 8, B01000001, B01111111, B00000000, B00000000, B00000000, // ] 3, 8, B00000010, B00000001, B00000010, B00000000, B00000000, // hat 4, 8, B01000000, B01000000, B01000000, B01000000, B00000000, // _ 2, 8, B00000001, B00000010, B00000000, B00000000, B00000000, // ` 4, 8, B00100000, B01010100, B01010100, B01111000, B00000000, // a 4, 8, B01111111, B01000100, B01000100, B00111000, B00000000, // b 4, 8, B00111000, B01000100, B01000100, B00101000, B00000000, // c 4, 8, B00111000, B01000100, B01000100, B01111111, B00000000, // d 4, 8, B00111000, B01010100, B01010100, B00011000, B00000000, // e 3, 8, B00000100, B01111110, B00000101, B00000000, B00000000, // f 4, 8, B10011000, B10100100, B10100100, B01111000, B00000000, // g 4, 8, B01111111, B00000100, B00000100, B01111000, B00000000, // h 3, 8, B01000100, B01111101, B01000000, B00000000, B00000000, // i 4, 8, B01000000, B10000000, B10000100, B01111101, B00000000, // j 4, 8, B01111111, B00010000, B00101000, B01000100, B00000000, // k 3, 8, B01000001, B01111111, B01000000, B00000000, B00000000, // l 5, 8, B01111100, B00000100, B01111100, B00000100, B01111000, // m 4, 8, B01111100, B00000100, B00000100, B01111000, B00000000, // n 4, 8, B00111000, B01000100, B01000100, B00111000, B00000000, // o 4, 8, B11111100, B00100100, B00100100, B00011000, B00000000, // p 4, 8, B00011000, B00100100, B00100100, B11111100, B00000000, // q 4, 8, B01111100, B00001000, B00000100, B00000100, B00000000, // r 4, 8, B01001000, B01010100, B01010100, B00100100, B00000000, // s 3, 8, B00000100, B00111111, B01000100, B00000000, B00000000, // t 4, 8, B00111100, B01000000, B01000000, B01111100, B00000000, // u 5, 8, B00011100, B00100000, B01000000, B00100000, B00011100, // v 5, 8, B00111100, B01000000, B00111100, B01000000, B00111100, // w 5, 8, B01000100, B00101000, B00010000, B00101000, B01000100, // x 4, 8, B10011100, B10100000, B10100000, B01111100, B00000000, // y 3, 8, B01100100, B01010100, B01001100, B00000000, B00000000, // z 3, 8, B00001000, B00110110, B01000001, B00000000, B00000000, // { 1, 8, B01111111, B00000000, B00000000, B00000000, B00000000, // | 3, 8, B01000001, B00110110, B00001000, B00000000, B00000000, // } 4, 8, B00001000, B00000100, B00001000, B00000100, B00000000, // ~ }; //Check your board, these are the DOUT, CS, and CLK pins for a TeensyDuino. int data = 11; // 8, DIN pin of MAX7219 module int load = 10; // 9, CS pin of MAX7219 module int clock = 13; // 10, CLK pin of MAX7219 module int maxInUse = 1; //change this variable to set how many MAX7219's you'll use MaxMatrix m(data, load, clock, maxInUse); // define module as m byte buffer[10]; // for writing the numbers long lastTime = 0; //for keeping track of time long minutes = 0s; // the minutes elapsed that the clock displays int secondCounter = 0; // a counter to set up the faux-second counter int inPin = 2; // the number of the Input pin for the reset switch int state = HIGH; // the start state of the input pin int reading; // will hold the current reading from the input pin int previous = LOW; // stores the previous reading from the input pin // the follow variables are long's because the time, measured in miliseconds, // will quickly become a bigger number than can be stored in an int. unsigned long time = 0; // the last time the output pin was toggled unsigned long debounce = 200UL; // the debounce time, increase if the output flickers // variables will change: int buttonState = 0; // variable for reading the pushbutton status char string1[] = "00"; void setup(){ m.init(); // module initialize m.setIntensity(4); // dot matix intensity 0-15 Serial.begin(9600); // serial communication initialize pinMode(inPin, INPUT); // initialize the reset switch pin } void loop() { //This creates the flashing dots upper right //that faux-count seconds. They just let you know it's //counting... ¯\_(ツ)_/¯ if(secondCounter < 500){ m.setDot(7,0,LOW); m.setDot(6,0,HIGH); secondCounter++; }; if(secondCounter >= 500){ // Erase one dot and light up the other m.setDot(7,0,HIGH); m.setDot(6,0,LOW); secondCounter++; if(secondCounter > 1000){ secondCounter = 0; } } // Here we count milliseconds up to 60000 = 1 minute and store the previous time if(millis()-lastTime > 60000){ minutes++; lastTime = millis(); } //ltoa converts long variable minutes to a string, loads it into the string1 array ltoa(minutes,string1,10); printString(string1); //Let's just check that the reset button hasn't been pressed reading = digitalRead(inPin); Serial.println(reading); //And if it has, reset the time to 0 and the lastTime to now. if(reading == HIGH){ lastTime = millis(); minutes = 0; char string1[] = "0"; //Flash the four dots in the upper right so user knows the push button is working m.setDot(7,0,HIGH); m.setDot(7,1,HIGH); m.setDot(6,1,HIGH); m.setDot(6,0,HIGH); //Now clear the screen and display the 0 minute start state m.clear(); ltoa(minutes,string1,10); printString(string1); } //And go back to the top... }; //Print the character by using that fancy array in progmem which tells each //LED whether it's on or off void printString(char* s) { int col = 0; int y = 0; while (*s != 0) { if (*s < 32) continue; char c = *s - 32; memcpy_P(buffer, CH + 7*c, 7); m.writeSprite(col, y, buffer); m.setColumn(col + buffer[0], 0); col += buffer[0]; s++; y++; } }
Final Assembly
Once you've tested the circuit on the breadboard, wire your pushbutton up in the housing. I mounted it on a bit of foam to give a nice bounce to the reset switch, passing the wires directly through the foam.
I mounted the Teensy to a mini breadboard and embedded it in a glob of hot glue to keep the wires in place. Solder if you like, but I'm a fan of the temporary nature of hot glue so I can easily repurpose a board when I'm bored of the project it's in. Ha ha. The hot glue also allows you a degree of flexibility where you mount the breadboard inside the box -- it's a bit cramped getting the guts all tucked in and tidy.
And boom. That's it!