Countdown stop watch

Hi everyone,

I need a start/stop countdown timer as part of a game I am developing. Basically the countdown timer is measuring how quickly a person can do something and then displaying a result between 1 and 99(or 00) if not completed within the alotted time.
Generally the alotted time would be maximum 20 seconds but this figure may vary. Start and finish of the alotted time would be determined by two seperate INPUTs (external switches). The first would go HIGH to initiate the countdown sequence and the second would go HIGH to stop the countdown sequence.

I have found this program and I think it will suit my purposes quite well.

/*
 59~0 second counter used with Freetronics DMD
 See http://www.freetronics.com/dmd for resources and a getting started guide.
 Note that the DMD library uses the SPI port for the fastest, low overhead writing to the
 display. Keep an eye on conflicts if there are any other devices running from the same
 SPI port, and that the chip select on those devices is correctly set to be inactive
 when the DMD is being written to.  
 */

// you always need the code from here...........
#include <DMD.h> // for DMD
#include <SPI.h> // SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <TimerOne.h> 
#include "SystemFont5x7.h"
#include "Arial_black_16.h"
#define DISPLAYS_ACROSS 1 // change to 2 for two screens, etc. 
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN); // creates instance of DMD to refer to in sketch
int a=60;
unsigned long b,c;
boolean n;

void ScanDMD() // necessary interrupt handler for refresh scanning of DMD
{ 
  dmd.scanDisplayBySPI();
}

void setup()
{
  //initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
  Timer1.initialize( 5000 );           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
  Timer1.attachInterrupt( ScanDMD );   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()  
  dmd.clearScreen( true );   //true is normal (all pixels off), false is negative (all pixels on)
  dmd.selectFont(Arial_Black_16);    
  Serial.begin(9600);
  b=millis();  
}
// ........... to here!

