Friday, October 4, 2013

Arduino Swim Lap Counter

I like to swim laps in my pool.... That might be a bit of a misstatement. I like to get the exercise that comes with swimming laps in my pool. The truth be told, however, swimming back and forth a hundred times is pretty boring. What made it worse was that I was keeping count of the laps in my head and if my mind would wander I would lose count.

In order that I could use the time more productively by thinking about my next project, or trying to figure out why my last project didn't work, I decided to build an electronic lap counter.







It keeps track of the number of laps, the time on the current lap, time on the previous lap, and total elapsed time.


A Little More Detail

The project is based on the Arduino microcontroller. I built the digits using LED Light Bars that I got from Sparkfun Electronics. I'm near-sighted so I wanted them big enough so that I could read them without my glasses. As noted above the timer keeps track of laps, current lap time, previous lap time, and total time, but I can control which of those values is displayed via the toggle switches that can be seen on the left side of the device in the picture above. It cycles through whichever values are selected for display by the toggle switches, showing each for about two seconds.

The trigger switch is build from a piece of aluminum bar, bent at a 90 degree angle, and then covered with closed-cell foam. I wanted to be able to activate it by either pushing horizontally on the part that sticks down into the pool or vertically by pushing down from the top. The switch triggers an interrupt on the Arduino so it is very dependable.

I experimented with RFID tags as triggers. I thought it would be good if my wife and I could both use it and it would know who was swimming and thus, maybe, keep track of workouts over time for both of us -- that sort of thing. The inexpensive RFID tags only have a range of a few inches, however, and so the touch-point needed to be so precise it just didn't seem practical. Thus, for now, it doesn't keep track of any information from workout to workout.

At the end of the day, however, I consider the project a complete success. While I would never confuse swimming laps with having fun, when I'm doing it I am using my mind for something more productive than counting.

7 comments:

  1. Do you have any builder notes, schematics or code that you are willing to share? This is exactly what I'm wanting to build.
    Thanks,
    Jim

    ReplyDelete
    Replies
    1. Jim, thanks for your interest. I'm afraid I wasn't the best at documenting this project but let me give you what I have on hand and then we'll see what you need after that. First, let me give you the Arduino code. I'll have to do it in a few pieces.

      #include
      #include "LedControl.h"
      #include

      /*
      pin 12 is connected to the DataIn
      pin 11 is connected to the CLK
      pin 10 is connected to LOAD
      We have only a single MAX7219.
      */
      LedControl lc=LedControl(12,11,10,1);

      /* delay between updates of the display */


      int StartStopSwPin=3; // Starts timer running
      int LapSwPin=7; // Low Displays Laps
      int ETSwPin=8; // Low displays elapsed time
      int ThisLapSwPin=9; // Low displays time for this lap
      int LastLapSwPin=5; // Low displays time for previous lap
      int LapLEDPin=A0; // Low means number of laps is being displayed
      int ETLEDPin=A1; // Low means elapsed time is being displayed
      int ThisLapLEDPin=4; // Low means time for this lap is being displayed
      int LastLapLEDPin=13; // Low means time for the previous lap is being displayed
      int LapCountPin=2; // This pin triggers lap increment, active low
      // int ViewDelay=2000;
      int TimeViewRepeats=30; // This controls the number of times that elapsed time, last lap, and this lap are recalculated and redisplayed in each cycle
      int LapViewRepeats=35; // This controls the number of times that laps is recalculated and redisplayed in each cycle
      int PiezoPin=6; //Lap buzzer

      unsigned long delaytime=100;
      int Laps=0;
      long ElapsedTime=0; // These three times indicate a number of tenths of a second.
      long ThisLapTime=0;
      long LastLapTime=0;

      // These are used to display the number of laps
      int LapThousands=0;
      int LapHundreds=0;
      int LapTens=0;
      int LapOnes=0;
      int LapTemp=0;

      // These are used to display numbers in the four digits, Digit0 being least significant
      int Digit0;
      int Digit1;
      int Digit2;
      int Digit3;

      // These states indicate whether the timer is running or not, that is if StartStopSwPin is low SystemState = RunningState, otherwise SystemState = StoppedState
      int StoppedState=0;
      int RunningState=1;
      int SystemState=StoppedState;



      // display the number of laps, blanking leading zeroes.
      void ShowLaps()

      {
      for (int i=0;i<LapViewRepeats;i++)
      {
      LapTemp=Laps;
      LapOnes=LapTemp%10;
      LapTemp=LapTemp/10;
      LapTens=LapTemp%10;
      LapTemp=LapTemp/10;
      LapHundreds=LapTemp%10;
      LapTemp=LapTemp/10;
      LapThousands=LapTemp/10;
      if (Laps<10)
      {
      lc.setChar(0,3,' ',false);
      lc.setChar(0,2,' ',false);
      lc.setChar(0,1,' ',false);
      lc.setDigit(0,0,(byte)LapOnes,false);
      }
      else if (Laps<100)
      {
      lc.setChar(0,3,' ',false);
      lc.setChar(0,2,' ',false);
      lc.setDigit(0,1,(byte)LapTens,false);
      lc.setDigit(0,0,(byte)LapOnes,false);
      }
      else if (Laps<1000)
      {
      lc.setDigit(0,0,(byte)LapOnes,false);
      lc.setDigit(0,1,(byte)LapTens,false);
      lc.setDigit(0,2,(byte)LapHundreds,false);
      lc.setChar(0,3,' ',false);
      }
      else
      {
      lc.setDigit(0,0,(byte)LapOnes,false);
      lc.setDigit(0,1,(byte)LapTens,false);
      lc.setDigit(0,2,(byte)LapHundreds,false);
      lc.setDigit(0,3,(byte)LapThousands,false);
      }
      delay(delaytime);
      }
      }

      // This is called if the number of hours is greater than 0 so with four digits we can only display hours and minutes
      void ShowHoursMins(int ShHours,int ShMins)
      {
      Digit2=ShHours%10;
      Digit3=ShHours/10;
      Digit0=ShMins%10;
      Digit1=ShMins/10;
      lc.setDigit(0,0,(byte)Digit0,false);
      lc.setDigit(0,1,(byte)Digit1,false);
      lc.setDigit(0,2,(byte)Digit2,true);
      if (Digit3==0)
      {
      lc.setChar(0,3,' ',false);
      }
      else
      {
      lc.setDigit(0,3,(byte)Digit3,false);
      }
      }

      Delete
    2. // This is called if the number of minutes is less than 10. Thus with four digits we can show minutes, seconds, and 10ths of a second
      void ShowMinsSecs10ths(int ShMins,int ShSecs,int Sh10ths)
      {
      Digit3=ShMins;
      Digit1=ShSecs%10;
      Digit2=ShSecs/10;
      Digit0=Sh10ths;
      boolean NonZero=false;
      if (Digit3==0)
      {
      lc.setChar(0,3,' ',false);
      }
      else
      {
      NonZero=true;
      lc.setDigit(0,3,(byte)Digit3,true);
      }
      if ((NonZero==false)&&(Digit2==0))
      {
      lc.setChar(0,2,' ',false);
      }
      else
      {
      NonZero=true;
      lc.setDigit(0,2,(byte)Digit2,false);
      }
      if ((NonZero==false)&&(Digit1==0))
      {
      lc.setChar(0,1,' ',true);
      }
      else
      {
      lc.setDigit(0,1,(byte)Digit1,true);
      }
      lc.setDigit(0,0,(byte)Digit0,false);
      }

      // This is called if the number of minutes is >10, thus with four digits we can only show minutes and seconds
      void ShowMinsSecs(int ShMins,int ShSecs)
      {
      Digit2=ShMins%10;
      Digit3=ShMins/10;
      Digit0=ShSecs%10;
      Digit1=ShSecs/10;
      lc.setDigit(0,0,(byte)Digit0,false);
      lc.setDigit(0,1,(byte)Digit1,false);
      lc.setDigit(0,2,(byte)Digit2,true);
      lc.setDigit(0,3,(byte)Digit3,false);
      }



      void ShowTime(long TempTime)
      {
      int Tenths=TempTime%10;
      TempTime=TempTime/10;
      int Seconds=TempTime%60;
      TempTime=TempTime/60;
      int Minutes=TempTime%60;
      TempTime=TempTime/60;
      int Hours=TempTime%60;
      if (Hours>0)
      {
      ShowHoursMins(Hours,Minutes);
      }
      else if (Minutes<10)
      {
      ShowMinsSecs10ths(Minutes,Seconds,Tenths);
      }
      else
      {
      ShowMinsSecs(Minutes,Seconds);
      }
      delay(delaytime);
      }

      void ShowElapsedTime()
      {
      for (int i=0;i40)&&(SystemState==RunningState))
      {
      Laps=Laps+1;
      LastLapTime=ThisLapTime;
      ThisLapTime=0;
      analogWrite(PiezoPin,128); // Turns on beep, it gets turned off in tick if it's been on for more that 15 ticks (1.5 seconds). Thus it beeps at every new lap.
      }
      }


      // interrupt handler for timer. Triggered every 100 milliseconds
      void tick()
      {
      ElapsedTime += 1;
      ThisLapTime += 1;
      if (ThisLapTime>15)
      {
      analogWrite(PiezoPin,0); // Turns off beep if it's been on for more that 15 ticks (1.5 seconds). It gets turned on in newlap.
      }
      }

      // interrupt handler for StartStopSwPin state change, indicating the timer should either be started or stopped
      void statechange()
      {
      if (digitalRead(StartStopSwPin)==HIGH)
      {
      MsTimer2::stop();
      detachInterrupt(0);
      SystemState=StoppedState;
      ElapsedTime=ElapsedTime-ThisLapTime;
      ThisLapTime=0;
      }
      else
      {
      MsTimer2::start();
      attachInterrupt(0,newlap,CHANGE);
      SystemState=RunningState;
      }
      }

      Delete

    3. void setup() {
      /*
      The MAX7219 is in power-saving mode on startup
      */
      lc.shutdown(0,false);
      lc.setIntensity(0,15);
      lc.setIntensity(1,15);
      lc.setIntensity(2,15);
      lc.clearDisplay(0);
      MsTimer2::set(100,tick);
      pinMode(StartStopSwPin,INPUT_PULLUP);
      pinMode(LapSwPin,INPUT_PULLUP);
      pinMode(ETSwPin,INPUT_PULLUP);
      pinMode(ThisLapSwPin,INPUT_PULLUP);
      pinMode(LastLapSwPin,INPUT_PULLUP);
      pinMode(LapLEDPin,OUTPUT);
      digitalWrite(LapLEDPin,HIGH);
      pinMode(ETLEDPin,OUTPUT);
      digitalWrite(ETLEDPin,HIGH);
      pinMode(ThisLapLEDPin,OUTPUT);
      digitalWrite(ThisLapLEDPin,HIGH);
      pinMode(LastLapLEDPin,OUTPUT);
      digitalWrite(LastLapLEDPin,HIGH);
      pinMode(LapCountPin,INPUT_PULLUP);
      // For testing
      // Serial.begin(9600);
      // delay(500);
      attachInterrupt(1,statechange,CHANGE);

      }

      // This just cycles through displaying or not based on their toggle switches laps, elapsed time, this lap time, last lap time.
      void loop()
      {
      int ButtonState;
      ButtonState=digitalRead(LapSwPin);
      if (ButtonState==LOW)
      {
      digitalWrite(LapLEDPin,LOW);
      ShowLaps();
      // delay(ViewDelay);
      digitalWrite(LapLEDPin,HIGH);
      }
      ButtonState=digitalRead(ETSwPin);
      if (ButtonState==LOW)
      {
      digitalWrite(ETLEDPin,LOW);
      ShowElapsedTime();
      //delay(ViewDelay);
      digitalWrite(ETLEDPin,HIGH);
      }
      ButtonState=digitalRead(ThisLapSwPin);
      if (ButtonState==LOW)
      {
      digitalWrite(ThisLapLEDPin,LOW);
      ShowThisLap();
      //delay(ViewDelay);
      digitalWrite(ThisLapLEDPin,HIGH);
      }
      ButtonState=digitalRead(LastLapSwPin);
      if (ButtonState==LOW)
      {
      digitalWrite(LastLapLEDPin,LOW);
      ShowLastLap();
      //delay(ViewDelay);
      digitalWrite(LastLapLEDPin,HIGH);
      }
      }

      Delete
    4. #include
      #include "LedControl.h"
      #include

      Apparently blogspot got confused by the less-than and greater-than symbols taking them for html markers so here they are again. These are the includes that go at the top of the code.

      Delete
    5. Oops, lost them again. Let's try it this way
      The two blank includes should be "less-than" Bounce.h "greater-than and "less-than" MsTimer2.h "greater-than

      Delete
    6. That turned out to be slightly painful.
      To make the digits I more or less followed the same steps as in this article from Sparkfun https://www.sparkfun.com/tutorials/47?_ga=1.73589650.1643652218.1422795565
      If you go this way do not use the kind of styrofoam that is made of a bunch of little balls pressed together like they did. Use the foam that comes in panels and is used for insulation.
      If I were doing it now I would consider using this instead https://www.sparkfun.com/products/8530

      I used the MAX7219 Serially Interfaced, 8-Digit LED Display Driver that you can see here https://www.sparkfun.com/products/9622

      I'm afraid I don't have an as-built schematic. I have a few pages of my notes but I'm not sure how useful they would be. First, let me say that I am not the King of hardware. I'm not even the Jack or 10 of hardware. If you are then the pinouts commented in the code and the datasheet for the MAX7219 should sort you out. If not I would be happy to open up the box and conjure up the circuit diagram if you would like.
      If you have questions on the code or need help with the circuit let me know.
      Regards,
      WAK

      Delete