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: PL0
Board: Pin 49 = PORTL0 (ICP4)
Timer 5Capture pin: PL1
Board: Pin 48 = PORTL1 (ICP5)
Timer nCapture register: ICRn
Count register: TCNTn
Control register B: TCCRnB
Bit 7: Noise canceling
Bit 6: Input capture edge select: 0: falling; 1: Rising
Bit 2..0: Input clock source: 000: none; 001: 1x; 110 External clock falling
Compare registers OCRnA, OCRnB, OCRnC
TIMSKn: Interrupt mask register
Bit 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 timers
Bit 7: Synchronization mode: 1: sync mode; 0: normal mode;
Bit 1: PRASY: ?
Bit 0: PSRSYNC: Reset for synchronische timers
#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[19];
// 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[BufferCount];
Measurement_t* Head = &MeasurementBuffers[0];
DisplayMeasurements_t MeasurementDisplayBuffers[MeasurementDisplayCount];
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 counter
if (CaptureCount < HighCountA)
CaptureCount4 = (TimerCycles4B << 16) + CaptureCount;
else if (CaptureCount < HighCountB)
CaptureCount4 = (TimerCycles4O << 16) + CaptureCount;
else
CaptureCount4 = ((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 counter
if (CaptureCount < HighCountA)
CaptureCount5 = (TimerCycles5B << 16) + CaptureCount;
else if (CaptureCount < HighCountB)
CaptureCount5 = (TimerCycles5O << 16) + CaptureCount;
else
CaptureCount5 = ((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 starts
void setup()
{
// Serial.begin(115200);
// Serial.println("Setup started");
noInterrupts();
// pinMode(48, INPUT);
// pinMode(49, INPUT);
// Halt timers
GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC);
// enable capture, compare a, compare b, and overflow interrupts
TIMSK4 = (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 prescaling
TCCR4B = (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 timers
GTCCR = 0;
for (uint16_t i = 1; i < BufferCount; i++)
{
MeasurementBuffers[i - 1].Next = &MeasurementBuffers[i];
MeasurementBuffers[i].Previous = &MeasurementBuffers[i - 1];
}
MeasurementBuffers[0].Previous = &MeasurementBuffers[BufferCount - 1];
MeasurementBuffers[BufferCount - 1].Next = &MeasurementBuffers[0];
MeasurementBuffers[0].Active = false;
for (uint16_t i = 0; i < MeasurementDisplayCount; i++)
{
MeasurementDisplayBuffers[i].State = dsUnused;
MeasurementDisplayBuffers[i].DisplayData[4] = ':';
MeasurementDisplayBuffers[i].DisplayData[5] = ' ';
MeasurementDisplayBuffers[i].DisplayData[14] = 'k';
MeasurementDisplayBuffers[i].DisplayData[15] = 'm';
MeasurementDisplayBuffers[i].DisplayData[16] = '/';
MeasurementDisplayBuffers[i].DisplayData[17] = 'h';
MeasurementDisplayBuffers[i].DisplayData[18] = 0;
}
interrupts();
u8g.firstPage();
do {
} while (u8g.nextPage());
DisplayUpdateSequence++;
// Serial.println("Setup is ready");
}
// Add the main program code into the continuous loop() function
void loop()
{
// these must be constistent
noInterrupts();
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[i].State == dsInitialized) && (MeasurementDisplayBuffers[i].Sequence == RunningMeasurement->Sequence)))
{
MeasurementDisplayBuffers[i].State = dsUninitialized;
MeasurementDisplayBuffers[i].DiffCount = RunningMeasurement->DiffCount;
MeasurementDisplayBuffers[i].Sequence = RunningMeasurement->Sequence;
}
}
else
MeasurementDisplayBuffers[i].State = dsUnused;
RunningMeasurement = RunningMeasurement->Next;
}
interrupts();
if ((LocCaptureState != PrevCaptureState) || (LocDisplayUpdateSequence != PrevDisplayUpdateSequence))
{
for (uint8_t i = 0; i < MeasurementDisplayCount; i++)
{
if (MeasurementDisplayBuffers[i].State == dsUninitialized)
{
uint16_t Seq = MeasurementDisplayBuffers[i].Sequence;
MeasurementDisplayBuffers[i].DisplayData[3] = '0' + Seq % 10;
Seq = Seq / 10;
if (Seq == 0)
MeasurementDisplayBuffers[i].DisplayData[2] = ' ';
else
MeasurementDisplayBuffers[i].DisplayData[2] = '0' + Seq % 10;
Seq = Seq / 10;
if (Seq == 0)
MeasurementDisplayBuffers[i].DisplayData[1] = ' ';
else
MeasurementDisplayBuffers[i].DisplayData[1] = '0' + Seq % 10;
Seq = Seq / 10;
if (Seq == 0)
MeasurementDisplayBuffers[i].DisplayData[0] = ' ';
else
MeasurementDisplayBuffers[i].DisplayData[0] = '0' + Seq % 10;
double MilliSecs = MeasurementDisplayBuffers[i].DiffCount * MilliSecondsTickPeriod;
double Speed = 150 * 3.6 / MilliSecs;
dtostrf(Speed, 7, 3, &MeasurementDisplayBuffers[i].DisplayData[6]);
MeasurementDisplayBuffers[i].DisplayData[13] = ' ';
MeasurementDisplayBuffers[i].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[i].State == dsInitialized)
{
u8g.drawStr(4, LineTop, MeasurementDisplayBuffers[i].DisplayData);
LineTop += 10;
}
}
} while (u8g.nextPage());
PrevCaptureState = LocCaptureState;
PrevDisplayUpdateSequence = LocDisplayUpdateSequence;
}
}