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.
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 2N5777 http://www.njsemi.com/datasheets/2N5777%20-%202N5780.pdf
https://archive.org/stream/ETIA197/ETI%201977-10%20October#page/n45/mode/2up
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.
#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;
}
}
#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)
#endif
U8G2_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[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 = 20;
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()
{
#ifdef SERIALOUT
Serial.begin(115200);
#endif
DEBUG_PRINT("Setup started");
noInterrupts();
pinMode(48, INPUT_PULLUP);
pinMode(49, INPUT_PULLUP);
// 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[14] = 'k';
MeasurementDisplayBuffers[i].DisplayData[15] = 'm';
MeasurementDisplayBuffers[i].DisplayData[16] = '/';
MeasurementDisplayBuffers[i].DisplayData[17] = 'h';
MeasurementDisplayBuffers[i].DisplayData[18] = 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() 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, 8, 3, &MeasurementDisplayBuffers[i].DisplayData[5]);
MeasurementDisplayBuffers[i].DisplayData[13] = ' ';
MeasurementDisplayBuffers[i].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[i].State == dsInitialized)
{
u8g2.drawStr(4, LineTop, MeasurementDisplayBuffers[i].DisplayData);
DEBUG_PRINT(MeasurementDisplayBuffers[i].DisplayData);
LineTop += 10;
}
}
//} while (u8g2.nextPage());
u8g2.sendBuffer();
PrevCaptureState = LocCaptureState;
PrevDisplayUpdateSequence = LocDisplayUpdateSequence;
}
}