Introduction: AVR Chronograph From Concept to PCB

A chronograph is a device used to measure the speed of a passing object.  In it's simplest form, this involves two sensors of some kind that 'see' the object, some device that can measure time, and some output to deliver the data to the user.

In this instructable, I'll be showing how I designed, programmed, and built a device like this with a couple extra handy features to make it more versatile than the one at your local paintball field.  But first, a little background...

I was experimenting with home made rocket motors (purely for weekend entertainment,) and a buddy and I started talking about optimizing the fuel mixture.  We quickly realized that we could never estimate the performance anywhere near accurately enough to be able to get any meaningful data about the effects of small changes to the fuel ratio.  The conversation then turned to measurement techniques.  It wasn't long before I was thinking sensors, counters, and LEDs!  The original idea was to use a counter driven by a 555 oscillator that starts when the rocket passes one sensor and stops when the rocket passes the second sensor.  Then simply display the status of the counter on a bunch of LEDs in binary...

OK, that could work, but I'd have to take a calculator with me for every launch.  Then there's the trade off between simplicity and accuracy.  A fast oscillator would be more accurate, but would require at least 2 or 3 counters cascaded to count all the pulses during the short time the rocket is between the sensors.  One counter can do the job, but only if the oscillator is slow enough that the counter doesn't overflow... I abandoned that idea immediately.

