Analog camera shutter speed tester

Hi everyone!
I'm trying to build a shutter speed tester for analog cameras, but I can't seem to get meaningful results, and I'm running out of ideas.

Background:

I've read dozens of articles, forums, schematics, and projects, and all of them report good accuracy up to 1/1000s, which is exactly what I need.
I've tried replicating the project with several variations, but I keep getting unreliable readings.

My setup:

For the hardware, I'm using a photodiode-based system, whether infrared or visible light. I first tested with an infrared photodiode following this schematic:

Then switched to a visible light photodiode following this other schematic:

Since these are photodiodes, I assume they are fast enough for my application. Moreover, I don’t need to measure the exact light intensity—just detecting a transition from total darkness to any visible light is enough to determine when the shutter opens and closes.

The code is even simpler:

int threshold = 1020;  
unsigned long startTime = 0;
bool measuring = false;

void setup() {
  Serial.begin(9600);
  
  Serial.println("Shutter speed tester");
  Serial.println("Ready");
}

void loop() {
  int analogValue0 = analogRead(0);
  int analogValue1 = analogRead(1);
  int analogValue2 = analogRead(2);
  int analogValue3 = analogRead(3);

  if (!measuring && (analogValue0 < threshold || analogValue1 < threshold || analogValue2 < threshold || analogValue3 < threshold)) {  
    startTime = micros();
    measuring = true;
  } 
  
  if (measuring && (analogValue0 >= threshold && analogValue1 >= threshold && analogValue2 >= threshold && analogValue3 >= threshold)) {  
    unsigned long d = micros() - startTime;
    measuring = false;
      
    float shutterSpeed = 1000000.0 / d;
    
    Serial.print("Shutter speed: ");
    Serial.print(d);
    Serial.print(" us (1/");
    Serial.print(shutterSpeed, 1);
    Serial.println(" sec)");
    
    delay(1000);
    Serial.println("Ready");
  }
}

The issue:

While I get reasonable readings for shutter speeds up to 1/125s, at faster speeds, accuracy drops significantly. By the time I reach 1/1000s, my readings are completely off—often showing 1/400s instead.

I’m certain that my cameras are accurate because I tested two different models with electronic shutters, both practically new, with only around a thousand actuations. Their shutter speeds should be spot on.

I place the photodiode behind the shutter curtain and use a powerful flashlight in front. However, I’ve noticed that my readings are inconsistent, especially when I change the flashlight or adjust the angle.

To address this, I even tried using an array of four photodiodes positioned at the four corners of the film plane. I start timing when the first sensor detects light and stop when all of them register darkness again. Despite this, my results remain unreliable over 1/125 and highly variable.

What am I doing wrong?

I’d really appreciate any insights!

Thanks in advance to anyone willing to help. :blush:

Not a good assumption. Check the data sheets.

These four measurements take about 450 microseconds to execute:

  int analogValue0 = analogRead(0);
  int analogValue1 = analogRead(1);
  int analogValue2 = analogRead(2);
  int analogValue3 = analogRead(3);

Do you have any reccomandation for a fast one that I could find on AliExpress?
Mine are these: https://www.aliexpress.com/item/1005006136399247.html
and these: https://www.aliexpress.com/item/1005004060113525.html

I was considering the possibility of delays with multiple readings, but a single reading didn’t make much of a difference. I implemented the four readings with four sensors as a last resort because I thought the issue might be related to the time required for the curtain to fully open and close.

No, because there are no data sheets.

If you are serious about this project, you must know what it is you are buying. Buy a photodiode that is fast enough for your project from a reliable distributor like Digikey, Mouser, etc.

Possibly taking the wrong approach for the type of shutter in question. You say nothing about the camera, but I suspect you would be taking the right approach if your shutter was a Compur type - but it isn't. I'm betting the signal is the 1/125 second shutter speed, which is commonly the maximum speed for flash photography on cameras with a focal plane shutter - and I suspect that is what your camera has. This means that the entire film surface is exposed for 1/125sec at the same time. If your camera has the 125 shutter setting in red, remember where you heard it first.

