Nerfo meter - measuring the speed of Nerf darts - final setup - Page 1
Forum section: electronics / projects

author: HendriXML
author_rank: Super Contributor
timestamp: August 24, 2019, 11:12:46 pm
content: In the final week of the summer holidays I would like to do some scientific experimenting and constructing together with my son (9).The goal is to measure the speed of Nerf darts. The setup will contain 2 laser beam light ports. The frame will be 3d printed in one piece.On the right side 2 pcb's (green rectangles) will be vertically mounted with the photodiodes (blue circles) on the backside.At first the change of exposure is measured using an oscilloscoop. With that it should be possible to do very precise time determinations. Maybe in the future a dedicated timer and Arduino with some user interface will do the timing. So the signalling part of the schematic can be implemented later on. But it is better to already save some space for it.About the schema:The component values are chosen mostly by guessing. They will be calculated later on.The laserdiode emission is automatically regulated, so it should be possible to react fast on only a 10% drop of exposure. (The photo diode will be in a "tunnel"). Another reason is to not get the U1B op amp in positive saturation.The transition from exposed to non exposed should be optimal in speed. Going from non exposed to exposed is less important.Feel free to comment!

author: Kleinstein
author_rank: Super Contributor
timestamp: August 25, 2019, 07:53:23 am
content: For fast reaction the photo-diode should be operated with some bias voltage (e.g. 3-5 V) in reverse direction. This reduces the diode capacitance - usually by something like a factor of 3 to 10 and makes the sensor correspondingly faster.The feedback path in the transimpedance-amplifier should not use a pot. The exact amplification should not matter that much. Pots tend to have more parasitic capacitance to ground and this can lead to instability. In addition they can pick up more stray signals / hum.For high speed the TIA gain should not be too large, as this slow down the TIA. Some 20 K is a common values for a fast TIA.The adjustment should be better done at the trigger level of the comparator. For use with a DSO, there is no need for the comparator - a series resistor at the OPs output would be a good idea to isolate it from capacitive load (cable). µCs tend to already include a comparator (the AVR internal one seems to be quite good), but often only one.For the simple TIA shown the good old LM393 should be fast enough.It is odd to use current regulation for the Laser(s): the modules normally just need a constant voltage, and bare laser diodes may need actual power regulation and extra optics. One might get away with bright LEDs (red or IR), if there is little background light.

author: HendriXML
author_rank: Super Contributor
timestamp: August 25, 2019, 09:53:46 am
content: Thanks for the reply!Quote from: Kleinstein on August 25, 2019, 07:53:23 amI should have placed the photo diode differently. The bias voltage is only 500 mV, I would like to keep it battery powered so I don't have a negative rail.Quote from: Kleinstein on August 25, 2019, 07:53:23 amThe pot will essentially determine the optical power of the laser diode. I will experiment with it to get 25% of the max current, and then replace it with a normal resistor. This capacitance to ground is also an issue with those 10 turn trim-pots?The comparator will collapse at 90% exposure relative to the (longer term) regulated one.Regulating the laser diode is done for the above reason, but also to counter attack longterm aging of the diode and the drop in brightness while in operation.The setup will have delays and inaccuracy's in it. I hope however to make them as consistent as possible between the two gates.

author: Kleinstein
author_rank: Super Contributor
timestamp: August 25, 2019, 11:50:20 am
content: 500 mV of reverse voltage for the PD is already better than zero bias. With battery operation the available voltage is limited, especially as the TLC072 is not rail to rail.There should be no need to use a relatively power hungry zener diode to stabilize the 3.3 V level. Normally just a low power 5 V LDO like MCP1703 should be good enough. Even just the battery voltage can be stable enough. R6 also draws quite some power.I now understand the laser regulation, using the same photo-diode as the detector. I would still adjust SensorVoltRef. and keep the TIA fixed. One may have to choose a suitable resistor though (e.g. 5 K, 10 K, 22K maybe 47 K). How critical a pot is depends on the resistance - at lower resistance (e.g. 10 K) a pot is probably OK, especially a relatively small form factor.The Laser control still looks odd, as Q2 has emitter and collector swapped.Worst case the 2N7002 will need more gate voltage to get some 30 mA out.

author: wilfred
author_rank: Super Contributor
timestamp: August 25, 2019, 12:32:12 pm
content: Circuits designed to measure the shutter speed of cameras would be a source of ideas for a sensor. These typically shone a light through to a sensor mounted at the film plane. They would set a counter running when the sensor received light. Your application would probably invert that and use the dart to block the light. With the time the light was blocked as the dart passed the sensor and the length of the dart you have what you need.I once had a device like this that used a 2N5777http://www.njsemi.com/datasheets/2N5777%20-%202N5780.pdfhttps://archive.org/stream/ETIA197/ETI%201977-10%20October#page/n45/mode/2up

