The Pi 4 killed IR night vision and nobody updated the boxes

The board in question here is an 8 mm OV5647, 5 MP module — the 52Pi ER-0021 design, resold under a dozen names — paired with two screw-on infrared LED pods and an IR-corrected M12 lens, running on a Raspberry Pi 4. In daylight it is excellent. At dusk the pods light up by themselves — hold a phone camera to them and you see the bright violet glow that means 850 nm is pouring out. And the picture goes black anyway.

Nothing in the logs is wrong. The sensor is bound, the driver is loaded, the stream is up, exposure and gain are pinned at maximum. Every component reports healthy because every component is healthy. The infrared never reaches the sensor.

The cause is one control line, and on a Raspberry Pi 4 nothing drives it.

First problem: working out what you own

Before any of the electrical detail, a warning that cost me more time than the actual fault. These boards are one OEM design resold under many labels, and the listings do not agree with each other about the single fact that matters — whether there is a switchable filter in there at all.

The same 8 mm OV5647 module is sold by one shop as “OV5647 Camera Module with IR-CUT, telephoto lens, manual switching for night view mode”, while the manufacturer’s own wiki page for that SKU lists the entire specification as “Weight: 55 g. Size: 4.3 × 3.5 × 3.2 cm. Chip: OV5647(5MP)” and never mentions IR-CUT, night vision or a filter anywhere. A third listing for a visually identical board describes automatic day/night switching driven by a photoresistor.

Three descriptions, three different mechanisms, one board. And it matters enormously, because the fixes diverge completely:

Nothing in software distinguishes these. dmesg, libcamera, v4l2-ctl and the vendor page all look the same either way. The only reliable test is mechanical: unscrew the lens barrel and look into the mount with a torch. A small carriage you can see and nudge sideways means a switchable filter. A square of glass fused to the sensor means there is nothing to switch.

Do that first. Everything below assumes you have a switchable filter and are trying to switch it.

Where the filter control actually lives

The camera talks to the Pi over a 15-pin flat cable. Eleven pins carry image data, clock and ground; two are I²C for configuring the sensor; one is power. That leaves exactly two general-purpose control lines, and the entire day/night mechanism hangs off one of them.

The 15-pin CSI connector, with pin 12 — the LED indicator line — highlighted as the one that carries filter control 15-pin CSI ribbon 123 456 789 1011 131415 12 ground · MIPI data · MIPI clock IO0 pwr en I²C · 3V3 CAM_IO1 — "LED indicator" the line the IR-CUT coil is wired to
Two control pins on the whole connector. Vendors put the filter on the one meant for a status LED.

Pin 12 was designed for exactly one job: driving the small red “camera active” LED on the 2013 Camera Module v1 — in a Raspberry Pi engineer’s words, “literally connected between that line and ground via a suitable resistor.” Third-party vendors noticed it was otherwise idle and wired their filter coils to it.

That is also why the software switch is called disable_camera_led. It was never a filter setting. It toggles an LED pin that happens to have a filter motor on the far end.

Pi 3 versus Pi 4, drawn

Same camera board. Same ribbon cable. Same disable_camera_led=1 in config.txt. The only thing that changes is what sits behind the socket.

On a Pi 3, CSI pin 12 reaches GPIO 134 through an I²C expander and the filter switches; on a Pi 4 no GPIO reaches that pin at all, so nothing can drive it Raspberry Pi 3 — filter switches camera board IR-CUT coil pin 12 CSI socket CAM_IO1 I²C expander GPIO 134 software can drive it Raspberry Pi 4 — dead end camera board identical pin 12 CSI socket CAM_IO1 NOT DRIVEN no GPIO on either chip reaches this pin Pi 1 Model B → GPIO 5  ·  Pi B+ / Pi 2 → GPIO 32  ·  Pi 3 → GPIO 134 (expander)  ·  Pi 4 & Zero 2 W → nothing The number changed every single generation. It was never an interface — just a pin that happened to be reachable.
The camera, the cable and the config line are identical in both rows. Only the destination differs.

Why the line went away

That control line does not come from the SoC. It lives on a small I²C GPIO expander with exactly eight lines. On a Pi 4, all eight are spoken for — this is the kernel’s own view, from a running board:

gpiochip1: 8 GPIOs, raspberrypi-exp-gpio
 gpio-0   BT_ON            Bluetooth power
 gpio-1   WL_ON            Wi-Fi power
 gpio-2   PWR_LED_OFF      power LED
 gpio-3   GLOBAL_RESET     board reset
 gpio-4   VDD_SD_IO_SEL    SD voltage select
 gpio-5   CAM_GPIO         camera power enable  ← pin 11, the survivor
 gpio-6   SD_PWR_ON        SD card power
 gpio-7   SD_OC_N          SD overcurrent detect

