Increasing the repeatability of a high speed photography trigger

Hi all

I’ve been working on a high speed photography trigger on and off ( mainly off ) for a while. The basic premise is;
Drip falls through laser pointing at photo-resistor, photo-resistor ‘sees’ drip pass, after ‘x’ time delay a photo is taken.
The time delay can be changed with the use of an increase and decease button and the drip is dropped via a 12v solenoid.
The solenoid is hooked up via a relay board and the camera is triggered using an optocoupler board as the camera just needs a closed circuit to operate.

I have managed to get a crude version of it working and it takes some nice photos but there is too much variation between each photo for my liking. My goal is to get it accurate and repeatable enough that if you take 10 photos in a row without changing any variable, you would end up with 10 photos almost (yes i’ll settle for almost) indistinguishable from each other. With the further goal of taking a string of photos increasing the time delay by x milliseconds each time and making a ‘slow motion stop motion video’ of a drip falling into frame and completing its splash.

From what I’ve read, using digitalRead and digitalWrite isn’t the most efficient way of dealing with the problem because of timing interrupts etc. and no doubt there are many other things I can do to improve it’s overall performance. Is direct port manipulation the way to go or is there another way of achieving my goal?

The full code is attached.

Thank you in advance for any time you put into reviewing my code and replying.

02.06.19_Cut_Back_Version_Speed_Increase01.ino (3.9 KB)

You should use delayMicroseconds for the timing, which has a much better resolution for small ms delays,
if you want to use that delay stuff.

I miss a state detection for the buttons/keys, I dislike all the delays.
Bounce2 is a good library to handle buttons/keys IMHO.

Please go and read the sticky so you know how to post code inline in your message. It makes it much easier for us when you make it look like this:

/*
This is a working version but with extras removed to see if it increases speed and reliability
*/

//#define printMessage                     // Sends info to SerialMonitor


const int laserThreshhold    =   985;      // If laserVal drops below laserThreshhold a photo is taken after photoDelayTime has passed

int photoDelayTime           =   10 ;      // Sets the photoDelayTime


const int laserSensor        =   A1;       // AnalogPin 1 as PhotoresistorPin. Read laser intensety , if drops below threshhold take photo
const int triggerButton      =   8;        // Read triggerButton , if pressed , open solenoid 
const int decreaseButton     =   9;        // Used to decrease photoDelay
const int increaseButton     =   10;       // Used to increase photoDelay
const int cameraTrigger      =   11;       // Optocoupler to trigger camera
const int laserPin           =   12;       // Emmits laser to photoresistor
const int solenoid           =   13;       // If triggerButton(8) pressed , open solenoid

int increaseSwitchState      =   0;        // Sets the value of increaseSwitchState to 0
int decreaseSwitchState      =   0;        // Sets the value of decreaseSwitchState to 0
int triggerState             =   LOW;

  
void setup()
{
// OUTPUTS
  pinMode(solenoid,       OUTPUT);
  digitalWrite(solenoid,  LOW);
  
  pinMode(laserPin,       OUTPUT);
  digitalWrite(laserPin,  LOW);

  pinMode(cameraTrigger,  OUTPUT);
  pinMode(cameraTrigger,  HIGH);

  
//INPUTS
  pinMode(increaseButton, INPUT);
  
  pinMode(decreaseButton, INPUT);
  
  pinMode(triggerButton,  INPUT);

  pinMode(laserSensor,    INPUT);
 


#ifdef printMessage                        // If PRINT_MESSAGE is defined at the top of the sketch
  Serial.begin(9600);                      // serial will start and print messages to teminal
#endif

delay(250);                                // Because why not  
 }

void loop() {

  int laserVal;                            
  laserVal = analogRead(laserSensor);      // Sets the laserVal by reading the value the photoresistor pin

#ifdef printMessage
  Serial.print("Laser Value:");
  Serial.print(laserVal);
  Serial.print("\t Photo Time Delay:");
  Serial.print(photoDelayTime);
  Serial.print("\t Laser Threshhold:");
  Serial.print(laserThreshhold);
  Serial.println("\t");
#endif

  increaseSwitchState = digitalRead(increaseButton);         // Sets increaseButton
if (increaseSwitchState == HIGH) {                           // Reads state of increaseButton, if pressed
  photoDelayTime ++;                                         // the delay time increases by 1 millisecond.
  
#ifdef printMessage
  Serial.println("Increase Button Pressed");
#endif
delay(200);                                                 // Stops button being read more
 }                                                          // than once per press

  decreaseSwitchState = digitalRead(decreaseButton);        // Sets decreaseButton
if (decreaseSwitchState == HIGH) {                          // Reads state of decreaseButton, if pressed
  photoDelayTime --;                                        // the delay time decreases by 1 millisecond.
  
#ifdef printMessage
  Serial.println("Decrease Button Pressed");
#endif
delay(200);                                                 // Stops button being read more
 }                                                          // than once per press

  triggerState = digitalRead(triggerButton);                // Reads triggerState
 if (triggerState == HIGH) {                                
    
  digitalWrite(solenoid, HIGH);
    delay(30);
  digitalWrite(solenoid, LOW);

#ifdef printMessage
  Serial.println("TriggerButton Pressed");
#endif

  }

  
  ///////////////////
  // LASER TRIGGER //
  /////////////////// 


if(laserVal < laserThreshhold){
  delay(photoDelayTime);
  digitalWrite(cameraTrigger, HIGH);
#ifdef printMessage
  Serial.println("Camera Triggered");
#endif
  delay(200);
  digitalWrite(cameraTrigger, LOW);
 }

}