void drawSecond(int second)
{
  switch(second)
  {
  case 0:
    dmd.drawString( 7,0, "00", 2, GRAPHICS_NORMAL );
    break;
  case 1:
    dmd.drawString( 8,0, "01", 2, GRAPHICS_NORMAL );
    break;
  case 2:
    dmd.drawString( 7,0, "02", 2, GRAPHICS_NORMAL );
    break;
  case 3:
    dmd.drawString( 7,0, "03", 2, GRAPHICS_NORMAL );
    break;
  case 4:
    dmd.drawString( 7,0, "04", 2, GRAPHICS_NORMAL );
    break;
  case 5:
    dmd.drawString( 7,0, "05", 2, GRAPHICS_NORMAL );
    break;
  case 6:
    dmd.drawString( 7,0, "06", 2, GRAPHICS_NORMAL );
    break;
  case 7:
    dmd.drawString( 7,0, "07", 2, GRAPHICS_NORMAL );
    break;
  case 8:
    dmd.drawString( 7,0, "08", 2, GRAPHICS_NORMAL );
    break;
  case 9:
    dmd.drawString( 7,0, "09", 2, GRAPHICS_NORMAL );
    break;
  case 10:
    dmd.drawString( 8,0, "10", 2, GRAPHICS_NORMAL );
    break;
  case 11:
    dmd.drawString( 9,0, "11", 2, GRAPHICS_NORMAL );
    break;
  case 12:
    dmd.drawString( 8,0, "12", 2, GRAPHICS_NORMAL );
    break;
  case 13:
    dmd.drawString( 8,0, "13", 2, GRAPHICS_NORMAL );
    break;
  case 14:
    dmd.drawString( 8,0, "14", 2, GRAPHICS_NORMAL );
    break;
  case 15:
    dmd.drawString( 8,0, "15", 2, GRAPHICS_NORMAL );
    break;
  case 16:
    dmd.drawString( 8,0, "16", 2, GRAPHICS_NORMAL );
    break;
  case 17:
    dmd.drawString( 8,0, "17", 2, GRAPHICS_NORMAL );
    break;
  case 18:
    dmd.drawString( 8,0, "18", 2, GRAPHICS_NORMAL );
    break;
  case 19:
    dmd.drawString( 8,0, "19", 2, GRAPHICS_NORMAL );
    break;
  case 20:
    dmd.drawString( 7,0, "20", 2, GRAPHICS_NORMAL );
    break;
  case 21:
    dmd.drawString( 8,0, "21", 2, GRAPHICS_NORMAL );
    break;
  case 22:
    dmd.drawString( 7,0, "22", 2, GRAPHICS_NORMAL );
    break;
  case 23:
    dmd.drawString( 7,0, "23", 2, GRAPHICS_NORMAL );
    break;
  case 24:
    dmd.drawString( 7,0, "24", 2, GRAPHICS_NORMAL );
    break;
  case 25:
    dmd.drawString( 7,0, "25", 2, GRAPHICS_NORMAL );
    break;
  case 26:
    dmd.drawString( 7,0, "26", 2, GRAPHICS_NORMAL );
    break;
  case 27:
    dmd.drawString( 7,0, "27", 2, GRAPHICS_NORMAL );
    break;
  case 28:
    dmd.drawString( 7,0, "28", 2, GRAPHICS_NORMAL );
    break;
  case 29:
    dmd.drawString( 7,0, "29", 2, GRAPHICS_NORMAL );
    break;
  case 30:
    dmd.drawString( 7,0, "30", 2, GRAPHICS_NORMAL );
    break;
  case 31:
    dmd.drawString( 8,0, "31", 2, GRAPHICS_NORMAL );
    break;
  case 32:
    dmd.drawString( 7,0, "32", 2, GRAPHICS_NORMAL );
    break;
  case 33:
    dmd.drawString( 7,0, "33", 2, GRAPHICS_NORMAL );
    break;
  case 34:
    dmd.drawString( 7,0, "34", 2, GRAPHICS_NORMAL );
    break;
  case 35:
    dmd.drawString( 7,0, "35", 2, GRAPHICS_NORMAL );
    break;
  case 36:
    dmd.drawString( 7,0, "36", 2, GRAPHICS_NORMAL );
    break;
  case 37:
    dmd.drawString( 7,0, "37", 2, GRAPHICS_NORMAL );
    break;
  case 38:
    dmd.drawString( 7,0, "38", 2, GRAPHICS_NORMAL );
    break;
  case 39:
    dmd.drawString( 7,0, "39", 2, GRAPHICS_NORMAL );
    break;
  case 40:
    dmd.drawString( 7,0, "40", 2, GRAPHICS_NORMAL );
    break;
  case 41:
    dmd.drawString( 8,0, "41", 2, GRAPHICS_NORMAL );
    break;
  case 42:
    dmd.drawString( 7,0, "42", 2, GRAPHICS_NORMAL );
    break;
  case 43:
    dmd.drawString( 7,0, "43", 2, GRAPHICS_NORMAL );
    break;
  case 44:
    dmd.drawString( 7,0, "44", 2, GRAPHICS_NORMAL );
    break;
  case 45:
    dmd.drawString( 7,0, "45", 2, GRAPHICS_NORMAL );
    break;
  case 46:
    dmd.drawString( 7,0, "46", 2, GRAPHICS_NORMAL );
    break;
  case 47:
    dmd.drawString( 7,0, "47", 2, GRAPHICS_NORMAL );
    break;
  case 48:
    dmd.drawString( 7,0, "48", 2, GRAPHICS_NORMAL );
    break;
  case 49:
    dmd.drawString( 7,0, "49", 2, GRAPHICS_NORMAL );
    break;
  case 50:
    dmd.drawString( 7,0, "50", 2, GRAPHICS_NORMAL );
    break;
  case 51:
    dmd.drawString( 8,0, "51", 2, GRAPHICS_NORMAL );
    break;
  case 52:
    dmd.drawString( 7,0, "52", 2, GRAPHICS_NORMAL );
    break;
  case 53:
    dmd.drawString( 7,0, "53", 2, GRAPHICS_NORMAL );
    break;
  case 54:
    dmd.drawString( 7,0, "54", 2, GRAPHICS_NORMAL );
    break;
  case 55:
    dmd.drawString( 7,0, "55", 2, GRAPHICS_NORMAL );
    break;
  case 56:
    dmd.drawString( 7,0, "56", 2, GRAPHICS_NORMAL );
    break;
  case 57:
    dmd.drawString( 7,0, "57", 2, GRAPHICS_NORMAL );
    break;
  case 58:
    dmd.drawString( 7,0, "58", 2, GRAPHICS_NORMAL );
    break;
  case 59:
    dmd.drawString( 7,0, "59", 2, GRAPHICS_NORMAL );
    break;
    case 60:
    dmd.drawString( 7,0, "60", 2, GRAPHICS_NORMAL );
    break;
    
  }
}

/*

void loop()
{
  for (int a=60; a>=0; --a)
  {
    drawSecond(a);
    delay(997);
    dmd.clearScreen( true );
  }  
}
*/

void loop()
{
  if (a==19 && n==false)
  {
    dmd.clearScreen( true );
    n=true;
  }
  drawSecond(a);
  c=millis()-b;
  if (c>=1000)
  {
    --a;    
    b=millis();
    if (a<0)
    {
      a=60;
    }
  }
}

However I need it to do two things slightly differently. (I am aware it currently only counts down from 60, but thats easy for me to change so it starts at 99 so I am not worried about that)

  1. It needs to be able to start counting down when an input (digital 2) goes HIGH and then stop the countdown when an input (digital 3) goes HIGH and then leave the remaining result showing on the DMD.

  2. As it stands the current countdown is one real time second for one displayed second. ie every second the display goes down by one increment. However I want to set it up that for every quarter of a second in real time, it counts down one increment. (naturally this means the countdown timer display will be going 4 times faster than it currently does.)

Now I have tried various ideas, particularly with problem 2 as listed above but just cant seem to get it going and as for problem one..........I really have no idea other than I know it needs to read the input as High or Low etc.

  drawSecond(a);

c=millis()-b;
  if (c>=1000)
  {
    --a;   
    b=millis();

Can I start with one suggestion? Meaningful data names.

How can anyone tell what is intended with the names a, b, c? How about startTime, finishTime, interval, or something like that?

Also hitting the space bar is easy. Instead of:

  c=millis()-b;

Try:

  c = millis () - b;

Thanks for your input Nick,

As I said, I found that program online. I did not write it, I have trouble writing my name.

I have no idea about programming and was hoping to get some useful input.

Your help would be most appreciated.

Best wishes.

Separate the display portion of the code from the counting / button processing portion of the code. You can run the display via a timer isr.

That code is IMO rather poor quality and needlessly long and complex. You could write a much better and simpler countdown timer yourself.

dhenry:
Separate the display portion of the code from the counting / button processing portion of the code.

I agree. I'd start with the 'blink without delay' example to trigger regular clock processing at one second (or whatever interval you want). Have a flag indicating whether the timer was running or stopped, and turn it on and off based on the state of the two switches. There are sample sketches that show you how to read and debounce switch inputs. In your regular clock processing, if the flag indicates the timer is running then decrement the timer value and redisplay it. It looks to me as if you just need to send your display device a string containing the two characters you want to display and you can generate that string in a couple of lines of code - no need for that ridiculous switch statement.

dhenry:
You can run the display via a timer isr.

Bad idea IMO. There are times when the cost and complexity of using interrupts is justified - a novice programmer outputting slowly-changing values out on a display device is not one of them.

  Timer1.initialize( 5000 );           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
  Timer1.attachInterrupt( ScanDMD );   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()

Looks like your code follows the interrupt approach I explained earlier. That's good.

void drawSecond(int second)
{
  switch(second)
  {
  case 0:
    dmd.drawString( 7,0, "00", 2, GRAPHICS_NORMAL );
    break;
  case 1:
    dmd.drawString( 8,0, "01", 2, GRAPHICS_NORMAL );
    break;

I don't quite understand the drawString function but it looks to be converting second into a string, with leading zero.

That's can be easily done:

unsigned char vBuffer[3]; //2 byte buffer


...
   //convert second to a string with leading zero
   //second assumed to be [0,99]
   vBuffer[1]='0'+(second % 10);
   vBuffer[0]='0'+(second /10);
   dmd.drawString(7, vBuffer, 2, NORMAL_GRAPHICS);

This would be considerably simpler.

dhenry:
This would be considerably simpler.

Don't forget to null-terminate the string you just created.