I had a few AVR Microcontrollers lying around (an arduino too, but building your own target system is better on every level.  I'll save that argument for another day...) and I realized that one of them could do the job perfectly.  And I wouldn't even need a calculator!  At that moment I started seriously designing this project and this instructable was born.

A Quick description of how it functions before we move on:

Two 12VDC photo sensors (projector/receivers with reflectors) detect the rocket, or kid, or ballistic fruitcake, or whatever you want to clock and send a signal to the two external interrupt pins on an ATMega328P through transistor buffers (we'll get into interrupts and stuff later, promise.)  The sensors I'm using are Omron E3F2-DS10B4-P1's.  These are easily the most expensive component, but I was able to borrow a pair of them for the developement and testing of this circuit.  Any device that can detect your target object and send a signal out will work fine though.  The circuit I show here works with the 12VDC from these sensors, but with a little modification it can also work at other voltages equally well.

The ATMega measures the time between the interrupts, reads the user defined distance between the sensors from some DIP switches, does all the math, reads the user defined output units (MPH, KPH, etc...) from more DIP switches, and finally multiplexes the output to three 7-segment displays through a single 74LS48 BCD to 7-segment decoder. 

Doesn't sound too bad right?  Well, all but the fruitcake anyway...

The sensors can be anywhere from 1 to 16 feet apart, and the output can be in miles/hr, kilometers/hr, feet/sec, or meters/sec.  The distance setting for the sensors allows the user to change the minimum and maximum measurable target speed without having to reprogram the chip.  This comes in handy when, after flying a rocket or two, the kids want to race their bikes...  There is still issue of timer overflow, and the variable distance is one way to somewhat get around it.  There is another way that I also used that I'll talk about later: timer prescaling.


This instructable is (hopefully) to be all inclusive.  Every step in the process will be discussed in detail from designing the hardware, to programming the AVR, to drawing the schematic and circuit board layout, to finally etching and soldering the board.  All of the software I use is completely free and very functional.  Atmel studio 6 is for the AVR development and ExpressPCB is for the schematic and board layout. Therefore, all screenshots and procedures will be based on these software.  There are other equally free and, arguably, more functional options out there, so choose what's best for you.  On the hardware side, I'll be using an AVR Dragon (~$50) for programming, a breadboard for prototyping, and hopefully a computer with a USB port goes without saying :)  

********A complete tools, materials, and parts list will be posted on step 11*******

By the way,  the AVRDragon requires at least a 6-pin ribbon cable to do the programming.  It'll need a 2x3 female header on each end (2.54mm.)

As for etching the board, I used a pre-sensitized double sided coper clad board (1oz, 1/2oz would be ok,)  some positive developer to remove the exposed photoresist, and ferric chloride to etch the copper.  I've never used the toner transfer method, so I'm not sure if the small traces would come out clean with that technique.

More on etching later.  First, lets just have a look at the schematic...


Step 1: Schematic and Breadboard

This picture shows the schematic in its entirety.  In the next few steps we'll be looking at sections in more detail so that everything will be clear.  

You can see here that there is a 6 pin header included for programming (ISP6.)  This will be used for the initial development and programming of the AVR, as well as installation of any future firmware modifications.  in the next step, I'll start with the basic connections to make the AVR run and accept a program.  After that, we will build out to include all the I/O one piece at a time.  

If this looks complicated, don't worry.  I'll try to make it as clear as possible.  If it looks simple, go ahead and breadboard it sparky!

I've included a pic of the breadboard as it looked before some of the later options were added.  It's functional, but not complete.  It should give you a clear view of what things should look like.

Step 2: Power and Minimal AVR Connections

Ok, These pics show the (almost) bare minimum requirements for powering and programming the AVR.  The '5 volt ok' light is not needed, but is a convenient way to see that the chip is powered.  Also, you could use the chip's internal RC oscillator instead of the crystal resonator, but we will be needing the crystal's accuracy for our measurement anyway.  If you prefer to use the internal oscillator, or ceramic resonator, or a trained parrot with a toggle switch go ahead.  I'll cover clock options when we get to setting up the AVR for the first time.

At the power input, J1 is simply +12VDC and ground from a 12 volt 'wall wart' that I had lying around.  It is perfect for powering the sensors, and it's a very common size to salvage from somewhere.  Immediately after J1 you'll see D1.  This diode serves only as protection from connecting the power supply backwards.  (and it drops the voltage by about 0.7V reducing the heat dissipated by the LM7805 slightly, but that's just a fortunate side effect...)

Since I'm using 12 volt sensors, I needed a way to bring that 12VDC down to a level suitable for powering the AVR, 7-segment decoder, and -well- everything else but the sensors.  The LM7805 is a +5 volt regulator that suits this purpose perfectly.   The regulator's job is to monitor it's own output voltage on pin 3, and adjust it's current output until the voltage is 5V.  We don't need to worry about HOW it does this; just that it does, and that it dissipates the 'extra' voltage as heat.  The capacitors on the input and output of the regulator are there to ensure that there is a little extra current available if the load quickly changes.  This gives the 7805 time to react to any changes on the load side, like all the LEDs suddenly turning on or the reset button being pressed and released.  **note**  I tested this circuit with supply voltage ranging from about 9VDC>>20VDC.  The regulator handles this range fine, but starts getting hot at the higher voltages.  For this reason, I added a heatsink to the 7805.

The power LED should be self explanatory.  R5 is the current limiting resistor to protect D3 and they are connected directly to the regulator's output and ground.  If there's 5V, there will be an illuminated LED.

Now to the fun stuff!  Obviously, at the center of the whole circuit is our ATMega328P designated uC1.  This thing has 28 pins, so count carefully!  The pins are laid out  on the schematic exactly as they are on the chip and labeled exactly as they are in the manufacturer's data sheet (if you haven't downloaded the datasheet PDF yet, go ahead and do that -http://www.datasheetarchive.com/ - go ahead and get the datasheets for the decoder and displays and transistors too - you might need them!)  This chip is going to require +5V, GND, a clock source, a pull-up resistor on the reset pin, and programming port connections before it can do anything besides sit there and look like some kind of weird robotic centipede, so let's get to it.  

Did you notice on the simplified schematic that VCC and AVCC are tied together at the 5V supply?  Vcc is the normal supply voltage and AVcc is the supply for the onboard analog-to-digital converter.  AVcc should be connected to Vcc even if the ADC is not used.  I hope you knew that GND goes to a ground point in the circuit, or the 0V side of the power supply.

The crystal resonator connects to pins 9 and 10 with a pair of parallel capacitors.  These capacitors are very small (22p.) and the whole group of 3 components should be mounted as closely to pins 9 and 10 as possible to prevent stray capacitance from fouling with the oscillator.  The manufacturer's datasheet for the crystal you use will state recommended sizes for these capacitors, so check it out first.  Again, if you intend to use the internal RC oscillator, you'll have to configure your AVR a little differently.  When we get into ATMEL Studio 6 we'll get to setting these things up with fuse bits and such... fun stuff

You may have noticed that there are multiple descriptions for most of the pins.  That's because you can program them to have different functions depending on your application.  Even the reset pin can be reprogrammed to be another general I/O pin, but don't do that because you'll lose your ability to program the chip as soon as you make that change!  Here's why... look at pin 19.  It's labeled PB5, meaning PORTB 5.  It has an alternate function used for programming labeled SCK (master/slave clock.)  Notice that this SCK pin and the RESET pin both go to the ISP6 programming port.  The programming functions on the pins are only active when the reset pin is grounded. The programmer (AVR Dragon in my case) makes the ground connection to the reset pin automatically when it communicates with the chip enabling the programming functions of the appropriate pins.  So, if your program sets the reset pin as a digital input... you can no longer use it to enable the programming functions on the other programming pins!  In short, 'reset' should ALWAYS be 'reset' - unless you're manufacturing 1,000,000 units and your software guy tells you during the first production run that he needs "just one more input."

So, that means that the other programming pins (SCK, MISO, MOSI) CAN be used as I/O pins! On the full schematic, you can see that I used pin 17 as both MOSI for programming AND a digital output for one bit to the 7-segment decoder.  This works fine as long as the reset pin doesn't get accidentally pulled low enough that the AVR 'thinks' that it is grounded while in normal operation.  That is the purpose of pull-up resistor R1.  R1 ensures that if nothing is connecting the reset pin to ground, then it will be high or 5V.  In the full schematic you'll see a pushbutton switch connected from pin1 to ground also.  It comes in handy if your chip needs a reboot for some reason during development (accidentally making it divide by 0 is a good way to get it to act stupid!  Don't ask how I know this...)

In the second pic you'll see a diagram of the 6 pin ISP header and the names of it's connections.  All there is left to do is make the connection between these header pins and the matching pins on the AVR, and we should be ready to power it up and test our programmer!

As I mentioned earlier, SCK is the master/slave clock - pin 19  This is how the programmer and the target chip know they're talking at the same speed.

MISO/MOSI - master input, slave output and master output, slave input - pins 18/17  These are the pins that actually do the talking.

Vcc/GND - are just the power supply connections - pins 7/8  The programming port has a Vcc connection for powering the target, or, in the case of the AVR Dragon, allowing the programmer to match the target device's voltage.

And finally, Reset - pin 1 Puts the chip in programming mode when grounded, resets to power-up condition when it changes from low to high, and serves as the communication pin during DebugWire mode (debugging function supported by ATMEL Studio; it's cool, but I don't use it much.)  This pin is also used to enter HVPP (high voltage parallel programming) mode when 12V is applied, but we won't be needing to do that.  It is worth noting that the reset pin is the ONLY pin to which you can safely apply 12V, and only for HVPP.

It's time to get this little circuit onto the breadboard (I hope you know how a breadboard works.) if not: 

https://www.instructables.com/id/Breadboard-How-To/

Check out the 3rd and 4th pics for the breadboarded programming circuit and programmer connection.  The crystal oscillator is not present in the pics, but it shouldn't matter at this point if you put it there or not.  After we finish configuring the AVR to use the crystal it will no longer work without it until you reconfigure it to use another clock source.

Now that we have this simple circuit built for powering and programming our chip, we can move on to ATMEL studio and start setting things up! 



Step 3: Intro to ATMEL Studio and Setting Up the AVR

The first thing to do, if you haven't already, is download and install ATMEL Studio 6 and purchase an AVR Dragon (or other supported programmer.)  The software is completely free, but does require registration and an email address to get an active download link.  Visit ATMEL's site and get the software...come back when you're done.

http://www.atmel.com/Microsite/atmel_studio6/default.aspx

You can also use other third party programmers or even just a serial cable with other software like WINAVR and AVRDude, but that is beyond the scope of this instructable.  There are a TON of tutorials google can find for you if you would rather use another method.

Ok, got the software installed? Got an AVRDragon? Got some other way to get the program loaded and just want to continue?

good

The first thing you'll notice when starting ATMEL Studio is the start page.  Here you'll find useful links to tutorials, help files, creating or opening projects and such.  We don't want to create a project just yet though.  The first thing we have to do is make sure that we can talk to our AVR and configure the clock source to allow it to use the external crystal resonator instead of the internal RC oscillator.

If you haven't done so already, get the AVRDragon ready.  You'll need to connect the 6 pin ISP header on the Dragon to your 6 pin header on the breadboard.  Then power the breadboard circuit and plug the USB cable into the Dragon.  

************************************************
If you have never used the AVRDragon before, I recommend reading this article:

http://people.ece.cornell.edu/land/courses/ece4760/AtmelStuff/dragon.pdf

There's a lot of very useful information there including how to set up the Dragon with a ZIF socket and 40 pin header to use the HVPP programming feature with any chip in the AVR family ... handy if you mess up and brick your AVR!

************************************************

Now that everything is powered and connected, go into ATMEL Studio and click Tools>>Device Programming (2nd pic.)  A new window will open with a few choices to make on the top left:  Tool, Device, and Interface (3rd pic).  For this case, Tool should be AVR Dragon, Device should be ATMega328P, and Interface should be ISP.  Make those selections and click apply.  The window will then change to show a slider for setting the ISP clock frequency.  The note under the slider states that the frequency must be lower that 1/4 the target's clock frequency.  I have found this to be true! They're not lying! If you have trouble connecting to your chip, lower this value to make sure that that's not the problem.  I would set it at 250Khz for the moment to be sure, then raise it once the crystal is installed or once the clock frequency is known.


At this point, click the read button next to either 'Device signature' or 'Target Voltage' to the right of the Interface seting.  If all is well, the LEDs on the Dragon will do a little blinking and some values will appear in those two fields.... Did it work?  If not, reduce the ISP clock setting and doublecheck you breadboard work.  I've been known to wire the 6 pin headers upside-down, so check that carefully... at least that problem is easily solved by flipping the breadboard around!

If it worked the 'Device signature' field will now contain a HEX number that we don't need to worry too much about, and the 'Target Voltage' field should be showing the voltage you have applied to the AVR.  The Dragon automatically matches this voltage for communication.  The Dragon's range is something like 1.8V - 5.5V ... that happens to be the same range as the AVR, so if your chip is running and it's not smoking the Dragon will match it's voltage.  

Next, click on the word 'Fuses' on the left.  You'll see some really odd looking fuse names that will be greyed out and have yellow question marks beside them until the Dragon finishes reading their current status from the AVR.  Once they show up and each one has a green check beside it you're ready to change the fuse bits...  see? this is easy!  In the early days of microcontrollers, the fuse bits were actually fuses, and if you blew the wrong one by mistake -- you blew it.  Now, it's not as bad, but you can still make your chip quit working pretty easily.  For example, if you do not yet have the crystal oscillator installed on the breadboard and you change the clock source to the external crystal - guess what?  You can no longer read from or write to the chip! Until you install the crystal of course.  Another option if you mess up here (maybe you picked 'external crystal' and you don't actually have one! oops!) is to connect the chip to the Dragon in HVPP mode.  That will allow you to clear the things you shouldn't have set and get back to work.  So, explore a little here, but be careful.  If you hover your mouse over the fuse names you'll get a sometimes inadequate description of the properties that it changes.

One more note on fuses before we change one.  This screen has pretty check boxes and drop down menus.  In actuality, these are settings that are represented by bits in a register buried inside the AVR.  Normally a set bit is a 1 and a cleared bit is a 0, but the fuse bits are opposite.  To set a fuse bit is to make it a 0 -- It's just old terminology held over from the days when they were actually fuses that you had to blow.  Setting the bit meant blowing the fuse, or removing the 5V making it 0.  In nearly every other context, setting a bit means making it a 1.

Ok, on to making this thing use the crystal.  The last fuse in the list is the one we need here: SUT_CKSEL.  This stands for "Start Up Time - ClocK SELect.  Pull down the drop down menu and look at some of the options.  The ones beginning with 'INTRCOSC' are all the modes that use the chips internal RC oscillator as the clock source.  Everything else requires some circuitry outside the chip to work.  You'll notice some that begin 'EXTLOFXTAL' and 'EXTFSXTAL' and 'EXTXOSC'.  These are the different options for using the external crystal resonator.  'EXTXOSC' is low power mode for the crystal oscillator.  This mode is used to save power in battery powered applications that do not require the AVR to provide clock sources for other sevices.  'EXTLOFXTAL' is low frequency crystal mode optimized for 32.768kHz watch crystals.  "EXTFSXTAL' is Full Swing crystal mode, meaning that the pulses produced are of the maximum amplitude given the supply voltage.  This mode uses more power than the low power mode, but is suitable to pass through to other devices that require a clock.

The clock source setting we will be using is shown in the last pic on this step:  EXTXOSC_8MHZ_xx_1KCK_14CK_65MS. So, What does all that other stuff mean?  In a nutshell, it says this:  External crystal oscillator, 8MHZ or greater, 1000 clock cycles start up time, and wait 14 clock cycles plus wait 65MS longer after reset. 

********** After writing this I learned that when using a crystal oscillator, the start up time should be longer to give the oscillator more time to come up to Vcc and stabilize.  Therefore, a better setting for this fuse would be:  EXTXOSC_8MHZ_xx_16KCK_14CK_65MS.  The circuit still works with either setting. *********************

This application is not sensitive to anything except the 8MHZ or greater part, but benefits from the 65MS wait, so click on that one to select it.  Now the green check beside 'SUT_CKSEL' has changed to a yellow exclamation point!  This indicates that there has been a change made that has not been programmed yet.  Go ahead and click program to write the new clock source to the chip (hope you have your crystal installed!)  After the Dragon blinks some more the green check mark will return and the change has been made.  You can click verify if you want to.  It will just read the status of the fuse bits again.  You can test the change, if you want to play, by removing the crystal and trying to verify.  You'll get an error message and you won't be able to do anything again until you re-install the crystal.

That's it for setting up the AVR.  In the next steps we will be looking at and building the remainder of the circuit on the breadboard, and then later, we'll start with the AVR's software.  Fun stuff to look forward to!









Step 4: Adding More Circuitry

We'll put ATMEL Studio aside for a while and get back to the circuit.  This step will be in several short parts with a description of the various circuit sections and a simplified schematic to go with each.  The power supply and programming connections were covered in step two, so I won't be going over that part again.

A short note before we continue:  The pins we will be using have several functions that they are capable of performing for us.  On the schematic, the letters and number on the outside of the box representing the chip are the generic pin names.  PB1, for example, stands for Port B pin 1.  The I/O pins are grouped into ports and can be used individually or in groups of up to the port size.  Port C is the oddball with only 7 pins, and port B has several of its pins being used as programming pins.  Remember that we can reuse all but the reset pin though, so only PC6 is off limits.  Also, PB6 and PB7 are used as the crystal oscillator pins, so those are off limits too, but only when you need to use an external oscillator.

Let's start with the sensor inputs.

Looking at the first pic, you can see that the Q1 and Q2 circuits are identical.  The sensors provide a 12VDC signal when they are active.  This is too much to send to an input pin directly, so I used these transistor switches to detect when the sensor is active and pull the input pin to ground.    

D4 and D5 are just there to make lining up the sensors easier.  I'm using a photo sensor with a reflector to bounce the beam back, so when they are lined up correctly the LEDs turn off and the input pins go high.  The software we will get to later will watch for these inputs to go low to indicate the passing of the target object.  Resistors R2 and R3 are pull-up resistors on these input pins to prevent false triggering from noise.  The AVR has internal pull-up resistors that you can enable for each input, but i found that they were not enabled fast enough at power up, and an interrupt was triggered right away every time.  The external pull-up resistors prevent this.  R14 and R15 were added late in the development to prevent triggering the interrupts by touching the wires in the circuit.

Have a look at the functions of the two pins connected to the transistors.  You'll see that they both have PCINT(number) and INT(number.)  PCINT stands for Pin Change INTerrupt and, if enabled, will trigger an interrupt every time the pin changes state from 0>>1 OR 1>>0.  That's good for some applications, but not for this one.  The other common function, INT0/INT1, means External Interrupt 0 and 1.  These are the only two interrupts of this kind on the chip, and they can be configured to trigger on the positive going OR the negative going edge of a pulse... That's perfect for our needs!  We want to trigger as soon as the pin goes from 5V to ground only; we don't care when the pin goes high again (unless you want to try to measure the length of the passing object, but that's another project!)

I know I promised more about interrupts... be patient.  For now, it's enough to know that when an interrupt is triggered the program stops what it is doing and executes the code we write for that interrupt.  After executing that code, the program goes back to what it was doing before like nothing ever happened.  We will be configuring the interrupts in software, so more about that in the software section, including some pitfalls to watch out for!

That's it for the sensor inputs!   So, get these additions on the breadboard and then we'll move on to some more I/O connections...

In the second pic now, there's our first output! R4 and D2 make the 'ready' light that indicates that the program is ready to take the next measurement.  This was a late addition after i had some problems with the second sensor triggering multiple times during a single measurement.  Now each sensor can trigger only once when the measurement is taken and then not again until some time passes.  After the time passes, the ready light comes on and the next measurement can take place.  SW4 was also a late addition.  Once the distance between the sensors is set (see next section), i wanted a way to be able to verify what value the program was using for that distance in the math.  Pressing this button causes that set point to be displayed on the output displays.

Ok, SW3.  This is a DIP package of 8 switches i used to get some data from the user into the chip at run time.  6 switches are used and they each connect to an input pin with the internal pull-up resistor enabled (we will see how to enable them later in software.)  The first 4 switches represent the distance between the sensors in feet.  The range is 1 - 16 feet in one foot increments (0000 = 16.)  Originally, the range was 1 - 15 and 0000 was 0, but that caused a divide-by-zero condition.  For that reason, all 4 inputs low equals 16.  Notice that the 4 inputs are not on consecutive input pins.  That poses a bit of a problem when they're supposed to be a 4-bit binary number.  Because of this, we will have to do a little bit manipulation in the software to get the 4 bits back together before they can be used as a binary value.  The last two switches of SW3 are two more inputs to allow the user to choose their preferred output units.  You can see the note about this on the schematic.  The possible output units are miles/hr, kilometers/hr, meters/sec, and feet/sec.  It's possible to use any units you want of course.  If you like furlongs/fortnight, you can do that just as easily by changing the arithmetic in the program! More goodies to look forward to! 

Pic 3 shows the 4 bit BCD (Binary Coded Decimal; just a decimal number represented by 4 bits.  Brush up on your BCD here: http://en.wikipedia.org/wiki/Binary-coded_decimal)  output to the BCD to 7-segment decoder, the 74LS48.    PortB0 - 3 are nicely grouped together making it easy to send them to the decoder's BCD inputs without any formatting.  The decoders pins labeled *capitol* A,B,C, and D are the BCD inputs.  So, once we have our number, we can just send it in binary out on portB and let the decoder do the rest.  SW1 is optional, and just tells the decoder to light up everything on the displays for testing purposes.  Vcc and ground should be self explanatory here, and we'll get to *lowercase* a,b,c,d,e, and f next... One last thing about the decoder.  The /LT pin we're using for the "lamp test" obviously, but what are those other two weird pins? /BI-/RBO is not-blanking input and not-ripple blanking output (The bar over the name is a logical 'not' meaning active low like the reset pin on the AVR.)  There are details about how these pins work in the datasheet, but if nothing else, the blanking input must not be allowed to go to ground or the displays will be blank! So i just connected it to the +5v supply.

Pic 4 looks more complicated than it actually is.  Each 7-segment display has each of its anode (positive) connections labeled lowercase  a - f.  The three displays just daisy chain together so that all the a's are tied together and all the b's are tied together and so on... Then they all connect to the corresponding pin on the decoder so that any data from the decoder goes to all three of the displays at the same time.  You might be wondering how you would display a three digit number that had different digits!  That is the purpose of Q3, Q4, and Q5.  Each display also requires a cathode (negative) connection to ground to be able to light up. So, if we can control which display is grounded AND control which digit the decoder is sending out, we can cycle through the three digits and three displays really fast to display a different digit on each one!  This process is called multiplexing.  The use of the 7-segment decoder and multiplexing reduces the number of output pins needed from 21 to only 7!  PortC0 - 2 are used to control the transistor switches that ground the displays.  This part is fun in software! I can't wait!

The last pic shows the completed circuit.  If you haven't already, breadboard it carefully and get ready to get back to ATMEL Studio...



Step 5: Intro to ATMEL Studio IDE

It's time to get back into ATMEL Studio and create the project.  We will be writing the software in C.  If you don't know anything about C yet, that's ok.  You can copy the code, or I will make it available to download if you want to take the easy road.  I'll also explain the code in sections similar to the way I explained the schematic.  Also, "C Programming for Microcontrollers" Is an excellent resource for beginners learning to program AVRs.  The PDF can be downloaded here:  http://www.4shared.com/office/2y4F0nMo/c_programming_for_microcontrol.html

Go back into ATMEL Studio 6, and on the start page, click 'new project'.  On the left of the window that opens, click C/C++ because we'll be writing our code in C.  Then in the center of the window, choose 'GCC C Executable Project' (see pic 2.)  GCC is the C compiler that will translate our code into a HEX file the AVR can understand.  Type a name for the project and choose a location, then click 'OK.'  Then another new window appears asking you to choose the device you're using (pic 3.)  Choose ATMega328P and before clicking ok, look on the right of the window.  Here you can download the device's datasheet and learn about supported tools.  Click 'OK' and we will finally see the ATMEL Studio IDE (integrated development environment - pic 4.)

On the right side you'll find the 'solution explorer' window.  This is handy for viewing the files used in your project.  The big window in the middle is the *.C file already open for you to start writing the code.  That's really all we need to get this project going, but you should go to the help menu and look around.  It will take you to ATMEL's site where you will find a ton of information about using this software.  In particular, look up some info about the AVR simulator.  It's basically a software version of an AVR chip that can test your program and show you many run-time states of the chip.  It's pretty cool, but I won't be covering it here since we don't really need it for this project.

In the main window, there'll be some green text with the project name, date, and author stuff at the top.  Every bit of text in green is a comment.  Anytime you type a "//" everything that follows on that line will be a comment.  OR, you can begin a comment with "/*" and end it later with "*/".  That way you can have comments that take up several lines very easily.  It's also useful during debugging to 'comment out' troublesome lines of code to test without them and easily 'uncomment' them later.

The '#include' instruction is the first real code we see.  It instructs the compiler to include (imagine that!) the file(s) that comes after the '#include' statement.  In this case <avr/io.h> is a hex file that gives the compiler specific information about the various AVR chips we can use.

int main(void) - this is the main program body, well, the start of it anyway.  "Main()'" is actually a function (more about functions later) that must be in every program and contains everything in our program.  Other functions can be called from within Main() allowing the flow of the program to leave Main(), but after the other function has completed its job, it must return to Main() to continue.

while(1) is an infinite loop where our main code will run over and over and over.  The '1' is the condition for the loop to run and can also be an expression like:  while(a == 1)  in which case the loop will continue until "a" doesn't equal one anymore.  We want our loop to run forever, so while(1) will do that for us.  The compiler looks at the expression in parenthesis after the 'while' and evaluates it as either being true or false.  Based on that, the loop will continue if true.  If we want it to run forever, we skip the expression and just tell the compiler that it is true all the time by putting a '1'.

*** in programming of this type, there are many ways to say basically the same thing in different contexts.  EX.  '1' is 'logical high' is 'true' is 'set' and '0' is 'logical low' is 'false' is 'clear'... it can get a little confusing, but... well, it's just confusing.  --- reference that PDF book I linked to above "C Programming for Microcontrollers."  It has all the info you need to make some sense out of C. ***

Did you notice the curly braces  "{  }" ?  These are how we tell the compiler to treat all the lines of code contained within the curly braces as a single block to be executed together.  For example, the while(1) loop will continuously execute all the instructions contained within the curly braces immediately following while(1).  If there were no curly braces, the while loop would only execute the first instruction after while(1) over and over.  Pay close attention to these curly braces.  every time there's an open curly brace ' { ', there must be a close curly brace ' } 'associated with it.  If you miss a close curly brace, the compiler will look for the next close curly brace that it finds and execute everything between the two as a block... Leaving one of these out is a good way to find yourself spending time scratching your head trying to figure out why your program won't do what you think it should!  


while(1){thisishowthecompilerseeswhatyoutype;it'shardforapersontoread,butthecompilerlikesit;}


That was hard to read wasn't it?  That's why the while(1) AND its associated curly braces are tabbed over to the right.  When the curly braces and the block they contain are tabbed over, it is much easier to identify the code as a block.  This formatting does nothing for the compiler; it's only to make the code easier for humans to read.  You can put extra spaces, tabs, and line breaks all you want; they're invisible to the compiler.  It's a good habit to format your code this way and to add lots of comments.  A year from now you'll never remember what you were thinking when you wrote that new function, and the comments/formatting will help bring you back to speed faster. 

Now we're ready to start adding our own code to the program.  In the next step we'll look at the completed code in sections to make understanding it as easy as possible.


Step 6: The Code

Now to the brains of the project! The code.

I'll paste the instructions here a section at a time and briefly explain what they do.  Remember "C programming for Microcontrollers" for a more in-depth tutorial explaining the use of operators and such.  The ATMega328P datasheet also is a good reference for learning the purpose of a register or bit.  I'll try to keep this part short, but informative.  Learning C is a project all by itself, so don't get discouraged if you get completely lost at first!  I know I did.

The actual code on this page will be in bold to make it easier to discern from text... Comments will be italicized. 


/*
* Speed_measurement.c
*
* Created: 9/15/2012 8:50:23 PM
*  Author: Thomas L
*/



#define F_CPU 14.7456E6 

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


These first few lines just give the compiler some information about what files it needs and the CPU clock speed.  We will need to use some delays later to make the AVR just wait, so we have to include the util/delay.h file.  This file contains the actual delay functions that we'll call in the main program.  The delay functions deliver a timed delay, so they require us to provide the CPU clock speed.  The #define F_CPU tells the compiler the speed of the crystal oscillator, and this number is then provided for all the functions that need to use it.





/************************************************************************/
/*                               declare global variables                                          */
/************************************************************************/

unsigned int result = 0;
int interruptcount = 0;
long int time = 0;
long int resetcount = 0;


These are global variable declarations.  If we want to assign a value to a variable, we have to assign the variable a type first.  For example 'int' is for integer and is a whole number represented by 16 bits (15 number bits and a sign bit.)  'Unsigned int' is also an integer, but with no sign bit.  This means it can only be positive, but it can be twice as large because of the extra number bit.  These variables are needed in the main() function as well as the interrupt routines, so I declared them here before the main() function begins.  This makes these variables global, meaning that they can be accessed and changed by any part of the program.

Also, at this point it's important to note, EVERY EXPRESSION MUST END WITH A SEMICOLON!!! I forget these all the time, and the compiler gives me errors... all the time.





int main(void)
{

DDRD = 0x00;    
              //portd input for external interrupts
DDRC = 0xDF;                   //portc output for 7 segment multiplexing and 1 input for distance display
DDRB = 0xFF;                  //portb output fot 7 segment data in bcd
PORTD |= 0xFF;                //enable portd pull up resistors
 //(int0 and int1 require external pull up resistors or 1 interrupt will be triggered at reset)

Now we have the main() function.  This is the start of the actual program, and it begins by configuring a few things.  The DDRn statements above assign values to the Data Direction Registers for the 3 ports.  These 8-bit registers configure the I/O pins as either inputs or outputs.  A 1 in a bit position will make the corresponding pin an output, and a 0 would make it an input. The values assigned can be in decimal, binary, or hexadecimal.  Decimal numbers are written normally, like 255.  Binary and hex have a prefix to tell the compiler what kind of number it is.  Binary would be 0b11111111. Hex would be 0xFF.  All three of these are equal to 255, and the compiler doesn't care which you use.  Hex is normally the number system of choice because it's easier to read than binary, and easier to relate to binary than decimal... The binary equivelant of the number assigned to any DDR is 8 bits representing the data direction of the 8 pins of the port.  The PORTD |= FF; assignment enables the pull-up resistors on those input pins.  If PORTD were configured as an output, then the assignment would change the output states of those pins

Learn your HEX to binary conversions and commit them to memory! It comes in handy...

***For a HEX tutorial:   http://www.microbuilder.eu/Tutorials/Fundamentals/Hexadecimal.aspx ***





EICRA |= (1<<ISC11)|(1<<ISC01);                  //configure external interrupts
EIMSK |= (1<<INT1)|(1<<INT0);   
TCCR1B |= (1<<CS12);                                    
//set prescaling for timer1 256

This the part where I usually have to pull out the datasheet for the AVR and do a little digging.  EICRA is the External Interrupt Control Register A.  This register has 4 bits we can set to configure how interrupts are triggered by the external interrupt pins INT0 and INT1.  Each of the 4 bits has a unique name to identify it when setting or clearing bits.  ISC00 and ISC01 are Interrupt sense control for INT0.  These set the type of change required on the INT0 pin to trigger the interrupt.  ISC10 and ISC11 are the same control for the INT1 pin.  Checking the datasheet will show that by setting ISC01 and ISC11, we configured both interrupt pins to trigger on the negative going edge only.  The expression (1<<ISC11) is a bit shift operator to set the ISC11 bit.  See "C programming for Microcontrollers" to learn all about operators and bit manipulation; it's a little too much to go into here.

The EIMSK (External Interrupt MaSK) register has two bits for enabling or disabling the external interrupts.  Both of these bits must be set or the interrupt routines will never run.

TCCR1B  (Timer/Counter Control Register 1 B) has several bits that can configure how the timer behaves.  All we need to worry about here is the prescaling value.  This just means, how many CPU clock pulses will be counted before the timer is incremented by 1.   In this case, I chose a prescale value of 256, represented by setting the CS12 bit (check the datasheet.)  This divides the CPU clock by 256 before applying it to the timer/counter helping to prevent counter overflows in the time frame we are trying to measure.



/************************************************************************/
/*                 declare variables for calculation and display                    */
/************************************************************************/


   unsigned int ones = 0;
   unsigned int tens = 0;
   unsigned int hundreds = 0;
   unsigned int x = 0;
   double ticsfoot = 0;
   double fps = 0;
   double fph = 0;
   double mph = 0;
   double kph = 0;
   double mps = 0;
   int distance = 0;


sei();       
//enable global interrupts

Ok, an easy section! This is just some more variable declarations and that sei() thing.  "sei()" is a command known to the compiler to mean 'enable global interrupts.'  No interrupts will trigger if this bit is not set.  "cli()" clears the bit disabling global interrupts.  We'll use that one later to prevent problems.  





while(1)
{

/************************************************************************/
/*                   get sensor distance in feet from pind 0,1,4,5                   */
/************************************************************************/


  int distanceinput = (~PIND & 0x33);
  int hibits = (distanceinput >> 2);                                
//getting pind bits 0,1 and 4,5 together to be
  int lobits = (distanceinput & 0x03);                           //distance value in BCD.  bits 2,3 are the
  distance = (hibits + lobits);                                          //ext interrupt pins already in use.

  if (distance == 0) distance = 16;

This little section reads the status of the 4 dip switches that allow the user to choose the distance between the sensors.  There's some bit manipulation going on here because the 4 inputs are not consecutive.  The variables 'hibits' and 'lobits' are just temporary storage places to sort out the bits.  The first line isolates the bits we want with the & operator.  The second line shifts the bits over 2 places the get the hi bits in the correct position.  The third line isolates only the lo bits.  Then in the fourth line the lo and hi bits are added to make them one number.  The 'if' statement there at the end prevents the user from choosing '0' and causing a 'divide-by-zero' condition later.




/************************************************************************/
/*                                    'ready' indicator LED                                           */
/************************************************************************/ 


  if (interruptcount == 0)
  {
   PORTC |= (1<<3);
  }
  else
  {
   PORTC &= (0<<3);
  }


This section finds out if there is a measurement in progress, and lights the ready LED if not.  The interruptcount variable is incremented in the interrupt routines to help the program keep track of what part of the measurement is currently taking place.  We'll see the interrupt routines near the end of the code.





/************************************************************************/
/*                           calculations to find speed in 4 units                          */
/************************************************************************/




  if (interruptcount == 2)                            //only calculate when both interrupts have occurred
  {
    cli();                                                            
//disable global interrupts

    ticsfoot = (time / distance);                    
//distance is distance between sensors in feet - ticsfoot is counter tics/foot
    fps = (57600 / ticsfoot);                           //57600 is counter tics/sec (cpu clk/prescaler)
    fph = (fps * 60 * 60);
    mph = (fph / 5280);
    kph = (mph * 1.609344);
    mps = (fps * 0.3048);

    EIMSK |= (1<<INT1)|(1<<INT0);      
    sei();                                                            
//re-enable external interrupts and global interrupts
  }

If the interruptcount variable has incremented twice (this happens in the interrupt routines later,) that means that both interrupts have occurred and the measured value is ready to be used in the calculations.  Here we see the cli() instruction that disables the interrupts.  It's good practice to do this when reading a variable that an interrupt has the ability to change.  Why?  The normal 'int' variables, for example, are 16 bits long, or two bytes.  The AVR is an 8 bit system, so it takes two instructions to read the 2 bytes of a single value.  It is possible, then, for an interrupt to occur after the first byte is read, but before the second. If you try to read a variable but the interrupt occurs after you read the first byte, The interrupt routine can change the value of the variable before it's finished being read!  The program will resume where it left off and read the second byte of the variable with data that doesn't match the first byte!  Then the value you have loaded is not what is expected and can cause problems.

The math here isn't too complicated.  The comments should be enough to figure out what is going on.  This is where we use the distance variable from the dip switches.  The time variable comes from the interrupt routines later.

The first interrupt disables itself in its routine so that it can only run once before the measurement is complete.  That's why the EIMSK instruction is there.  It re-enables both external interrupts so that whichever one was first and got disabled will be enabled again.




/************************************************************************/
/*                                   choose output options                                         */
/************************************************************************/


   if (!(PIND & (1<<PIND6)) && (PIND & (1<<PIND7)))                      //choose feet/sec
   { 
    round(fps);
    result = fps;
   }
    else if (PIND & (1<<PIND6) && !(PIND & (1<<PIND7)))                
//choose meters/sec
    {
     round(mps);
     result = mps;
    }
     else if (PIND & (1<<PIND6) && (PIND & (1<<PIND7)))                
 //choose kilometers/hr
     {
      round(kph);
      result = kph;
     }
      else                                                                                                            
//default miles/hr
    {
       round(mph);
       result = mph;
      }


  if (result >= 999) result = 999;


 This whole section is reading the last two dip switches to determine the desired output units.  Then whichever value is needed is stored in the result variable.  

The round() function is an easy way to get integer values as our output without introducing too much error.  This function is included in the math.h file that is already included in the program by the delay.h file that we included manually.  If you look to the solution explorer window on the right side in ATMEL Studio, you'll see the project name followed by "Dependencies" (labeled in the pic.)  After you build the solution for the first time (Click Build>>Build Solution,)  all of the files included in the program will be listed here.  You can then open the delay.h file that we included at the beginning of the code and have a look at it.  If you scroll through it , you should find the #include <math.h> statement near the top.  Farther down you'll find the _delay_ms() function that we call to create the 1ms delay later.

The 'if' at the end prevents trying to display a number that won't fit on our 3 displays.





/************************************************************************/
/*                   delay to stop multiple "2nd interrupt" triggers                 */
/*                        without delaying main code execution                        */
/************************************************************************/


  resetcount++;

  if ((resetcount >= 0x00FF) && (interruptcount >= 2))                      
 //resetcount upper limit determines delay
  {                                                                                                                      //before reset. 0x00FF approx. 3 sec
   interruptcount = 0;
   resetcount = 0;
  }


 This part is (sort of) a timer made out of the main while(1) loop.  Every time the program loops past this point, resetcount is incremented.  The calculations only occur if interruptcount is exactly 2; so, if the second interrupt occurs again it won't mess up the math.  If the interruptcount is 2 or more, nothing else can be measured until the resetcount increments up to 0x00FF giving about 3 seconds delay for the target object to leave the sensor area.  We can't use a normal delay here because we need the displays to be showing us the measured value.  When interruptcount is reset to 0, the ready LED is lit and the next measurement can be made.






/********************************************************************************/
/*                 display int result on 3 digit seven segment display                      */
/*  delay gives seven segment decoder time to decode and display digits */                                                                   
/********************************************************************************/


if (!(PINC & (1<<PINC5)))                                       //to display distance setting on display
  {                                                                                //only while button is pressed
  result = distance;
  }
else


This 'if' just watches the push button that is a request to have the distance set point shown on the displays.  The latest speed calculation is stored into result every time the output units section (above) is executed in the loop, so result only equals distance while the button is pushed.





hundreds = (result / 100);       //get 100's place digit
  x = (result % 100);
  PORTB = (0x00|hundreds);
  PORTC |= (1<<2);                                                  
 // write digit
  _delay_ms(1);
  PORTC &= (0<<2);

  tens = (x / 10);                                                        
   // get 10's place digit
  x = x % 10;
  PORTB = (0x00|tens);
  PORTC |= (1<<1);                                                      
 // write digit
  _delay_ms(1);
  PORTC &= (0<<1);

  ones = x;                                                                    
 // get 1's place digit
  PORTB = (0x00|ones);
  PORTC |= (1<<0);                                                        
// write digit
  _delay_ms(1);
  PORTC &= (0<<0);    
}   
}


Finally! to the display!  Now that 'result' equals a valid 3 digit decimal number, we need to break it up into single digits and send them to the displays in turn.  We'll get each digit and store it in a variable called ones, tens, or hundreds.  The hundreds place digit is easy; just divide by 100.  The variable is an 'int' type, so nothing after the decimal point is stored.  'x' is a temporary variable to store the next needed value.  The '%' operator does the division, but returns the remainder to 'x'.  This gives us the tens and ones digit to deal with.  Divide that by 10 and repeat for the ones.  That's an easy way to separate all your digits to go to the displays, and can very simply be expanded to work with larger numbers.

The PORTB instructions write the digits to the output pins going to the 7-segment decoder.  At nearly the same time, the PORTC instructions turn on the output to the corresponding display's ground transistor to light up the digit.  Then the delay holds the display on for 1ms before turning the transistor back off and moving on to the next digit.  Pretty cool. 

The closing curly braces end the While(1) loop code block and end the main() function code block.






/************************************************************************/
/*                                     sensor 1 interrupt                                                */
/************************************************************************/

ISR(INT0_vect)
{
if (interruptcount == 0)
{
  TCNT1 = 0x0000;                                              
   //reset counter to 0
  interruptcount++;                                               //increment interrupt count
  EIMSK &= (1<<INT1)|(0<<INT0);                      //disable INT0

}
else if (interruptcount == 1)
{
  time = TCNT1;                                    
//capture counter value
  interruptcount++;                               //increment interrupt count

}
else resetcount = 0;

}


/************************************************************************/
/*                                       sensor 2 interrupt                                              */
/************************************************************************/

ISR(INT1_vect)
{
if (interruptcount == 0)
{
  TCNT1 = 0x0000;                                                    
  //reset counter to 0
  interruptcount++;                                                     //increment interrupt count
  EIMSK &= (0<<INT1)|(1<<INT0);                            //disable INT1

}
else if (interruptcount == 1)
{
  time = TCNT1;                                                      
   //capture counter value
  interruptcount++;                                                  //increment interrupt count

}
else resetcount = 0;
}



Now, the two interrupt routines...  They're exactly the same except for one instruction, so I'll just talk about one.  

First, notice that the interrupt routines are outside the main() function.  The program completely stops what its doing, leaves the main() function, and executes the interrupt routine.  After the interrupt routine is complete, it returns to the main() function where it left off.

The first line, ISR(INT0_vect), is the name of the interrupt from the datasheet.  If the interrupt is enabled in the EIMSK register AND global interrupts are enabled, the program will jump to this line when the interrupt is triggered.

You'll see that the interrupt routines are basically made up of a compound 'IF ... ELSE IF' statement that only does a few things.  Usually, it's best to keep interrupt routines very short, changing only what must be changed and quickly getting back to the main() function.  Using the interruptcount variable to track the number of interrupts that have occurred allows us to make the routines identical, and also allows the sensors to 'see' target objects coming from either direction.

Whichever interrupt executes first resets the TCNT1 timer (the one we set the prescaler for in the beginning,) then increments the interruptcount variable, then disables itself so that it cannot be triggered extra times by an irregularly shaped target object.  

Since the first interrupt incremented interruptcount, the second one will run the next couple lines.  This time we capture the value in the timer TCNT1 and store it in the variable 'time' to be used in the calculations.  Then interruptcount is incremented again.  

With interruptcount now equal to 2, the calculations are performed and the resetcount is counting up to 0x00FF before it will reset everything for the next measurement.  If the second interrupt is triggered again for some reason, resetcount is set to 0 and the wait begins again.  This is how I prevented multiple triggers of the 2nd interrupt for as long as needed for the target object to be out of the way.

Whew, I hope that made at least a little sense... In the next step, we will load the program into our chip and test the breadboarded circuit!

Step 7: Building the Solution; Loading the Program.

Once all the code is written (actually you'd do this a lot earlier during development) you need to build the solution.  This is when the compiler translates all your code into a HEX file.  The HEX file is what actually gets loaded onto the chip and is the only thing the chip can understand.  If you've ever seen a HEX file, you know that it's completely UN-readable by humans, so thank the compiler.  The first pic shows the menu item to click: Build>>Build Solution.

At this point, you'll see a bunch of stuff go by in the output window at the bottom, and if there are any compiler errors you'll see them here also.  If you typed the code exactly as I have in the last step you shouldn't have any errors.  Just in case, I'll copy and paste the code in its entirety directly from my file at the end of this step.  I just figured out that I can upload files also, so I included a ZIP file with all the ATMEL Studio files.

At this point you should connect the Dragon to the USB port on you computer.

In the 2nd pic, on the upper right, you'll see "ISP on AVR Dragon" hilighted.  Click here to bring up a window to configure the tool settings just like when we set the fuse bits.  Make sure AVR Dragon is selected, and ISP for the interface, and check the ISP clock.  Below that, programming settings should already have "erase entire chip" selected and "preserve EEPROM" unchecked.  Power your breadboard circuit, connect the 6 pin ribbon cable from the dragon, and we're good to go.

Now to load the program to the chip.  You could click Tools>>device programming>>production file  and then browse to the file and program.  But! It is far easier to simply click Debug>>Start Without Debugging (3rd pic.)  This automatically recompiles the program and loads it to the chip without another word as long as there are no compile rerrors.

Thats it! If that worked you should have a functioning chronograph on a breadboard!  You can play with it by touching 12VDC to the sensor inputs (the transistor circuits; not the input pins) with a jumper wire.  Also, play with the DIP switches and test the 'display distance' button.  Once you get some number as an output, you can test the 'output units selection' DIP switches and verify the math.

Now would also be a pretty good time to make sure your sensors work correctly with the circuit.  I'm assuming that probably noone will use the same sensors that I have, so test yours now before we start thinking about etching a permanent board...better to make changes now while the circuit is free of solder.

Next, we'll have a look at creating the schematic and board layout with ExpressPCB...


*************************************CODE STARTS BELOW THIS LINE******************************************

/*
* Speed_measurement.c
*
* Created: 9/15/2012 8:50:23 PM
*  Author: Thomas L
*/


#define F_CPU 14.7456E6 

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>


/************************************************************************/
/*       declare global variables                    */
/************************************************************************/
unsigned int result = 0;
int interruptcount = 0;
long int time = 0;
long int resetcount = 0;






int main(void)
{

DDRD = 0x00;       //portd input for external interrupts
DDRC = 0xDF;       //portc output for 7 segment multiplexing and 1 input for distance display
DDRB = 0xFF;       //portb output fot 7 segment data in bcd
PORTD |= 0xFF;       //enable portd pull up resistors
           //(int0 and int1 require external pull up resistors or 1 interrupt will be triggered at reset)

EICRA |= (1<<ISC11)|(1<<ISC01);   //configure external interrupts
EIMSK |= (1<<INT1)|(1<<INT0);   //
TCCR1B |= (1<<CS12);     //set prescaling for timer1 256





/************************************************************************/
/*   declare variables for calculation and display               */
/************************************************************************/

   unsigned int ones = 0;
   unsigned int tens = 0;
   unsigned int hundreds = 0;
   unsigned int x = 0;
   double ticsfoot = 0;
   double fps = 0;
   double fph = 0;
   double mph = 0;
   double kph = 0;
   double mps = 0;
   int distance = 0;


sei();        //enable global interrupts




while(1)
{



/************************************************************************/
/*    get sensor distance in feet from pind 0,1,4,5           */
/************************************************************************/

  int distanceinput = (~PIND & 0x33);
  int hibits = (distanceinput >> 2);      //getting pind bits 0,1 and 4,5 together to be
  int lobits = (distanceinput & 0x03);     //distance value in BCD.  bits 2,3 are the
  distance = (hibits + lobits);       //ext interrupt pins already in use.

  if (distance == 0) distance = 16;



/************************************************************************/
/*        'ready' indicator LED                    */
/************************************************************************/ 

  if (interruptcount == 0)
  {
   PORTC |= (1<<3);
  }
  else
  {
   PORTC &= (0<<3);
  }



/************************************************************************/
/*                 calculations to find speed in 4 units                */
/************************************************************************/



  if (interruptcount == 2)         //only calculate when both interrupts have occurred   
  {
    cli();               //disable global interrupts

    ticsfoot = (time / distance);      //distance is distance between sensors in feet - ticsfoot is counter tics/foot
    fps = (57600 / ticsfoot);       //57600 is counter tics/sec (cpu clk/prescaler)
    fph = (fps * 60 * 60);
    mph = (fph / 5280);
    kph = (mph * 1.609344);
    mps = (fps * 0.3048);

    EIMSK |= (1<<INT1)|(1<<INT0);      //
    sei();            //re-enable external interrupts and global interrupts
  }







/************************************************************************/
/*                        choose output options                         */
/************************************************************************/

   if (!(PIND & (1<<PIND6)) && (PIND & (1<<PIND7)))     //choose feet/sec
   {
    round(fps);
    result = fps;
   }
    else if (PIND & (1<<PIND6) && !(PIND & (1<<PIND7)))    //choose meters/sec
    {
     round(mps);
     result = mps;
    }
     else if (PIND & (1<<PIND6) && (PIND & (1<<PIND7)))   //choose kilometers/hr
     {
      round(kph);
      result = kph;
     }
      else             //default miles/hr
      {
       round(mph);
       result = mph;
      }


  if (result >= 999) result = 999;







/************************************************************************/
/*              delay to stop multiple "2nd interrupt" triggers         */
/*                   without delaying main code execution               */
/************************************************************************/

  resetcount++;

  if ((resetcount >= 0x00FF) && (interruptcount >= 2))      //resetcount upper limit determines delay
  {                   //before reset. 0x00FF approx. 3 sec
   interruptcount = 0;
   resetcount = 0;
  }








/************************************************************************/
/*     display int result on 3 digit seven segment display        */
/*  delay gives seven segment decoder time to decode and display digits */                                                                   
/************************************************************************/

if (!(PINC & (1<<PINC5)))       //to display distance setting on display
  {            //only while button is pressed
  result = distance;
  }
else



  hundreds = (result / 100);      //get 100's place digit
  x = (result % 100);
  PORTB = (0x00|hundreds);
  PORTC |= (1<<2);        // write digit
  _delay_ms(1);
  PORTC &= (0<<2);

  tens = (x / 10);        // get 10's place digit
  x = x % 10;
  PORTB = (0x00|tens);
  PORTC |= (1<<1);        // write digit
  _delay_ms(1);
  PORTC &= (0<<1);

  ones = x;          // get 1's place digit
  PORTB = (0x00|ones);
  PORTC |= (1<<0);        // write digit
  _delay_ms(1);
  PORTC &= (0<<0);    


}   
}






/************************************************************************/
/*                        sensor 1 interrupt                            */
/************************************************************************/
ISR(INT0_vect)
{
if (interruptcount == 0)
{
  TCNT1 = 0x0000;          //reset counter to 0
  interruptcount++;         //increment interrupt count
  EIMSK &= (1<<INT1)|(0<<INT0);      //disable INT0

}
else if (interruptcount == 1)
{
  time = TCNT1;          //capture counter value
  interruptcount++;         //increment interrupt count

}
else resetcount = 0;

}

/************************************************************************/
/*                        sensor 2 interrupt                            */
/************************************************************************/
ISR(INT1_vect)
{
if (interruptcount == 0)
{
  TCNT1 = 0x0000;         //reset counter to 0
  interruptcount++;        //increment interrupt count
  EIMSK &= (0<<INT1)|(1<<INT0);     //disable INT1

}
else if (interruptcount == 1)
{
  time = TCNT1;         //capture counter value
  interruptcount++;        //increment interrupt count

}
else resetcount = 0;
}

Step 8: The Schematic and Board Layout

If you haven't already, you need to download and install a copy of ExpressPCB.  It is a free software that includes two programs.  ExpressSCH for creating a schematic of your circuit and ExpressPCB for creating the circuit board layout.  Once the schematic is complete, you can link that file to your layout file and the software will show you the points on the layout that should be connected together...pretty cool!  The company provides board manufacturing services also if you'd rather not play with acid... I like to play though.

The software you need can be downloaded here:  http://www.expresspcb.com/expresspcbhtm/download.htm

I'm not going to go into detail here because the quick start guides and help files are pretty good with these programs.  I suggest that you play with them, and at least get a feel for how they work for your next project.  I'll include images of the artwork I used to etch my board in the next step, so even if you can't get your own layout designed you can still etch the board for this project.

The main thing I want you to notice here is the many 'vias' on the board layout.  If you click View>>Options and then only select the top layer, you'll see several circuit paths in red.  These traces do not go directly to any components.  Every trace on this layer goes to a hole in the board that will make a connection to a trace on the other side of the board.  To make the connection, we'll have to insert a small piece of wire into the hole and solder both sides.  My first double-sided board didn't have any vias; the top side traces just went directly to the components.  I later found that soldering on the top side layer for some components was impossible because the component itself was in the way! ... oops, lesson learned.  So, the vias are placed so that ALL component soldering can be done on the bottom layer only! 

The .SCH and .PCB files are included in the ZIP archive below.  Also included are the custom component libraries that will be needed to open the files with the custom components I had to make when designing this project.  The directories for the custom component files can be found in program files>>ExpressPCB.

Playing with these programs is the best way to learn how they work.  Download the ZIP archive and save an extra copy of each to play with.  Drawing connections and traces is really very easy, and mastering this process will make etching future custom PCBs possible.

Step 9: Etching the Board

Time to etch this board! Almost done!

****First note -- Do not use the first and second images on this step to etch your board!  Use the images in the ZIP archive at the end of this step; they are the images that I am sure will be the correct size.  Better yet, use ExpressPCB to open and print the layers from the .PCB file.****

To etch this board you'll need 7 things minimum.  A double-sided pre-sensitized copper clad board, transparency film for laser printers, some positive developer, some ferric chloride, a plastic container a little larger than the board, a daylight compact fluorescent light bulb, and a piece of glass larger than the board (glass from a picture frame works well.)  You'll also need some tiny hardened drill bits to drill all the holes.  I'll cover bit sizes in the next step.

I bought the double sided pre-sensitized copper board and chemicals from http://www.minntechelec.com/.  They have many other prototyping supplies, but the board, positive developer, and ferric chloride etchant are the bare minimum.  The 6"X6" board is enough to do this project twice, and the smallest available quantity of the chemicals will be fine.

I ordered the transparency film from Office Max.

The 2nd pic shows the board and chemicals as they arrived from Minn tech, and the 3rd pic shows the transparency film.

Begin by printing 2 copies of the top AND bottom copper layers of the layout onto the transparency film.  A laser printer with quality of at least 600dpi is required.  I use 2 copies of each layer to double them up to get better filtering of the UV light during exposure.  I found that if I hold up just one layer to the light I can see a little bleeding through.  After printing, cut out the images leaving a little extra clear film around the edges.  Then line up the two top layer pieces as perfectly as possible and use a couple small pieces of tape to hold them together.  Line up and tape the two bottom layer pieces the same way.

Now the tricky part.  The top and bottom layers need to line up perfectly on opposite sides of the board so that all the holes line up correctly when drilling.  The best method I've found is using 'registration holes' drilled through the transparency film layers AND the copper board to use as alignment guides.  First, take the top and bottom layer pairs you just taped together and line them up.  Then, tape them together the same way you taped the individual layers before so that you now have 4 layers of film with all 4 of your layer images perfectly lined up and secured with tape.  Set this aside for now while we prepare the board.

The 6"x6" board will work well for this project if it is simply cut in half.  This leaves a little extra board around the edges, but preparation is easier and the registration holes I mentioned earlier can be outside the circuit area.  The board manufacturer lists some cutting methods in the instruction sheet that comes with the board.  You can try them if you have the equipment.  I marked the center line on both sides and scored the line many times with a razor knife.  Then, holding the board flat with the line on the edge of a table, snapped it in half.  Some light sanding on the rough edge afterward made it look very clean.  

***note:  The tiny fibers from the board material are extremely irritating to the respiratory system! You should wear a face mask in a well ventilated area when cutting or drilling these boards! ***

Once the board is cut to size, place the 4 lined up images on the board (all together on the same side) and tape them down.  Make sure all the circuit paths are completely on the board.  Then, take the smallest drill bit you have and drill two holes on opposite corners through all 4 image layers AND the board.  These are the registration holes we will use to line things up during exposure.  Once these holes are drilled, you can separate the images from the board, and separate the top and bottom layer images from each other.  Leave the duplicates together.

**************************************************************************************************************************************

Now it's time to expose the photo-resist layer of the board.  It's best to prepare the Positive Developer first.  Wearing protective gloves, mix one part developer with 10 parts water.  It won't take a lot; just enough to cover the board in a small plastic container.  An old tupperware like container is perfect for this.  I would guess about 20-30mL of developer to 200-300mL water should be plenty.  After it's mixed, place the lid on the container and put it in a safe place for now.

The exposure setup doesn't need to be complex (see 6th pic.)  Just a daylight CFL bulb clamped about 8" from a flat surface in an otherwise fairly dark area.  Get the light set up and then go steal the glass from that 8x10 picture frame protecting that picture of Grandma from 1992.  Grandma can have it back in about 20 minutes...

Now would be a good time to put the circuit images on opposite sides of the board and inspect it to make sure you know which way is up, front, etc.  After this next part, there's no turning back!

Darken the room as much as you can while still being able to see what you're doing.  You can use red LEDs if you like, but just low normal light is ok if you don't take too long.  Carefully peel back the protective film from the top layer of the copper board and put it flat on the table under the light bulb (that should currently be OFF.)  Then place the top layer image on the board and use the bit that was used to drill the holes to line the holes up perfectly (it helps to use 2 bits actually, or a pin or something else to fit in the registration holes.)

Once it is lined up as perfectly as possible, place the piece of glass on top and make sure the transparency film is totally flat against the board.  Look at a clock and turn on the light bulb for 8 minutes...........

When the time is up turn the light off, remove the top layer image, flip the board over and repeat for the bottom layer.

put your gloves back on at this point.
8 more minutes.................

This time, remove all the transparency film and take the board immediately to the container of developer you mixed earlier.  Submerge the board and agitate the liquid by gently sloshing it around.  You should very quickly see the circuit image begin to appear.  With gloves, grab the board and flip it over and agitate a little more.  when the image looks well defined and the copper is clearly visible (for me this happened fast, maybe 10 seconds) remove the board from the solution an rinse it with fresh water to stop the reaction.  Inspect the board for any photoresist that did not completely come off.  You can touch up with a cotton swab dipped in the developer solution, then re-rinse.  The developed board is shown in the 8th pic.

If you see any areas where the photoresist came off where it shouldn't have, you can fix it.  Use a fine tip sharpie permanent marker to draw in those areas.  The ink from the marker does a great job preventing the copper from being etched, so wherever you draw will remain copper after the acid etching.

Now take a similarly sized tupperware container and fill it with enough ferric chloride to submerge the board.  Still got your gloves on right?  This works a little faster if the acid is heated SLIGHTLY.  I fill the sink to about 2" with hot water from the tap and float the tupperware with the acid in it for a few minutes.  

Place the board in the acid and agitate just like you did with the developer.... keep going ... this part takes more like 20 minutes.  Flip the board occasionally.  Every few minutes, take the board out and (without getting drops of acid everywhere) hold it in front of a light and see if the bare copper areas are clear of copper yet.  With a back light, the bare board will be translucent enabling you to tell when the copper is gone.  When all the unwanted copper is gone, rinse in fresh water and inspect your work.  Remaining unwanted copper can be touched up with a cotton swab with a little ferric chloride on it just like the developer before.  Leaving the board in the acid too long will eventually etch away all the photoresist and all the copper, so don't take a break!  Monitor the entire etching process.

When the developing and etching is complete, store the remaining used chemical in a sealed plastic bottle.  Do not just pour this stuff down the drain!  It is terrible for the environment and for your plumbing.  Be sure to label the bottles with the contents and a warning about the corrosive properties.  Later you can find a local disposal facility to process it for you.  (By later I mean after you use the old stuff again... it isn't cashed yet)

You should now have a nicely etched image of both layers of the circuit on both sides of the board!  The greenish photoresist that remains on the traces can stay there (according to the board manufacturer) and serve to protect the copper from corrosion without the need to tin plate it.  Next we need to drill all the tiny holes and solder the components! Get your drill press ready...   





Step 10: Drilling and Soldering

The drilling is tedious work that takes a careful eye, but no special knowledge or technique.  A badly drilled hole can destroy one of the tiny traces and require repair with a jumper wire when you solder, so you must take your time.  You'll need at least a small drill press, or better, dremel drill press capable of higher speed.  I used a regular tabletop drill press set to the highest speed with no problems.  The bits you'll need probably won't be available at the hardware store.  For most of the holes, a 0.75mm or 0.8mm bit is needed.  For the larger holes, I selected the best matched bit size from the Dremel 628 precision bit set.  These bits range from 1/32" - 1/8" in 1/16" increments.  With this set and one (2 if you're prone to breaking small things) 0.8mm bit, you can get all the holes drilled.  My drill press and the 0.8mm bit can be seen in the 2nd and 3rd pics.

Keep in mind that dust from the board will accumulate.  This dust should ideally be vacuumed away while drilling.  Be sure to wear a dust mask to keep this stuff out of your nose, throat, and lungs!

The etching process created small copper free holes in the center of each pad on the board.  This marks the center of each hole, and  acts as a pilot hole helping to center your bit on the target.  Carefully drill each hole selecting the bit that best fits in the copper free hole in each pad.

When you're done, wipe the board clean and warm up you soldering iron... 

The first image shown on this step is called the mechanical drawing.  It shows only the components and how they fit onto the board.  ExpressPCB will generate this drawing for you automatically in the File>>Export menu.  The first thing to do is find all the holes that are NOT on this drawing.  These are the vias.  You must put a small piece of wire through each via and solder on both sides.  Once all the vias are soldered, you can use this drawing with the schematic to solder the components to the board.  If it's not obvious, the components insert from the top layer side and solder on the bottom layer side...

.... that's pretty much it I guess!  The next step will have the parts list.  I'll include another step after that dedicated to setting up and using this device.  

Step 11: Tools, Parts, and Materials

Below is a list of everything needed to complete this project with the exception of the software covered in earlier steps.


****************************************************************Tools**********************************************************

          Item                                                                               Digikey Part    

1. Computer with a USB port                                                   
2. Solderless breadboard                                                    922327-ND
3. AVR Dragon                                                                       ATAVRDRAGON-ND
4. 2x3 IDC connector  ribbon cable                                   MC Pros part         FC06P
5. Drill Press                                                                               
6. 0.8mm Drill bit                                  Midwest Circuit Technology Part        DB-0315-400
7. Dremel 628 precision bit set
8. Soldering iron and solder
9. Clamp light with 18W+ daylight CFL bulb
10. Glass from 8x10 picture frame
11. Plastic container (tupperware type)


*************************************************Parts*****************************************

          Part ID                           Value/Part NO                          QTY                Digikey PART

1. R1, R14, R15, R16                10KΩ                                       4                   10KQBK-ND
2. R2, R3, R6, R7                        4.7KΩ                                     4                    4.7KQBK-ND
3. R4                                              180Ω                                      1                    180QBK-ND
4. R5                                              680Ω                                      1                     680QBK-ND
5. R8, R9, R10                             1KΩ                                         3                    1.0KQBK-ND
6. R11, R12, R13                        100Ω                                       3                     100QBK-ND

7. C1                                              0.33uF                                    1                     P5349-ND
8. C2                                              1uF                                          1                     P10389TB-ND
9. C3, C4                                       22pF                                       2                     BC1005CT-ND

10. Q1, Q2, Q3, Q4, Q5              2N3904                                   5                    2N3904-APCT-ND

11. DSP1, DSP2, DSP3             SHD-A103                              3                    160-1573-5-ND

12. U1                                            LM7805                                  1                     LM7805CT-ND

13. D1                                          1N4004                                  1                    1N4004-E3/54GICT-ND
14. D2, D3, D4, D5                     3MM LED                                 4                     1080-1115-ND

15. IC1                                        74LS48                                  1     Futurlec part# 74LS48

16. uC1                                      ATMega328P                         1     ATMEGA328P-PU-ND

17. X1                                          14.7456MHZ CRYSTAL        1                      X175-ND

18. SW1, SW2, SW4                 TACTILE PUSHBUTTON      3                     450-1650-ND
19. SW3                                      DIP SWITCHE 8pos              1                      450-1417-ND

20. J1                                          JACK, WALL X-FORMER      1                      CP-202A
21. J2                                          TERMINAL BLOCK 1X6        1                      A97988-ND
22. ISP6                                     2X3 MALE HEADER (2.54)   1                      A26568-ND

23. DIP28 IC SOCKET                                                               1                      A100210-ND
24. DIP16 IC SOCKET                                                               1                      A100206-ND

25. 12VDC power adapter                                                         1                     salvaged

26.  Two sensors of your choice capable of detecting the target object, operating at 12VDC, and providing 12VDC as the signal indicating detection of the object.  The sensors I used are pricey, but anything can work if it meets the conditions listed in the previous sentence...



**********************************************Materials************************************************

                              ITEM                                                      QTY          MINN Tech product code

1. Double Sided Presensitized Copper Clad Board 6"x6"                 1                          650
2. Ferric Chloride Etchant                                                                  1                     415-500ML
3. Positive Photo Resist Developer                                                   1                     418-500M
4. Transparency Film                                                                          1                     416-T                               

Step 12: Setup and Operation

Done? Wanna measure how fast some fast stuff is going?  

The hardest part about setting this thing up is getting something to hold the sensors where they need to be.  I built some wooden stands that you can see in the pics, but anything stable that you can work out would be fine.  I thought about a couple of tripods, but I didn't have the extra money to buy even a second one (not to mention a 3rd and 4th one!)  That might be an option for you if tripods are already in your collection of goodies.

The wiring is simple.  The schematic shows the wiring in the same physical orientation as the actual terminals.  There's a 12V and a ground for each sensor, and the two terminals that go to the transistor switches are from the sensor's output signal.  The logic in the controller will be watching for the input pins to go from 5V to 0V to indicate a detection.  So, be sure to wire whatever sensor you use so that they send the 12V to the transistor switch while it is seeing the target object because turning the transistor 'on' pulls the input pin low.  My sensors can be wired either way so this was easy.  If you find that your sensors cannot work this way, then you can simply invert the logic in the chips software.  (I can help with that if you have trouble.  You'd have to change this line in the code: EICRA |= (1<<ISC11)|(1<<ISC01);  to change from triggering on the negative going edge to the positive going edge...)

Once the sensors are connected, decide how far apart you want them to be in feet.  Then using the first 4 dip switches, set that number in binary (0000 for 16 and 1000-1111 for 1-15.  The LSB is to the left; sorry about any confusion.)  Power the board and verify the distance is correct with the 'display distance' button (SW4; pic 2.)

Set the sensors at this distance.  Don't guess; use a measuring tape.  That way your measurement will be as accurate as possible.  At this point I like to wave my hand in front of the sensors just to see everything work.  Make a few slow passes to get low numbers and then fast to get bigger numbers.  If you go slow and get an unusually large number, the timer overflowed.  If this is about the speed of your target object then you should change the sensor distance to a lower number.  Bringing the sensors closer together allows you to measure slower objects without losing precision.

So, it's all powered, sensors are aligned, and distance is measured as accurately as possible .... let your target object go! hopefully the sensors were able to see it the first time out and you can now see the speed on the display.  Remember the last 2 dip switches?  If they're both off then the readout is in MPH.  Have a look at the table on the bottom of the schematic for the other output options.  Switch to KPH and do the math.  Was it right?


Step 13: Final Thoughts

This project was not originally going to be an Instructable, but it begged to be written as one.  From the start, I had a measurement that needed to be taken and I didn't have the money to buy a commercial solution (I DID spend money on this project, but only on things that i can reuse for other projects.)

The actual measurements that I wanted to take were completed when the circuit was still on the breadboard.  The PCB was not something I had planned to make, but decided later to try it as a learning project.  If you find yourself with a lot of sloppy looking perfboard circuits, it's well worth learning to etch your boards.  I found that it's not very hard, and the results are far superior to perfboards or point to point wiring.

Even if your circuit never makes it off the breadboard, I hope this Instructable was informative.  The first half dealing with only the schematic and the code could easily have been an -ible all by itself.  I felt that This project, in it's entirety, was a valuable learning experience for me, so I decided to present it in it's entirety in hopes that it will be valuable to someone else.

Oh, in case anyone was wondering...  The first homemade rocket i measured with this thing went 231MPH within it's allotted flight range of about 80ft.  And, now that this circuit's original purpose is done and over with, this board still sees bike racing action on my sidewalk from time to time.  It turned out to be a good way to introduce the kids to the world of electronics while making it seem less like a science and more like something fun.

Hack It! Contest

Participated in the
Hack It! Contest

Instructables Design Competition

Participated in the
Instructables Design Competition