Open-source project
veebch/photon avatar
veebch/photon

Photon: a DIY incident light meter built on a Raspberry Pi Pico

A DIY incident light meter

600 stars63 forksPythonGPL-3.0

At a glance

What is it?
Photon is an open-source incident light meter that reproduces the core function of professional meters with a Raspberry Pi Pico, a colour OLED, a Pimoroni BH1745 light sensor and a rotary encoder. It measures ambient brightness and the red, green and blue components of the light, and it needs soldering.
Who is it for?
Photon suits a film photographer who wants a spot reading instead of the camera's own metering guess, and who is willing to solder a few joints and print an enclosure, because the whole device is a Pico, a sensor breakout and a screen. It does not suit someone who wants a device they can buy tomorrow, and it does not suit a video shooter who needs fast refresh readings, since the calculation path is built around an incident reading taken at the subject.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 47 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a spot reading beats the camera's internal meter

Photon is described as an open-source incident light meter, and the Background section explains the motivation in terms of film photography with old cameras.

The argument is that the sophisticated computation baked into modern cameras devotes a lot of effort guessing how much light is falling on the subject, and that if you have the option of getting to the subject and taking a reading, no guessing is required. Everything becomes easier.

That is a specific and defensible claim, and it is narrower than it first looks. A spot meter tells you the illuminance at one point. It does not integrate over the frame, so it says nothing about how the light falls across the scene or what the camera's own reflective metering would average. For a photographer working with a zone system, or shooting on a sunny 16 that requires knowing the actual level rather than the reflected level, that is exactly the number you want.

The device reproduces some of the functionality of more expensive tools using a few inexpensive and readily available parts. Currently it measures ambient light brightness, as well as the red, green and blue components of the light, which the README says might allow White Balance readings in future iterations.

So the colour measurement is present but the white balance feature is not. That distinction matters, because a photochromic sensor already gives you the RGB channels, and the work of turning them into a usable white balance or a colour temperature is where most of the engineering would sit. The sensor is the easy part.

The project is by veebch, it is GPL-3.0 licensed with a LICENSE file at the root, the primary language is Python, and the last push was on 2026-09-06. There are no GitHub releases, so the code lives on the branch rather than in tagged artefacts.

The soldering requirement is acknowledged up front rather than hidden, with a link to a video on it and the note that it is easy.

The bill of materials is six parts and one optional shim

The component list is short enough that the total cost of the device is dominated by two modules.

The Raspberry Pi Pico, or a Pico Lipo so you do not need the separate power shim. A Waveshare 128x128 full colour OLED screen. The Pimoroni LiPo Pico shim, only if you are using a plain Pi Pico. A rotary encoder for adjusting settings and changing the priority mode. Two momentary switches, one keyboard switch to measure light and one 6x6mm microswitch to set the ISO adjust mode. The Pimoroni BH1745 luminance and colour sensor breakout. And a LiPo or LiIon battery.

Two details in that list carry design decisions rather than just part numbers. The encoder is described as both adjusting settings and changing priority mode, which connects it to the calculation appendix, where the same encoder choice determines whether the remaining value is computed as a shutter speed or an aperture. And the Pico Lipo variant exists so that the battery and the shim are one board rather than two, which removes a solder joint from the build.

The BH1745 is the sensing element and it does the colour work, which is why the software section insists on a specific firmware image. You need to use the Pimoroni image to be able to use the Pimoroni drivers for the light sensor, so the device is not running stock MicroPython with a bit-banged sensor library.

The case is a 3D printed enclosure, with stl files in the cases directory, and the README says to fit the parts into it once you have tested that things are working. The order is deliberate: test on the bench first, then print.

The README also flags a coming item: COMING SOON, a custom sensor with Flash Mode, linking to a separate project page. That is the one piece of the roadmap stated in the README, and it is a different sensor rather than a firmware update to the existing one.

Wiring is four tables and one caution about VCC and GND

The hardware section is mostly pin tables, and they are worth reproducing because they are the part of the build you cannot infer.