author: HendriXML
author_rank: Super Contributor
timestamp: August 25, 2019, 02:35:57 pm
content: The orientation of Q2 was indeed wrong, nice catch.I dropped the zener diode. The circuit will be powered by a stabilized 5V source (https://www.eevblog.com/forum/projects/battery-voltage-to-5v-regulation/msg2323938/#msg2323938- without the current shutdown) which will be powered by 5 batteries. That circuit I've put in a nice 3D printed box, so it is easy to use in these kind of experiments. It gives an indication that the batteries are too low, so that is a nice feature.The resistor divider keeps things from the rails. I used a TLC072 because I have them on shelve.The power consumption is not a major design spec, because the experiments will be short in duration.The "constant" current using the 2 diodes and the transistor is not one I really like, normally it's very inaccurate due to tolerances and temperatures, diode currents and what not. But in this situation it is more used as a current limiter. However a resistor could have done that trick as well. But it's good to build up some experience. And I get two of these circuits, so I can see how well they behave compared to each other.

author: Kleinstein
author_rank: Super Contributor
timestamp: August 25, 2019, 02:48:17 pm
content: To reduce the temperaure effect of the diodes + transistor current limit, one could use a LED in stead of the 2 diodes. Red / orange leds have a TC that is relatively similar to the transistor BE voltage TC. So the resulting current limit can be quite stable. However the MOSFETs on resistance will still have quite some TC, so the current would still go down with temperature.In Principle one could get away without the FET: the Transistor could still be controlled by the OP, with the LED limiting the maximum base voltage.

author: HendriXML
author_rank: Super Contributor
timestamp: August 25, 2019, 02:53:46 pm
content: Quote from: wilfred on August 25, 2019, 12:32:12 pmUsing 2 gates the circuit can work without knowledge of the dart length. We plan on shooting different ones (and calculate their kinetic energy). Using a bit more distance also lowers the relative errors.Talking about shutter speeds, I would have like to take some carefully timed photo's with a more advanced setup. But my camera doesn't have a mirror lock function so it will be probably too slow. Using the dart speed it should be possible to calculate the moment of an impact. Using a flash could give some nice action photo.I will test my camera's responds and measure the time between (remote) firing the shutter and the flash moment to see how long it takes and whether it is repeatable.

author: HendriXML
author_rank: Super Contributor
timestamp: August 25, 2019, 03:19:02 pm
content: Quote from: Kleinstein on August 25, 2019, 02:48:17 pmNice to know about the leds.I once did a few test with some 2N3904's and found out that the BE (saturation) voltage (?) where quite different between each other. Because of the low shunt voltage the differences had a large impact on the current limiting. Eventually I'll look in to some cheap constant current IC's.

author: HendriXML
author_rank: Super Contributor
timestamp: August 25, 2019, 11:44:57 pm
content: It became a bit to crowded on the small perfboard so the current limiting is now done via a resistor.There's also a blue led which signals whether the laser exposes the photo diode or is blocked. Handy for checking stuff.To bad the soldering on the perfboard also involves "creating tracks" otherwise it was much easier for my son to do it. But first I'll test it on a breadboard, C1, R8, R9 need to be determined yet.

author: HendriXML
author_rank: Super Contributor
timestamp: August 26, 2019, 10:06:20 pm
content: Today we had fun creating the laser and sensor mounting model in Fusion 360. It's not finished, the "beam tunnel" will be subtracted, but only a small diameter. It will be drilled trough to get a better result.I keep my fingers crossed that the alignments will be ok. I will drill the laser tunnel first and see where the red dot goes after installing the laser.The PCB will be vertically mounted, the stands however won't be printed. Because the laserdiode has very short leads to the op amp, I hope the EM interference is minimal. In another setup with longer leads turning devices on and off influenced too much.The length will be 200 mm, the distance between the gates about 150 mm.

author: HendriXML
author_rank: Super Contributor
timestamp: August 26, 2019, 10:46:38 pm
content: The model had to loose some weight, there's still a lot to improve in that regard, but everything under 200 gr of PLA is fine by me.

author: HendriXML
author_rank: Super Contributor
timestamp: August 27, 2019, 12:22:38 pm
content: I mounted the circuit on a breadboard.The laser regulation is oscillating at 240 kHz measuring SensorVolt. The PID regulation is probably to fast. I'll try to finetune that. It may also that the TIA might need some a little capacitor as feedback (but that would slow down the response).When the laser is driven by a 90 Hz AWG signal the SensorVolt falls in about 2 us. The 10% fall will happen a bit faster.If a Nerf dart would travel at 15 m/s this would correspond to 30 um (that very little compared to the 150000 um between the sensors and even small relative to the beam/sensor diameter).

author: HendriXML
author_rank: Super Contributor
timestamp: August 28, 2019, 10:16:02 pm
content: After some problems with a LM311, which wasn't a LM311 the circuit is now working.I slowed down the laser power response at Q1, so the output is regulated nicely. But the PID will to be tuned a bit more to have "the slowness" evenly spread.Also I changed the voltage at which the comparator collapses. One of the LM211 inputs needs to be 1.5V from the positive rail. In the previous schematic it was very close to that with no real benefits.Potmeter RV1 is now replaced by R13, with a value of 5K (If I remember correctly).Will do some final tuning and benchmarking later. From what I saw today the circuit works fast enough for the purpose.

author: HendriXML
author_rank: Super Contributor
timestamp: August 29, 2019, 11:32:12 am
content: I did some testing of the sensing response times using incrementing the laser diode current. At some point the TIA op amp will saturate.Lower power means less response time.This is a good reason to automatically control the laser power like the circuit does.Red is input square waveBlue is amplified sensor signalYellow is comparator output.

author: HendriXML
author_rank: Super Contributor
timestamp: August 29, 2019, 11:50:12 am
content: I also did some testing breaking the beam with a nerf dart.The sensor curves look very similar because it is mostly determined by the slowness of the nerf dart.When the dart hits the beam it will gradually block more of the beam and make the exposure on the sensor less.If that takes 100 us than with the speed of 15 m/s the distance at which this happens would be 1500 um thus 1.5mm. This indeed near the diameter of the laser beam/photo diode.This principle will lower the accuracy of one gate measurement, however because it's repeated on the second one the precision might be much better.This will depend on how well the sensors are lit.But measuring distance with 1 mm accuracy would anyways be a bit of a challenge. So the distance travelled would be something like 150mm +/- 3 mm.

author: HendriXML
author_rank: Super Contributor
timestamp: August 29, 2019, 12:04:35 pm
content: Also shooting the dart straight/perpendicular is important. Maybe the setup should of less width, so its more obvious when the aiming was wrong.But we will be shooting different diameters of darts, so that's not a great solution.Having two vertical gates as well to check straightness would be nice, but for now to complex.

author: HendriXML
author_rank: Super Contributor
timestamp: September 02, 2019, 12:27:08 pm
content: So the mounting device is 3D printed. It came out great (17 hours). I had only printed the laser beam holes to 1.5 mm. The idea was to drill them more accurately. Letting the drill follow that path. That worked perfectly for one laserdiode hole (6 mm). The other one is slightly non perpendicular. I really could have used a bench drill. However the lasers are Chinese so they got angles them self. With a bit of matching and turning the sensor will be lit just fine!However the next time I would add larger holes in the 3D model. Going from 1.5 - 2 - 2.5 - 3 - 3.5 - 4 - 4.5 - 5 - 5.5 - 6 and drilling very slowly takes a lot of time, however this is critical not to heat up the drill.I also made a PCB layout just for laying out the used components in order and usable with a breadboard. It takes a little effort, but my son will be soldering one of the PCB's so the session shouldn't take too long.

author: HendriXML
author_rank: Super Contributor
timestamp: September 05, 2019, 12:39:59 pm
content: Yesterday my son did a very good job on soldering the components. Most of them we did from the the top of the perfboard. That makes it more easy to bend the legs to form traces. (Also faster) That part I've done alone. I'll post some pics of the finished product.So I hooked the 2 gates on 2 scope channels for a testing out a script.The script uses SCPI to set things up, retrieve the data and does some calculating. In the script I just took the number of samples between the zero crossings.Because of the 250MSa/s, this is off course very accurate.The accuracy of the experiment is in that regard entirely determined by the distance between the gates/laser beams and the place at which the beam is registered as broken. The distance error will be around 2 mm combining those errors.So that would make the speed calculation around 1% accurate.Because the error will be mostly systematic, this setup can surely be used in calculating the added power to the Nerf shooter when modding with more precision. However this we won't do, because then it would probably loose it's function as a toy.The next step would be to have the time measured without using a scope.Getting data gate ASample number: 874998Getting data gate BSample number: 3334790Sample difference: 2459792Time difference: 9,83917 msSpeed: 15,24519 m/sSpeed: 54,88269 km/u

author: HendriXML
author_rank: Super Contributor
timestamp: September 06, 2019, 11:31:04 am
content: This is how the setup looks like. On the back is a battery voltage to 5V regulatorhttps://www.eevblog.com/forum/projects/battery-voltage-to-5v-regulation/msg2280605/?all#lastPostin a 3D printed box. The nice thing of this box is that the air slots are zigzagged, so you can't see inside the box or poke a straight object in it.With this projecthttps://www.eevblog.com/forum/projects/very-stable-temperature-control/msg2514615/#msg2514615the thermal dissipation capacity of the box and heatsink inside could be determined by measuring the power it takes to keep for instance a stable 60 deg. That is something I'd like to experiment with in the future.As also can be seen the laser is regulated to a very dim light source.

author: HendriXML
author_rank: Super Contributor
timestamp: September 06, 2019, 02:58:07 pm
content: One thing that happened while using longer leads and my bench power supply was some instability with the comparator output. Because it drives a LED the VCC would drop a bit which would lead to a drop in the ref voltage. This would stop after a few ups and downs because of the hysteresis. Using my 5V regulator this has not happened, it may react just a bit faster on a voltage drop or just the shorter leads make the difference.However I want to process the signal digitally and like it to behave as designed. Adding C5 on the ref voltage dividers makes it behave just fine.

author: HendriXML
author_rank: Super Contributor
timestamp: September 27, 2019, 10:21:46 am
content: I found an AT Mega2560 Pro board on Ali Express, which is a lot smaller and can be soldered on more easily. This one has an Cristal resonator, so it should be pretty accurate. Many times more that the distance part of the speed measurement.Timing on the AT Mega is discussed in this thread in more depth. (Timers and interrupts, TCXO's)https://www.eevblog.com/forum/projects/measuring-time-span-very-precise/?all#lastPostEssentially the script uses 2 timers: timer 4 and timer 5. Each of them has a pin which when triggered saves the current counter to a location and schedules an interrupt.The counters are made in sync. So one pin can be used to get the starting counter (photo gate A), the other one the ending counter (photo gate B). Some info from the ATMega2560 datasheet that is of importance:Timer 4Capture pin: PL0Board: Pin 49 = PORTL0 (ICP4)Timer 5Capture pin: PL1Board: Pin 48 = PORTL1 (ICP5)Timer nCapture register: ICRnCount register: TCNTnControl register B: TCCRnBBit 7: Noise cancelingBit 6: Input capture edge select: 0: falling; 1: RisingBit 2..0: Input clock source: 000: none; 001: 1x; 110 External clock fallingCompare registers OCRnA, OCRnB, OCRnCTIMSKn: Interrupt mask registerBit 5: Capture interrupt: 0: off; 1: on;Bit 3: Output compare C interrupt: 0: off; 1: on;Bit 2: Output compare B interrupt: 0: off; 1: on;Bit 1: Output compare A interrupt: 0: off; 1: on;Bit 0: Overflow interrupt: 0: off; 1: on;Global registersGTCCR: General timer counter register - halt timersBit 7: Synchronization mode: 1: sync mode; 0: normal mode;Bit 1: PRASY: ?Bit 0: PSRSYNC: Reset for synchronische timersCode:#include <U8glib.h>#include <HardwareSerial.h>U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NO_ACK);enum CaptureState_t { csStart, csCapture4, csErrorCapture5 };enum DisplayState_t { dsUnused, dsUninitialized, dsInitialized };typedef uint16_t sequence_t;typedef uint64_t counter_t;struct Measurement_t{sequence_t Sequence;counter_t DiffCount;bool Active;Measurement_t* Previous;Measurement_t* Next;};struct DisplayMeasurements_t{sequence_t Sequence;counter_t DiffCount;DisplayState_t State;char DisplayData;// 0001: xxx.xxx km/h};volatile counter_t TimerCycles4A = 0;volatile counter_t TimerCycles4B = 0;volatile counter_t TimerCycles4O = 0;volatile counter_t TimerCycles5A = 0;volatile counter_t TimerCycles5B = 0;volatile counter_t TimerCycles5O = 0;volatile counter_t CaptureCount4;volatile counter_t CaptureCountDiff;volatile uint16_t DisplayUpdateSequence = 0;volatile CaptureState_t CaptureState = csStart;sequence_t CurrentMeasurementSequence = 1;uint16_t PrevDisplayUpdateSequence = 0;CaptureState_t PrevCaptureState = csStart;double MilliSecondsTickPeriod = 1000.0 / 16000000.0;const uint16_t HighCountA = 0xFFFF / 3;const uint16_t HighCountB = 0xFFFF / 3 * 2;const uint8_t BufferCount = 10;const uint8_t MeasurementDisplayCount = 5;Measurement_t MeasurementBuffers;Measurement_t* Head = &MeasurementBuffers;DisplayMeasurements_t MeasurementDisplayBuffers;ISR(TIMER4_COMPA_vect){TimerCycles4A++;}ISR(TIMER4_COMPB_vect){TimerCycles4B++;}ISR(TIMER4_OVF_vect){TimerCycles4O++;}ISR(TIMER4_CAPT_vect){DisplayUpdateSequence++;uint16_t CaptureCount = ICR4;CaptureState = csCapture4;// Using the cycle count that is linked to the captured counterif (CaptureCount < HighCountA)CaptureCount4 = (TimerCycles4B << 16) + CaptureCount;else if (CaptureCount < HighCountB)CaptureCount4 = (TimerCycles4O << 16) + CaptureCount;elseCaptureCount4 = ((TimerCycles4A - 1) << 16) + CaptureCount;}ISR(TIMER5_COMPA_vect){TimerCycles5A++;}ISR(TIMER5_COMPB_vect){TimerCycles5B++;}ISR(TIMER5_OVF_vect){TimerCycles5O++;}ISR(TIMER5_CAPT_vect){uint16_t CaptureCount = ICR5;uint64_t CaptureCount5;if (CaptureState != csCapture4){CaptureState = csErrorCapture5;return;}// Using the cycle count that is linked to the captured counterif (CaptureCount < HighCountA)CaptureCount5 = (TimerCycles5B << 16) + CaptureCount;else if (CaptureCount < HighCountB)CaptureCount5 = (TimerCycles5O << 16) + CaptureCount;elseCaptureCount5 = ((TimerCycles5A - 1) << 16) + CaptureCount;CaptureState = csStart;DisplayUpdateSequence++;Head = Head->Previous;Head->Sequence = CurrentMeasurementSequence;Head->DiffCount = CaptureCount5 - CaptureCount4;Head->Active = true;CurrentMeasurementSequence++;}// The setup() function runs once each time the micro-controller startsvoid setup(){// Serial.begin(115200);// Serial.println("Setup started");noInterrupts();// pinMode(48, INPUT);// pinMode(49, INPUT);// Halt timersGTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC);// enable capture, compare a, compare b, and overflow interruptsTIMSK4 = (1 << ICIE4) | (1 << OCIE4A) | (1 << OCIE4B) | (1 << TOIE4);TIMSK5 = (1 << ICIE5) | (1 << OCIE5A) | (1 << OCIE5B) | (1 << TOIE5);TCCR4A = 0;TCCR5A = 0;// no noise cancelling, falling edge, no prescalingTCCR4B = (0 << ICNC4) | (0 << ICES4) | ((0 << CS42) | (0 << CS41) | (1 << CS40));TCCR5B = (0 << ICNC5) | (0 << ICES5) | ((0 << CS52) | (0 << CS51) | (1 << CS50));OCR4A = HighCountA;OCR5A = HighCountA;OCR4B = HighCountB;OCR5B = HighCountB;TCNT4 = 0;TCNT5 = 0;// just to be sure..TimerCycles4A = 0;TimerCycles4B = 0;TimerCycles4O = 0;TimerCycles5A = 0;TimerCycles5B = 0;TimerCycles5O = 0;// Continue timersGTCCR = 0;for (uint16_t i = 1; i < BufferCount; i++){MeasurementBuffers[i - 1].Next = &MeasurementBuffers;MeasurementBuffers.Previous = &MeasurementBuffers[i - 1];}MeasurementBuffers.Previous = &MeasurementBuffers[BufferCount - 1];MeasurementBuffers[BufferCount - 1].Next = &MeasurementBuffers;MeasurementBuffers.Active = false;for (uint16_t i = 0; i < MeasurementDisplayCount; i++){MeasurementDisplayBuffers.State = dsUnused;MeasurementDisplayBuffers.DisplayData = ':';MeasurementDisplayBuffers.DisplayData = ' ';MeasurementDisplayBuffers.DisplayData = 'k';MeasurementDisplayBuffers.DisplayData = 'm';MeasurementDisplayBuffers.DisplayData = '/';MeasurementDisplayBuffers.DisplayData = 'h';MeasurementDisplayBuffers.DisplayData = 0;}interrupts();u8g.firstPage();do {} while (u8g.nextPage());DisplayUpdateSequence++;// Serial.println("Setup is ready");}// Add the main program code into the continuous loop() functionvoid loop(){// these must be constistentnoInterrupts();uint16_t LocDisplayUpdateSequence = DisplayUpdateSequence;CaptureState_t LocCaptureState = CaptureState;Measurement_t* RunningMeasurement = Head;for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (RunningMeasurement->Active){if (!((MeasurementDisplayBuffers.State == dsInitialized) && (MeasurementDisplayBuffers.Sequence == RunningMeasurement->Sequence))){MeasurementDisplayBuffers.State = dsUninitialized;MeasurementDisplayBuffers.DiffCount = RunningMeasurement->DiffCount;MeasurementDisplayBuffers.Sequence = RunningMeasurement->Sequence;}}elseMeasurementDisplayBuffers.State = dsUnused;RunningMeasurement = RunningMeasurement->Next;}interrupts();if ((LocCaptureState != PrevCaptureState) || (LocDisplayUpdateSequence != PrevDisplayUpdateSequence)){for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (MeasurementDisplayBuffers.State == dsUninitialized){uint16_t Seq = MeasurementDisplayBuffers.Sequence;MeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;double MilliSecs = MeasurementDisplayBuffers.DiffCount * MilliSecondsTickPeriod;double Speed = 150 * 3.6 / MilliSecs;dtostrf(Speed, 7, 3, &MeasurementDisplayBuffers.DisplayData);MeasurementDisplayBuffers.DisplayData = ' ';MeasurementDisplayBuffers.State = dsInitialized;}}u8g.setFont(u8g_font_profont11);u8g.firstPage();do {switch (LocCaptureState){case csStart:{u8g.drawStr(0, 8, "Shoot!!");break;}case csCapture4:{//Serial.println();// Serial.println("Timer running..");u8g.drawStr(0, 8, "Timer started");break;}case csErrorCapture5:{// Serial.println("Timer hasn't started!!");u8g.drawStr(0, 8, "Timer hasn't started!!");break;}default:u8g.drawStr(0, 8, "??");//Serial.println("??");break;}uint8_t LineTop = 20;for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (MeasurementDisplayBuffers.State == dsInitialized){u8g.drawStr(4, LineTop, MeasurementDisplayBuffers.DisplayData);LineTop += 10;}}} while (u8g.nextPage());PrevCaptureState = LocCaptureState;PrevDisplayUpdateSequence = LocDisplayUpdateSequence;}}

author: HendriXML
author_rank: Super Contributor
timestamp: September 27, 2019, 10:30:05 am
content: The script uses circular measurement buffers, because only 5 can be shown at a time I used a max of 10 measurements, but in theory many more measurements can be stored in FIFO order.Each measurement is prepared (initialized) before going in to a display loop. To keep things fast that is essential, because anything in that loop is done multiple time when drawing "pages" (which mean parts of the screen buffer). (Thus saving buffer memory)Because the main loop is light weight the responsiveness is kept high.

author: HendriXML
author_rank: Super Contributor
timestamp: September 30, 2019, 10:05:23 pm
content: I updated the code to use a more recent library U8g2lib. Also I had some stability issues with the display, turning the busclock down seems to solve that.This library can now update an entire screenbuffer at once (consuming more memory).Code:#include <U8g2lib.h>#define DEBUG 0#if DEBUG#define SERIALOUT;#endif#ifdef SERIALOUT#include <HardwareSerial.h>#endif#ifdef U8X8_HAVE_HW_I2C#include <Wire.h>#endif#ifdef SERIALOUT#define DEBUG_PRINT(x) Serial.println (x)#define DEBUG_PRINT(x)#endifU8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);enum CaptureState_t { csStart, csCapture4, csErrorCapture5 };enum DisplayState_t { dsUnused, dsUninitialized, dsInitialized };typedef uint16_t sequence_t;typedef uint64_t counter_t;struct Measurement_t{sequence_t Sequence;counter_t DiffCount;bool Active;Measurement_t* Previous;Measurement_t* Next;};struct DisplayMeasurements_t{sequence_t Sequence;counter_t DiffCount;DisplayState_t State;char DisplayData;// 0001: xxx.xxx km/h};volatile counter_t TimerCycles4A = 0;volatile counter_t TimerCycles4B = 0;volatile counter_t TimerCycles4O = 0;volatile counter_t TimerCycles5A = 0;volatile counter_t TimerCycles5B = 0;volatile counter_t TimerCycles5O = 0;volatile counter_t CaptureCount4;volatile counter_t CaptureCountDiff;volatile uint16_t DisplayUpdateSequence = 0;volatile CaptureState_t CaptureState = csStart;sequence_t CurrentMeasurementSequence = 1;uint16_t PrevDisplayUpdateSequence = 0;CaptureState_t PrevCaptureState = csStart;double MilliSecondsTickPeriod = 1000.0 / 16000000.0;const uint16_t HighCountA = 0xFFFF / 3;const uint16_t HighCountB = 0xFFFF / 3 * 2;const uint8_t BufferCount = 20;const uint8_t MeasurementDisplayCount = 5;Measurement_t MeasurementBuffers;Measurement_t* Head = &MeasurementBuffers;DisplayMeasurements_t MeasurementDisplayBuffers;ISR(TIMER4_COMPA_vect){TimerCycles4A++;}ISR(TIMER4_COMPB_vect){TimerCycles4B++;}ISR(TIMER4_OVF_vect){TimerCycles4O++;}ISR(TIMER4_CAPT_vect){DisplayUpdateSequence++;uint16_t CaptureCount = ICR4;CaptureState = csCapture4;// Using the cycle count that is linked to the captured counterif (CaptureCount < HighCountA)CaptureCount4 = (TimerCycles4B << 16) + CaptureCount;else if (CaptureCount < HighCountB)CaptureCount4 = (TimerCycles4O << 16) + CaptureCount;elseCaptureCount4 = ((TimerCycles4A - 1) << 16) + CaptureCount;}ISR(TIMER5_COMPA_vect){TimerCycles5A++;}ISR(TIMER5_COMPB_vect){TimerCycles5B++;}ISR(TIMER5_OVF_vect){TimerCycles5O++;}ISR(TIMER5_CAPT_vect){uint16_t CaptureCount = ICR5;uint64_t CaptureCount5;if (CaptureState != csCapture4){CaptureState = csErrorCapture5;return;}// Using the cycle count that is linked to the captured counterif (CaptureCount < HighCountA)CaptureCount5 = (TimerCycles5B << 16) + CaptureCount;else if (CaptureCount < HighCountB)CaptureCount5 = (TimerCycles5O << 16) + CaptureCount;elseCaptureCount5 = ((TimerCycles5A - 1) << 16) + CaptureCount;CaptureState = csStart;DisplayUpdateSequence++;Head = Head->Previous;Head->Sequence = CurrentMeasurementSequence;Head->DiffCount = CaptureCount5 - CaptureCount4;Head->Active = true;CurrentMeasurementSequence++;}// The setup() function runs once each time the micro-controller startsvoid setup(){#ifdef SERIALOUTSerial.begin(115200);#endifDEBUG_PRINT("Setup started");noInterrupts();pinMode(48, INPUT_PULLUP);pinMode(49, INPUT_PULLUP);// Halt timersGTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC);// enable capture, compare a, compare b, and overflow interruptsTIMSK4 = (1 << ICIE4) | (1 << OCIE4A) | (1 << OCIE4B) | (1 << TOIE4);TIMSK5 = (1 << ICIE5) | (1 << OCIE5A) | (1 << OCIE5B) | (1 << TOIE5);TCCR4A = 0;TCCR5A = 0;// no noise cancelling, falling edge, no prescalingTCCR4B = (0 << ICNC4) | (0 << ICES4) | ((0 << CS42) | (0 << CS41) | (1 << CS40));TCCR5B = (0 << ICNC5) | (0 << ICES5) | ((0 << CS52) | (0 << CS51) | (1 << CS50));OCR4A = HighCountA;OCR5A = HighCountA;OCR4B = HighCountB;OCR5B = HighCountB;TCNT4 = 0;TCNT5 = 0;// just to be sure..TimerCycles4A = 0;TimerCycles4B = 0;TimerCycles4O = 0;TimerCycles5A = 0;TimerCycles5B = 0;TimerCycles5O = 0;// Continue timersGTCCR = 0;for (uint16_t i = 1; i < BufferCount; i++){MeasurementBuffers[i - 1].Next = &MeasurementBuffers;MeasurementBuffers.Previous = &MeasurementBuffers[i - 1];}MeasurementBuffers.Previous = &MeasurementBuffers[BufferCount - 1];MeasurementBuffers[BufferCount - 1].Next = &MeasurementBuffers;MeasurementBuffers.Active = false;for (uint16_t i = 0; i < MeasurementDisplayCount; i++){MeasurementDisplayBuffers.State = dsUnused;MeasurementDisplayBuffers.DisplayData = ':';MeasurementDisplayBuffers.DisplayData = 'k';MeasurementDisplayBuffers.DisplayData = 'm';MeasurementDisplayBuffers.DisplayData = '/';MeasurementDisplayBuffers.DisplayData = 'h';MeasurementDisplayBuffers.DisplayData = 0;}interrupts();//u8g2.firstPage();//do//{//} while (u8g2.nextPage());DisplayUpdateSequence++;u8g2.setBusClock(50000);//u8g2.setBusClock(200000);u8g2.begin();DEBUG_PRINT("Setup is ready");}// Add the main program code into the continuous loop() functionvoid loop(){// these must be constistentnoInterrupts();uint16_t LocDisplayUpdateSequence = DisplayUpdateSequence;CaptureState_t LocCaptureState = CaptureState;Measurement_t* RunningMeasurement = Head;for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (RunningMeasurement->Active){if (!((MeasurementDisplayBuffers.State == dsInitialized) && (MeasurementDisplayBuffers.Sequence == RunningMeasurement->Sequence))){MeasurementDisplayBuffers.State = dsUninitialized;MeasurementDisplayBuffers.DiffCount = RunningMeasurement->DiffCount;MeasurementDisplayBuffers.Sequence = RunningMeasurement->Sequence;}}elseMeasurementDisplayBuffers.State = dsUnused;RunningMeasurement = RunningMeasurement->Next;}interrupts();if ((LocCaptureState != PrevCaptureState) || (LocDisplayUpdateSequence != PrevDisplayUpdateSequence)){for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (MeasurementDisplayBuffers.State == dsUninitialized){uint16_t Seq = MeasurementDisplayBuffers.Sequence;MeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;Seq = Seq / 10;if (Seq == 0)MeasurementDisplayBuffers.DisplayData = ' ';elseMeasurementDisplayBuffers.DisplayData = '0' + Seq % 10;double MilliSecs = MeasurementDisplayBuffers.DiffCount * MilliSecondsTickPeriod;double Speed = 150 * 3.6 / MilliSecs;dtostrf(Speed, 8, 3, &MeasurementDisplayBuffers.DisplayData);MeasurementDisplayBuffers.DisplayData = ' ';MeasurementDisplayBuffers.State = dsInitialized;}}u8g2.setFont(u8g_font_profont11);u8g2.clearBuffer();//u8g2.firstPage();//do {switch (LocCaptureState){case csStart:{u8g2.drawStr(0, 8, "Shoot!!");DEBUG_PRINT("Shoot");break;}case csCapture4:{DEBUG_PRINT("Timer started");u8g2.drawStr(0, 8, "Timer started");break;}case csErrorCapture5:{DEBUG_PRINT("Timer hasn't started!!");u8g2.drawStr(0, 8, "Timer hasn't started!!");break;}default:u8g2.drawStr(0, 8, "??");DEBUG_PRINT("??");break;}uint8_t LineTop = 20;for (uint8_t i = 0; i < MeasurementDisplayCount; i++){if (MeasurementDisplayBuffers.State == dsInitialized){u8g2.drawStr(4, LineTop, MeasurementDisplayBuffers.DisplayData);DEBUG_PRINT(MeasurementDisplayBuffers.DisplayData);LineTop += 10;}}//} while (u8g2.nextPage());u8g2.sendBuffer();PrevCaptureState = LocCaptureState;PrevDisplayUpdateSequence = LocDisplayUpdateSequence;}}

author: HendriXML
author_rank: Super Contributor
timestamp: September 30, 2019, 10:20:56 pm
content: To finalize the project I printed an enclosure that has the mega board and the display mounted.I'm glad that printing the 45 deg display mount went ok.For mounting the board I used the board photo as a reference, which resulted in accurate screw holes locations. The hex screw have heat shrink added to them, to isolate the screw from the board, not to harm it and to give a good fit for the 2 rather large holes.Under the left-down location of the atmega chip, there's another stand, but without a screw. The board is mounted in such way that every pin could have been used.

author: HendriXML
author_rank: Super Contributor
timestamp: September 30, 2019, 11:05:33 pm
content: The reduction of the clock speed got me thinking: did I wire the I²C connection correctly?(I think not).What kind of wires would be best to connect to the display?

author: HendriXML
author_rank: Super Contributor
timestamp: October 01, 2019, 12:21:04 am
content: Quote from: HendriXML on September 30, 2019, 11:05:33 pmFrom searching the internet, I found that twisting all 4 wires seems to be the correct way.