Showing posts with label Arduino LCD. Show all posts
Showing posts with label Arduino LCD. Show all posts

Wednesday, September 19, 2012

Lap Timer Build Along Part 3 - Timer Based Transponder

This post continues the RCArduino Lap Timer Build Along Series.

Previous steps can be found here -

Initial build along and videos -
http://rcarduino.blogspot.com/2012/07/lap-timer-build-along-part-one.html

Introduction to the timer based transponder and the required parts list -
http://rcarduino.blogspot.com/2012/08/lap-timer-build-along-part-2-transponder.html

Here are two quick demos of the system in action at the Pro RC Track in Dubai, the first shows the bread board build along lap timer and an enclosed stripboard version. The second video shows external audio enabled for lap time countdowns and fastest lap/not fastest lap indicator tones.




Transponder Overview
The transponder works by generating a simple infrared signal very similar to the one your TV Remote generates. This is an established technique which is widely used at RC Tracks, kart tracks and even automobile race tracks. If your track has an existing IR Transponder you can use the existing signal and do not need to build your own transponder. Part 4 will show you how to do this.


If you plan to build your own transponder read on.

The transponder can be powered in one of two ways -

1) Using dedicated power - If you are using the transponder in an automobile or kart, you can use a 9 Volt battery or 4 AA batteries to power the circuit directly.

2) Using buffered/shared power - The 555 Timer is a notoriously noisy component, if you intent to share power with any sensitive equipment such as an RC Receiver, you will need to buffer the shared power from the 555 timer. A cheap and effective solution is to use a 7805 voltage regulator, the final stages of this post show how to add this component.

RC Car Power
The transponder design has been extensively tested in many cars at the ProRC Track in Dubai. The transponder can easily be connected to the balance plug of a LIPO battery using a 3 pin section of PCB Header or a male balance plug if you have one. When sharing power in this way it is important to use the 7805 regulator circuit to prevent interference with the models RC Systems.

One of the test cars - The current m-chassis lap record holder at the Pro RC Track - lots of carbon, anodized aluminium and threaded oil shocks all around.

 
If you are not using shared power you will not need to complete the final few steps of the build (stop at step10) however if you expect to share power with any sensitive equipment at some point in the future it is worth including these steps.

How does the transponder work ?

The transponder uses the same technique and components as your TV Remote - an Infra Red light emitting diode is switched on and off 38,000 times per second, this is our carrier frequency. The carrier is generated by one of the 555 timers however in order to transmit a signal or data we need to alter the carrier in some way. The transponder uses a very simple signal - a pulse of 500 microseconds, this is generated by the second timer which simply switches the first timer on and off.

In the final part of the build series we will add the IR Detector to our Arduino circuit, the IR Detector is tuned to the 38Khz signal of the transponder. Remember we are using two timers ? One to generate the 38Khz signal and another to pulse it at 1Khz ? This provides a very simple encoding which allows us to look for pulses 500us long and know that we are receiving transponder signals rather than reflected sunlight, TV Remote signals or signals from other track side transponders.


Some Alternative Transponders

The job of the transponder is to 1) Switch the diode on and off at a frequency of 38,000 times a second. 2) To enable and disable 1) At a frequency of 1,000 times a second. 

In order to do this we use two connected 555 timers, the first generates the 38Khz signal and the second one enables and disables this signal at the much lower frequency of 1Khz. 

The transponder circuit can be replaced with any simple circuit capable of generating two fixed frequency square waves. 

Options include microcontrollers, NAND oscillators, op-amp oscillators - anything that can generate a squarewave.



Building The 555 Timer Transponder - Before and After
The transponder can be built on a piece of strip board 16 holes wide by 14 deep. To make sure that this is the final size of you board, remember that the rows and columns you cut will be unusable, you need 16*14 inside the cut area.

The copper strips should be running from left to right across your board - this is the orientation used throughout the build.
The Schematic
The circuit is reasonably simple with only 11 components required for pit wall use and an additional four if you plan to use the circuit in an RC vehicle.

