Introduction: DIY Photographic Lightmeter

About: I'm a 36 year old DIY enthusiast from Vienna Austria with a strong background in mechatronics/automation. My DIY field is mainly video/audio/motion control. If you want to see what i do between posting it as…

This Instructable shares some ideas on building a simple small and cheap incident lightmeter.

As Instructables won't let me insert my own videos, try this link :

https://youtu.be/avQD10fd52s

The goal for me was a light-meter to accompany my Bronica ETRSi medium format film camera.

Things i wanted it to feature:

  • single ASA (100) because i almost only use ASA 100 film
  • as small as possible
  • only give me combinations that my Bronica can reproduce, which means f2.8-f22 and 1sec to 1/500th sec
  • no nonsense features, except plain times and aperture values

Things i used:

  • Adafruit(Vishay) VEML 7700 digital Lux-meter ( around 5$)
  • Adafruit Trinket M0 micro controller (around 9$)
  • 128x32 OLED display (around 10$)
  • a pushbutton to temporarily turn it on (some cents)
  • a tiny piece of strip-board, because i try to not use cables, but you can certainly use cables too

Step 1: Basic Calculations | Lux to EV

The sensor i bought uses two features that let me decide on it:

  • outputs 16bit lux values instead of "dimension-less" light values
  • outputs the values via I2C

A photographic light meter uses Exposure Values (EV) the sensor i bought uses Lux values, which is a complete different scale. So the first step is to get EVs from the Lux values provided the sensor.

A quick glimpse on wikipedia and you can find a formula for incident metering and convert EV to Lux:

E =2.5 * 2^EV

where E is measured in Lux.

As we already got the Lux value from the sensor and want the EV value, we have to re-form the formula, which gets us to:

EV = log2 (E/2.5)

So that is the first calculation that has to be done in order to get photographic values out of the lightmeter.

In the attached lookup table you can see all values that will be used in this lightmeter, together with the according Lux and EV values.

Step 2: Presenting the Values on the Display | Adafruit GFX Library

I first tried to present the values in whole steps, because thats what i can set my Bronica to, but that lead me to a problem:

  • Let's assume the Lux sensor outputs a value of exactly 20480, that would mean its exactly EV 13 so i could for example set my camera on f4 and 1/500th of a second and would be good to go.

  • Next, let's assume the Lux sensor would output 20479 Lux, 1 Lux under EV13, that would output an EV value of 12, but its just a Lux away from EV13.

So i would set my camera on f2.8 and 1/500th of a second which would overexpose 1 stop without me even knowing how close i was to EV13.

Conclusion: we need some kind of analog display of the values to at least see how close or far away the meter is from the next or previous EV step.

After trying to use the built in letters and font of the GFX library i decided to use two custom graphics that will move across the OLED screen.

One for the aperture values, one for the times.

The GFX Library uses 8bit values to present graphics, so i made an xls sheet (see image above).

  • every value has the exact same amount of pixels per value
  • times and apertures have exactly the same amount of values per row
  • I added the neccessary "B" on the beginning of each byte and the "," at the end
  • I then exported it to a plain text and voila: i got the third graphic attached

Time values start by 1/8th a second and aperture values start by f2.8.

Using the lookup table of the previous step we know this represents 160 Lux or EV6.

The darkest values would then be f22 and 1/500th of a second.

Again via the lookup table we can see that means 655360 Lux or EV18

So far so good.

So on EV6 the aperture graphic has to be on the far left, the times on the far right, and vice versa on EV18

Step 3: Reading and Compensating of the Lux Values | VEML7700

While scrolling through the datasheet of the Vishay VEML7700 Adafruit uses for their board, i found a rather disturbing notice:

The sensor only works linear between 0 and 1000Lux (!)

see the screenshot with the orange (linear) line and the blue (actual sensor output) line

Sunlight (EV15) is around 80.000 Lux, which means without compensation of the non-linear part of the sensor it would be complete useless as a light meter.

Vishay knows that, so they provided their customers with another pdf called Designing the VEML7700 Into an Application.

