Introduction: Attiny Chronograph
I made an air cannon last summer, and though it's fun to shoot nerf darts at inane speeds for hours, it's a little difficult to quantify exactly how fast "really frickin' fast" is without a high speed camera or professional speed measurey thing. To that end, I created this, my own speed measurer with only a dozen or so common parts.
Step 1: Theory
This project uses two infrared emitter-phototransistor pairs, spaced precisely three inches apart. By measuring the time it takes for the object to break each beam, the speed of the object can be calculated. It uses a crystal oscillator for stability, and 3x7 segment LED display for output. It can measure, in theory, up to about 10,000 ft/sec, but offers a resolution of 1 microsecond, which at 900 ft/sec is about 4 ft/sec, which is not bad considering that that's over the speed of a commercial airliner, with the resolution of someone powerwalking. At that point, calibration plays more a role in the resultant speed than anything else.
Step 2: Parts
1 x attiny 2313.
2 x phototransistor (I got them from Radioshack).
2 x IR Leds (ditto, they come in a pack with the transistors).
1 x at least 4in long x 1in PVC pipe. Bigger means you can use larger projectiles, but smaller ones are less consistent. longer pipes and further spacing of the gates means more accurate measurements, but requires that the measured object move faster, so that it passes through the center of the tube at the beginning and end.
1 x coupling, of the same size as the pipe above.
1 x reducer, from whatever the size of the barrel is that's firing the projectile to the size above.
1 x 4-conductor phone cord, a few feet long, or 12-20 feet of stranded core wire.
1 x 3 AA or AAA battery holder.
1 x 15~20mhz crystal. I used a 16.590mhz. The exact value will be corrected for later.
1 x 3x7 segment display. Either three individual displays, or one block of three.
1 x diode, really any diode will work. The lower the voltage drop the better.
1 x power button or switch.
2 x pulldown resistors. Anything above 10k and less that 10M should work fine.
Step 3: Pre-pre-code.
The very first thing to do is get an old version of arduino, 1.0.3 or earlier. You don't need to install it, as it can run just fine with no install (I'd advise keeping several versions of arduino on your computer, as many libraries and programs only run on certain versions). Now, follow the directions here: https://www.instructables.com/id/Arduino-ATtiny2313 You can use a breadboard instead of making a shield for it, since you only need to program it once, but I love attiny2313s, so I've made several for different versions of arduino (uno, mega, micro, etc.). Now, we're going to mess up your working code with weird, different stuff.
First, connect your oscillator to the proper pins on the attiny (4 and 5). It doesn't matter which way. I'd advise putting some female headers on the programming shield (if you made one), to make testing oscillators easier in the future, but you can just put it in the IC socket before inserting the 2313 too, there's usually enough room to fit both. Now go to your sketchbook, under "hardware", select the version of tiny that you have installed, and open "boards", then select "cores" and "tiny", and open, "wiring" and "tone" (or simply remove "tone" and place it in a different directory. We don't need it for this project). In each of these files, search for "16000000L". In boards, seach for ATtiny2313 @ 8 MHz. Change each of the 16000000L (there may be more than one in each file) to the speed of your oscillator (make sure to leave the "L"), and save the files, but keep them open as we may need to change this later. Now, go to http://www.engbedded.com/fusecalc/ if you want to set values yourself (which is very useful for other projects), or just use the values I used for this setup. (for a tutorial on how to use this, go to http://www.ladyada.net/learn/avr/fuses.html). The values I used are low:DE high:DB extended:FF These values should be put into the "boards" file, about 15 lines under the line "attiny2313at8.name=ATtiny2313 @ 8 MHz". There, you'll see three lines "attiny2313at8.bootloader.low_fuses=0xE4
attiny2313at8.bootloader.high_fuses=0x9F
attiny2313at8.bootloader.extended_fuses=0xFF". Replace the three values after each "0x" with the values from above. When finished, these lines should look like this:
attiny2313at8.bootloader.low_fuses=0xDE
attiny2313at8.bootloader.high_fuses=0xDB
attiny2313at8.bootloader.extended_fuses=0xFF
Now, change the value about 6 lines down that reads 8000000L to the value of your oscillator, making sure to leave the L. Finally, change the line you searched for a the beginning "attiny2313at8.name=ATtiny2313 @ 8 MHz" and change the part after the equals sign to whatever you want. This is what it will be named in the arduino IDE later. If you got through all of that, congrats! we're halfway to uploading code!.
Step 4: Pre-code.
At this point, exit and re-open the arduino IDE. You should now see under "board" a new choice next to "attiny2313 @ 1MHz" that is whatever you typed in at the end of the last step. If not, check your files and try again. Select that board. Make absolutely sure that your arduino is set up to upload code to the attiny (test it first with the standard 1MHz option first). Now, select the changed file, and click tools>burn bootloader. It should blink some LEDs and display stuff for a minute or so, then tell you it burned sucessfully. If not, make sure everything is hooked up and try again. (To make the attiny work normally after changing the bootloader, just select the 1MHz option and burn the bootloader again). Now, with the crystal installed, you should be able to download and run programs as you did before. Now comes tuning.
Find some clock source that is very accurate, such as http://time.gov/HTML5/ Load the following program onto your attiny.
void setup(){
pinMode(8,OUTPUT);
digitalWrite(8,HIGH);
delay(3600000);
digitalWrite(8,LOW);
}
void loop(){}
now, connect an LED to pin 8 (to ground through a resistor), turn it on and record exactly what time you did so. (the LED should come on as soon as power is connected). Wait ~50 minutes, then come back to the attiny. The LED should still be on. Watch it, and as soon as it turns off, record the time (it may be well under or over an hour). Now, convert the time that it took into seconds, and find this: ((speed of your crystal in Hz)*3600 )/ (# of seconds it took to turn off). Replace each of the "16000000L" and "8000000L" from the last step with this new number. Save each of the files, restart arduino, and re-burn the bootloader. Now, re-upload the code, and time it again. It should turn off with well under a minute of variance. If not, repeat the trial, doing the same thing as before until the value is satasfactory. You now have a calibrated clock, and we're ready to go.
Step 5: Tube It Up.
Now comes the second vital part to making this accurate. A drill press here really helps, but I got the first revision of this to work with just a hand drill. Using a 5mm bit (or 3/16, which is close enough), drill two holes EXACTLY 3" apart, or whatever number you selected, all the way through both sides of the pipe. If you have a caliper, they're very useful here. Before inserting the IR gates, measure the distance between the inside and outside of each set of holes, then average all four numbers. This is the distance between your holes. You need to do this before uploading the code to the attiny, as this number will be used in the code.
Step 6: Code. Finally.
Alright, now we're finally ready to load the actual code. Just copy and paste this code into the IDE. Then, go to the 13th line, where pgt is set to 250000. Change the 250000 to 1000000 / (12 / distance) where distance is the distance between holes from the last step. Do not use decimals, round to the nearest whole number.
boolean ts;
int value01 = 0;//the value in the tenths place
byte value10 = 0;//value in the tens place
unsigned long anaval;//value in the ones place
byte value = 0;// test value to be displayed
const int val = 40; //the delay
long temp;
boolean extra10;//these are just where we want the decimal
boolean extra1;
boolean extra;
unsigned long time;//the micros() value at the time of interrupt
unsigned long lasttime;
unsigned int pgt=250000; //the number for 3 inches between photogates. If this number is displayed, it means there was an error, and no time interval was recorded.
//calculated as 1000000/(12/distanceininches). gives output in ft/sec
//use no floats to save space (theres very little on the 2313)
void setup() {
DDRB = B11111111;//sets port B to output (most pins on the left side)
DDRD = B1110000;//sets the 3 pins that switch the digits to out, all others to in.
//DDRA = B000; if using an external oscilator, do NOT mess with port A, thats reset and oscillator.
PORTD=B0001111;
// PORTA=B000;
attachInterrupt(1,stv,RISING);//we only want to interrupt on the first gate, we don't care about the second yet.
}
void loop() {
istobig();//these three just output the number. The timing is done in the interrupts
divide();
sendout();
}
void stv(){//starts timing, and attaches the correct interrupt
time=micros();//if you change the code, this ALWAYS must be in the same order as stopv(). Otherwise, the difference between breaking the gate and recording the time will be different, and thus mess up the result.
attachInterrupt(0,stopv,RISING);//set one for the other interrupt pin
detachInterrupt(1);//and turn this one off, until the other interrpt is tripped
}
void stopv(){
lasttime=micros();//this must be the same operation, and in the same place as time=micros() above.
anaval=pgt/(abs(lasttime-time)/10);//do the math to get ft/sec
attachInterrupt(1,stv,RISING);//reattach the interrupt to start the timer,
detachInterrupt(0); //and disconnect this one.
}
void istobig(){//checks if the number is too big. If it is, it reduces it accordingly.(by a factor of 10);
temp = anaval;
if (temp >1000){
extra = 1;
temp = temp/10;
}else{
extra = 0;
}
if (temp >1000){
extra1 = 1;
temp = temp/10;
}else{
extra1 = 0;
}
if (temp >1000){
extra10 = 1;
temp = temp/10;
}else{
extra10 = 0;
}}
void divide(){
value01 = temp;//sets tenths between 0 and 900
value = temp/10;//sets ones between 0 and 90
value10 = temp/100;//sets tens between 0 and 9
value = int(value);//rounds the variables
value10 = int(value10);
value01 = int(value01);
value01 = value01 - (value * 10);//removes all but the tenths-place digit
value = value - (value10 * 10);//removes all but the ones-place digit
}
void sendout(){
extra*=4;
PORTD=B0001111;
temp=value01;
output();
PORTD=B1001111;
delayMicroseconds(val);
extra=extra1*4;
PORTD=B0001111;
temp=value;
output();
PORTD=B0101111;
delayMicroseconds(val);
extra=extra10*4;
PORTD=B0001111;
temp=value10;
output();
PORTD=B0011111;
//delayMicroseconds(val2);
//no delay needed, the math takes long enough
}
void output(){//sets portB based on the number to be displayed
switch(temp){
case 0:
PORTB = B00000101 - extra; //extra adds a decimal point.
break;
case 1:
PORTB = B11011101 - extra;
break;
case 2:
PORTB = B01000110 - extra;
break;
case 3:
PORTB = B01010100 - extra;
break;
case 4:
PORTB = B10011100 - extra;
break;
case 5:
PORTB = B00110100 - extra;
break;
case 6:
PORTB = B00100100 - extra;
break;
case 7:
PORTB = B01011101 - extra;
break;
case 8:
PORTB = B00000100 - extra;
break;
case 9:
PORTB = B00011100 - extra;
break;
}}
Step 7: Electrical.
Now that the code is loaded, we need to start making connections. Looking at the bottom of the code in the previous step, connect the pins of port B on the attiny to the 8 connections of your display, keeping in mind that 0 means it's active, and the third pin (pin 2) is the decimal point. Since all displays are different, this will change with each one. pins 7,6, and 5 on port D go to the right, middle, and left displays, accordingly. Connect the oscillator to pins 4 and 5, as well as power, and two of the wires in the phone cord to pins 6 and 7. Add the pulldown resistors between these two pins and ground. Connect the two remaining phone cord wires to positive and ground. Now, apply power to it. The display should read 000. If you connect the two wires from pins 6 and 7 to +V, one after the other, quickly, you should see the number on the far right (and possibly the middle number if you're really fast) change value. If so, then add the diode in series with the whole circuit (connected to +V on the attiny and phone cord, with the band facing towards it), and connect the batteries and power switch.
Finally, connect the IR leds to power with the proper resistor (I used a 1K, but your LEDs may be different), and the phototransistors between the two "signal" lines on the phone cord, and V+. Taping the LEDs and transistors to a table facing each other in two pairs (one LED and one transistor per pair), and breaking the two in succession quickly should, again, cause the displayed number to change. If that works, the wiring is done.
Step 8: Final Assembly.
We're in the home stretch now. Put and glue the IR LEDs and phototransistors into the holes in the tube we made earlier, testing frequently to make sure that it still works. Once all four are in and glued, mark which way produces a number when the gates are broken (only one way should work). Now, add the coupling and reducer to the input end, and wrap the while thing thuroughly in electrical tape or duct tape. Test it again, and put the attiny, batteries and display in its own box, leaving a few feet of wire between the two. If it works now, you're done! A basic test is to drop items through it from various heights. Each time you increase the height, speed should increase accordingly. With a 1-1/4 pipe, this works well with nerf darts, especially when fired from an air cannon using a 1/2" PVC barrel (which works very well with nerf darts), though a standard nerf gun works as well. In theory, this should even work with guns, though the device may be damaged by the muzzle flash, or pressure wave. Have fun, and remember not to look into the operational end of the device.