Due to the angle in some of the pictures, its useful to double check your component placement against the schematic, also have a look at pictures from later stages for a cross reference.
1) Placing The 555 Timers
 Place the two timers with the notch facing the top of your circuit. Leave four rows of space above the top timer, one row between the top and the bottom and you should be left with one more empty row below the second timer.

The timers should be placed with six free columns on either side. See the picture for reference.

Important: The copper tracks on your strip board should be running from left to right confirm this before soldering.

2) Add Decoupling Capacitors
Add a 0.1uf ceramic capacitor as a decoupling capacitor across pins 1 and 8 (top left and top right) of each 555 Timer. I have soldered mine directly to pins 1 and 8 to save space.

 3) Add Positive Power
Add the connections to positive power.

When we add the 7805 regulator in a later step, the third row (counting from the top) will be our 5 volt power supply rail. Connect this to PIN 8 (top right) of both 555 Timers.

In this case I am using orange wire for the connections to the 5 volt power rail.

At this point we can also connect PIN 4 (bottom right) of the top 555 Timer to 5 volts. This is the reset pin, it is active low, so by connecting it to +5volts we ensure that the timer is never reset.
4.1) Add Ground Connections
4.2) Add Timing Resistors
Two steps in one here -
1) Add ground connections. Pin 1 (top left) of each 555 timer needs to be connected to the ground power rail. When we add the 7805 regulator, the second row (counting from the top) will be ground, connect this row to Pin 1 of each timer, I have used brown wire for this connection in the picture.

2) Timing resistors -
Now we add the first of our timing components. In order from top to bottom we add a 1K resistor (brown,black,red) from the 5volt power rail (third row down) to pin 7 of the top 555 Timer. Next we add a 10K resistor (brown, black, orange) from the same pin 7 to the row which we left free between the two 555 timers. The final resistor is a 1K (brown, black, red) resistor connected from pin 7 of the bottom 555 timer to the very bottom row of our board.

5) Add Timing Resistors Continued
Next we need to add two more resistors to the second timer.

Connect a 220 resistor (red, red, brown) from pin 8 to pin 7 of the lower 555 timer.

Connect a 680 resistor (blue, green, brown) from pin 7 to pin 7. Yes, thats right from pin 7 to 7, its the blue resistor in the picture and for it to have any effect we need to cut the copper track beneath the resistor. Its also a good opportunity to mention that we need to cut the tracks which would otherwise connect the 555 timer pins left to right. Refer to the picture later in the post.

6.1) Add Timing Capacitors
6.2) Add Timing Connections
Add the 1uf electrolytic capacitor (big black can) between pins 2 and 1 of the top 555 timer. Note that these capacitors are directional, there is a strip with a minus sign printed on it which indicates which side should connect to ground (pin 1) in the picture the strip is facing away from the camera. The other side is connected to pin 2.

Next we connect the 0.01uf ceramic capacitor between pins 1 and 2 of the lower timer. This capacitor is not directional.

Finally we need to add connections between pin 2 and 6 (trigger and threshold) of the timers. In the picture I have used yellow wire for this, it would have been possible to route the wire directly across the chips from pin 2 to 6 however in this case I have chosen to route two wires, one down from pin 6 to the empty row immediately below the chip and one back up from from the empty row to pin 2. Repeat this for each of the two timers.


Electrolytic Capacitor Orientation
Larger value capacitors tend to be available as electrolytic capacitors, these have an appearance something like a can. Electrolytic capacitors are directional, to ensure that they are correctly placed, the cans have a band marked with a minus sign to indicate the side/pin which should be connected to ground.



7) Connect The Timers Together
 This one is easy. We want to connect the output of the first timer (top) to the enable of the second timer (bottom).

This allows the top timer which is running at 1000hz to switch the bottom timer on and off to generate our coded 38Khz signal.

This connection is shown by the white wire connecting pin 3 (output) of the top timer to pin 4 (reset) of the bottom timer.
8) LED Current Limiting Resistor
As with any LED we need to limit the current passing through our infra red LED. As we are pulsing the LED it is only on for very short durations and so we have the option of passing more current through it. In this case I am using a 100 Ohm resistor (brown, black, brown) to limit the current to 50 milli amps, you could use a lower resistor for more power which would give a greater lap detection distance however on a narrow RC Track which loops back on itself a 100 Ohm resistor is perfect.

The 100 Ohm resistor is connected from pin 3 (output) of the lower 555 timer to pin 1 (ground). In the next step we will add the LED and cut the track between pin 3 and the resistor.

9) Double Check
Its a good time to double check progress. Here is the circuit constructed so far shown from the left side, double check yours against this and the previous images.


10) Add The IR LED
Not a great picture, but its an easy step.

Like all LEDs, the infra red ones are directional. This means the LED will only light if connected the correct way. As we cannot see infra red, its worth a little extra explanation to make sure we get this part right.

Most LEDs have on leg which is longer than the other, the long one should be connected to the positive (closest to the chip) and the short to negative (the path to ground is provided by the 100 Ohm resistor). However I have recently bought a batch of LEDs where the legs were cut incorrectly. To double check you LED placement, look at the inside of the LED you should see to metal plates which are roughly triangular. The smaller of the two plates is the positive side of the LED and should be connected nearest to pin 3 of the bottom timer. The other leg should be connect next to the 100 Ohm resistor where it joins the same row as pin 3. You will need to leave at least one spare hole in the board between the LED legs, you will need to bend one of the legs into an S/Z shape to do this.

As both ends of the LED are connected through the strip of copper running from the resistor to pin 3, we need to cut the copper strip to force the electricity to pass through the LED. Cut the track in the spare hole you left between the two LED Legs - refer to the underside picture for reference.

Almost Finished !
The original transponder design is finished here and if you intend to use the transponder for motor racing or kart racing, just add a positive power connection to row 3 (third row down counting from the top and linked to the orange positive power wires) and a ground connection to row 2 (second row down connected to the brown ground wires).

Add a 9 volt battery or even better 4 AA Batteries and go racing !

If you intend to use the transponder in an RC Vehicle and want to share the RC Power you will need to buffer your RC Receiver from the switching noise generated by the 555 timer. The most effective way I have found to do this is by using a 7805 regulator.

11) Adding The Regulator
The 7805 regulator prevents the switching noise in the transponder circuit from being transmitted through the power circuit and into your RC Receiver.

For the regulator to function it also needs two capacitors as supporting components which we will add in a later step.

For now place the 7805 regulator in the 10th hole counting from left to right. The regulator should be orientated so that the heat sink is on the right side of the circuit and the legs point down towards the 555 timers.


You might want to cut the heat sink off the 555 timer, this makes it easier to mount in your cars window, do this before soldering so that you do not damage the circuit while cutting.


13.1) Add The Capacitors
13.2) A Diode
13.3) Power Connection
Lets do this in the order in which power enters our circuit.

1) Solder your positive power wire ( the red wire) to the very top row of the circuit, is the top left most hole.

2) Solder your ground (black wire) connection to the second row, if we have followed the steps correctly this should also be the same row as the middle pin of the 7805 regulator.

3) Connect a 0.1uf decoupling capacitor between the positive and negative power rails, in the picture you can see that I added mine to the left of the wires, you can add yours to the right as long as its immediately next to the incoming power connections.

4) Add a diode. Diodes are directional components, they only conduct electricity in the one direction, the direction is indicated by a band printed on the diode. In the picture the silver band indicates the direction in which electricity is allowed to flow. The band is facing the 7805 regulator meaning that electricity can flow into our circuit but if electricity is connected in the reverse direction the diode will prevent it from flowing and damaging our circuit.

The diode is connected to the positive power track at the top left of our circuit, once we cut the copper track underneath the diode the electricity will have to travel through the diode to reach our circuit. If we accidentally connect power in the wrong way, the diode will prevent damage to the rest of the circuit from the reverse power.

5) Add the 7805 Supporting capacitors. In order to regulate the power to our circuit, the 7805 requires some additional external components, in this case we are using two 100uf electrolytic capacitors. Like all electrolytic capacitors these are directional components, a band on the capacitor indicates the side which should be connected to ground. The middle pin of the 7805 is the ground, it should be soldered in the second track down on our board which will be the ground rail. The outer pins are the input power supply and the 5volt regulated output, each of these should be connected to the center ground pin through one of the 100uf Capacitors - make sure that the band indicating the negative side of each capacitor is connected to the center ground pin of the 7805.

Final Steps
Our final step is to cut the copper tracks under the 555 timers so that the left and right pins are not shorted together, a 3mm drill bit will do this nicely.

Final checks and trouble shooting -
1) The copper tracks on the underside of the circuit should be cut in the following locations -

In the picture the copper tracks are running from left to right across the underneath of the board. The red marks indicate where a track should be cut, in order from top to bottom these points are -

Diode - We need to cut the track directly underneath the diode in the top left of the circuit, this will force the electricity through the diode ensuring that if we connect power the wrong way around, no electricity will flow and no damage will occur.

555 Timers - We need to cut the tracks between the left and right pins or the two timers, this means 4 cuts on the first time and four on the bottom so that the pins are not shorted by the copper tracks.

680 Ohm Resistor - This resistor is connected to pin 7 of the second (lower) 555 timer and runs horizontally along the board. As this is the same direction as the copper tracks under the board, we need to cut the track under the resistor otherwise the electricity will by pass it through the copper track.

IR LED - This component is also connected horizontally across the board. In order for the electricity to pass through the LED we need to cut the track between its two legs, in the picture the cut is marked one hole to the left of where the positive leg of the LED is soldered next to pin 3 (output) of the lower 555 timer.


2) Confirm that all of the components are correctly orientated.

3) Test the circuit - in order to test the circuit we need an IR Detector, a simple test circuit is provided in the following link, if you can light the red LED with the signal from your transponder, well done you have a working transponder.

http://learn.adafruit.com/ir-sensor/testing-an-ir-sensor

Additional References -

NE 555 Datasheet with pinout
http://www.ti.com/lit/ds/symlink/ne555.pdf

7805 Regulator Datasheet
http://www.sparkfun.com/datasheets/Components/LM7805.pdf

Next time - Adding the IR Detector to our lap timer circuit.

Bonus Step -

As an optional step you might want to consider adding the amplifier circuit shown here -

http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html

The external audio mode can be switched on and off through the lap timer menu for extra volume when you need it. The new lap time countdown mode that you can hear in the video can also be switched on and off through the system menu.


External Audio and best lap time countdown demo -


 Stay tuned ...

Duane B

Sunday, August 19, 2012

Adding Audio to Arduino Projects

Sometimes a project just needs to be louder, whether its a synthesizer, alarm clock, autonomous robot or the RC Arduino lap timer.

In the case of the lap timer, I want people in the club house to know when a lap record has been broken, it all adds to the pressure and the fun of racing.

One incredibly simple solution to getting more sound from a micro controller is the LM386 series of amplifier chips. These can give project quality audio for less than a dollar.  They even make a passable one dollar MP3 docking station and are the basis of the 'little gem' guitar amplifier.


LM386-N4 Big Audio In A Tiny Package - 

Parts List - 
1 x LM386-N4
2 x 100uf Electrolytic Capacitors
1 x 0.1uf Capacitor
1 x 100Ohm Resistor
1 x 10K Potentiometer

Circuit pictured is used to drive a PC Speaker in the RCArduino Lap Timer project - See the video.


The LM386 Minimal Components Circuit

The datasheet for the LM386-N4 which I am using provides some example circuits but these require non standard capacitors - by non standard I really mean that most of us keep 'decade' capacitors meaning the tens - 0.01uf , 0.1uf, 1uf, 10uf, 100uf. The sample circuits require 250uf and 0.05 uf capacitors.