At speeds higher than the maximum flash speed, the film surface is exposed piecemeal by a travelling slot, horizontal (Leica type) or vertical (Contax) . The best shutter in the business is the Copal, but I never found exactly how it works, although it does use the same general principle. The varying exposure above 1/125 may be expedited by varying the width of the slot, the curtain speed remaining the same.

You seem to be certain your cameras are accurate. I wouldn't be surprised if this is true. You might be better off testing this by reviewing the resultant pictures rather than looking for monsters under the bed.

If it's a focal plane shutter, with two curtains, the curtains move at a constant speed no matter what the shutter speed setting is. The traveling slit just varies in size with the shutter speed setting. For a fast speed, the second curtain just starts moving very shortly after the first curtain starts moving, so the slit is very narrow.

Even so, I think the time that a diode sees light should still give valid results. But I don't think you have time to take analog readings. Your sensor circuit should be set up to provide essentially digital output high or low. Then you could set up the "capture" interrupt for timer1 to record the timer reading on the first transition ( high-to low), then the second transition (low to high), then calculate the difference.

Misleading.
For flash synch, typically 1/125 or slower, the second curtain doesn't start until the first has finished its trip. It's not a slit. The entire aperture is exposed for varying lengths of time.

I agree. And the flash sync speed is the speed just below the speed at which the second curtain begins to move before the first curtain has fully opened. It's the fastest speed which still allows the flash to expose the entire frame.

But I believe it's still true that when the curtains move, they move at a fixed speed. They don't move faster at faster shutter speeds. The second curtain just moves sooner.

Hello everyone, and thanks for your replies!

Yes, I’m aware that the curtains travel at different speeds, but I was thinking that if the photodiode acts as the film plane, it should measure the same amount of light that the film would receive, regardless of the curtain movement, since the light coming through the lens is diffuse.

As I mentioned, I have a nearly unused camera that I could use as a reference. Additionally, I have 15 other analog cameras from different eras to test.
More than a real necessity, this is also a study project—I’d like to better understand how actual shutter speed affects the final image.

I see that this project seems to be working, and uses a specific photodiode: Shutter Speed Tester for Film Cameras | Arduino Project Hub
I am having little difficulties in getting that photodiode here in Germany, but I found alternative ones and I was wondering if someone could help me to understand if they should be suitable.

No, it quite clearly describes a phototransistor.

Phototransistors are significantly easier to use than photodiodes, and fast enough for this project.

FYI, these are wiring diagrams, they are not schematics. The schematics on that project page you linked to are schematics. A breadboard wiring diagram is also shown on that page. It's correctly not described as a schematic.

I doubt it. The exposure on film is the time the moving slot in the curtain exposes a grain of photosensitive material on the film. That is a lot smaller than any photodiode/transistor. I just think you are on the wrong tram. If you are ever going to get this to work, I'm betting you will need some sort of optical device in front of the photosensor, and the Arduino side of things is the least of your problems.

Well, he's not trying to measure the amount of light received by the phototransistor. He's trying to measure the time period during which any light at all is received. But yes, the lens on the phototransistor will pick up light for a longer time than a single grain would, and that difference would become more significant as the slit becomes very narrow. Maybe a pinhole transistor would work. :slight_smile:

On the Arduino side, you could set the 16-bit timer1 running with an appropriate prescaler, with the capture interrupt set to trigger on a high-to-low edge.

When the edge occurs, the timer count is captured immediately. Then in the ISR the captured value is transferred to a variable, and the capture edge is switched to low-to-high. This can all be done quite rapidly.

Then on the next interrupt the second value is captured, and the loop() can then deal with calculating the difference and converting to a shutter speed.

But this requires setting up the phototransistor circuit with enough gain that its output is effectively digital - either high or low.

