After I upgraded my Raspberry Pi Zero W to Nerves v1.11.0 (and more recently to v1.11.1), my application could no longer access the Raspberry Pi Camera Module 2 via the Picam library. The nerves_system_rpi0 v1.28.0 changelog explains:
Switch from the Raspberry Piβs deprecated MMAL media support to DRM and libcamera. This is a big change if you use the display or camera that has been a long time coming. Please plan some time to make the upgrade.
The libcamera documentation provided a starting point for exploring new Nerves camera interface. I tried to list available cameras:
iex(1)> cmd("rpicam-jpeg --list-cameras")
No cameras available!
0
Two weeks of intermittent experimentation yielded these observations:
- The libcamera apps were included in the Nerves firmware. (Good)
- Over a dozen
/dev/video**devices were created at boot time. (Good) - The firmware lacked an overlay for the camera. (Bad)
As described in the libcamera configuration documentation,
Raspberry Pi OS automatically detects most common cameras and loads an appropriate overlay; Nerves does not. This may be a Nerves feature (reduce bloat) or a Nerves bug (something is missing).
Pull request #221 for nerves_system_rpi4 provided an example of adding a camera overlay to fwup.conf. More generally, the Nerves documentation includes the sections Overwriting Files in the Boot Partition and Device Tree Overlays.
Copy fwup.conf and config.txt to the projectβs config directory for customization.
joel@Joels-Dell-XPS:~/hello_nerves$ cp deps/nerves_system_rpi0/fwup.conf config/
joel@Joels-Dell-XPS:~/hello_nerves$ cp deps/nerves_system_rpi0/config.txt config/
Modify config/config.exs to use the custom fwup.conf.
*** config/config.exs.orig 2024-08-07 11:00:34.563016026 -0400
--- config/config.exs 2024-08-08 11:52:27.443095965 -0400
***************
*** 12,16 ****
# https://hexdocs.pm/nerves/advanced-configuration.html for details.
! config :nerves, :firmware, rootfs_overlay: "rootfs_overlay"
# Set the SOURCE_DATE_EPOCH date for reproducible builds.
--- 12,16 ----
# https://hexdocs.pm/nerves/advanced-configuration.html for details.
! config :nerves, :firmware, rootfs_overlay: "rootfs_overlay", fwup_conf: "config/fwup.conf"
# Set the SOURCE_DATE_EPOCH date for reproducible builds.
Modify config/fwup.conf to
- Use the custom
config.txt. - Add a file resource for the IMX219 camera overlay.
- Add steps in three tasks to copy the overlay file to the firmware boot partition. The first two tasks write to the A partion; the last task writes to the B partition.
*** deps/nerves_system_rpi0/fwup.conf 2024-08-07 23:30:25.000000000 -0400
--- config/fwup.conf 2024-08-08 12:34:46.493093918 -0400
***************
*** 19,21 ****
file-resource config.txt {
! host-path = "${NERVES_SYSTEM}/images/config.txt"
}
--- 19,21 ----
file-resource config.txt {
! host-path = "${NERVES_APP}/config/config.txt"
}
***************
*** 56,57 ****
--- 56,61 ----
+ file-resource imx219.dtbo {
+ host-path = "${NERVES_SYSTEM}/images/rpi-firmware/overlays/imx219.dtbo"
+ }
+
mbr mbr-a {
***************
*** 150,151 ****
--- 154,156 ----
on-resource ramoops.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/ramoops.dtbo") }
+ on-resource imx219.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/imx219.dtbo") }
***************
*** 215,216 ****
--- 220,222 ----
on-resource ramoops.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/ramoops.dtbo") }
+ on-resource imx219.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/imx219.dtbo") }
on-resource rootfs.img {
***************
*** 287,288 ****
--- 293,295 ----
on-resource ramoops.dtbo { fat_write(${BOOT_B_PART_OFFSET}, "overlays/ramoops.dtbo") }
+ on-resource imx219.dtbo { fat_write(${BOOT_B_PART_OFFSET}, "overlays/imx219.dtbo") }
on-resource rootfs.img {
Modify config/config.txt to load the camera overlay.
*** deps/nerves_system_rpi0/config.txt 2024-08-07 23:30:25.000000000 -0400
--- config/config.txt 2024-08-09 11:11:53.793018875 -0400
***************
*** 55 ****
--- 55,59 ----
#dtparam=act_led_trigger=none
+
+ # Load the Raspberry Pi Camera 2 overlay
+ dtoverlay=imx219
+ camera_auto_detect=0
Rebuild the firmware.
joel@Joels-Dell-XPS:~/hello_nerves$ mix firmware
==> nerves
==> hello_nerves
Nerves environment
MIX_TARGET: rpi0
MIX_ENV: dev
Generated hello_nerves app
|nerves| Building OTP Release...
* [Nerves] validating vm.args
* skipping runtime configuration (config/runtime.exs not found)
* creating _build/rpi0_dev/rel/hello_nerves/releases/0.1.0/vm.args
Updating base firmware image with Erlang release...
Copying rootfs_overlay: /home/joel/hello_nerves/_build/rpi0_dev/nerves/rootfs_overlay
Copying rootfs_overlay: /home/joel/hello_nerves/rootfs_overlay
Building /home/joel/hello_nerves/_build/rpi0_dev/nerves/images/hello_nerves.fw...
Firmware built successfully! π
Now you may install it to a MicroSD card using `mix burn` or upload it
to a device with `mix upload` or `mix firmware.gen.script`+`./upload.sh`.
Upload the firmware over the network (or burn a MicroSD card).
joel@Joels-Dell-XPS:~/hello_nerves$ ./upload.sh 172.31.52.93
Path: ./_build/rpi0_dev/nerves/images/hello_nerves.fw
Product: hello_nerves 0.1.0
UUID: fcc0965d-62d2-5fa9-f3db-77924ccb963a
Platform: rpi0
Uploading to 172.31.52.93...
fwup: Upgrading partition B
100% [====================================] 31.96 MB in / 34.21 MB out
Success!
Elapsed time: 16.492 s
Connect to the Raspberry Pi Zero W via ssh. Check the version numbers, firmware partition, network addresses, etc.
Interactive Elixir (1.17.2) - press Ctrl+C to exit (type h() ENTER for help)
ββββββ ββββ
ββ ββββββ ββ
ββ ββ ββ ββ N E R V E S
ββ ββββββ ββ
ββββ ββββββ
hello_nerves 0.1.0 (fcc0965d-62d2-5fa9-f3db-77924ccb963a) arm rpi0
Serial : 00000000ae60f1ba
Uptime : 2 minutes and 26 seconds
Clock : 2024-07-11 07:02:36 UTC (unsynchronized)
Temperature : 36.3Β°C
Firmware : Valid (B) Applications : 38 started
Memory usage : 53 MB (17%) Part usage : 0 MB (0%)
Hostname : nerves-f1ba Load average : 0.10 0.08 0.03
usb0 : 172.31.52.93/30, fe80::ac91:dfff:fe14:edbd/64
Nerves CLI help: https://hexdocs.pm/nerves/iex-with-nerves.html
Toolshed imported. Run h(Toolshed) for more info.
Cross fingers for luck and list available cameras.
iex(1)> cmd("rpicam-jpeg --list-cameras")
Available cameras
-----------------
0 : imx219 [3280x2464 10-bit RGGB] (/base/soc/i2c0mux/i2c@1/imx219@10)
Modes: 'SRGGB10_CSI2P' : 640x480 [206.65 fps - (1000, 752)/1280x960 crop]
1640x1232 [41.85 fps - (0, 0)/3280x2464 crop]
1920x1080 [47.57 fps - (680, 692)/1920x1080 crop]
3280x2464 [21.19 fps - (0, 0)/3280x2464 crop]
'SRGGB8' : 640x480 [206.65 fps - (1000, 752)/1280x960 crop]
1640x1232 [83.70 fps - (0, 0)/3280x2464 crop]
1920x1080 [47.57 fps - (680, 692)/1920x1080 crop]
3280x2464 [21.19 fps - (0, 0)/3280x2464 crop]
0
Stream video from the camera to demonstrate that it works. The Raspberry Pi camera software documentation provides the commands. In iex, enter a shell command:
iex(10)> cmd("rpicam-vid -t 0 --inline --listen -o tcp://0.0.0.0:8888")
[0:32:52.216628000] [206] INFO Camera camera_manager.cpp:284 libcamera v0.2.0
[0:32:52.318085000] [207] WARN RPiSdn sdn.cpp:40 Using legacy SDN tuning - please consider moving SDN inside rpi.denoise
[0:32:52.337195000] [207] WARN RPI vc4.cpp:392 Mismatch between Unicam and CamHelper for embedded data usage!
[0:32:52.341804000] [207] INFO RPI vc4.cpp:446 Registered camera /base/soc/i2c0mux/i2c@1/imx219@10 to Unicam device /dev/media3 and ISP device /dev/media1
[0:32:52.342112000] [207] INFO RPI pipeline_base.cpp:1102 Using configuration file '/usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml'
On another computer open the VLC Media Player. Select Media/Open Network Stream from the menu. Enter the media stream URL tcp/h264://nerves.local:8888. VLC will display the delayed video stream after a few seconds. Meanwhile, on Nerves, rpicam-vid will log the connection and begin logging frames sent.
Preview window unavailable
Mode selection for 640:480:12:P
SRGGB10_CSI2P,640x480/0 - Score: 1000
SRGGB10_CSI2P,1640x1232/0 - Score: 1444.49
SRGGB10_CSI2P,1920x1080/0 - Score: 1636.67
SRGGB10_CSI2P,3280x2464/0 - Score: 2162.49
SRGGB8,640x480/0 - Score: 2000
SRGGB8,1640x1232/0 - Score: 2444.49
SRGGB8,1920x1080/0 - Score: 2636.67
SRGGB8,3280x2464/0 - Score: 3162.49
[0:41:48.022678000] [206] INFO Camera camera.cpp:1183 configuring streams: (0) 640x480-YUV420 (1) 640x480-SBGGR10_CSI2P
[0:41:48.029428000] [207] INFO RPI vc4.cpp:621 Sensor: /base/soc/i2c0mux/i2c@1/imx219@10 - Selected sensor format: 640x480-SBGGR10_1X10 - Selected unicam format: 640x480-pBAA
#0 (0.00 fps) exp 33289.00 ag 8.00 dg 1.00
#1 (29.98 fps) exp 33289.00 ag 8.00 dg 1.00
#2 (29.97 fps) exp 33289.00 ag 8.00 dg 1.00
#3 (29.98 fps) exp 33289.00 ag 8.00 dg 1.00
When the VLC playback stops, rpicam-vid will terminate because the TCP connection is lost. I consider this to be a successful conclusion.
#231 (29.97 fps) exp 33289.00 ag 8.00 dg 1.00
#232 (29.97 fps) exp 33289.00 ag 8.00 dg 1.00
#233 (29.97 fps) exp 33289.00 ag 8.00 dg 1.00
terminate called after throwing an instance of 'std::runtime_error'
what(): failed to send data on socket
134
For extra credit and in the interests of science, I experimented to learn which of these config.txt lines were necessary.
# Load the Raspberry Pi Camera 2 overlay
dtoverlay=imx219
camera_auto_detect=0
The table below shows whether libcamera detected the camera for various combinations of retaining, omitting, or altering the configuration lines. Libcamera detects the camera when auto-detection is enabled or the overlay is explicitly loaded.
| dtoverlay=imx219 | Omit dtoverlay= | |
|---|---|---|
| camera_auto_detect=0 | Detects camera | No camera available |
| camera_auto_detect=1 | Detects camera | Detects camera |
| Omit camera_auto_detect= | Detects camera | No camera available |






