As I didn't have these values, I built the sample circuits with 100's in series and 0.1's in parallel to get 200uf and 0.05uf. After playing with the circuit for a while I removed the series and parallel capacitors leaving just a single 100uf and one 0.01 uf capacitor. This variation of the recommended circuit works perfectly well, and is now included in the built version of the RCArduino Lap Timer.

So for a super simple chip to add quality sound and volume to a project order a few LM386N4's its amazing how many projects can benefit from bigger sound when its as easy as this.


Simplified LM386N4 Circuit To Drive PC Speakers From A Micro controller

Caution : The new generation of 32 Bit ARM chips used by the Arduino Due are less able to sink and source current than the AVR Chips used in the 8-bit Arduinos. A number of users have reported burnt out DAC (Digital To Analog Converter) outputs while using the Arduino Due. It is suggested that a series current limiting resistor should be used between the Arduino Due DAC Outputs and external circuitry.

There are currently a number of topics covering the Arduino DAC outputs and pin protection in general on the Arduino Due forum - http://arduino.cc/forum/index.php/board,87.0.html

The circuit below shows a suitable circuit for 8-bit AVR Arduinos - UNO, Mega, Leonardo etc.



 Data sheet with alternative circuits and full application details -

http://www.ti.com/lit/ds/symlink/lm386.pdf

The RCArduino Five Dollar Synthesizer, Arduino audio played through the LM386 amplifier driving a PC Speaker

Project details here -
http://rcarduino.blogspot.com/2012/10/five-dollar-synthesiser.html

 


The Auduino
The five dollar synth is a great project if your up to re purposing an existing keyboard, if not, you really have to build an Auduino.

The Auduino is one of the best sounding Arduino Audio projects, it is also the easiest to build requiring only five potentiometers.
 
Update: The first video is my own Auduino, any others I post are enhanced Auduino's which for one reason of another are nicer than mine, so skip mine and have a look at what everyone else is able to get from this simple sketch through the use of clever additions -

My Own Audiuno - 
Totally standard Audiuno code with output direct to a PC Speaker using the LM386 Amplifier circuit shown in this post.


Auduino By DenkiTribe

A very musical Audiuno based jam -

The project enclosure seems to be closely related to a pizza box but we can forgive that for the very musical session. Modifications are obviously the stylus based pitch control and the addition of external echo.



Auduino By 'TheHangMansAxe'

As far as I can tell, thehangmansaxe has enhanced his auduino with two additions

1) An LED and LDR that 'gates' the output, for those of us with no audio background, gating is essentially connecting and disconnecting a signal from the output.

In DIY Projects LDRs are often used for this as they have an output which is a rough approximation to many stringed instrument where the initial note is loud but then decays away over time. LDRs initially react to light very quickly, but when the light is removed, they settle more slowly allowing the not to linger slightly.

At around the 1 minute mark the user shows the LED switching on and off through the side of the instrument.


2) Thermin style note sensing. The user does not provide any details, but I assume that the note is being controlled by infra red bouncing off the users hand. The original Audiuno design provided on tinkerkit uses five analogue inputs to control the sound, generally these are connected to puts but can also be replaced by any device capable of providing and analogue output.

UPDATE - 25/10/2012 - Added this section to explain how the Auduino works. It needs diagrams and rewritting for reduced length and increased clarity.


How does the Auduino work ?
While I am a big fan of the Auduino, its not that well documented. It is described as a 'granular synthesiser', I spent a lot of time reading up on granular synthesis and reverse engineering the code before I was able to understand exactly how the Auduino generates its particular sound.

A granular synthesizer is usually described as generating sound by rapidly repeating a small 'grain' of sound and if you read through the Auduino code you will certainly find repeated reference to grains however what and where are the grains ?