In this pdf you can find a formula to compensate the sensors non-linearity:

LUX_CORR = 6.0135e-13*pow(LUX,4)-9.3924e-9*pow(LUX,3)+8.1488e-5*pow(LUX,2)+1.0023*LUX

Where LUX_CORR is the corrected Lux-Value and LUX is the value the sensor outputs.

Those are the variables i used, the used different ones in their sheet.

What bugs me a bit is that Adafruit doesn't mention this with one single word on their page, their documentation, their library or elsewhere.

So the first few days i was wondering why my lightmeter only outputs 20000 Lux maximum even in direct sunlight.

If you take a look at the graph with the red and the blue line you can see why: because it cant go higher without the compensation formula.

But there is another hint hidden in the documentation of the sensor:

This compensation formula only works if you set the sensor to 25ms and a gain ratio of 1/8.

Thats done pretty easily with Adafruits library by adding :

veml.setGain(VEML7700_GAIN_1_8);
veml.setIntegrationTime(VEML7700_IT_25MS);

in your void setup()

So after setting it to 1/8 and 25ms and adding the compensation formula you can measure up to 120000 lux, way enough to cover sunlight at 80-100k Lux.

Step 4: Arduino / C-code

As it depends on your used display and preferred controller i won't go too much into detail, just a few thoughts and hints to add, especially when using the Adafruit libraries and the 128x32 px OLED:

  • in the void setup:


i set the VEML library-part to :

veml.setGain(VEML7700_GAIN_1_8);

veml.setIntegrationTime(VEML7700_IT_25MS);

veml.setLowThreshold(10000);

veml.setHighThreshold(20000);

veml.interruptEnable(true);


  • in the void loop:

be sure to add the compensation:



int LUX_CORR = 6.0135e-13*pow(LUX,4)-9.3924e-9*pow(LUX,3)+8.1488e-5*pow(LUX,2)+1.0023*LUX;



to get EVs from Lux use this line:

float EV = log2((LUX_CORR/2.5));



  • moving the bitmaps

to make sure the bitmaps only move when values are between 160Lux and 655360Lux as stated in a previous step, wrap it in an if clause like that:

if ( LUX_CORR > 159 && LUX_CORR <655361)


Next we need to map the EV values to coordinates, as the range of the EVs are double digits and we want to move them from out of the display over 128px across the whole display we need bigger values.

As we already got a float number we just multiply that by 100 and use that integer to map the coordinates

int EV_DSPL = EV*100;


and:


TIME = map(EV_DSPL, 600, 1900, -260, 39);
APERTURE = map(EV_DSPL, 600, 1900, 39, -260);


As you can see in my case the minimum position of the bitmap would be -260px and the maximum would be 39px

What also can be seen here is that i switched the coordinates so that the two bitmaps move in the opposite direction

Next we need to move the bitmaps according to the coordinates by :

display.drawBitmap((TIME), (0), TIMES_bmp, 352, 16, 1);
display.drawBitmap((APERTURE),(15), APERTURES_bmp, 352, 16, 1);



And thats all that needs to be done


As a bonus i display straight EV and Lux values when the sensor outputs Values under 160Lux, just because i wanted to see stuff when testing it.

Step 5: Putting It Together

As both, the display and the sensor are using I2C to communicate, building the actual hardware is as simple as it can possibly be.

Just connect the Data,Clock ground and 3V lines with the Arduino and you are set to go.

I added a graphic how i did it with a stripboard, but as is said earlier you can use cables or even build a wing for it, it all depends which controller and display you use.

On my graphic, the white dots are supposed to be connected to the display and sensor and the yellow dots connect to the the Trinket.

The only exeption would be the data-pin of the I2C line that connects to the display, that pin also connects to the Trinkets data pin.

I chose not to use an on/off switch but instead use a pushbutton and two 3V button cells to temporarily power it up as long as i press the button. It powers up in under 1/10 of a second so thats quick enough for me to spare a button and make it smaller.