The light sensor connects to GPIO 4 and 5, which are SDA and SCL on the BH1745. The OLED connects to five pins: 19 for DIN and MOSI, 18 for CLK and SCK, 17 for CS, 20 for DC and 21 for RST, with the corresponding OLED pin numbers 25, 24, 22, 26 and 27. The rotary encoder uses GPIO 6 for CLK, 7 for DT and 8 for SW. The two switches connect one end to GND and the other to GPIO 15 for measure and GPIO 22 for ISO.

There is a caution that applies across the board: VCC and GND are also connected to the Pico for all except the switch. So the switch is the only component that does not get a direct supply pair, which is the normal arrangement for a pull-up switch input but is the sort of detail that produces a confusing fault if you get it wrong.

The power arrangement comes first in the assembly order. If you are using the shim, solder it to the Pico following the Pimoroni instructions, then connect the LiPo or LiIon battery to the shim.

So the assembly sequence is: shim and battery first, then the sensor, the display, the encoder and the switches on their listed GPIO pins, then test, then print the case and squeeze the parts into it.

The README is direct about the difficulty. There is soldering involved, but the linked video is presented as evidence that it is easy, and the more in-depth description of the incident metering case is in a second video.

Flashing the Pico needs the Pimoroni image and an upload tool

The software section has two parts: the firmware image and the upload path.

For the image, download a uf2 image from the Pimoroni GitHub repository releases and install it on the Pico following those instructions. The README is explicit about why: you need the Pimoroni image to be able to use the Pimoroni drivers for the light sensor.

The repository itself is then cloned.

code
cd ~
git clone https://github.com/veebch/photon.git
cd photon

On macOS or Linux you prepare the local upload environment once, in a virtual environment with the development requirements installed.

sh
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt

Uploading Photon and restarting the Pico is then a single script.

sh
.venv/bin/python tools/upload_pico.py

The uploader normally detects the only connected MicroPython board. If more than one serial device is connected, you select the Pico explicitly, and the port flag is passed to the uploader.

sh
.venv/bin/python -m mpremote devs
.venv/bin/python tools/upload_pico.py --port /dev/cu.usbmodem101

Linux port names commonly look like /dev/ttyACM0. Two operational notes: close Thonny, serial monitors and other programs using the Pico before uploading, because a board held open by another program will not be written to, and add --dry-run to inspect the commands without changing the board.

After the upload, all required files are copied to the Pico, which is then soft-reset so main.py starts, and it will also autorun after later power cycles. That autorun property is what turns the device into a self-contained tool rather than something that needs a host attached.

The reading is an exposure value derived from illuminance and ISO

The appendix is where the numbers come from, and it is short enough to follow.

The simple calculations that lead to a reading are based on the Wikipedia entry on exposure value. The illuminance returned by the Pimoroni BH1745, written as L, is converted to an exposure value for ISO 100 using E_v equal to the base 2 logarithm of L divided by C, where C is the light meter calibration constant.

That exposure value is then adjusted for the chosen ISO, with E_ISO equal to E_v plus the base 2 logarithm of ISO divided by 100. The ISO 100 reference is what makes the second step a single logarithm: the sensor gives you a light level, the first formula converts it to a standard exposure value at ISO 100, and the second shifts it for the ISO you dialled in.

Then, depending on the priority on the light meter, the remaining value is calculated as a shutter speed t equal to N squared divided by two to the power of the adjusted exposure value, or as an aperture N equal to the square root of t. That is where the rotary encoder comes back in: choosing the priority decides which of the two you are shown, and you read the other one off the display.

So the device is a two-value output. You pick either shutter speed or aperture priority, set the ISO, take a reading, and the meter gives you one of the exposure triangle values with the others derivable.

The dependency on C is the part an adopter should think about. The calibration constant is a property of the light meter design, and it is a single number in a formula that everything else flows from. The README points at the Wikipedia article on light meter calibration constants rather than giving a value, which means the constant is the thing to check against the sensor and the calibration approach if your readings look offset from a reference meter.

A tutorial on getting the most out of the meter is in the docs section of the repository.

Where Photon is the wrong tool

The first wrong-tool case is a photographer who wants a device now rather than a project. Photon is a build, not a product. You solder, you flash a specific firmware image, you print a case, and you learn the pin table. If you need an incident meter this afternoon, buying one is cheaper than the weekend this takes.