Precisely my goal!
There are many many projects on the web that use a photodiode/phototransistor and claim to be able to read speeds up to 1/1000 with good precision. And there are replies to this projects from other people that implemented those projects and work for them as well. So it is doable, I must be doing something wrong here. I may understand that the photodiodes I bought are maybe too slow, but I also tried a phototransistor I bought with the photodiodes, TEMT6000, which is a bit on the slow side, but still fast enough in theory up to 1/4000 so good enough. And I have the same problem.. And there is a person that claims to be using it and having readings up to 1/1500 that are consistent. I tried the same code and... nothing!
I will try to write some code to try your solution, @ShermanP !

I wrote an IR capture sketch to capture the raw signal from any IR remote control, and it uses the method I described. It captures from a 38KHz IR receiver module, not a phototransistor, but the timer register coding may be useful to you.

https://github.com/gbhug5a/SimpleIRRaw

I will definitely try and update you!
The module I have bought are these:
https://www.aliexpress.com/item/1005007467317908.html

Remember this post?

That is your big problem.

The solution is to speed up the time it takes to perform a digital to analogue conversion, by altering the pre-scaler values on the D/A' registers.

Or better still avoid using the digital to analogue converter, and just use a straightforward digital input. These are much much faster.

Yes, if you post a link to the data sheets.

Try to imagine that forum members have no idea what parts you are talking about.

The module I have bought are these:
https://www.aliexpress.com/item/1005007467317908.html

The IR detectors in those modules are totally unsuitable for fast timing measurements, because they respond only to light modulated at 38 kHz.

This is kinda interesting re photodiode vs phototransistor:

https://www.youtube.com/watch?v=DNAgJrnj4EM

You still haven't enlightened anybody as to what type of shutter you are testing, but you might get some inspiration here. In the first post, he describes the release of the second curtain being timed against the proportion of gate exposed by the first - hardly a slit. I first thought he was alluding to a Copal shutter, which may be true, but now recalling a bad flash picture long ago, this would explain a Leica shutter. I now rather suspect that the travelling slit, while the classic explanation of focal plane shutters, actually only applies to the likes of Graflex (shooting racing cars with oval wheels).

Hello everyone, apologies for the delayed responses.
After a lot of testing, here are some updates.

Let’s start with what I have available at home, without the need to buy anything else or wait 10 days to get additional parts.
I have an ESP32 S3 and a handful of Arduino Nanos.
As for sensors, here’s what I have:

Cameras: all focal plane shutter, except the Olympus

  • Canon EOS 300V (the reference one)
  • Canon EOS 33V
  • Konica T3N
  • Konica T4
  • Konica FT-1
  • Konica TC
  • Olympus RC35 (leaf shutter)
  • Minolta 7000