The IDE even offers a “copy for forum” option, making it even easier!

Yes, those delays are ugly. Really can do a lot better.

But that won’t be the problem of your erratic timing. Nor will be the digitalRead() and digitalWrite() calls, not even the much slower analogRead() call - not on a time scale of 30 ms. That matters when you’re doing microsecond level timing.

I do notice the word “solenoid”. Your trigger is activated by a solenoid? That would be my prime suspect. It’s mechanical, and millisecond timing is not easy with mechanical things. An electronic shutter would react much faster and more predictable.

nebtrebmal:
Drip falls through laser pointing at photo-resistor, photo-resistor 'sees' drip pass, after 'x' time delay a photo is taken.

I reckon a photo resistor would be much too slow for that sort of thing. Try using a photo transistor.

...R

AnalogRead() is pretty slow. A better approach might be to use the analog compare capabilities of the chip and have it interrupt on detection so you get a much more consistent starting reference.

here is a link

blh64:
AnalogRead() is pretty slow.

Slow - at just over 100µs for a reading. Nothing compared to the 30 ms delay that follows.

You can use an LDR as a digital input. I have lots of them on a model train layout. It could be used to trigger an interrupt.

...R

OP will have to make some changes to their setup.
LDR is too slow for a water drop, not sure what they use, should be some kind of phototransistor.
Code mentions a threshold value of 985, it triggers when the output drops below that. That's very close to 5V - and a very small drop in the reading. Additional hardware will be needed to create a digital trigger out of that.

This has been discussed before - a month or two back IIRC.

Sensing a water drop is non-trivial, whether with laser or otherwise. It depends entirely on the shape of the drop and the consequent path of the beam through the drop.

You may be better off detecting scattered light and not using a laser which might produce interesting diffraction effects.

wvmarle:
Slow - at just over 100µs for a reading. Nothing compared to the 30 ms delay that follows.

Compare to how long the drop will block/refract the light from the sensor.

With a phototransistor to detect the light, digital port read could be checking that space > 100x more often than analog read and actually count sequential reads the drop does block -- unless blocking code stops that of course.

For such speeds I'd opt for an interrupt.

If the drop was charged it could be detected dropping through a coil, moving charges make magnetic fields.

5V Arduino digital pins will go HIGH if raised to 2.8V and will go LOW if dropped below 1.1V. A small circuit may be needed with the phototransistor to get the output into that range, maybe not. Shield the detector from incidental light, the view from the back of a tube can be pretty shielded, only sees a narrow cone to front.

Thank you all for your responses. I've had some time to think about what people have said and give a few things a try.

wvmarle:
Please go and read the sticky

Thank you, I've done as you suggested :slight_smile:

First of all I took the advice of Robin2 and swapped out the photo-resistor for a phototransistor (Omrom EE-SX671) I found at work. It was easy to install and change the code to get it working.
It has prooved a little unreliable in triggering the photoDelayTime though..I filmed the drip fall through the phototransistor in slow-motion and found the the drip was activating the Omrom by turning off an inbuilt LED on the phototransistor which was too quick to see with the naked eye but this signal isn't being picked up by the arduino. My guess is that it's because I'm not using interrupts like blh64 and wvmarle suggested and is therefore missing the signal unless it happens when the programme is checking it? Interrupts will be the next thing I add.

Thanks Whandall - I've taken the analogRead function out of the code now that I have a phototransistor in place of the photo-resistor but will keep your comment about using compare instead of analogRead in mind and do some research on it as it's something I've not looked at before.

wvmarle:
I do notice the word "solenoid". Your trigger is activated by a solenoid? That would be my prime suspect. It's mechanical, and millisecond timing is not easy with mechanical things. An electronic shutter would react much faster and more predictable.

I see your point but my thinking is that the solenoid is being activated before any critical timing starts. It's the drip being detected by the phototransistor that starts the photoDelayTime and the drip can't fall until the solenoid has opened. Maybe I'm missing something though?

Thanks Paul__B I'll have a look at some threads from a while back, sounds like a helpful avenue to pursue. I was using the high-speed video on my phone to see how long it takes for the drop to 'pull' itself into a drop shape.

From what I've read on interrupts, you cannot use delays within them however I need a time delay to stop the camera from taking a photo as soon as the phototransistor is triggered. I'll do my own research on this but if anyone has any links to useful threads etc or further advice then it would be appreciated.

Something I ommited from my original post was that I'm working with an Arduino Uno

wvmarle:
OP will have to make some changes to their setup.
LDR is too slow for a water drop, not sure what they use, should be some kind of phototransistor.
Code mentions a threshold value of 985, it triggers when the output drops below that. That's very close to 5V - and a very small drop in the reading. Additional hardware will be needed to create a digital trigger out of that.

Interestingly the threshhold worked quite well and triggered almost everytime. The main issue is the repeatability of the exact time the actual photo is taken. Hopefully adding an interrupt along with the phototransistor will help quite a lot

nebtrebmal:
I see your point but my thinking is that the solenoid is being activated before any critical timing starts. It's the drip being detected by the phototransistor that starts the photoDelayTime and the drip can't fall until the solenoid has opened. Maybe I'm missing something though?

In this case it's lack of information. All I could see was a reference to a solenoid in your code. Not what the purpose of that solenoid is (I guessed to operate the camera trigger). If it is to release the drop, then it won't affect the triggering.

From what I've read on interrupts, you cannot use delays within them however I need a time delay to stop the camera from taking a photo as soon as the phototransistor is triggered. I'll do my own research on this but if anyone has any links to useful threads etc or further advice then it would be appreciated.

You record the current value of millis() in the ISR, and set a bool variable indicating that a drop is falling. Then a little later (by checking the millis() value) you operate the trigger. See this tutorial on how it works.

wvmarle:
In this case it's lack of information. All I could see was a reference to a solenoid in your code. Not what the purpose of that solenoid is (I guessed to operate the camera trigger). If it is to release the drop, then it won't affect the triggering.

You record the current value of millis() in the ISR, and set a bool variable indicating that a drop is falling. Then a little later (by checking the millis() value) you operate the trigger. See this tutorial on how it works.

My bad, I should have made it clearer. I have an optocoupler to trigger the camera - I'm under the impression that this is the quickist way to actualy trigger the camera for the way I want to use it.

Cool that's really helpful, thank you. I'll read through the link you provided and give it ago!

My guess is that it's because I'm not using interrupts like blh64 and wvmarle suggested and is therefore missing the signal unless it happens when the programme is checking it? Interrupts will be the next thing I add.

No need to use interrupts; that just creates complications and is actually slower than polling a digital pin, if polling is done properly.

Post a wiring diagram for your phototransistor. The choice of load resistor is critical.

After the drop is detected, it is not a problem to use delay() before triggering the shutter.

jremington:
No need to use interrupts; that just creates complications and is actually slower than polling a digital pin, if polling is done properly.

Depends on how it's done, and why.
In this case I have the feeling that interrupts are appropriate, as the signal is very short. It doesn't add real complications, as what's done in the interrupt would otherwise be done during polling, and you basically replace the complexity of state change detection by the complexity of an interrupt.
The actual reaction to the interrupt is delayed: the interrupt does ensure the quickest and most predictable reaction time on the photointerruptor, though on a millisecond scale with nothing else really to do in loop() it probably doesn't make a difference, and polling should work as well.

Is it possible that the camera is responsible for the variation in shutter release you are seeing? It has its own operating system doing who knows what at any particular time.

Also, if you have the millis interrupt enabled, perhaps that is responsible for the variability.

I would also say that I think this type of high-speed photography is usually done in the dark with the shutter wide open, and taking the picture by firing the strobe, possibly several times in sequence in the same picture.

as the signal is very short.

Define "short". Free fall is glacial compared to a 16 MHz clock tick.

Here is a little table showing how far a drop free-falls in the first 50 milliseconds. In the last 5 milliseconds, a 3 mm drop has not even moved by its diameter. Advanced techniques are not needed to detect this motion.

  sec	d (mm)
0.000	0.0
0.005	0.1
0.010	0.5
0.015	1.1
0.020	2.0
0.025	3.1
0.030	4.4
0.035	6.0
0.040	7.8
0.045	9.9
0.050	12.3