Outside of the Auduino project, the term grain is most often used to describe grains of sound which are sampled from real life speech, instruments or environmental sounds - a grain is a very short sample in the order of 1/10 to 1/10,000th of a second as opposed to the sampled vocal and drum tracks that you might be familiar with.

Pereshaped points out in the comments below that the synthesis technique used by the Auduino is closer to 'Vosim' that grain synthesis.
 
The Auduino code is actually very clever and efficient, instead of storing a grain the Auduino generates the grain in realtime using a simple counter.
The variables grainPhaseAcc and grainPhaseAcc2 are basically just counters. They count up, then down at a rate determined by grainPhaseInc and grainPhaseInc2. If you were to plot the value of these variables over time each one would give you a triangle waveform.

  // Increment the phase of the grain oscillators
  grainPhaseAcc += grainPhaseInc;
  grain2PhaseAcc += grain2PhaseInc;

The two variables grainPhaseInc and grainPhaseInc2 are directly controlled by two of the Auduino inputs. Adjust the potentiometers up and down and you will hear a frequency component of the output rise and fall in pitch as the relevant triangle wave increases and decreases in frequency.

These are not our main pitch control though, vary them up and down and while the quality of the note will change dramatically, the pitch of the note will stay the same.

The note pitch is controlled by the variable synchPhaseInc. This has an interesting job to do, it controls the rate at which yet another counter - synchPhaseAcc overflows. Whenever this counter overflows, it resets the the two triangle waveforms to an initial synchronized position. This periodic resetting of the two waveforms is what causes repetition of a repeatable 'grain' of sound. The rate of repetition gives the output its pitch.


Its actually a lot more interesting than that, what makes the Auduino sound so engaging is that as you increase or decrease the output frequency so you adjust the amount of the grain that is repeated adding additional layers of colour to the output tones.

The final stroke of genius in the Auduino design (its not my design so I am allowed to say this) is the use of the pentatonic scale. Instead of allowing you to choose any frequency you like the main pitch control is mapped to the musical scale know as the pentatonic scale. This is what gives the Auduino a kind of bluesy sound and ensures that you will never hit a duff note. For more on the pentatonic scale check out the wikipedia article.


A picture speaks a thousand works and Miro2424 has kindly posted this video of an Auduino in action on youtube. In the video you will see the two triangle waves superimposed on each other, you will see and hear how they are used to create both the pitch and the tone of the sound.


Auduino By Miro2424
I have been trying to learn how an 'addative', 'grain' or 'frequency on frequency' synthesizer like the Auduino works. This clip from Miro2424 shows the Auduino output visualised through what I am guessing is a high end PC Sound card. In the clip you can see the two triangular grains super imposed on each other and how they are used to create and vary the sound. Very happy to have found this, it makes it all easier to understand.




Everyone should have at least one Auduino, if you have a spare Arduino and 5 potentionmeters you can build one right now - http://code.google.com/p/tinkerit/wiki/Auduino

UPDATE - 01/02/2012
Here is another great variation on the Auduino by Moshang, this one also has the coolest name 'The Groovesizer' and also the best looking case of any I have seen so far.

The groovesizer extends the Auduino with a built in sixteen step sequencer.

Full details here - http://moshang.net/soundjeweler_blog/technique/groovesizer-diy-16-step-sequencer-and-synth/




UPDATE - 16/11/2012 - The Auduino is the original work of Peter Knight, the project home page appears to be inactive. RCArduino has previously reported a bug fix to the project which has not been updated. On the basis that the project is no longer active, a full version of the Auduino code including the bug fix and an added echo effect can be found on RCArduino -
http://rcarduino.blogspot.com/2012/11/auduino-with-delay.html
These two RC Arduino projects can also be built using identical hardware, upload them to your Auduino for a change of scene, you can always re upload the Auduino when your finished.

http://rcarduino.blogspot.com/2012/10/arduino-modular-synthesizer-part-one.html



Duane B

Friday, July 27, 2012

Lap Timer Build Along Part One

Creative Commons 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);

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;
  }
}


 // 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 */
};