Setup:

  • I used the photodiodes (both IR and visible light) with a 10k resistor and read them using analogRead, on both Arduino and ESP32. For the IR, I placed an IR emitter diode on the opposite side of the lens, and for visible light, I used a simple LED flashlight.
  • I tested the sender and receiver pair in digital mode, modulating the sender at 38 kHz with another Arduino and reading the receiver with the ESP32 (which is faster than Arduino for digital reads).
  • Finally, I connected the TEMT6000 (since it's on a breakout board) to 5V and read the output using analogRead on the ESP32.

Findings:

  • Regardless of the solution or code implementation (whether using analogRead directly or analyzing the high/low states of the digital pin), I was never able to get reliable readings faster than 1/125s: the readings increase, but with progressively larger errors (for example, 1/500 is read as 1/300, and 1/1000 as slightly over 1/500).
  • I’m confident in the accuracy of one of my cameras, as it is new (with fewer than 2000 shots, maybe 2500 after all this testing :sweat_smile:) and of the latest generation, with a shutter controlled by a microprocessor.
  • The ISO203 sensor, which paradoxically has the potential for the fastest reading based on its datasheet, is the only one I couldn’t get to work reliably. I believe it’s due to the breakout board it’s mounted on, though I didn’t try connecting it directly to the Arduino with the proper resistors (honestly, after days of testing, I lost the motivation to do so).
  • I can’t say for sure if using the photodiode suggested in one project (NJL7502L) would give reliable readings, but honestly, it costs about 15 euros in Germany, and I’m not eager to spend more money. There are alternative sensors suggested, like those from OSRAM, but the minimum order quantity is 10 pieces, and I really don’t feel like making another purchase.

Possible Conclusions:

  • None of my sensors are fast enough to read the correct times: according to the datasheets, I should be able to get reliable readings at least up to 1/500 at least with the TEMT6000, but that's not the case.
    OR
  • Due to the measurement method using flash sync and the movement of the shutter blades, it is impossible to measure reliably beyond the flash sync speed, which is 1/125 (this is still unclear to me, as many projects claim precision up to 1/1000).

How I Concluded:

In the end, I built a small circuit using the TEMT6000 and ESP32-S3, which gives me the same accuracy as the other methods I tried but it is at least more practical: very good up to 1/125s, with necessary compensation for faster shutter speeds, but repeatable and consistent. With a shutter speed of 1/1000 over 10 measurements, I get errors of about 1/150, which is acceptable for my needs.
Ultimately, I just needed an estimate of how my cameras perform to know if I need to compensate when shooting, and the accuracy is good enough for my purposes. I calibrated the software compensation on my reference camera and found that some of my other cameras are pretty close, while others are quite far off (some are even half the indicated shutter speed), so I’m satisfied overall.

Here are some photos of the device (I used a Kallax L plastic piece from Ikea as the structure :laughing:).


And here's the code:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "esp_timer.h"

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_SDA 2
#define OLED_SCL 1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

#define TEMT6000_PIN 8

volatile uint32_t shutterSpeed = 0;
TaskHandle_t displayTaskHandle;
static bool measuring = false;

const int LIGHT_THRESHOLD = 1000;
int lastLightLevel = 0;

void IRAM_ATTR checkLightChange() {
  int lightLevel = analogRead(TEMT6000_PIN);

  if (!measuring && lightLevel > LIGHT_THRESHOLD && lastLightLevel <= LIGHT_THRESHOLD) {
    shutterSpeed = esp_timer_get_time();
    measuring = true;
  } 
  else if (measuring && lightLevel <= LIGHT_THRESHOLD && lastLightLevel > LIGHT_THRESHOLD) {
    shutterSpeed = esp_timer_get_time() - shutterSpeed;
    measuring = false;
    xTaskNotifyGive(displayTaskHandle);
  }

  lastLightLevel = lightLevel;
}

void displayTask(void *pvParameters) {
  while (true) {
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

    display.clearDisplay();
    display.setCursor(0, 5);
    display.println("Shutter Speed Tester");
    display.setCursor(15, 25);
    display.println("Shutter Speed:");
    display.setCursor(20, 40);
    display.print(shutterSpeed);
    display.println(" us");

    float speedFraction = 1000000.0 / shutterSpeed;
    float correctionFactor = (speedFraction < 125) ? 1.0 :
                             (speedFraction <= 250) ? 1.04 :
                             (speedFraction <= 500) ? 1.12 :
                             (speedFraction <= 1000) ? 1.3 :
                             (speedFraction <= 2000) ? 1.5 : 1.7;
    
    display.setCursor(20, 50);
    display.print("1/");
    display.print((int)(speedFraction * correctionFactor));
    display.display();
    vTaskDelay(5000 / portTICK_PERIOD_MS);

    display.clearDisplay();
    display.setCursor(0, 5);
    display.println("Shutter Speed Tester");
    display.setCursor(50, 25);
    display.println("READY");
    display.display();
  }
}

void setup() {
  pinMode(TEMT6000_PIN, INPUT);
  Wire.begin(OLED_SDA, OLED_SCL);
  
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    while (true);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 5);
  display.println("Shutter Speed Tester");
  display.setCursor(50, 25);
  display.println("READY");
  display.display();
  
  xTaskCreatePinnedToCore(displayTask, "DisplayTask", 4096, NULL, 1, &displayTaskHandle, 0);
}

void loop() {
  checkLightChange();
}

Let me know if you have ideas or comments :slight_smile: