RCArduinoPersonalLapTimer by DuaneB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Based on a work at rcarduino.blogspot.com.
Update - I have three big ideas for a version 2.0 of the lap timer system, they build on the capabilities of version 1.0, extending its functionality to a completely new area and adding what I hope will be a really cool user interface feature.
Before I begin work on version 2.0 I am completing the Version 1.0 Build Along - Starting right now.
What do we get at the end of Part One -
- Record and review race and practice sessions of upto 500 Laps
- Scroll through summaries of each session to view best and average lap times and the number of session laps
- Scroll through individual laps within any session
- A bonus feature for part 1 only - The "Fake A Lap" button
- The "Fake A Lap" button allows you to test your build before we introduce the automatic lap capture features in - parts two and three.
- At the end of part one you will have a fully function manual lap timer - your very own self built multi lap, multi session stop watch !
Build this in Part One today (infra red lap capture in part 2 and build your own transponder in part 3)
Older posts covering the functionality, design and a demonstration using an RC Car can be found here -
Menu and RC Car Demonstration -
Features -
Interface Design -
Older videos -
Timing an RC Car fitted with the transponder we will build in part three. |
A sample of the session and lap review menus
|
Everything in version 1.0 will be used as the basis for version 2.0. So lets get started -
Personal Lap Timer V1.0 Part 1
Goal - Build the user interface
Requirements - If you can grab a set of parts that looks like this, you can build part one of the lap timer in one hour.
- An Arduino UNO or compatible (Arduino UNO recommended for beginners)
- An LCD Display
- A 10K Potentiometer
- Hook up wire, breadboard or strip board for connections.
- Four push buttons
- Four 10K pull up resistors for use with the push buttons
- If you want to add the 'Fake A Lap' button you will require one extra button, this button is not required in later stages.
About LCD Displays
There are several libraries for driving LCD Displays with Arduino including one which is installed by default as part of the Arduino application. This is the library we will use, it provides an easy to use interface for driving 'character' LCD displays. These are LCD Displays that already know how to show text and common characters. To keep things simple, the lap timer uses a very common 16*2 character display giving us two lines of sixteen characters to display our race and setup information.
If you do not have an LCD, most electronics suppliers will have them in stock. The approximate cost is 10 dollars or 7 Pounds, if you buy the LCD from a project orientated supplier they will tend to include a 10K potentiometer used to adjust the contrast and some headers for soldering.
LCDs are well known and commonly used components within the micro controller community, for two very good introductions see the links copied below -
Oomlout - A UK Based supplier with downloadable PDF tutorials (scroll to the end of the page) -
Adafruit - A US Based supplier with online tutorials -
Lap Timer Build Along Part One
Step One - Power Connections
1) Connect the red positive power wire from the Arduino 5V Pin to the red positive rail of the breadboard
2) Connect the black ground wire from the Arduino GND Pin to the black (mine is blue) ground rail of the breadboard
3) Check that there are no breaks in the breadboard power rail, mine has a break which I am connecting across using the short green and red wires in the picture below.
I am using a double size breadboard, but if you only have access to
smaller breadboards its not going to be a problem. See the two small red
and green wires in the picture ? They are there to connect the two
halves of this double breadboard together, you can do the same with two
smaller breadboards to make a larger prototyping area.
Step Two - Add The Buttons and Pull Up Resistors
1) Add your four push buttons, make sure to place them the correct way, in general push button pins are in a rectangular layout, the pins are connected to each other in the 'long direction'. The push buttons work across the short side, connecting and disconnect the short side as the button is pushed and released. In the picture the short side is pointing up.
2) Add the 10K pull up resistors, here you can see that I am connecting the top right pin on each button to the red power rail through a 10K pull up resistor. When the buttons is pressed - as mentioned in 1) above, it will connect to the pin closest to it, this is what we want so in the next step we will be connecting the closest (top right) to ground. Note that the top right pin is permanently connected inside the button to the bottom right pin, this is a common mistake to make - the button is permanently on if you position it the wrong way.
Step Three - Add The LCD
1) If your LCD Has headers installed you can plug it straight into your bread board at this point, if not solder in the headers and then plug it in. If you do not have headers, you can solder jumper wires to the LCD and connect it this way. I often use cut off sections of old printer ribbon cables for these types of connections.
Step Four - Add Ground Connections to the buttons
1) In step two we placed our four buttons (menu up,down,ok and cancel) and connected them through a 10K pull up resistor to the red positive rail of the breadboard. Now we want to connect the other side of the button to the black ground rail of the breadboard. Throughout the build along I am using red wire for positive connections and green wire for ground connections, it will make your own build easier if you follow a similar convention.
To understand why we are connecting the buttons this way, read any of the many Arduino Button Tutorials.
Step Five - Add The LCD Power Connections
1) We need to provide power to the LCD and the LCD Back light, to do this on most Arduino compatible LCDs we need to connect the outer two LCD connections to the ground rail and the connections immediately inside each ground connection to the red positive power rail. For alternative explanations of the same wiring refer to the LCD Links provided earlier in the post. There is one additional connection we need to make which is to connect the read write pin (R/W) to ground, we are only ever writing to the LCD so can hold this low. The end result whichever instructions you find easiest to follow should match the picture.
Step Six - Add The LCD Contrast Adjustment
As mentioned in the LCD Section at the start of the post, if you buy
your Arduino compatible LCD from a project orientated supplier they will
generally include a header for breadboard connections and a 10K
potentiometer to adjust the contrast. If you didn't get a potentiometer
with you display, any 10K potentiometer will do
1) Connect the left pin of your potentiometer to the red power rail and the right pin to the ground rail of the breadboard.
2) Connect the center pin of your potentiometer to third pin from the left of the LCD - assuming your LCD Orientation matches the picture. I have used orange wire for this connection in the picture.
This connection allows us to adjust the contrast of the LCD, very useful in different light conditions.
Step Seven - Connect the LCD To your Arduino
1) Use jumper wires to connect the LCD Pins to your Arduino, on the Arduino side I am using digital pins 7,8,9,10,11 and 12.
2) On the LCD, these pins are connected as follows -
All LCD Pins are numbered counting from left to right starting with 1.
Arduino PIN | LCD PIN |
Digital Pin 12 | Register Select (Pin 4) |
Digital Pin 11 PIN | Clock/Enable (Pin 6) |
Digital Pin 10 | Bit 4 (Pin 11) |
Digital Pin 9 | Bit 5 (Pin 12) |
Digital Pin 8 | Bit 6 (Pin 13) |
Digital Pin 7 | Bit 7 (Pin 14) |
The camera has fisheyed the picture slightly so it looks as if the connections on the left are one or two pins further left than they are. Follow the connections in the table and you will be fine.
Step Eight - Progress Check - Hello World
At this point you should be able to run any of the LCD Examples from the Arduino application. A good starting point is to check the connections with the 'helloworld' sketch. To do this -
1) Start the Arduino Application
2) In the File menu, select - Examples/LiquidCrystal/HelloWorld
3) You should now be looking at the example sketch 'HelloWorld' which is part of the Arduino download.
4) Find the line in the sketch that initialises the LCD Object, it should look something like the following -
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
This initialises an object named lcd of the class LiquidCrystal. As part of the initialisation our program is telling the object which pins to use. We want to use different pins, so we need to replace the line above with the following -
// Initialise the LCD using 12,11,10,9,8,7
// LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // Original PINs
LiquidCrystal lcd(12,11,10,9,8,7);
// LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // Original PINs
LiquidCrystal lcd(12,11,10,9,8,7);
Now upload the sketch and if you have connected everything correctly you should see Hello World and the time displayed on you LCD. If not, double check the connections and try again.
Step Nine - Connect Buttons and Finish
1) Connect the four buttons by connecting a jumper wire between the pull up resistor and the top left pin of each button.
2) The buttons should be connected to the Arduino as follows -
Arduino PIN | Lap Timer Button (buttons from left to right) |
Digital Pin 6 | Menu Up (Left Most Button) |
Digital Pin 5 | Menu Down |
Digital Pin 4 | Ok |
Digital Pin 3 | Cancel (Right Most Button |
Step Ten - Upload
1) At this point we have completed Part One of the Lap Timer Build Along and can upload the Part One sketch to our build - I originally intended to include some test data so that at the end of Part One you would be able to use the system to scroll through some sessions and review the best, average and individual lap times within a session but then I thought - why not just add one more button ?
Bonus Step - The Big Red "Fake A Lap" Button
The lap timer uses infra red beacons or an infrared transponder to detect laps, we will get to detecting these in step two and building one in step three.
If we add one more button we can use the system as a manual lap timer right now on day one of the build.
1) Add the 'Fake A Lap' button using the same approach as the other four buttons - 10K pull up resistor etc.
2) Connect the button to the Arduino by adding a jumper from the button to digital pin 2. We are using interrupts to detect laps and so it must be digital pin 2.
The Code -
There are three files lapTimerBuildAlongPart1.pde, LapTimes.cpp and LapTimes.h
If it looks like a lot of code, it isn't, its mostly comments to explain what the code does.
LapTimerBuildAlongPart1.pde -
// RCArduinoPersonalLapTimer Part 1 by DuaneB is
// licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.
#include <avr/pgmspace.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include "laptimes.h"
//*******************************************************************************************
// USER INTERFACE DEFINITIONS
//*******************************************************************************************
// initialise the liquidCrystal Library
// Initialise the LCD using 12,11,10,9,8,7
LiquidCrystal lcd(12,11,10,9,8,7);
// PINs for user interface buttons - use any
#define KEY_OK_PIN 6
#define KEY_CANCEL_PIN 5
#define KEY_UP_PIN 4
#define KEY_DOWN_PIN 3
// bit flags used in key functions getKeys, waitForKeyPress, waitForKeyRelease
#define KEY_NONE 0
#define KEY_OK 1
#define KEY_CANCEL 2
#define KEY_UP 4
#define KEY_DOWN 8
#define KEYPRESS_ANY B11111111
// display width + 1, used by getRamString to copy a PROG_MEM string into ram
#define DISPLAY_ROW_BUFFER_LENGTH 17
//*******************************************************************************************
// Lap Capture definitions
//*******************************************************************************************
#define LAP_CAPTURE_LED 13
#define BUZZER_PIN A0
// minimum and maximum duration of qualifying IR Pulse
#define MIN_PULSE_DURATION 200
#define MAX_PULSE_DURATION 500
// start and end of pulse
uint32_t ulStartPulse;
uint32_t ulEndPulse;
volatile uint32_t ulPulseDuration;
// flags to manage access and pulse edges
volatile uint8_t bIRPulseFlags;
//
volatile uint32_t ulNewLapStartTime;
#define IR_PULSE_START_SET 1
#define IR_PULSE_END_SET 2
//*****************************************************************
// Global Instance of CLapTimes class
//*****************************************************************
CLapTimes gLapTimes(new CEEPROMLapStore());
//////////////////////////////////////////////////////////////////////////////////
//
// doShowSessionSummaries
//
// implements the show session summary menu
// allows the user to scroll up and down through summaries of the recorded sessions
//
//////////////////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin(9600);
Serial.println("In Setup");
lcd.begin(16, 2);
lcd.print("Lap Timer");
lcd.setCursor(0,1);
lcd.print("Version 0.9 Beta");
delay(3000);
pinMode(KEY_OK_PIN,INPUT);
pinMode(KEY_CANCEL_PIN,INPUT);
pinMode(KEY_UP_PIN,INPUT);
pinMode(KEY_DOWN_PIN,INPUT);
pinMode(LAP_CAPTURE_LED,OUTPUT);
pinMode(BUZZER_PIN,OUTPUT);
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
showTotals();
Serial.println("Out Setup");
}
//////////////////////////////////////////////////////////////////////////////////
//
// base loop, implements root of menu system
//
// allows the user to scroll up and down through summaries of the recorded sessions
//
//////////////////////////////////////////////////////////////////////////////////
void loop()
{
// lets keep control of the loop
while(true)
{
// wait for a key command to tell us what to do
Serial.println("Beginning Loop");
switch(waitForKeyPress(KEYPRESS_ANY))
{
// start recording
case KEY_OK:
doRecord();
break;
// delete all sessions
case KEY_CANCEL:
doConfirmDeleteSessions();
break;
// scroll through recorded session summaries
case KEY_UP:
case KEY_DOWN:
doShowSessionSummaries();
break;
}
showTotals();
waitForKeyRelease();
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doRecord
//
// start recording new sessions, update screen every second
// check for new laps
// record new laps
// show lap time for a few seconds at the end of a lap
// update and show new best lap if its a new session best
//
//////////////////////////////////////////////////////////////////////////////////
void doRecord()
{
lap_handle_t currentLapHandle = gLapTimes.createNewSession();
uint32_t ulOldLapStartTime = millis();
lap_time_t bestLapTime = 0XFFFF;
uint32_t ulLastTimeRefresh = millis();
char *pStringTimeBuffer = NULL;
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("Recording")));
attachInterrupt(0,captureLap,CHANGE);
while((getKeys() != KEY_CANCEL) && (currentLapHandle != INVALID_LAP_HANDLE))
{
Serial.println(ulPulseDuration);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Check for new laps captured
//////////////////////////////////////////////////////////////////////////////////////////////////////
if((IR_PULSE_END_SET|IR_PULSE_START_SET) == bIRPulseFlags)
{
uint32_t ulLastLapDuration = ulNewLapStartTime - ulOldLapStartTime;
ulOldLapStartTime = ulNewLapStartTime;
lap_time_t lapTime = CLapTimes::convertMillisToLapTime(ulLastLapDuration);
gLapTimes.addLapTime(currentLapHandle,lapTime);
currentLapHandle = gLapTimes.moveNext(currentLapHandle);
// new best lap
if(lapTime < bestLapTime)
{
bestLapTime = lapTime;
}
lcd.clear();
lcd.print(getRamString(PSTR("Best Lap ")));
lcd.print(CLapTimes::formatTime(bestLapTime,true));
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Last Lap")));
// use this to show lap time
lcd.print(CLapTimes::formatTime(lapTime,true));
// or this to show delta time
//lcd.print(CLapTimes::formatTime(lapTime-bestLapTime,true));
digitalWrite(LAP_CAPTURE_LED,HIGH);
digitalWrite(BUZZER_PIN,HIGH);
delay(400);
if(lapTime == bestLapTime)
{
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
delay(200);
digitalWrite(LAP_CAPTURE_LED,HIGH);
digitalWrite(BUZZER_PIN,HIGH);
delay(400);
}
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
// dont look for another lap for 2 seconds
delay(2000);
// give ownership of the shared variables back to the ISR
bIRPulseFlags = 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Update screen with current lap time
//////////////////////////////////////////////////////////////////////////////////////////////////////
uint32_t ulCurrentLapTime = millis();
if((ulCurrentLapTime - ulLastTimeRefresh) > 1000)
{
ulLastTimeRefresh = ulCurrentLapTime;
lcd.clear();
if(bestLapTime != 0XFFFF)
{
lcd.print(getRamString(PSTR("Best Lap ")));
lcd.print(CLapTimes::formatTime(bestLapTime,true));
}
else
{
lcd.print(getRamString(PSTR("Recording")));
}
pStringTimeBuffer = CLapTimes::formatTime(CLapTimes::convertMillisToLapTime(ulCurrentLapTime - ulOldLapStartTime),false);
if(pStringTimeBuffer != NULL)
{
lcd.setCursor(0,1);
lcd.print(pStringTimeBuffer);
}
else
{
// If we do not complete a lap for 9m59s display an idle message until a key is pressed
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Idle")));
waitForKeyPress(KEYPRESS_ANY);
ulOldLapStartTime = millis();
}
}
}
if(currentLapHandle == INVALID_LAP_HANDLE)
{
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Memory Full!")));
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doConfirmDeleteSessions
//
// Delete all sessions - if we are using storage for the first time we may need
// to call this function to initialise the storage to a known value. The user
// can access this function by pressing cancel on the root menu. This will bring
// up a confirmation message asking the user to press ok to delete all laps.
//
//////////////////////////////////////////////////////////////////////////////////
void doConfirmDeleteSessions()
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("OK to Reset")));
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Cancel to go Back")));
// we pressed cancel to get here - so lets wait for cancel to be released before we look for more input
waitForKeyRelease();
if(KEY_OK == waitForKeyPress(KEY_OK|KEY_CANCEL))
{
gLapTimes.clearAll();
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doShowSessionSummaries
//
// implements the show session summary menu
// allows the user to scroll up and down through summaries of the recorded sessions
// user can press ok to enter the session and scroll through the session laps
//
//////////////////////////////////////////////////////////////////////////////////
void doShowSessionSummaries()
{
boolean bFinished = false;
uint8_t nSession = 0;
do
{
lap_handle_t lapHandle = 0;
uint16_t nSessionAverage = 0;
uint16_t nSessionBest = 0;
uint16_t nSessionLapCount = 0;
Serial.println(nSession);
lapHandle = gLapTimes.getSessionHandle(nSession);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("SNo:")));
lcd.print(nSession);
// if theres no laps for this session or its the first session but it doesnt contain any laps
if(lapHandle == INVALID_LAP_HANDLE || (lapHandle == 0 && gLapTimes.getLapTime(lapHandle)==0))
{
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Empty Session")));
}
else
{
Serial.println(lapHandle);
gLapTimes.getSessionSummary(lapHandle,nSessionAverage,nSessionBest,nSessionLapCount);
lcd.print(getRamString(PSTR(" Laps:")));
lcd.print(nSessionLapCount);
lcd.setCursor(0,1);
// Best Lap Time
lcd.print(CLapTimes::formatTime(nSessionBest,true));
// Average Lap Time
lcd.print(" ");
lcd.print(CLapTimes::formatTime(nSessionAverage,true));
}
waitForKeyRelease();
switch(waitForKeyPress(KEYPRESS_ANY))
{
case KEY_UP:
nSession++;
break;
case KEY_DOWN:
nSession--;
break;
case KEY_CANCEL:
bFinished = true;
break;
case KEY_OK:
if(nSessionLapCount != 0)
{
doLapScroll(gLapTimes.getSessionHandle(nSession));
}
break;
}
}while(!bFinished);
}
//////////////////////////////////////////////////////////////////////////////////
//
// showTotals shows the number of sessions, laps and laps left
// as the root of the menu
//
//////////////////////////////////////////////////////////////////////////////////
void showTotals()
{
Serial.println(getRamString(PSTR("Entering showTotals")));
uint16_t nSessions = 0;
uint16_t nLapsRecorded = 0;
uint16_t nLapsRemaining = 0;
gLapTimes.getTotals(nSessions,nLapsRecorded,nLapsRemaining);
lcd.clear();
lcd.print(getRamString(PSTR("Sessions=")));lcd.print(nSessions);
lcd.setCursor(0, 1);
lcd.print(getRamString(PSTR("Laps=")));lcd.print(nLapsRecorded);
lcd.print(getRamString(PSTR("Left=")));lcd.print(nLapsRemaining);
Serial.println(getRamString(PSTR("Leaving showSummaryData")));
}
//////////////////////////////////////////////////////////////////////////////////
//
// doLapScroll
//
// scroll through the laps within a session, startLapHandle points to the start
//
//////////////////////////////////////////////////////////////////////////////////
void doLapScroll(lap_handle_t startLapHandle)
{
boolean bFinished = false;
lap_handle_t currentLapHandle = startLapHandle;
lap_handle_t tmpLap = currentLapHandle;
uint8_t nLapNumber = 0;
do
{
lcd.clear();
lcd.setCursor(0,0);
if(tmpLap == INVALID_LAP_HANDLE)
{
lcd.print(getRamString(PSTR("No More Laps")));
delay(2000);
lcd.clear();
}
lcd.print(getRamString(PSTR("Lap No.")));
lcd.print(nLapNumber);
lcd.setCursor(0,1);
if(currentLapHandle != INVALID_LAP_HANDLE)
{
char *pTime = CLapTimes::formatTime(gLapTimes.getLapTime(currentLapHandle),true);
lcd.setCursor(0,1);
lcd.print(pTime);
}
waitForKeyRelease();
uint8_t sKey = waitForKeyPress(KEYPRESS_ANY);
switch(sKey)
{
case KEY_DOWN:
case KEY_UP:
(sKey == KEY_UP) ? tmpLap = gLapTimes.moveNext(currentLapHandle) : tmpLap = gLapTimes.movePrevious(currentLapHandle);
if(tmpLap != INVALID_LAP_HANDLE)
{
if(gLapTimes.getLapTime(tmpLap) != EMPTY_LAP_TIME)
{
currentLapHandle = tmpLap;
(sKey == KEY_UP) ? nLapNumber++ : nLapNumber--;
}
else
{
tmpLap = INVALID_LAP_HANDLE;
}
}
break;
case KEY_OK:
tmpLap = currentLapHandle;
break;
case KEY_CANCEL:
bFinished = true;
break;
}
}
while(!bFinished);
}
//////////////////////////////////////////////////////////////////////////////////
//
// Key related helpers
//
// getKeys - pole keys
// waitForKeyPress - block waiting for keys based on a mask
// waitForKeyRelease - block waiting until no kets are pressed
//
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//
// getKeys
//
// read the inputs and create a bit mask based on the buttons pressed
// this does not block, need to review whether we should make this block, in most
// cases we loop waiting for a key, sometimes we also loop waiting for no key
// could put both options here with an input parameter.
//
//////////////////////////////////////////////////////////////////////////////////
short getKeys()
{
// Use bit flags for keys, we may have a future use for
// combined key presses
short sKeys = KEY_NONE;
if(digitalRead(KEY_UP_PIN) == LOW)
{
sKeys |= KEY_UP;
}
if(digitalRead(KEY_DOWN_PIN) == LOW)
{
sKeys |= KEY_DOWN;
}
if(digitalRead(KEY_OK_PIN) == LOW)
{
sKeys |= KEY_OK;
}
if(digitalRead(KEY_CANCEL_PIN) == LOW)
{
sKeys |= KEY_CANCEL;
}
return sKeys;
}
//////////////////////////////////////////////////////////////////////////////////
//
// waitForKeyRelease
//
// we can enter a function while the activating key is still pressed, in the new
// context the key can have a different purpose, so lets wait until it is released
// before reading it as pressed in the new context
//
//////////////////////////////////////////////////////////////////////////////////
void waitForKeyRelease()
{
do
{
// do nothing
}
while(getKeys() != KEY_NONE);
// debounce
delay(20);
}
//////////////////////////////////////////////////////////////////////////////////
//
// waitForKeyPress
//
// convenience function, loop doing nothing until one of the sKeyMask keys is
// pressed
//
//////////////////////////////////////////////////////////////////////////////////
uint8_t waitForKeyPress(uint8_t sKeyMask)
{
uint8_t sKey = KEY_NONE;
do
{
sKey = getKeys() & sKeyMask;
}
while(sKey == KEY_NONE);
digitalWrite(BUZZER_PIN,HIGH);
delay(20);
digitalWrite(BUZZER_PIN,LOW);
return sKey;
}
// A helper that copies a string from program memory into a buffer in sram
// we need this because our code can only use strings held in sram
// this fetches strings that are stored in program memory as and when
// we need them.
char * getRamString(PGM_P pString)
{
// NEED TO ADD A CHECK HERE TO ENSURE pString < DISPLAY_ROW_LENGTH
static char pBuffer[DISPLAY_ROW_BUFFER_LENGTH];
return strcpy_P(pBuffer,pString);
}
//////////////////////////////////////////////////////////////////////////////////
//
// captureLap
//
// In part 1 we will fake a lap if we detect INT0 being pulled low
// In part 2 we will add a simple IR Detector which you can test with
// a TV Remote
// In part three we will add a more complex IR Detector which will allow
// us to detect a transponder and ignore any other IR Signal
//////////////////////////////////////////////////////////////////////////////////
void captureLap()
{
uint8_t bLapCaptureState = digitalRead(2);
digitalWrite(LAP_CAPTURE_LED,bLapCaptureState);
if(bLapCaptureState == LOW)
{
bIRPulseFlags = (IR_PULSE_END_SET|IR_PULSE_START_SET);
ulNewLapStartTime = millis();
}
else
{
bIRPulseFlags = 0;
}
}
// licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.
#include <avr/pgmspace.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include "laptimes.h"
//*******************************************************************************************
// USER INTERFACE DEFINITIONS
//*******************************************************************************************
// initialise the liquidCrystal Library
// Initialise the LCD using 12,11,10,9,8,7
LiquidCrystal lcd(12,11,10,9,8,7);
// PINs for user interface buttons - use any
#define KEY_OK_PIN 6
#define KEY_CANCEL_PIN 5
#define KEY_UP_PIN 4
#define KEY_DOWN_PIN 3
// bit flags used in key functions getKeys, waitForKeyPress, waitForKeyRelease
#define KEY_NONE 0
#define KEY_OK 1
#define KEY_CANCEL 2
#define KEY_UP 4
#define KEY_DOWN 8
#define KEYPRESS_ANY B11111111
// display width + 1, used by getRamString to copy a PROG_MEM string into ram
#define DISPLAY_ROW_BUFFER_LENGTH 17
//*******************************************************************************************
// Lap Capture definitions
//*******************************************************************************************
#define LAP_CAPTURE_LED 13
#define BUZZER_PIN A0
// minimum and maximum duration of qualifying IR Pulse
#define MIN_PULSE_DURATION 200
#define MAX_PULSE_DURATION 500
// start and end of pulse
uint32_t ulStartPulse;
uint32_t ulEndPulse;
volatile uint32_t ulPulseDuration;
// flags to manage access and pulse edges
volatile uint8_t bIRPulseFlags;
//
volatile uint32_t ulNewLapStartTime;
#define IR_PULSE_START_SET 1
#define IR_PULSE_END_SET 2
//*****************************************************************
// Global Instance of CLapTimes class
//*****************************************************************
CLapTimes gLapTimes(new CEEPROMLapStore());
//////////////////////////////////////////////////////////////////////////////////
//
// doShowSessionSummaries
//
// implements the show session summary menu
// allows the user to scroll up and down through summaries of the recorded sessions
//
//////////////////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin(9600);
Serial.println("In Setup");
lcd.begin(16, 2);
lcd.print("Lap Timer");
lcd.setCursor(0,1);
lcd.print("Version 0.9 Beta");
delay(3000);
pinMode(KEY_OK_PIN,INPUT);
pinMode(KEY_CANCEL_PIN,INPUT);
pinMode(KEY_UP_PIN,INPUT);
pinMode(KEY_DOWN_PIN,INPUT);
pinMode(LAP_CAPTURE_LED,OUTPUT);
pinMode(BUZZER_PIN,OUTPUT);
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
showTotals();
Serial.println("Out Setup");
}
//////////////////////////////////////////////////////////////////////////////////
//
// base loop, implements root of menu system
//
// allows the user to scroll up and down through summaries of the recorded sessions
//
//////////////////////////////////////////////////////////////////////////////////
void loop()
{
// lets keep control of the loop
while(true)
{
// wait for a key command to tell us what to do
Serial.println("Beginning Loop");
switch(waitForKeyPress(KEYPRESS_ANY))
{
// start recording
case KEY_OK:
doRecord();
break;
// delete all sessions
case KEY_CANCEL:
doConfirmDeleteSessions();
break;
// scroll through recorded session summaries
case KEY_UP:
case KEY_DOWN:
doShowSessionSummaries();
break;
}
showTotals();
waitForKeyRelease();
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doRecord
//
// start recording new sessions, update screen every second
// check for new laps
// record new laps
// show lap time for a few seconds at the end of a lap
// update and show new best lap if its a new session best
//
//////////////////////////////////////////////////////////////////////////////////
void doRecord()
{
lap_handle_t currentLapHandle = gLapTimes.createNewSession();
uint32_t ulOldLapStartTime = millis();
lap_time_t bestLapTime = 0XFFFF;
uint32_t ulLastTimeRefresh = millis();
char *pStringTimeBuffer = NULL;
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("Recording")));
attachInterrupt(0,captureLap,CHANGE);
while((getKeys() != KEY_CANCEL) && (currentLapHandle != INVALID_LAP_HANDLE))
{
Serial.println(ulPulseDuration);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Check for new laps captured
//////////////////////////////////////////////////////////////////////////////////////////////////////
if((IR_PULSE_END_SET|IR_PULSE_START_SET) == bIRPulseFlags)
{
uint32_t ulLastLapDuration = ulNewLapStartTime - ulOldLapStartTime;
ulOldLapStartTime = ulNewLapStartTime;
lap_time_t lapTime = CLapTimes::convertMillisToLapTime(ulLastLapDuration);
gLapTimes.addLapTime(currentLapHandle,lapTime);
currentLapHandle = gLapTimes.moveNext(currentLapHandle);
// new best lap
if(lapTime < bestLapTime)
{
bestLapTime = lapTime;
}
lcd.clear();
lcd.print(getRamString(PSTR("Best Lap ")));
lcd.print(CLapTimes::formatTime(bestLapTime,true));
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Last Lap")));
// use this to show lap time
lcd.print(CLapTimes::formatTime(lapTime,true));
// or this to show delta time
//lcd.print(CLapTimes::formatTime(lapTime-bestLapTime,true));
digitalWrite(LAP_CAPTURE_LED,HIGH);
digitalWrite(BUZZER_PIN,HIGH);
delay(400);
if(lapTime == bestLapTime)
{
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
delay(200);
digitalWrite(LAP_CAPTURE_LED,HIGH);
digitalWrite(BUZZER_PIN,HIGH);
delay(400);
}
digitalWrite(LAP_CAPTURE_LED,LOW);
digitalWrite(BUZZER_PIN,LOW);
// dont look for another lap for 2 seconds
delay(2000);
// give ownership of the shared variables back to the ISR
bIRPulseFlags = 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Update screen with current lap time
//////////////////////////////////////////////////////////////////////////////////////////////////////
uint32_t ulCurrentLapTime = millis();
if((ulCurrentLapTime - ulLastTimeRefresh) > 1000)
{
ulLastTimeRefresh = ulCurrentLapTime;
lcd.clear();
if(bestLapTime != 0XFFFF)
{
lcd.print(getRamString(PSTR("Best Lap ")));
lcd.print(CLapTimes::formatTime(bestLapTime,true));
}
else
{
lcd.print(getRamString(PSTR("Recording")));
}
pStringTimeBuffer = CLapTimes::formatTime(CLapTimes::convertMillisToLapTime(ulCurrentLapTime - ulOldLapStartTime),false);
if(pStringTimeBuffer != NULL)
{
lcd.setCursor(0,1);
lcd.print(pStringTimeBuffer);
}
else
{
// If we do not complete a lap for 9m59s display an idle message until a key is pressed
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Idle")));
waitForKeyPress(KEYPRESS_ANY);
ulOldLapStartTime = millis();
}
}
}
if(currentLapHandle == INVALID_LAP_HANDLE)
{
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Memory Full!")));
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doConfirmDeleteSessions
//
// Delete all sessions - if we are using storage for the first time we may need
// to call this function to initialise the storage to a known value. The user
// can access this function by pressing cancel on the root menu. This will bring
// up a confirmation message asking the user to press ok to delete all laps.
//
//////////////////////////////////////////////////////////////////////////////////
void doConfirmDeleteSessions()
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("OK to Reset")));
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Cancel to go Back")));
// we pressed cancel to get here - so lets wait for cancel to be released before we look for more input
waitForKeyRelease();
if(KEY_OK == waitForKeyPress(KEY_OK|KEY_CANCEL))
{
gLapTimes.clearAll();
}
}
//////////////////////////////////////////////////////////////////////////////////
//
// doShowSessionSummaries
//
// implements the show session summary menu
// allows the user to scroll up and down through summaries of the recorded sessions
// user can press ok to enter the session and scroll through the session laps
//
//////////////////////////////////////////////////////////////////////////////////
void doShowSessionSummaries()
{
boolean bFinished = false;
uint8_t nSession = 0;
do
{
lap_handle_t lapHandle = 0;
uint16_t nSessionAverage = 0;
uint16_t nSessionBest = 0;
uint16_t nSessionLapCount = 0;
Serial.println(nSession);
lapHandle = gLapTimes.getSessionHandle(nSession);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(getRamString(PSTR("SNo:")));
lcd.print(nSession);
// if theres no laps for this session or its the first session but it doesnt contain any laps
if(lapHandle == INVALID_LAP_HANDLE || (lapHandle == 0 && gLapTimes.getLapTime(lapHandle)==0))
{
lcd.setCursor(0,1);
lcd.print(getRamString(PSTR("Empty Session")));
}
else
{
Serial.println(lapHandle);
gLapTimes.getSessionSummary(lapHandle,nSessionAverage,nSessionBest,nSessionLapCount);
lcd.print(getRamString(PSTR(" Laps:")));
lcd.print(nSessionLapCount);
lcd.setCursor(0,1);
// Best Lap Time
lcd.print(CLapTimes::formatTime(nSessionBest,true));
// Average Lap Time
lcd.print(" ");
lcd.print(CLapTimes::formatTime(nSessionAverage,true));
}
waitForKeyRelease();
switch(waitForKeyPress(KEYPRESS_ANY))
{
case KEY_UP:
nSession++;
break;
case KEY_DOWN:
nSession--;
break;
case KEY_CANCEL:
bFinished = true;
break;
case KEY_OK:
if(nSessionLapCount != 0)
{
doLapScroll(gLapTimes.getSessionHandle(nSession));
}
break;
}
}while(!bFinished);
}
//////////////////////////////////////////////////////////////////////////////////
//
// showTotals shows the number of sessions, laps and laps left
// as the root of the menu
//
//////////////////////////////////////////////////////////////////////////////////
void showTotals()
{
Serial.println(getRamString(PSTR("Entering showTotals")));
uint16_t nSessions = 0;
uint16_t nLapsRecorded = 0;
uint16_t nLapsRemaining = 0;
gLapTimes.getTotals(nSessions,nLapsRecorded,nLapsRemaining);
lcd.clear();
lcd.print(getRamString(PSTR("Sessions=")));lcd.print(nSessions);
lcd.setCursor(0, 1);
lcd.print(getRamString(PSTR("Laps=")));lcd.print(nLapsRecorded);
lcd.print(getRamString(PSTR("Left=")));lcd.print(nLapsRemaining);
Serial.println(getRamString(PSTR("Leaving showSummaryData")));
}
//////////////////////////////////////////////////////////////////////////////////
//
// doLapScroll
//
// scroll through the laps within a session, startLapHandle points to the start
//
//////////////////////////////////////////////////////////////////////////////////
void doLapScroll(lap_handle_t startLapHandle)
{
boolean bFinished = false;
lap_handle_t currentLapHandle = startLapHandle;
lap_handle_t tmpLap = currentLapHandle;
uint8_t nLapNumber = 0;
do
{
lcd.clear();
lcd.setCursor(0,0);
if(tmpLap == INVALID_LAP_HANDLE)
{
lcd.print(getRamString(PSTR("No More Laps")));
delay(2000);
lcd.clear();
}
lcd.print(getRamString(PSTR("Lap No.")));
lcd.print(nLapNumber);
lcd.setCursor(0,1);
if(currentLapHandle != INVALID_LAP_HANDLE)
{
char *pTime = CLapTimes::formatTime(gLapTimes.getLapTime(currentLapHandle),true);
lcd.setCursor(0,1);
lcd.print(pTime);
}
waitForKeyRelease();
uint8_t sKey = waitForKeyPress(KEYPRESS_ANY);
switch(sKey)
{
case KEY_DOWN:
case KEY_UP:
(sKey == KEY_UP) ? tmpLap = gLapTimes.moveNext(currentLapHandle) : tmpLap = gLapTimes.movePrevious(currentLapHandle);
if(tmpLap != INVALID_LAP_HANDLE)
{
if(gLapTimes.getLapTime(tmpLap) != EMPTY_LAP_TIME)
{
currentLapHandle = tmpLap;
(sKey == KEY_UP) ? nLapNumber++ : nLapNumber--;
}
else
{
tmpLap = INVALID_LAP_HANDLE;
}
}
break;
case KEY_OK:
tmpLap = currentLapHandle;
break;
case KEY_CANCEL:
bFinished = true;
break;
}
}
while(!bFinished);
}
//////////////////////////////////////////////////////////////////////////////////
//
// Key related helpers
//
// getKeys - pole keys
// waitForKeyPress - block waiting for keys based on a mask
// waitForKeyRelease - block waiting until no kets are pressed
//
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//
// getKeys
//
// read the inputs and create a bit mask based on the buttons pressed
// this does not block, need to review whether we should make this block, in most
// cases we loop waiting for a key, sometimes we also loop waiting for no key
// could put both options here with an input parameter.
//
//////////////////////////////////////////////////////////////////////////////////
short getKeys()
{
// Use bit flags for keys, we may have a future use for
// combined key presses
short sKeys = KEY_NONE;
if(digitalRead(KEY_UP_PIN) == LOW)
{
sKeys |= KEY_UP;
}
if(digitalRead(KEY_DOWN_PIN) == LOW)
{
sKeys |= KEY_DOWN;
}
if(digitalRead(KEY_OK_PIN) == LOW)
{
sKeys |= KEY_OK;
}
if(digitalRead(KEY_CANCEL_PIN) == LOW)
{
sKeys |= KEY_CANCEL;
}
return sKeys;
}
//////////////////////////////////////////////////////////////////////////////////
//
// waitForKeyRelease
//
// we can enter a function while the activating key is still pressed, in the new
// context the key can have a different purpose, so lets wait until it is released
// before reading it as pressed in the new context
//
//////////////////////////////////////////////////////////////////////////////////
void waitForKeyRelease()
{
do
{
// do nothing
}
while(getKeys() != KEY_NONE);
// debounce
delay(20);
}
//////////////////////////////////////////////////////////////////////////////////
//
// waitForKeyPress
//
// convenience function, loop doing nothing until one of the sKeyMask keys is
// pressed
//
//////////////////////////////////////////////////////////////////////////////////
uint8_t waitForKeyPress(uint8_t sKeyMask)
{
uint8_t sKey = KEY_NONE;
do
{
sKey = getKeys() & sKeyMask;
}
while(sKey == KEY_NONE);
digitalWrite(BUZZER_PIN,HIGH);
delay(20);
digitalWrite(BUZZER_PIN,LOW);
return sKey;
}
// A helper that copies a string from program memory into a buffer in sram
// we need this because our code can only use strings held in sram
// this fetches strings that are stored in program memory as and when
// we need them.
char * getRamString(PGM_P pString)
{
// NEED TO ADD A CHECK HERE TO ENSURE pString < DISPLAY_ROW_LENGTH
static char pBuffer[DISPLAY_ROW_BUFFER_LENGTH];
return strcpy_P(pBuffer,pString);
}
//////////////////////////////////////////////////////////////////////////////////
//
// captureLap
//
// In part 1 we will fake a lap if we detect INT0 being pulled low
// In part 2 we will add a simple IR Detector which you can test with
// a TV Remote
// In part three we will add a more complex IR Detector which will allow
// us to detect a transponder and ignore any other IR Signal
//////////////////////////////////////////////////////////////////////////////////
void captureLap()
{
uint8_t bLapCaptureState = digitalRead(2);
digitalWrite(LAP_CAPTURE_LED,bLapCaptureState);
if(bLapCaptureState == LOW)
{
bIRPulseFlags = (IR_PULSE_END_SET|IR_PULSE_START_SET);
ulNewLapStartTime = millis();
}
else
{
bIRPulseFlags = 0;
}
}
// LapTimes.cpp
#include "arduino.h"
#include "laptimes.h"
#include <../../../../libraries/EEPROM/EEPROM.h>
// Does what it says, gets a lap time from EEPROM - does not do any validation
lap_time_t CEEPROMLapStore::getLapTime(lap_handle_t lapHandle)
{
lap_time_t lapTime = (EEPROM.read((lapHandle*sizeof(uint16_t))+1)<<8);
lapTime += EEPROM.read(lapHandle*sizeof(uint16_t));
return lapTime;
}
// Does what it says, sets a lap time in EEPROM - does not do any validation
void CEEPROMLapStore::setLapTime(lap_handle_t lapHandle,lap_time_t lapTime)
{
EEPROM.write(lapHandle*sizeof(uint16_t),lowByte(lapTime));
EEPROM.write((lapHandle*sizeof(uint16_t))+1,highByte(lapTime));
}
// Initialise the lap store to EMPTY_LAP_TIME through out
// this is an important function, we find empty space by looking
// for one EMPTY_LAP_TIME that defines the end of a session followed
// immediatley by another EMPTY_LAP_TIME, this show that there
// are not sessions following the previous session in which case
// we are free to create a new session.
// We cannot be sure what SD, Memory or EEPROM will contain on the first run
// and so it is important we have this option to initialise the storage to
// a known value.
void CEEPROMLapStore::clearAll()
{
for(uint16_t unIndex = 0;unIndex < (getMaxLaps()*sizeof(lap_time_t));unIndex++)
{
EEPROM.write(unIndex,EMPTY_LAP_TIME);
}
}
// Return the maximum number of laps for this storage media (or device ATMega8,328,1240 etc)
uint16_t CEEPROMLapStore::getMaxLaps()
{
return EEPROM_LAP_STORE_MAX_LAPS;
}
//*******************************************************************************************
// CLapTimes
//
// A lot of the work in this class is simply finding the start and end of sessions, and
// finding space to start a new session.
//
// With more memory I would have used headers to do a lot of the work inside CLapTimes
// A file system could also have done a lot of the work.
//
// It isn't pretty and could be refactored but it works.
//
//*******************************************************************************************
// Initialise CLapTimes which whichever class we want to provide the actual lap storage
// all lap storage is through the ILapStore interface and so we can use any class
// that implements this interface. Only CEEPROMLapStore is provided in this release,
// others may follow
CLapTimes::CLapTimes(ILapStore *pLapStore)
{
m_pLapStore = pLapStore;
}
// The end of a session is marked by an empty lap (0)
// to create a new session, we first look at the very first lap, if its invalid, there are no sessions
// and we can start a new one from position 0.
// if there is a valid lap at position 0 we need to scan for two consecutive invalid laps. A single invalid lap indicates
// the end of an existing session, if this is followed by anything other than an invalid lap, it is the beginning
// of a new session, if its followed by an invalid lap then we have found the end of the existing sessions and
// can use the second invalid lap handle as the start of our new session.
lap_handle_t CLapTimes::createNewSession()
{
lap_handle_t newSessionLapHandle = 0;
lap_handle_t currentLapHandle = 0;
// if the first lap is a valid lap - we need to scan through the recorded laps
// and sessions to find two consecutive invalid laps - the first we leave in place to
// mark the end of the existing sessions, the second is a free space for us to create
// a new session.
if(m_pLapStore->getLapTime(newSessionLapHandle) != EMPTY_LAP_TIME)
{
// assume the worst - there is no space left
newSessionLapHandle = INVALID_LAP_HANDLE;
// loop until we have a valid lap handle or we reach the end of the lap store
while(newSessionLapHandle == INVALID_LAP_HANDLE && currentLapHandle < m_pLapStore->getMaxLaps())
{
// loop until we reach the end of the lap store or we find an empty lap time
while(currentLapHandle < m_pLapStore->getMaxLaps() && (m_pLapStore->getLapTime(currentLapHandle) != EMPTY_LAP_TIME))
{
currentLapHandle++;
};
// we found an invalid lap, so check the the next lap handle is less than the end of the lap store
// and that the content of the next lap is an empty lap meaning it is free for us to use
if(((currentLapHandle+1)<m_pLapStore->getMaxLaps()) && (m_pLapStore->getLapTime(++currentLapHandle) == EMPTY_LAP_TIME))
{
// Yay ! we got two consecutive empty laps so lets set the firstLapHandle so we can start our new session.
newSessionLapHandle = currentLapHandle;
}
}
}
return newSessionLapHandle;
}
void CLapTimes::setLapTime(lap_handle_t lapHandle,lap_time_t lapTime)
{
m_pLapStore->setLapTime(lapHandle,lapTime);
}
lap_time_t CLapTimes::getLapTime(lap_handle_t lapHandle)
{
return m_pLapStore->getLapTime(lapHandle);
}
// scan through all of the recorded laps, total the number of sessions, total the number of laps
// recorded and return and indicative number of remaining laps - its indicative becuase
// each session requires on end of session marker so 10 sessions of 5 laps takes 60 laps
// (10 * 5 laps + 10 end of session markers) one session of 5 sessions of 10 laps takes 55 laps
// (5 * 10 + 5 end of session markers)
void CLapTimes::getTotals(uint16_t &nSessions,uint16_t &nLapsRecorded,uint16_t &nLapsRemaining)
{
lap_handle_t lapHandle = 0;
nSessions = 0;
nLapsRecorded = 0;
nLapsRemaining = 0;
while(lapHandle < m_pLapStore->getMaxLaps() && (m_pLapStore->getLapTime(lapHandle) != EMPTY_LAP_TIME))
{
// we have a session so count it
nSessions++;
// and count the laps within the session
while(lapHandle < m_pLapStore->getMaxLaps() && (m_pLapStore->getLapTime(lapHandle++) != EMPTY_LAP_TIME))
{
nLapsRecorded++;
}
}
nLapsRemaining = m_pLapStore->getMaxLaps() - nLapsRecorded;
}
void CLapTimes::clearAll()
{
m_pLapStore->clearAll();
}
// This is similar to get totals but works within a session only, returns the average of all laps in the session,
// the best lap and the total number of laps
lap_handle_t CLapTimes::getSessionSummary(lap_handle_t lapHandle,uint16_t &nSessionAverage,uint16_t &nSessionBest,uint16_t &nSessionLapCount)
{
nSessionAverage = 0;
nSessionBest = 0xFFFF;
nSessionLapCount = 0;
lap_time_t nLapTime = 0;
uint32_t nTotalTime = 0;
while((INVALID_LAP_HANDLE != (nLapTime = m_pLapStore->getLapTime(lapHandle))) && (nLapTime != EMPTY_LAP_TIME))
{
nTotalTime += nLapTime;
if(nLapTime < nSessionBest)
{
nSessionBest = nLapTime;
}
nSessionLapCount++;
lapHandle++;
}
nSessionAverage = nTotalTime/nSessionLapCount;
return nLapTime;
}
lap_handle_t CLapTimes::addLapTime(lap_handle_t lapHandle,lap_time_t lapTime)
{
if(lapHandle < m_pLapStore->getMaxLaps())
{
m_pLapStore->setLapTime(lapHandle,lapTime);
}
else
{
lapHandle = INVALID_LAP_HANDLE;
}
return lapHandle;
}
lap_handle_t CLapTimes::moveNext(lap_handle_t lapHandle)
{
if(lapHandle < m_pLapStore->getMaxLaps())
{
lapHandle++;
}
else
{
lapHandle = INVALID_LAP_HANDLE;
}
return lapHandle;
}
lap_handle_t CLapTimes::movePrevious(lap_handle_t lapHandle)
{
if(lapHandle >= 1)
{
lapHandle--;
}
else
{
lapHandle = INVALID_LAP_HANDLE;
}
return lapHandle;
}
// given a session number, find it the start of the session and return a handle to it
lap_handle_t CLapTimes::getSessionHandle(uint8_t nSession)
{
lap_handle_t currentLapHandle = 0;
uint8_t nCurrentSession = 0;
uint16_t nLapTime = 0;
while(nCurrentSession != nSession && currentLapHandle < m_pLapStore->getMaxLaps())
{
// loop until we read the max laps or we find and empty lap
do
{
currentLapHandle++;
}
while((currentLapHandle) < m_pLapStore->getMaxLaps() && m_pLapStore->getLapTime(currentLapHandle) != EMPTY_LAP_TIME);
nCurrentSession++;
if(currentLapHandle < m_pLapStore->getMaxLaps())
{
// move next to step over the 0 terminator for the previous session
currentLapHandle++;
// if the first lap of the session is empty there is no session
// so return invalid lap to indicate no session found.
if(getLapTime(currentLapHandle) == EMPTY_LAP_TIME)
{
currentLapHandle = INVALID_LAP_HANDLE;
}
}
else
{
currentLapHandle = INVALID_LAP_HANDLE;
}
}
return currentLapHandle;
}
// 10 minutes is 600 seconds or 600,000 milli seconds, this is too big to fit into a uint32_t
// so we divide by 10 to convert the value into a lap_time_t which contains the lap time in 100's
// of seconds.
lap_time_t CLapTimes::convertMillisToLapTime(uint32_t ulTime)
{
return ulTime/10;
}
// turn a lap_time_t into a time string formatted as - m:ss:dd
// bPrecision turns 100's on or off
char* CLapTimes::formatTime(lap_time_t time,unsigned char bPrecision)
{
char *pResult = NULL;
lap_time_t nSeconds = time/100;
lap_time_t nMinutes = nSeconds/60;
lap_time_t nHundredths = 0;
if(nMinutes <= 9)
{
if(bPrecision)
{
nHundredths = time - (nSeconds*100);
}
nSeconds -= (nMinutes * 60);
m_pTimeStringBuffer[7] = 0;
m_pTimeStringBuffer[6] = (nHundredths%10)+'0';
m_pTimeStringBuffer[5] = (nHundredths/10)+'0';
m_pTimeStringBuffer[4] = '.';
m_pTimeStringBuffer[3] = (nSeconds%10)+'0';
m_pTimeStringBuffer[2] = (nSeconds/10)+'0';
m_pTimeStringBuffer[1] = ':';
m_pTimeStringBuffer[0] = nMinutes + '0';
pResult = m_pTimeStringBuffer;
}
return pResult;
}
char CLapTimes::m_pTimeStringBuffer[9];/*m:ss:dd - dd represents hundredths of a second */
// LapTimes.h
// If we assume that lap data will always be set to 0
// Session ends will always be 0
// we only ever return invalid handle
//*******************************************************************************************
// Lap storage and retreival definitions
//*******************************************************************************************
#define EMPTY_LAP_TIME 0
#define INVALID_LAP_HANDLE 0XFFFF
typedef uint16_t lap_handle_t;
typedef uint16_t lap_time_t;
//*******************************************************************************************
// ILapStore
//
// Defines a pure virtual class (C++ Terminology) or interface (Java Terminology)
// It simply defines the functions that can be used to get, set and clear laps
// in a lap store.
//
// The following lap store is provided
// 1) CEEPromLapStore - this one stores laps in the EEPROM
// If you wanted to add SD Card Storage you could define a new class CSDCardLapStore
//
//*******************************************************************************************
class ILapStore
{
public:
virtual lap_time_t getLapTime(lap_handle_t lapHandle) = 0;
virtual void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime) = 0;
virtual void clearAll() = 0;
virtual uint16_t getMaxLaps() = 0;
};
//*******************************************************************************************
// CEEPROMLapStore
//
// Store laps in memory -
//
// For - simple, easy, its just using an array in memory
// Against - lose all laps if power is lost or Arduino is reset
//
//*******************************************************************************************
#define EEPROM_LAP_STORE_MAX_LAPS 500
class CEEPROMLapStore : public ILapStore
{
public:
virtual lap_time_t getLapTime(lap_handle_t lapHandle);
virtual void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
virtual void clearAll();
virtual uint16_t getMaxLaps();
protected:
lap_time_t m_LapTimes[EEPROM_LAP_STORE_MAX_LAPS];
};
//*******************************************************************************************
// CLapTimes
//
// A lot of the work in this class is simply finding the start and end of sessions, and
// finding space to start a new session.
//
// With more memory I would have used headers to do a lot of the work inside CLapTimes
// A file system could also have done a lot of the work.
//
// It isn't pretty and could be refactored but it works.
//
//*******************************************************************************************
class CLapTimes
{
public:
CLapTimes(ILapStore *pLapStore);
lap_handle_t createNewSession();
void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
lap_time_t getLapTime(lap_handle_t lapHandle);
void getTotals(uint16_t &nSessions,uint16_t &nLapsRecorded,uint16_t &nLapsRemaining);
void clearAll();
lap_handle_t getSessionSummary(lap_handle_t lapHandle,uint16_t &nSessionAverage,uint16_t &nSessionBest,uint16_t &nSessionLapCount);
lap_handle_t addLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
lap_handle_t moveNext(lap_handle_t lapHandle);
lap_handle_t movePrevious(lap_handle_t lapHandle);
lap_handle_t getSessionHandle(uint8_t nSession);
static lap_time_t convertMillisToLapTime(uint32_t ulTime);
static char* formatTime(lap_time_t time,unsigned char bPrecision);
protected:
ILapStore *m_pLapStore;
public:
static char m_pTimeStringBuffer[9];/*m:ss:dd - dd represents hundredths of a second */
};
// Session ends will always be 0
// we only ever return invalid handle
//*******************************************************************************************
// Lap storage and retreival definitions
//*******************************************************************************************
#define EMPTY_LAP_TIME 0
#define INVALID_LAP_HANDLE 0XFFFF
typedef uint16_t lap_handle_t;
typedef uint16_t lap_time_t;
//*******************************************************************************************
// ILapStore
//
// Defines a pure virtual class (C++ Terminology) or interface (Java Terminology)
// It simply defines the functions that can be used to get, set and clear laps
// in a lap store.
//
// The following lap store is provided
// 1) CEEPromLapStore - this one stores laps in the EEPROM
// If you wanted to add SD Card Storage you could define a new class CSDCardLapStore
//
//*******************************************************************************************
class ILapStore
{
public:
virtual lap_time_t getLapTime(lap_handle_t lapHandle) = 0;
virtual void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime) = 0;
virtual void clearAll() = 0;
virtual uint16_t getMaxLaps() = 0;
};
//*******************************************************************************************
// CEEPROMLapStore
//
// Store laps in memory -
//
// For - simple, easy, its just using an array in memory
// Against - lose all laps if power is lost or Arduino is reset
//
//*******************************************************************************************
#define EEPROM_LAP_STORE_MAX_LAPS 500
class CEEPROMLapStore : public ILapStore
{
public:
virtual lap_time_t getLapTime(lap_handle_t lapHandle);
virtual void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
virtual void clearAll();
virtual uint16_t getMaxLaps();
protected:
lap_time_t m_LapTimes[EEPROM_LAP_STORE_MAX_LAPS];
};
//*******************************************************************************************
// CLapTimes
//
// A lot of the work in this class is simply finding the start and end of sessions, and
// finding space to start a new session.
//
// With more memory I would have used headers to do a lot of the work inside CLapTimes
// A file system could also have done a lot of the work.
//
// It isn't pretty and could be refactored but it works.
//
//*******************************************************************************************
class CLapTimes
{
public:
CLapTimes(ILapStore *pLapStore);
lap_handle_t createNewSession();
void setLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
lap_time_t getLapTime(lap_handle_t lapHandle);
void getTotals(uint16_t &nSessions,uint16_t &nLapsRecorded,uint16_t &nLapsRemaining);
void clearAll();
lap_handle_t getSessionSummary(lap_handle_t lapHandle,uint16_t &nSessionAverage,uint16_t &nSessionBest,uint16_t &nSessionLapCount);
lap_handle_t addLapTime(lap_handle_t lapHandle,lap_time_t lapTime);
lap_handle_t moveNext(lap_handle_t lapHandle);
lap_handle_t movePrevious(lap_handle_t lapHandle);
lap_handle_t getSessionHandle(uint8_t nSession);
static lap_time_t convertMillisToLapTime(uint32_t ulTime);
static char* formatTime(lap_time_t time,unsigned char bPrecision);
protected:
ILapStore *m_pLapStore;
public:
static char m_pTimeStringBuffer[9];/*m:ss:dd - dd represents hundredths of a second */
};
I was getting an error with the code. Are you able to send me them in a zip file? :)
ReplyDeleteRcarduino: Lap Timer Build Along Part One >>>>> Download Now
Delete>>>>> Download Full
Rcarduino: Lap Timer Build Along Part One >>>>> Download LINK
>>>>> Download Now
Rcarduino: Lap Timer Build Along Part One >>>>> Download Full
>>>>> Download LINK LM
Great that you are building it, if you are a member of Arduino forum PM me with your email and I will send you a zip - I am DuaneB.
ReplyDeleteI added two new features today and there is a new video from the start of a competition we ran in Dubai yesterday here - http://www.youtube.com/watch?v=ryQFShhtzlQ
I also want need the codes in zip file..please email me: ravin_ath@yahoo.com tq
DeletePlase send it to me to! :) adamand64@gmail.com
DeleteI realy like your project!
Good work!
Best regards
Adam
waiting for path two. rx & tx design interface.
ReplyDeleteregards,
Writing the next part right now ...
ReplyDeleteDuane B
90% finished the transponder build along. Will review and publish tomorrow.
ReplyDeleteDuane B
Part 3, the transponder build is available in draft form it will be revised in the next few days as I proof read and correct any mistakes, but for now, you can find it here - http://rcarduino.blogspot.com/b/post-preview?token=HQVl3DkBAAA.70nqzH8G__H-pdmS7RQRpw.1Te7lGoix_snmeKkggz0pw&postId=5033051796673601056&type=POST
ReplyDeleteDuane B
Part 3 here http://rcarduino.blogspot.com/2012/09/lap-timer-build-along-part-3-timer.html
ReplyDeleteDuane B
Here is My lap timer system
Deletebutton looklike toucscreen ^^, It's hidden by carbon sticker.
http://www.temppic.com/img.php?22-11-2012:1353556289_0.18088500.jpg
http://www.temppic.com/img.php?22-11-2012:1353556290_0.18600600.jpg
http://www.temppic.com/img.php?22-11-2012:1353556290_0.94690400.jpg
http://www.temppic.com/img.php?22-11-2012:1353556291_0.85936800.jpg
http://www.temppic.com/img.php?22-11-2012:1353556292_0.79002300.jpg
I am so going to try this. What's the basic costs of it? (I know the Arduino's are cheap considering what they are).
ReplyDeleteAll of the other parts cost less than an Arduino UNO. The only tricky part is building the transponder.
ReplyDeleteDuane.
When I compile and upload the code display is still empty :(((( . Please can you help me??
ReplyDeleteIt´s weird because when I upload any stopwatch program it works... So problem is somewhere in code I think.
John
You almost certainly have a mismatch between the LCD Pins in the code and the LCD Pins you have wired. LCD Projects can use any four or six pins, there is no reason to think that one program uses the same pins as another. The part one and latest laptimers actually use different pins to drive the LCD. I suggest you download the latest project here - http://rcarduino.blogspot.ae/2014/02/rcarduino-downloads.html - read the instructions first, then look for the link under -LapTimerStandAloneComplexISRAudio.zip and check your pins against the pins used in the code - I expect them to be different. Duane.
ReplyDeleteto use LapTimerStandAloneComplexISRAudio, for the the speaker to work, should the amplifier using a digital pin 4 or Analog 5?. thanks
ReplyDeleteHello, I have a few days studying your work, and is amazing.
ReplyDeleteThe code that is in the zip file works fine with IR and transponder that you decribes? Because the code of the zip is diferent of the code of this page.
I try to do this project, but I have a number of questions. Anyway I'll try because I already have the arduino one, but I have not sufficient knowledge to understand this complex and working code.
Congratulations for this post and for your excellent work.
Regards. (And sorry for my bad english)
Hi, The code in the zip works with the IR transponder and has a few other features including a count down with audio etc. The zip uses different pins to connect to the lcd etc. Its easy to see which pins if you read the comments in the .ino file in the zip. Hope that helps, Duane
ReplyDeleteHi!
ReplyDeleteI have only one question because your work is amazing!!
Do you know what is the max distance in meter that the IR and Transponder support?
thank you in advance!
What distance are you looking for ? I would not expect any problems over the width of an automotive race track, but if you are look for 100 meters that would be an issue.
ReplyDeleteDuane.
I am looking for about 10 -15 meters. And I dont know if with this distance work well ;)
DeleteHave you test about this condition??
Thank you very much for yours answer!
I cant see any problem with that Distance, what will you be timing ?
DeleteDuane.
want to create a motorbike laptimer, and the track's weight is about 10m.
DeleteThen I will bought all component and test it :).
When I test it I comment here the result :).
Thank you very much!
Hey did it worked for the range of 10 meters??
DeleteShould work fine, I believe there are commercial systems based on IR
ReplyDeleteHi, great project. I sent you a message on the arduino forum board, can you send me a zip code as well?
ReplyDeleteNames Atrixa,
Thanks
There are zip files for this and some other projects on the downloads post - http://rcarduino.blogspot.ae/2014/02/rcarduino-downloads.html
ReplyDeleteDuane
Thanks for the quick reply.
DeleteI've just finished part 1 after a few hiccups but I'm unable to get the fake a lap button working.
Any ideas?
Cheers
Did you get it working ? What Arduino are you using ? If its still not working have you tried any of the basic button example sketches to test your hardware for a bad button, connection, burnt out pin, wire etc ?
ReplyDeleteDuane
can you post the only for part 1 lap timer source code?
ReplyDeleteHave a look here - http://rcarduino.blogspot.ae/2014/02/rcarduino-downloads.html
ReplyDeleteDuane.
why my button is not functional..and i am stucked at "Lap Timer Version 0.9 Beta"
DeleteWhat model of Arduino are you using ? did you try your button circuit with any of the simple example button sketches that you can find in the Arduino Examples ?
DeleteDuane
1) What model or Arduino are you using ?
Delete2) Are you using the latest code from here - https://docs.google.com/file/d/0B8WjAvuO3LnId1lCVGV3ZTJ0RzQ ?
3) Before you upload the latest code, did you update your circuit to use the pins described in the comments at the start of the code ?
Let me know
Duane.
Duane,
ReplyDeleteI really like what you have done with the arduino lap timer for RC cars.
I'm into 1/64 scale slot cars and would like to track 4 cars laps, last times and best times.
My knowledge on programming is not there but I do have the hardware.
I have also built the gantry loaded with LEDs and have inserted photo sensors in my track directly below the LEDs.
Can you help?
Thanks
Steve
You project can definitely be completed using Arduino and a moderate amount of programming. Since you are new to programming, I would suggest that you start by writing a very simple sketch to use the digitalRead function to read each of your sensors and make a simple output like turning on and LED using digitalWrite whenever the sketch detects a car passing. Once you have proved your detection circuit is working you can start introducing the timing element using the micros() function.
DeleteDuane
I'm excited about this project. This sounds like a logical way to get started.
DeleteThanks for your suggestion!
Steve
Can I get the Shematic diagram..email me : ravin_ath@yahoo.com
ReplyDelete1) What model or Arduino are you using ?
Delete2) The latest code is here - https://docs.google.com/file/d/0B8WjAvuO3LnId1lCVGV3ZTJ0RzQ ?
3) Before you upload the latest code, you will need to update your circuit, there are some changes in the pins between the button example and the code above. The changes are described in the comments at the start of the code.
Duane.
Iam using Arduino UNO..Do you have the schematic of circuit??
DeleteFor the previous code i had error like below:
Deleteof Arduino: 1.6.0 (Windows 8), Board: "Arduino Uno"
Using library EEPROM in folder: C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\EEPROM
Using library LiquidCrystal in folder: C:\Program Files (x86)\Arduino\libraries\LiquidCrystal
C:\Program Files (x86)\Arduino/hardware/tools/avr/bin/avr-g++ -c -g -Os -w -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10600 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR -IC:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino -IC:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\standard -IC:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\EEPROM -IC:\Program Files (x86)\Arduino\libraries\LiquidCrystal\src C:\Users\PC\AppData\Local\Temp\build2578151900733383309.tmp\LCD2.cpp -o C:\Users\PC\AppData\Local\Temp\build2578151900733383309.tmp\LCD2.cpp.o
LCD2.ino:5:22: fatal error: Laptimes.h: No such file or directory
compilation terminated.
Error compiling.
I need the zip file for the LapTimerBuildAlongPart1 project which not using buzzer..Please reply as soon as possible .tq
DeleteYou can find the link on the downloads page, and copied below -
Deletehttps://docs.google.com/file/d/0B8WjAvuO3LnIS18wb3QzMm1LM3c
But when I complile it :
DeleteC:\Users\PC\AppData\Local\Temp\build8170582610724596129.tmp\LapTimes.cpp:3:50: fatal error: ../../../../libraries/EEPROM/EEPROM.h: No such file or directory
#include <../../../../libraries/EEPROM/EEPROM.h>
please help me..tq
Did you try any of the standard EEPROM examples such as the one that comes with the arduino editor or this one - http://arduino.cc/en/Tutorial/EEPROMWrite ?
DeleteDuane.
Hi,
DeleteFirst of all, thanks for sharing your work with us.
I've had the same issue, resolved by modifying the line 3 of LapTimes.cpp.
#include instead of #include <../../../../libraries/EEPROM/EEPROM.h>
Now i'm stuck on Lap Timer/Version 0.9 Beta :(
I try to figure out how to make it work
Greetings from France
I can see with the serial monitor that i'm stuck on the setup.
DeleteIn Setup
EnIn Setup
EnIn Setup
.....
It's a bit better with line 98 => // showTotals();
I can navigate in the menu but there is some bugs
?!? Pretty strange
DeleteIt's EEPROM.h instead of ../../../../libraries/EEPROM/EEPROM.h
OK. Now it work after clearing EEPROM with the EEPROM example eeprom_clear and deleting the showtotals calls, lines 95 and 131
Deleteplease send me the zip code
ReplyDeletethank you
This comment has been removed by the author.
ReplyDeleteHi,
ReplyDeletecan you please send me latest ZIP file. I like to build this my self.
How accurate is this compared to an professional system?
Thank you
What do I need to change to get v1.0 running on an Arduino Mega 2560?
ReplyDeleteThere isn't much that you would need to change, most of the code uses standard libraries. As the project is so simple and easily fits on an UNO, I would suggest that your time would be better spent just spending 20 dollars on an UNO rather than porting the code, keep the Mega for something more complex.
DeleteDuane B
I downloaded the zip file and i still get an error when verifying. I am fairly new to this i might be making a simple mistake.
ReplyDeleteI downloaded the zip file and i still get an error when verifying. I am fairly new to this i might be making a simple mistake.
ReplyDeleteWhat board are you compiling for ? What error do you get ?
DeleteDuane B
Board: "Arduino/Genuino Uno"
DeleteC:\2015 saved files\E Sketches\irlaptimer\zip\LapTimerBuildAlongPart1\LapTimerBuildAlongPart1.ino:9:22: fatal error: laptimes.h: No such file or directory
#include "laptimes.h"
^
compilation terminated.
exit status 1
Error compiling.
This report would have more information with
"Show verbose output during compilation"
enabled in File > Preferences.
That was me as anonymous btw. Thanks for the quick replies. I am trying to get this going for racecar, a few of my competitors can't afford a nice lap timer. I am hoping it will work with my Longacre transponder.
DeleteOne quick thing you can try is change
ReplyDelete#include "laptimes.h"
to
#include
Duane
Ok thank you. Now when I compile I get this error:
DeleteArduino: 1.6.6 Hourly Build 2015/10/02 04:41 (Windows 10), Board: "Arduino/Genuino Uno"
C:\2015 saved files\E Sketches\irlaptimer\zip\LapTimerBuildAlongPart1theone\LapTimerBuildAlongPart1\LapTimerBuildAlongPart1.ino: In function 'void doRecord()':
LapTimerBuildAlongPart1:165: error: 'getRamString' was not declared in this scope
lcd.print(getRamString(PSTR("Recording")));
^
C:\2015 saved files\E Sketches\irlaptimer\zip\LapTimerBuildAlongPart1theone\LapTimerBuildAlongPart1\LapTimerBuildAlongPart1.ino: In function 'void doConfirmDeleteSessions()':
LapTimerBuildAlongPart1:284: error: 'getRamString' was not declared in this scope
lcd.print(getRamString(PSTR("OK to Reset")));
^
C:\2015 saved files\E Sketches\irlaptimer\zip\LapTimerBuildAlongPart1theone\LapTimerBuildAlongPart1\LapTimerBuildAlongPart1.ino: In function 'void doShowSessionSummaries()':
LapTimerBuildAlongPart1:324: error: 'getRamString' was not declared in this scope
lcd.print(getRamString(PSTR("SNo:")));
^
C:\2015 saved files\E Sketches\irlaptimer\zip\LapTimerBuildAlongPart1theone\LapTimerBuildAlongPart1\LapTimerBuildAlongPart1.ino: In function 'void showTotals()':
LapTimerBuildAlongPart1:380: error: 'getRamString' was not declared in this scope
Serial.println(getRamString(PSTR("Entering showTotals")));
^
exit status 1
'getRamString' was not declared in this scope
This report would have more information with
"Show verbose output during compilation"
enabled in File > Preferences.
Hi, I'm receiving the exact same error 'getRamString' was not declared in this scope when I compile. Has anyone found the solution to this? Thanks!
DeleteHi, Anyone solved this issaue?
DeleteGood evening Mr Duane , my name is daniel . I am an enthusiast model like her. I saw that she has created quite a lap counter with Arduino . Having a arduino I'm going to create even I for my training sessions .
ReplyDeleteI wrote all his sketches and reproduced the hardware , but I have a problem .
The display every three seconds I see a flash as if you restart , and Arduino does not feel the keys pull-down .
Would you help me?
I would be so grateful !!
Excuse me if I'm not clear on the language .
thanks in advance
Best regards
Daniele
P. S. : I'm sorry for the basic language
Hi Daniel,
Deletei had the same problem.
my workaround for this problem was disable all the serial outputs in the code. after that it works
This comment has been removed by the author.
DeleteHello, first of all i have already made transporder (part3) and its working.
Deletei had same problem with Daniele Guardo, than i saw your suggestion, i try it, its fixing flashing problem, menu up and down keys works, its normally shows "ok to reset" on display, than when i press the okey button, it resets it self. thats all. how can i make this code work. everything is ready except the code.. What should i see on the display normally?
ihave just realize "LapTimerStandAloneComplexISRAudio" Sketch. Than i uploaded it, made button input changes, its now shows "press ok to record" when i press menu buton, but if i press, its reboot yourself again.. is there any idea?
Deletei tought my LCD library couse this problem, because iam useing an older libabry, i changed LCD than installed most recent libabry from adafruid but nothing changed. arduino is still rebooting yourself. is this about SDK version? this project had made since 2012, may be code is trying to call an command which is not unavailable right now ??
Deletenew information, if i connect a power bank, it automaticly show on display "Best lap" on first line and on second line it shows time which start from 4:20 .. non of buttons work except one that if i press it, arduino starts rebooting yourself..
DeleteHi do u have a updated working codes and schematic diagram please please I need it :)
ReplyDeleteMy email is blade.312@hotmail.com Thank you!!
Please I really need this program working schematic:D
ReplyDeleteSir can you please send me the working updated codes and schematic diagram? Please I need it. Thank you. :)
ReplyDeleteBTW great project
markdominicboquio@gmail.com
Hello everyone. if you use most updated version, arduino gonna will reboot itself. it doesnt work. You need to use the same IDE with developers used which is arduino-1.0.5-r2-windows.exe
ReplyDeleteHi Duane, good one from you. I'll appreciate if you can please send the zip file for the code to my box. powerfloeng@gmail.com. Thanks in advance.
ReplyDeleteHi, I have followed part 1 correctly as far as i can see. I have the 4 buttons working and the code seems to be correct on the Uno , the only problem is, my "Fake lap" button doesnt seem to do anything. I have it set up like the other 4 buttons, and plugged in to Digital Pin 2 ( next to the down button in Digi Pin 3)
ReplyDeleteWhen I start "recording" the timer counts up, but nothing activates a lap. I have tried putting the wire into digital pins 3-6 and it works as a button as in up down cancel or ok so I know the button, wire and resistor are fine.
Any ideas ??
Hi Duane, thank you for this project. I would like to try to build one. May I ask you for final code bundle ? to timemaster@trillinis.com
ReplyDeleteThanks
Michal
Hey Duane! Amazing little timer and i love the memory feature! Thank you very much for sharing the design and setup instructions. Ive built a timing gate using a security IR sensor and emitter, ones used to trigger automatic gates. I will be using to time myself on different moto gymkhana courses, except i need to start each lap from standstill... Which means i need to pass the start/finish gate to start the timer, complete the course and pass the start/finish gate again to stop it, then i have to turn around and line up at the starting position again before beginning the next lap. Im using your StandAlone timer code, so far ive managed to get it to work with my gate, and i've also implemented a "while" command after the countdown to wait for pin 2 to go low (gate passed) before starting the session. What im struggling with is how do i get it to wait until i pass the gate again before starting timing my next lap? My coding skills are very low and i mostly copy and paste bits i find online, which isn't really doing the trick with a more in depth code like yours... Any chance you could help? Thanks in advance!
ReplyDeletehai i have a problem here
ReplyDeleteHi Duane
ReplyDeleteCan you please mail me t your zip file?
johanblom25@gmail.com
Really great article, Glad to read the article. It is very informative for us, Thanks for posting norton.com/setup
ReplyDeleteWhat's up, this weekend is pleasant in support of me, since this point in time i am reading this great informative post here at my residence.
ReplyDeleteWow, This is one of the best blogs I have ever read I get So much useful information from this article. Thank you for sharing this with us. mcafee.com/activate, www.mcafee.com/activate, mcafee activation code
ReplyDeleteThe technology sector changes rapidly. It seems like every day a new gadget is being made available to the public. For those "techies" interested in the latest and greatest technological information, a good tech news blog or website is a great way to stay informed.
ReplyDeleteIn this contemporary world, it has become liable to access each and everything with modern technologies; therefore, gaming peripherals are the best gaming devices which comfort the gamers who love to play the ideal games with easy and accessible device to play their skills.
ReplyDeleteIf you think about it you can see that the news media has been evolving for some time. Business news was once only available in a newspaper that changed when television arrived on the scene.
ReplyDeleteIt is very important to keep yourself updated as far as the technology is concerned. You should try to get your hands of the latest technology and gadgets as soon as they hit the market.
ReplyDeleteOutreach Galaxy is a platform that merchandise guest posts only with no limitation to minimum guest posts or any SEO metrics. Further, there’s no evaluation for home page features or any other requirements
ReplyDeleteIn the sports arena, peak performance in sports has always been a much sought after state by players and coaches of all levels. Whether the athletes are school boys soccer players or Olympians striving for their Gold medals, peak performance in sports has always attracted athletes and coaches alike.
ReplyDeleteCareme Tutor” is a web based platform founded in 2018 that connects an increasing number of teachers with students for private, home sessions in the Pakistan.
ReplyDeleteRcarduino: Lap Timer Build Along Part One >>>>> Download Now
ReplyDelete>>>>> Download Full
Rcarduino: Lap Timer Build Along Part One >>>>> Download LINK
>>>>> Download Now
Rcarduino: Lap Timer Build Along Part One >>>>> Download Full
>>>>> Download LINK