Introduction: Race Time Board


My Cousin runs a small not for profit Quadbike Enduro club. The race is two and a half hours and often the riders stop to ask how much time is left. To allow the riders to carry on racing I designed and built a Race Time Board out of 2.5" 7-segment displays.

I had laying around 4 such displays however they are common cathode which didn't lead itself to a very efficient circuit and later I brought new common anode displays. If you use this circuit, you need to use common anode displays otherwise you will waste power and have to deal with lots of heat!

Any I get ahead of myself. The overall requirements of the timer were to be able to display remaining time, indicated when the race is over and warn riders to slow down. As the race is mainly 2h 30m the unit defaults to this on power-on. However this time is able to be changed if need be. There is a remote button to activate the 'slow' warning and a later addition was a switch to turn off the panel buttons to prevent accidental changes to the timer.

Step 1: Don't Use Common Cathode!

Initially the circuit was going to use common cathode displays (cc), as they are what I had to hand. To drive them I have been using the TPIC6B595 SIPO shift register as the displays have a forward voltage of 9.6V. The 6B595 has 8 DMOS transistor outputs which are all connected to ground. In order to run a CC display the output of the chip needed to be inverted. To do this resistors were added between the chip and display and tied to the +12V line. It works, with a few caveats.

Firstly you have to remember to invert the logic in the program - 1 is off and 0 is on. Secondly as the resistors have to also act as the limiting resistors for the display they are quite small value, as such when a segment is on, the resistor is directly connected to ground. This means a large current flows and the resistor heats up. By my calculations that meant each resistor had to cope with 1.2w! I tried to get around this by adding two resistors in parallel and doubling the value of each resistor, however the power still meant too much heat was created. In the end I switched to common anode (CA).

The switch meant that power usage dropped from a peak of 1.5A to just 0.8A from the 12v line. Given that this has to run off of a battery it is a good thing.

Step 2: Circuit

The circuit below shows the final design. Controlling it all is an ATMEGA328P with the Arduino firmware. This is connected to three TPIC6B595's via 3 pin serial link. Pin 4 on the Arduino is the serial out, this connects to the serial in on the first 595. The remaining 595's are connected via their serial outs in series. Each shift register takes 8 bits to fill, as it receives more, they 'overflow' to the serial out. Therefore if you send 3 bytes you will fill all the registers. Pins 5 and 6 are the Register Clock and Serial Clock. Pin 6 is used with pin 4 to transfer the data byte out to the input register of the 595. Once all three registers have been loaded, pin 5 toggles and transfers the data to the displays.

There are four buttons which control the unit. The first button has two functions. A short press with start or stop the timer. If it is held for about 2.5 seconds it will enter a mode allowing the time to be changed. Holding it again for 2.5 seconds returns to countdown mode.

The minute and hour buttons do nothing when the timer is running. When the timer is paused or finished, pressing both with reset to the set time. When it is in 'set mode' they count up for either hours or minutes.

All the buttons are active low, so they are tied to +5V by resistors.

The second image shows the three displays on the breadboard. These are actually the earlier CC displays and as such the resistors aren't there anymore. The overall layout hasn't changed though.

Here is a video of the design working: http://www.youtube.com/watch?v=8d8zYTmVOIU

Step 3: Program

Here is the program for it. I have used the Debounce library to reduce input noise from the buttons. The clock procedure is from Rob Faludi's open source clock. I have to be honest, I borrowed the code for testing a long/short button press and I can't remember where. Sorry! I would also like to thank Graynomad from the Arduino forums for helping me sort out the initial problems I had with the display. (Just so people know, you only need to toggle Trck once all the registers are loaded, not after each one. If you do it after each one, you end up with ghosting and the display looks bad!)

I also apologise, I am not the best a commenting my code. I am trying to get better at it and do it as I go. If you have any questions about it please ask I will try and answer!








#include <Debounce.h>


/*
Count Down Timer

Usage - Three buttons: Mode, Hour and Minute.

Short press Mode = Start/Pause timer
Long press Mode (2.5sec) = Enter/exit timer setting mode

With timer paused press Minute and Hour together to reset clock

In Timer Setting mode, press Hour/Minute to increment time settings.

*/