Eight lines, eight jobs. Restoring pin 12 would have meant dropping Bluetooth power, or SD card power, or board reset — or fitting a larger expander — in order to serve a decorative LED that the then-current camera module (IMX219, shipping since 2016) had not used in three years. The Pi 4 is also a ground-up design rather than an edited Pi 3, so the line was never so much removed as never re-added.

Given that table, cutting it was the right call. The failure is downstream of it.

Why nobody told you

Vendors built a mechanical filter control on top of a cosmetic LED output. Watch the interface decay across generations: GPIO 5, then GPIO 32, then GPIO 134 behind an expander, then nothing. That was never a contract; it was an implementation detail that happened to be reachable.

And then they kept selling it. The Pi 4 launched in June 2019. Manufacturer documentation still lists disable_camera_led=1 as the primary switching method. Resellers still print “supports Raspberry Pi 4” on boxes containing a filter that cannot switch on one. The workaround — one pad wired to a header GPIO — has been known the whole time and appears, if at all, as an undocumented footnote.

No software patch can exist

This is worth stating flatly, because it is the kind of problem that looks patchable and swallows an evening.

Enumerate every GPIO a Pi 4 has — 58 on the SoC plus 8 on the expander, 66 in total — and grep the lot for camera lines. You get exactly one hit:

gpio-5  (CAM_GPIO | regulator-cam1)  out hi     ← pin 11, power enable

Pin 11 is live but claimed by the camera’s own power regulator, and its sysfs state is read-only. Pin 12 has no GPIO number on either chip, at any offset. There is nothing for gpioset, pinctrl, raspi-gpio, /sys/class/gpio, a device-tree overlay or a custom kernel module to address.

Overlays reassign pins that already have traces. They cannot fabricate one. A missing conductor is the rare class of problem software genuinely cannot reach.

The workaround

Bypass the dead pin entirely. Most of these boards expose the filter terminal as a small plated through-hole beside the IR-LED connector; run a wire from it to any free pin on the 40-pin header and drive it yourself.

The workaround: a jumper wire from the camera board's filter pad to a free GPIO on the 40-pin header, bypassing the dead CSI pin 12 entirely camera board filter pad pin 12 — abandoned one jumper wire 40-pin GPIO header any free GPIO Better than the original design: a header GPIO can be driven at any moment, where disable_camera_led only ever applied at boot.
Route around the missing conductor rather than trying to conjure it back.

Then it is ordinary software again:

pinctrl set 17 op dl     # night: filter out
pinctrl set 17 op dh     # day:   filter in

Polarity varies between board revisions, and vendor documentation contradicts itself on it — drive it one way, look at the picture, flip it if wrong. Ten seconds settles what the datasheet won’t.

Three fixes, ranked by cost

Move the filter by hand — free, two minutes. Unscrew the lens barrel and the filter carriage sits directly behind it. Push it aside with a toothpick; never metal, and never touch the sensor glass. These carriages are level-held, so with nothing driving them they stay where you put them. Infrared works from then on, permanently. The cost is that daytime colour goes pink, because nothing blocks IR in daylight either — which is exactly what every NoIR camera looks like, and why the ov5647_noir.json tuning file exists.

One jumper wire — free, ten minutes. The diagram above. Restores real automatic day/night switching, and makes you immune to whatever happens to the CSI connector in future models. This is the fix the Raspberry Pi forums point people to.

Swap to a NoIR module — €15 to €30. A NoIR camera has no filter at all, so there is nothing to switch and the missing pin stops mattering. An M12-mount NoIR OV5647 board keeps your existing lens, IR pods and ribbon; you replace only the board. Same permanent pink daylight as option one, by design.

A fourth option exists and is not worth it: disable_camera_led=1 genuinely works on a Pi 3. Downgrading a whole computer to recover one line — and still needing rpi3-gpiovirtbuf to reach it through the expander — costs more than a jumper wire.

Check your own board before you open it

Some modules sold with IR pods have no switchable filter at all — just a daylight filter bonded to the sensor, the pods being a bolt-on accessory. Vendor pages are frequently silent about which you have.

Unscrew the lens and look into the mount with a torch. A carriage you can see and move means options one and two apply. Glass fused to the sensor means only option three will help.

Measure it, don’t squint at it

A night-time preview tells you nothing useful. Grab a frame and read its mean luminance instead:

ffmpeg -rtsp_transport tcp -i rtsp://<host>:8554/cam \
       -frames:v 1 -f image2 frame.jpg

With a subject one to three metres away and the pods lit, a working IR path lands in the tens on a 0–255 scale. A blocked one sits near 1, indistinguishable from the sensor’s noise floor.

One trap worth knowing, because it is easy to fool yourself: a brightness jump is not proof of infrared. Check colour as well. 850 nm illumination lands on all three Bayer channels at once and comes out near-monochrome, so if channel means diverge and hue varies across the frame, visible light got in from somewhere and you have measured the wrong thing.

Sources

hardware