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:
- a switchable filter on a Pi 4 → fixable, by hand or with one wire
- a fixed daylight filter bonded to the sensor → not fixable at all; the pods will never be visible to it and the board has to be replaced
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.
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.
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.
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
- Raspberry Pi Forums — night mode on an OV5647 IR-CUT camera with a Pi 4B: “Pi 4 doesn’t support camera LEDs which means it can’t control the IR filter. Some of those cameras can have a position for a wire that allows manual control if connected to a GPIO.”
- Raspberry Pi Forums — camera LED and shutdown control: the v1-only LED, and which boards stopped driving the line.
- Raspberry Pi Forums — using CSI pin 12: “Several of the Pi boards no longer even drive the line (eg Pi4, 02W).” That the pin is unreachable in software is verified separately by enumerating the board’s GPIOs, above.
- Arducam — Raspberry Pi camera pinout: the full 15-pin CSI assignment.
- Raspberry Pi — camera documentation: official modules and their NoIR variants.