#define timeroffest 0 //adjustment for error in timing

#define resetPin 8
#define minutePin 9
#define hourPin 10
#define sloPin 11

Debounce minutePinDB = Debounce(25, minutePin);
Debounce hourPinDB = Debounce(25, hourPin);

#define debounce 50 // ms debounce period to prevent flickering when pressing or releasing the button
#define holdTime 2500 // ms hold period: how long to wait for press+hold event

#define Tserial 4
#define Tclk 6
#define Trck 5

#define DP_BIT B00001000

byte character[10] = {
B01110111, B00010100, B10110011, B10110110, B11010100, B11100110, B11100111, B00110100, B11110111, B11110100};

boolean runMode = false;
boolean setMode = false;
boolean run=0, reset=0, slow = false;

int second=118, minute=0, hour=0; // declare working time variables

int sMin=30, sHour=2; //declare set time variables

unsigned long autostart = 0;



void setup() {

pinMode(resetPin, INPUT); // pins for normally open switches to set the time
pinMode(minutePin, INPUT);
pinMode(hourPin, INPUT);
pinMode(sloPin, INPUT);

pinMode(Tserial, OUTPUT);
pinMode(Tclk, OUTPUT);
pinMode(Trck, OUTPUT);

digitalWrite(Trck, LOW);

checkButtons();

minute = sMin;
hour = sHour;

// Intro Sequence
outputChar(129); // r
outputChar(0); // clr
outputChar(0); // clr
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);

outputChar(0); // clr
outputChar(129); // r
outputChar(0); // clr
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);

outputChar(0); // clr
outputChar(0); // clr
outputChar(129); // r
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);

outputChar(195); // t
outputChar(0); // clr
outputChar(129); // r
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);

outputChar(0); // clr
outputChar(195); // t
outputChar(129); // r
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);

outputChar(199); // b B11000111
outputChar(195); // t B11000011
outputChar(129); // r B10000001
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(2000);

outputChar(0); // clr
outputChar(0); // clr
outputChar(0); // clr
digitalWrite(Trck, HIGH);
digitalWrite(Trck, LOW);
delay(250);
}





void loop() {

if (setMode == true) {
setTime();
}
else if (runMode == true){
clock();
}

if (hour == 0 && minute == 0 && setMode == false) {
runMode = false;
displayFin();
}
else if (slow == true) displaySlo();
else displayOut();

if (runMode == 0 && reset == 1) {
second = 118;
minute = sMin;
hour = sHour;
reset = 0;
}



checkButtons(); // runs a function that checks the setting buttons
}














void setTime() {
static boolean minStat = false, hrStat = false;
int minBut, hrBut;

minutePinDB.update();
hourPinDB.update();

minBut = minutePinDB.read();
hrBut = hourPinDB.read();

if (hrBut == LOW && hrStat == false) {
sHour = sHour + 1;
if (sHour > 9) sHour = 0;
hrStat = true;
}
if (hrBut == HIGH && hrStat == true) {
hrStat=false;
}

if (minBut == LOW && minStat == false) {
sMin = sMin + 1;
if (sMin > 59) sMin = 0;
minStat = true;
}
if (minBut == HIGH && minStat == true) {
minStat=false;
}

hour = sHour;
minute = sMin;
second = 118;
}




void checkButtons() {

static long btnDnTime; // time the button was pressed down
static long btnUpTime; // time the button was released
static boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered
static int R = 0; //Mode button state
static int Rstate = 0;

R = digitalRead(resetPin); //read Mode button state
//Test for button pressed and store the down time
if (R == LOW && Rstate == HIGH && (millis() - btnUpTime) > long(debounce))
{
btnDnTime = millis();
}
//Test for button release and store the up time
if (R == HIGH && Rstate == LOW && (millis() - btnDnTime) > long(debounce) && setMode == false)
{
if (ignoreUp == false) runMode = !runMode; //test if Low-High set mode should be toggled
else ignoreUp = false;
btnUpTime = millis();
}
//Test for button held down for longer than the hold time
if (R == LOW && (millis() - btnDnTime) > long(holdTime))
{
setMode = !setMode; //toggle Set mode to Timing mode or vice versa
runMode = 0;
ignoreUp = true;
btnDnTime = millis();
}
Rstate = R;

minutePinDB.update();
hourPinDB.update();

if (runMode == 0) {
if (minutePinDB.read() == LOW && hourPinDB.read() == LOW) reset = 1;
}

if (digitalRead(sloPin) == LOW) slow = true;

}