The second case is video. The calculation path is built around an incident reading taken at the subject, which is the model for still photography where you can walk up to the scene and measure. Video metering is a continuous thing across changing light, and the README does not describe any averaging or refresh behaviour.

The third case is a digital photographer who already has a spot meter in the camera. A camera with a spot metering mode does the same job without a build, and the argument the README makes about avoiding the camera's guesswork is aimed at cameras without one, particularly old film cameras with no metering at all.

The fourth case is someone who needs white balance now. The README is clear that the RGB components are measured but that white balance readings might allow in future iterations. So you get the raw colour channels and the colour work is not done.

The build also has failure modes worth planning for. The solder joints on a Pico and a display module are the first thing to get wrong, and a miswired I2C sensor produces no reading rather than a clear error, so the wiring tables are worth checking twice before powering up. The board also needs the Pimoroni uf2 image rather than stock MicroPython, so the firmware you flash depends on the driver you use, and the sensor will not work under the default image even though everything else will.

On contribution, the README describes a fork and feature branch workflow with pull requests welcome and encouraged, and it adds an unusual invitation: if that all looks a bit too programmy, and you have some photography expertise that you think could be embedded in the code, then raise an issue on GitHub or contact the author. That is a genuine invitation to a domain expert, and it hints that the remaining gaps in the project are as much about photography practice as about code.

On licence, the project is under the GNU General Public License Version 3, stated in the README and with a LICENSE file at the root. For a personal build that is a non-issue. For anyone planning to publish a commercial version of the design or sell the assembled device, the copyleft obligation attaches to the derivative work, and that is a question for whoever handles licence compliance rather than for this repository.

Editorial conclusion

Photon suits a film photographer who wants a spot reading instead of the camera's own metering guess, and who is willing to solder a few joints and print an enclosure, because the whole device is a Pico, a sensor breakout and a screen. It does not suit someone who wants a device they can buy tomorrow, and it does not suit a video shooter who needs fast refresh readings, since the calculation path is built around an incident reading taken at the subject. Before building, read the appendix on the exposure value calculation, because the reading depends on the light meter calibration constant and on the priority mode you select with the rotary encoder, and note that the sensor driver requires the Pimoroni uf2 image rather than stock MicroPython.

Frequently asked questions

What is Photon?

Photon is an open-source incident light meter. It reproduces some of the functionality of more expensive tools using a Raspberry Pi Pico, an OLED screen, a Pimoroni BH1745 colour sensor, a rotary encoder and two switches, and it measures ambient light brightness along with the red, green and blue components.

What parts does a Photon light meter need?

A Raspberry Pi Pico or Pico Lipo, a Waveshare 128x128 full colour OLED, a Pimoroni BH1745 luminance and colour sensor breakout, a rotary encoder, two momentary switches, a LiPo or LiIon battery, and the Pimoroni LiPo Pico shim if you are not using the Pico Lipo.

How do I flash the firmware onto the Photon Pico?

Install a uf2 image from the Pimoroni GitHub releases onto the Pico first, because the Pimoroni drivers for the light sensor need that image. Then clone the repository, create a virtual environment, install requirements-dev.txt, and run .venv/bin/python tools/upload_pico.py, adding --port to select the board if more than one serial device is connected.

How does Photon calculate the exposure reading?

The illuminance from the BH1745 is converted to an exposure value at ISO 100 using the base 2 logarithm of the illuminance divided by the light meter calibration constant, then adjusted for the chosen ISO, and then converted into either a shutter speed or an aperture depending on the priority selected with the rotary encoder.

Does Photon support white balance readings?

Not yet. The device measures the red, green and blue components of the light, and the README says this might allow White Balance readings in future iterations. The colour measurement is present, the white balance feature is not.

What license is Photon released under?

Photon is released under the GNU General Public License Version 3, stated in the README with a LICENSE file at the root of the repository. The project has no GitHub releases, and the last push was on 2026-09-06.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. README
  4. veebch/photon on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/veebch-photon.svg)](https://hysenlabs.com/projects/veebch-photon)

Was this useful?