I need help with 8-segment stopwatch

Hi, I need to create a stopwatch with arduino uno 8-segment 4 digit display. It has 3 buttons, the first starts/stops it, the second laps(the time still runs but it displays only the time when the button was pressed), and the third button resets the timer. The problem comes from displaying a float number, I am very confused by multiplexing and display logic in general and I would be very grateful by any guidance or advice, especially for displayNumber(). thank you.

#include "funshield.h"

constexpr int Button1 = button1_pin;
constexpr int Button2 = button2_pin;
constexpr int Button3 = button3_pin;

class Button {
public:
  Button() {}
  void setup(int pin) {
    pinMode(pin, INPUT); 
  }
  bool isPressed(int pin, bool wasDown) {
    if(digitalRead(pin) == LOW && wasDown == HIGH){
      wasDown = !wasDown;
      return true;
    }
    return false;
  }
};

class DisplayNumber {
public:
  DisplayNumber() {}
  void writeGlyph(byte glyph, byte pos_bitmask) {
    digitalWrite(latch_pin, LOW);
    shiftOut(data_pin, clock_pin, MSBFIRST, glyph);
    shiftOut(data_pin, clock_pin, MSBFIRST, pos_bitmask);
    digitalWrite(latch_pin, HIGH);
  }
  void setup(){
    pinMode(latch_pin, OUTPUT);
    pinMode(clock_pin, OUTPUT);
    pinMode(data_pin, OUTPUT);
  }
  int countDigits(int num) {
    int count = 0;
    if (num == 0){
      return 1;
    }
    while (num > 0) {
      num /= 10;
      count++;
    }
    return count;
  }
  void displayNumber(int integerPart, int fractionalPart) {
    int integerDigits = countDigits(integerPart);
    int i = 0;
    for (i; i < integerDigits; i++) {
      int digit = integerPart % 10;
      writeGlyph(digits[digit], 1 << (integerDigits - 1 - i));
      integerPart /= 10;
    }
    writeGlyph(dot, 0b0010);
    writeGlyph(digits[fractionalPart], 0b0001);
  }
  private:
  int dot = 0x7F;
};
Button button1;
Button button2;
Button button3;
DisplayNumber display;

class Time{
public:
  float time = 0.0;
  void run(){
    counter();
    display.displayNumber(getInt(time), getFract(time));  
    
  }
  void stop(){
    display.displayNumber(getInt(time), getFract(time));  
  }
  void lap(){
    display.displayNumber(getInt(time), getFract(time));   
    counter();
  }
  void reset(){
    time = 0.0;
    display.displayNumber(getInt(time), getFract(time));  
  }
  float getTime(float time){
    return time;
  }
private:
  int duration = 100;
  void counter(){
    while(time < 1000.0){
      time += 0.1;
      millisDelay(duration);
    }
  }
  int getInt(float k){
    k = (int)k;
    return k;
  }
  int getFract(float k){
    k = k - getInt(k);
    return k * 10;
  }

  void millisDelay(int delayTime){
    long int start_time = millis();
    while ( millis() - start_time < delayTime) ;
  }
};

class Control{
public:
  Control() : mode("stop") {}
  void theController(int button){
    switch(button){
      case 1:
        if (mode == "run"){
          time.stop();
          mode = "stop"; 
        }
        else if(mode == "stop"){
          time.run();
          mode = "run";
        }
        break;
      case 2:
        if(mode == "run"){
          time.lap();
          mode = "lap";
        }
        else if(mode == "lap"){
          time.run();
          mode = "run";
        }
        break;
      case 3:
        if(mode == "stop"){
          time.reset();
          mode = "stop";
        }
        break;
    }
  }
private:
  Time time;
  String mode; 
};
Control control;

void setup() {
  button1.setup(Button1); 
  button2.setup(Button2);
  button3.setup(Button3);
  display.setup();
}

void loop() {
  bool wasDown = button1.isPressed(Button1, false); 
  bool wasDown2 = button2.isPressed(Button2, false); 
  bool wasDown3 = button3.isPressed(Button3, false); 
  if(button1.isPressed(Button1, wasDown)) {
    control.theController(1);
  }
  if(button2.isPressed(Button2, wasDown2)) {
    control.theController(2);
  }
  if(button3.isPressed(Button3, wasDown3)){
    control.theController(3);
  }
}

1. Then do you want to learn how a multipexed 7-segment (it is 8-segment considerng the dot segment) display unit works?

2. How many digits are there in your display unit? Are they common cahthode or common anode devices? Are you using 74HC595 chip to drive your display unit?

3. What is time format?
MIN SEC 1/100thSec (23 15. 34)

4. I would suggest that you first try your stopwatch running on a non-class based skech and then try on class-based sketch which is expected to be at basic (simple and elementary) level.

  • shouldn't time.run(); be invoked in loop() depending on some stopwatch state (e.g. Run)?

  • wouldn't it make things easier to measure/increment time in milliseconds rather than time += 0.1;

  • the integer and fraction of seconds can computed by

timeSec    = timeMsec / 1000;
timeSubSec = timeMsec % 1000;

yes, start simpler

1 Like

I thought that having a float variable is more suitable for this since the displayNumber() is designed to work with a number? And I have no idea how to do it another way..

????

  • shouldn't the pin be configured as INPUT_PULLUP so that when pressed, the pin is LOW?

  • since the pin # is known to the object, why doe is need to be passed to isPressed()?

  • why doesn't the class keep initialize and keep track of the pin state instead of requiring that it be passed as an argument to isPressed()?

  • shouldn't there be some debounce mechanism (e.g. delay (20))?

look this over

class Button {
    byte pin;
    byte stateLst;

  public:
    Button (byte pin )
    {
        this->pin = pin;
        pinMode (pin, INPUT_PULLUP);
        stateLst = digitalRead (pin);
    }

    bool isPressed ()
    {
        byte state = digitalRead (pin);
        if (stateLst != state)  {
            stateLst = state;
            delay (20);         // debounce

            if (LOW == state)
                return true;
        }
        return false;
    }
};

Button but1 (A1);
Button but2 (A2);
Button but3 (A3);

void loop ()
{
    if (but1.isPressed ())
        Serial.println (" but1");

    if (but2.isPressed ())
        Serial.println (" but2");

    if (but3.isPressed ())
        Serial.println (" but3");
}


void setup ()
{
    Serial.begin (9600);
}

learning the language doesn't mean knowing how to use it. Tom Cargill books describes poorly structured examples of C++ programs using classes.

something else to look over

class Watch {
    enum { Stop, Run };
    int state = Stop;

    unsigned long msec0;
    unsigned long msec;

  public:
    Watch () { }

    void start () { state = Run; }
    void stop  () { state = Stop; }
    void reset () { msec = msec0 = millis (); }

    void run   () {
        if (Run == state)
            msec = millis ();
    }
    unsigned long time  () { return msec - msec0; }
    void disp ()  {
     // unsigned sec  = msec / 1000;
        unsigned sec  = msec / 10;          // sped up for testing
        unsigned mins = (sec / 60) % 60;
        unsigned hr   = (sec / 3600);

        char s [20];
        sprintf (s, " %2u:%02u:%02u.%03lu  %8lu",
            hr, mins, sec % 60, msec % 1000, msec);
        Serial.println (s);
    }
};

// -----------------------------------------------------------------------------
class Button {
    byte pin;
    byte stateLst;

  public:
    Button (byte pin )
    {
        this->pin = pin;
        pinMode (pin, INPUT_PULLUP);
        stateLst = digitalRead (pin);
    }

    bool isPressed ()
    {
        byte state = digitalRead (pin);
        if (stateLst != state)  {
            stateLst = state;
            delay (20);         // debounce

            if (LOW == state)
                return true;
        }
        return false;
    }
};

// -----------------------------------------------------------------------------
Button but1 (A1);
Button but2 (A2);
Button but3 (A3);

Watch watch;

unsigned long msecLst;

// -----------------------------------------------------------------------------
void loop ()
{
    watch.run ();

    if (but1.isPressed ())
        watch.start ();

    if (but2.isPressed ())
        watch.stop ();

    if (but3.isPressed ())
        watch.reset ();

    // timer for when to display time
    unsigned long msec = millis ();
    if (msec - msecLst >= 1000)  {
        msecLst += 1000;
        watch.disp ();
    }
}


void setup ()
{
    Serial.begin (9600);
}

OP is not answering to the questions/queries of post #2; as a result, efforts could not be employed to prepare a tutorial starting from a non-class sketch to a class-based sketch.

you can lead a horse to water but can't make it drink.

@torchin
1. You may start with building a 6-digit multiplex display unit (Fig-1, compatible with SPI.h Library) to show the time of the Stopwatch in the following format.
23 45. 14 (MIN SEC 1/100thSEC) = DP5DP4 DP3DP2. DP1DP0

max7219-6digitStopwatchSPI
Figure-1:

2. Load the following sketch (no use of max7219.h Library):

#include<SPI.h>
byte registerAddress[] = {0x0C, 0x09, 0x0A, 0x0B}; //control registers of MAX7219, see data sheets
byte registerData[] = {0x01, 0x00, 0x01, 0x07};
//normal modeo; no-decode;intensity;8-digit scan
byte timeArray[8];  //to hold cc-codes (no-decode format)
byte digitAddress[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};//DP0-DP7

const byte dataArray[] = {0x7E, 0x7E, 0xFE, 0x7E, 0x7E, 0x7E, 0x00, 0x00};//initial time: 00 00.00
byte lupTable[] =
{ 0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70,
  0x7F, 0x7B, 0x77, 0x1F, 0x4E, 0x3D, 0x4F, 0x47
}; //0, 1, ...., E, F ; no-decode cc-code see data sheets

byte myMIN;
byte mySEC;
byte myMSEC;
unsigned long int presentMillis = millis();
#define Start_Stop 4
#define Lapse_Resume 5
#define Reset 6

void setup()
{
  pinMode(10, OUTPUT);  //LOAD Pin of MAX7219
  pinMode(Start_Stop, INPUT_PULLUP);
  pinMode(Lapse_Resume, INPUT_PULLUP);
  pinMode(Reset, INPUT_PULLUP);
  //-------------------
  SPI.begin();
  // bitSet(SPCR, 4);              //UNO is Master SPI
  //  SPI.setBitOrder(MSBFIRST);    //MSB_bit will be transferred first
  SPI.setClockDivider(SPI_CLOCK_DIV128); //TX rate = 16MHz/128 = 125 kbit
  //  SPI.setDataMode(SPI_MODE1);//MOSI is sampled at the rising edge of CLK
  //------------------------------------------------
  digitalWrite(10, LOW);      //Low at LOAD pin

  //---keep intializing the Mode of Operation------------
  for (int i = 0; i < 4; i++)
  {
    SPI.transfer(registerAddress[i]);
    SPI.transfer(registerData[i]);
    digitalWrite(10, LOW);
    digitalWrite(10, HIGH); //assert LH/LL on LOAD pin
    digitalWrite(10, LOW);
  }
  //---- timeArray update for intial time: 00 00. 00  --------
  memcpy(timeArray, dataArray, 8); //Initial time: 00 00. 00
  /*   timeArray[0] = lupTable[3];   //no-decode cc-code of digit-0
     timeArray[1] = lupTable[1];   //
     timeArray[2] = lupTable[2];   //
     timeArray[3] = lupTable[0];   //
     timeArray[4] = lupTable[0];   //
     timeArray[5] = lupTable[4];   //
     timeArray[6] = lupTable[0];   //
     timeArray[7] = lupTable[0];   //*/
  //--keep transferring time on display unit via MAX7219-----
  timeToDisplay();

  while (digitalRead(Start_Stop) != LOW)
  {
    ;   //wait for Start Button to press
  }
  delay(250);  //Button debounce
}

void loop()
{
  if (millis() - presentMillis < 10) //10 ms
  {
    if (digitalRead(Start_Stop) == LOW)
    {
      while (1); //Stop Button is pressed; Press UNO RESET Button to start again
    }
  }
  else
  {
    presentMillis = millis();
    myMSEC = myMSEC + 1;
    if (myMSEC == 100)
    {
      mySEC = mySEC + 1;
      myMSEC = 0;

      if (mySEC == 60)
      {
        myMIN = myMIN + 1;
        mySEC = 0;

        if (myMIN == 100)
        {
          myMIN = 0;
        }
      }
    }
    for (int i = 0; i < 6; i++)//no-decode cc-coes for time digits
    {
      timeArray[0] = lupTable[myMSEC % 10];
      timeArray[1] = lupTable[myMSEC / 10];
      //-----------------------------------
      timeArray[2] = lupTable[mySEC % 10];
      timeArray[2] =  timeArray[2] | 0x80; //place point
      timeArray[3] = lupTable[mySEC / 10];
      //----------------------------------------
      timeArray[4] = lupTable[myMIN % 10];
      timeArray[5] = lupTable[myMIN / 10];
      //-------------------------------------
      timeArray[6] = 0x00;//blank code for DP6
      timeArray[7] = 0x00;//Blank code for DP7
      //---------------------------------------
      timeToDisplay();
    }
  }
}

void timeToDisplay()
{
  //--keep transferring time on display via MAX7219-------
  for (int i = 0; i < 8; i++)
  {
    SPI.transfer(digitAddress[i]); //Digit (DP0 = right most position)
    SPI.transfer(timeArray[i]);   //
    digitalWrite(10, LOW);        //assert LL/LH/LL on LOAD pin
    digitalWrite(10, HIGH);
    digitalWrite(10, LOW);
  }
}

3. Press RESET Button of UNO. Check that display shows: 00 00. 00.
4. Press Start (Start_Stop) Button. Check that Stopwatch is counting time at 10 ms interval.
5. Press Stop (Start_Stop) Button. Check that Stopwatch has stopped counting.
6. Implement the functions of Lapse_Resume Button and Reset Button.

7. Create sketch for the Stopwatch if Fig-1 using max7219.h Library which does not use SPI Protocol but BitBang to communicate with MAX7219 Display Controller. Check Library to verify that Fig-2 is the correct wiring between UNO and the Display+Controller Module.
max7219-6digitStipwatch
Figue-2:

sketch:

#include <max7219.h>
MAX7219 max7219;

byte myMIN = 0;
byte mySEC = 0;
byte myMSEC = 0;
#define Start_Stop 4

unsigned long int presentMillis = 0;

void setup()
{
  Serial.begin(9600);
  pinMode(Start_Stop, INPUT_PULLUP);
  max7219.Begin();

  max7219.DisplayChar(0, ((myMSEC % 10) + '0'), 0); //Position 0 with no decimal point
  max7219.DisplayChar(1, (myMSEC / 10) + '0', 0); //Position 1
  //----------------
  max7219.DisplayChar(2, (mySEC % 10) + '0', 1); //Position 2 with decimal point
  max7219.DisplayChar(3, (mySEC / 10) + '0', 0); //Position 3
  //-------------------------------------------
  max7219.DisplayChar(4, (myMIN % 10) + '0', 0); //Position 4
  max7219.DisplayChar(5, (myMIN / 10) + '0', 0); //Position 5
  
  while (digitalRead(Start_Stop) != LOW)
  {
    ; //wait
  }
  delay(200); //Button debouncing
}

void loop()
{
  if (millis() - presentMillis < 10) //10 ms
  {
    //check buttons
  }
  else
  {
    presentMillis = millis();
    myMSEC = myMSEC + 1;
    if (myMSEC == 100) //100x10 = 1000 ms = 1 sec 00 01 ... 09 0A, ....
    {
      mySEC = mySEC + 1;
      myMSEC = 0;

      if (mySEC == 60)
      {
        myMIN = myMIN + 1;
        mySEC = 0;

        if (myMIN == 60)
        {
          myMIN = 0;
        }
      }
    }
    //---- show time--------------------
    max7219.DisplayChar(0, ((myMSEC % 10) + '0'), 0); //Position 0 with no decimal point
    max7219.DisplayChar(1, (myMSEC / 10) + '0', 0); //Position 1
    //----------------
    max7219.DisplayChar(2, (mySEC % 10) + '0', 1); //Position 2 with decimal point
    max7219.DisplayChar(3, (mySEC / 10) + '0', 0); //Position 3
    //-------------------------------------------
    max7219.DisplayChar(4, (myMIN % 10) + '0', 0); //Position 4
    max7219.DisplayChar(5, (myMIN / 10) + '0', 0); //Position 5
  }
}

8. Create class-based sketch for the Stopwatch of Fig-1.
....pending

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.