void clock() {
static unsigned long lastTick = 0; // set up a local variable to hold the last time we moved forward one second
// (static variables are initialized once and keep their values between function calls)

// move forward one second every 1000 milliseconds
if (millis() - lastTick >= 500+timeroffest) {
lastTick = millis();
second--;
}

// move back one minute every 60 seconds
if (second < 0) {
minute--;
second = 118; // reset seconds to zero
}

// move back one hour every 60 minutes
if (minute < 0) {
hour--;
minute = 59; // reset minutes to zero}
}

if (hour < 0) {
hour = 0; // reset hours to zero
}

}




void displayOut() {

static byte dp_bit = 0;

byte set_bit = 0;

static byte secFlash;
static unsigned long update = 0;

byte output;

if (millis() - update >= 4) {

update = millis();

if (secFlash != second) {
dp_bit ^= DP_BIT; // toggle the DP bit
secFlash = second;
}
else if(runMode == false){
dp_bit = DP_BIT;
}

if (setMode == true){
set_bit ^= DP_BIT;
}
else set_bit = 0;

outputChar(character[minute % 10] ^ set_bit);
outputChar(character[minute / 10]);
outputChar(character[hour] ^ dp_bit);

digitalWrite(Trck, HIGH); // latch data to OPs
digitalWrite(Trck, LOW);
}
}



void displayFin()
{
static unsigned long flashtimer = 0;
static byte flash = true;
static int flashdelay = 0;

int flashtime[2] = {
250, 750 };

if (millis() - flashtimer > flashdelay) {
flash = 1 - flash;
flashtimer = millis();
flashdelay = flashtime[flash];
}

if (flash == true){
outputChar(B10010111);
outputChar(B10000101);
outputChar(B11100011);
}
else {
outputChar(B00000000);
outputChar(B00000000);
outputChar(B00000000);
}
digitalWrite(Trck, HIGH); // latch data to OPs
digitalWrite(Trck, LOW);
}



void displaySlo()
{
static unsigned long slowtimer = 0;
static byte flashslow = true;
static int slowdelay = 0, slowcount = 0;

int slowtime[2] = {
200, 500 };

if (millis() - slowtimer > slowdelay) {
flashslow = 1 - flashslow;
slowtimer = millis();
slowdelay = slowtime[flashslow];
slowcount++;
}

if (flashslow == true){
outputChar(character[0]);
outputChar(B01000011);
outputChar(character[5]);
}
else {
outputChar(B00000000);
outputChar(B00000000);
outputChar(B00000000);
}
digitalWrite(Trck, HIGH); // latch data to OPs
digitalWrite(Trck, LOW);

if (slowcount >= 21) {
slow = false;
slowcount = 0;
}
else slow = true;

}


void outputChar(byte c) {
for(int i=0; i<=7; i++) {
digitalWrite(Tserial, bitRead(c, i));
digitalWrite(Tclk, HIGH);
digitalWrite(Tclk, LOW);
}
}

Step 4: Finished Article!

To finish off I built a case for it, painted it bright yellow. To that a carry handle, two eye hooks for hanging and two clips on the back to stake it to the ground with (depending on the course they are at). The buttons are recycled from a disused games machine.

Future changes:
One that I plan to do soon is to add a red coloured gel so that you can't see the circuit, only the leds shining through.

Add a light sensor to adjust brightness depending on the ambient levels (lower for darker, higher for brighter).

Maybe see about changing the boards to a purpose made one rather then breadboard. However it would need to be 8"x3" and that is a lot of money for a prototype board!

LED Contest

Participated in the
LED Contest