30 Dec 2020

Multiple DS18B20 Temperature Probes


In my last post I hooked up one DS18B20 to a RaspberryPi. The neat thing about the DS18B20 and the 1-wire protocol is that multiple such devices can be connected together in a star topology. What's really great about this is that identifying an individual sensor doesn't depend on its position in a chain (for example) each device has its own unique 64-bit identifier. This means the layout of the probes doesn't have to be determined ahead of time. Also the length of the wires attached to the sensor can vary by a considerable amount. Therefore sensors near and far can be connected to the same microcontroller.

I had a number of varieties of protoboard, one of which is this type which features long buses:


This style of board works perfectly for this scenario.

I also happened to have a bunch of these x3 male headers. Most likely from some servo project.


Wouldn't it be great if there were a way to add a connector to the end of the temperature probes so they could plug into this header easily? As it turns out, it's Pololu to the rescue! Among the many things they sell, they also have crimp connector housings in various configurations, and all the other accessories to go with it.


I know this probably sounds like I have some relationship with Pololu but I don't. I know other electronics suppliers provide such items too, but Pololu's site makes it quite easy to buy the right things the first time around and give me the confidence to try this out (thanks to their supporting videos and information). After a little practice on test wires I'm able to add connectors to my temperature probes:


Cutting off a piece of protoboard and soldering on the header gives me a hub into which to plug all the temperature probes. Since we need a 4.7k pull-up resistor in the design anyway, I might as well add it to the hub so it's ready to go:


You'll notice there's no picture of the underside 😉 It's bad enough showing this hack job from the top, I'm not about to show the disaster on the other side. Thankfully all the buses are still separate from each other, and that's all that matters 😂

With the pull-up resistor in place I'm forcing the middle bus to be the ground. The connectors aren't keyed, so I just have to line up the VDD and DQ lines together myself.


…and with the RaspberryPi:


Now when I boot my device I get a kernel log message about my GPIO setup and one log message each for each of the sensors attached to my hub:

[    5.135283] gpio-25 (onewire@19): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    5.202823] w1_master_driver w1_bus_master1: Attaching one wire slave 28.01131bb70fee crc 30
[    5.497062] w1_master_driver w1_bus_master1: Attaching one wire slave 28.01131b7ad963 crc f6
[    5.599175] w1_master_driver w1_bus_master1: Attaching one wire slave 28.01131b62790b crc 83

Note: these messages don't appear side-by-side in the log, but they do appear in this order.

Now when I look in the 1-wire sysfs location I find:

root@raspberrypi3-64:~# cd /sys/bus/w1/devices/
root@raspberrypi3-64:/sys/bus/w1/devices# ls -l
lrwxrwxrwx    1 root     root             0 Dec 29 04:22 28-01131b62790b -> ../../../devices/w1_bus_master1/28-01131b62790b
lrwxrwxrwx    1 root     root             0 Dec 29 04:22 28-01131b7ad963 -> ../../../devices/w1_bus_master1/28-01131b7ad963
lrwxrwxrwx    1 root     root             0 Dec 29 04:22 28-01131bb70fee -> ../../../devices/w1_bus_master1/28-01131bb70fee
lrwxrwxrwx    1 root     root             0 Dec 29 04:22 w1_bus_master1 -> ../../../devices/w1_bus_master1

To read the temperatures of the probes I could visit each probe individually and simply "cat" the "temperature" file. This will cause the kernel to perform the "Convert T [44h]" command followed by the "Read Scratchpad [BEh]" command on each probe individually.

A small performance improvement can be had by visiting the master driver device and issuing a "trigger" command to its "therm_bulk_read" sysfs entry, then reading the temperatures from the probe devices individually. This causes the "Convert T [44h]" command to be issued to all the temperature probes connected to this master.

Notice that once the bulk command is given, the value of "therm_bulk_read" will read -1 if at least one sensor is still performing the conversion, 1 if one of the sensors has not had its temperature read out, and 0 once all the temperatures have been read for the most recent bulk conversion:

root@raspberrypi3-64:~# cd /sys/bus/w1/devices/
root@raspberrypi3-64:/sys/bus/w1/devices# ls -1
28-01131b62790b
28-01131b7ad963
28-01131bb70fee
w1_bus_master1
root@raspberrypi3-64:/sys/bus/w1/devices# cat w1_bus_master1/therm_bulk_read 
0
root@raspberrypi3-64:/sys/bus/w1/devices# echo "trigger" > w1_bus_master1/therm_bulk_read 
root@raspberrypi3-64:/sys/bus/w1/devices# cat w1_bus_master1/therm_bulk_read 
1
root@raspberrypi3-64:/sys/bus/w1/devices# cat 28-01131b62790b/temperature 
23375
root@raspberrypi3-64:/sys/bus/w1/devices# cat w1_bus_master1/therm_bulk_read 
1
root@raspberrypi3-64:/sys/bus/w1/devices# cat 28-01131b7ad963/temperature 28-01131bb70fee/temperature 
23125
23437
root@raspberrypi3-64:/sys/bus/w1/devices# cat w1_bus_master1/therm_bulk_read 
0

Note that I can't issue the bulk command and read out the status fast enough to demonstrate the -1 case. Writing the "trigger" to start the bulk command appears to block until all the probes have performed their temperature conversions.

The interesting thing about the bulk command is that it allows all the probes to check their temperatures at roughly the same time, then the code can take its time reading out the temperatures that existed when the bulk command was given. If each probe were to be visited one at a time, the timing of the temperature checks from all the probes would be skewed. This might not be a problem, but it's handy having this feature available.

Another neat thing about this setup is that the kernel actively probes the 1-wire bus during its operation. Therefore adding probes is plug-and-play at runtime! If a new probe is added to the bus at runtime, the kernel detects it, adds it to the list of devices, and prints a message indicating the probe's unique serial number. This feature makes it easier to add probes to an installation and correlate which probe is measuring which temperature. For example, if you have a project that needs 5 probes, each probe measuring a different part of a project, you can boot your hardware with the probes un-attached then attach them one-by-one to the bus at runtime taking note of their serial numbers and the part of the project each probe is measuring as they're being added.

29 Dec 2020

Temperature Readings with the DS18B20 and OpenEmbedded/Yocto



There are many ways to read a temperature in an electronics project. Probably the easiest way under Linux is with one of those pre-wired waterproof DS18B20 devices. Wire-up the probe to your RaspberryPi, enable a device-tree overlay, and read a file.

Wiring

The datasheet for the DS18B20 has all the information you'll need (and more). The probe has 3 wires: ground (black), DQ data in/out (yellow), and VDD (red). Technically the device doesn't actually need VDD connected, it can siphon enough parasitic power from DQ in normal operation. But since there's already a RaspberryPi in the design, it provides a convenient 5V, and connecting VDD makes the conversion potentially faster and more stable... might as well use it.

The datasheet recommends using a 4.7k pull-up on the DQ line. Grabbing 5V and ground from the RaspberryPi, I've wired up the probe as follows:


The 3-wire bundle comes from the probe, the individual wires run back to the RaspberryPi:


Connected to the RaspberryPi, the 3-wire bundle is my console cable, the individual wires run to the protoboard. The black wire is on ground, the red wire is on +5V. In my case I've decided to use the RaspberryPi's GPIO04, therefore the yellow wire is connected to pin07.



Build

By default the meta-raspberrypi OE/Yocto BSP layer uses the out-of-tree raspberrypi kernel. One of the differences between it and upstream is that the out-of-tree raspberrypi kernel contains all the device-tree overlays specific to the RaspberryPi. To interface with the DS18B20 the 1-wire overlay needs to be enabled in the RaspberryPi's config.txt. In OpenEmbedded/Yocto we use the RPI_EXTRA_CONFIG variable to enable arbitrary configurations in config.txt.

In addition to enabling the 1-wire protocol, we also need to specify the GPIO pin on which we want it to operate. In my specific case, given how I've wired the circuit, I'm using GPIO04, therefore I relay that information as follows:

RPI_EXTRA_CONFIG = "\
# enable 1-wire on gpio 4 \n\
dtoverlay=w1-gpio,gpiopin=4"

If you're building a core-image-minimal, you'll probably also want to add the following to your build:

CORE_IMAGE_EXTRA_INSTALL += " \
        ${MACHINE_EXTRA_RRECOMMENDS} \
        "

Building, flashing, and running the image from OpenEmbedded/Yocto you should see something along the following flash by on the serial console as it boots:

[    5.047539] gpio-4 (onewire@4): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    5.090044] w1_master_driver w1_bus_master1: Attaching one wire slave 28.01131b62790b crc 83

This is good, it means everything is configured properly and the kernel has noticed the temperature probe connected to GPIO04 via 1-wire.

Note that in the case of the DS18B20, each device has a unique 64-bit serial number. So your output should be similar but will be slightly different. The "28" identifies this specific 1-wire device (i.e. the DS18B20), the hex value following the decimal point is the 48-bit unique serial number of my specific device. The last byte is a CRC of the first 56 bits.

Read A File

If everything is working properly and you've seen the previous messages in your system log as the device boots then everything should be set. Any devices that are detected can be found under /sys/bus/w1:

root@raspberrypi3-64:~# cd /sys/bus/w1
root@raspberrypi3-64:/sys/bus/w1# ls
devices            drivers            drivers_autoprobe  drivers_probe      uevent
root@raspberrypi3-64:/sys/bus/w1# cd devices/
root@raspberrypi3-64:/sys/bus/w1/devices# ls
28-01131b62790b  w1_bus_master1
root@raspberrypi3-64:/sys/bus/w1/devices# cd 28-01131b62790b/
root@raspberrypi3-64:/sys/devices/w1_bus_master1/28-01131b62790b# ls -l
-rw-r--r--    1 root     root          4096 Dec 29 05:35 alarms
lrwxrwxrwx    1 root     root             0 Dec 29 05:35 driver -> ../../../bus/w1/drivers/w1_slave_drive
r
--w-------    1 root     root          4096 Dec 29 05:35 eeprom
-r--r--r--    1 root     root          4096 Dec 29 05:35 ext_power
drwxr-xr-x    3 root     root             0 Dec 29 04:26 hwmon
-r--r--r--    1 root     root          4096 Dec 29 05:35 id
-r--r--r--    1 root     root          4096 Dec 29 05:35 name
drwxr-xr-x    2 root     root             0 Dec 29 05:35 power
-rw-r--r--    1 root     root          4096 Dec 29 05:35 resolution
lrwxrwxrwx    1 root     root             0 Dec 29 05:35 subsystem -> ../../../bus/w1
-r--r--r--    1 root     root          4096 Dec 29 04:28 temperature
-rw-r--r--    1 root     root          4096 Dec 29 04:26 uevent
-rw-r--r--    1 root     root          4096 Dec 29 05:35 w1_slave
root@raspberrypi3-64:/sys/devices/w1_bus_master1/28-01131b62790b# cat temperature 
23625
root@raspberrypi3-64:/sys/devices/w1_bus_master1/28-01131b62790b# hexdump -C id
00000000  28 0b 79 62 1b 13 01 83                           |(.yb....|
00000008

As you can see, the temperature probe is automatically detected, and Linux handles all the details. Simply reading the "temperature" sysfs entry causes the kernel to ask the device to perform a temperature reading, then fetch the value from the device. Under the hood a lot of details are being handled by the kernel. Note, the reported temperature at this point is 23.625°C.

Notes

If I wanted to switch to, say, GPIO25 instead of GPIO04, all that would be required is to change where the yellow wire connects to the RaspberryPi's 40-pin header (switch from pin07 to pin22) and modify RPI_EXTRA_CONFIG as follows:

RPI_EXTRA_CONFIG = "\
# enable onewire on gpio 25 \n\
dtoverlay=w1-gpio,gpiopin=25"

Build, update the µSD card, apply power… Now when I boot I get:

[    5.113801] gpio-25 (onewire@19): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    5.473042] w1_master_driver w1_bus_master1: Attaching one wire slave 28.01131b62790b crc 83

And everything continues to work as before:

root@raspberrypi3-64:~# cat /sys/bus/w1/devices/28-01131b62790b/temperature 
23500

27 Dec 2020

psplash Improvements for OpenEmbedded/Yocto

The psplash project uses fbdev graphics to show a logo on a screen during bootup and shutdown. It was started in 2006 and is meant for embedded systems. It is integrated into OpenEmbedded/Yocto quite well; simply add "splash" [NOTE: not "psplash"] to IMAGE_FEATURES and you're on your way.

IMAGE_FEATURES += "splash"

Of course there are caveats. For example the psplash program is run by the init system. On most embedded systems this means that it only runs after the bootloader, and only once the kernel gets to the point of starting userspace. Another caveat is that your kernel config needs to include (y) the appropriate fbdev driver into the kernel (and not as a module) otherwise by the time the module is loaded, psplash will be done its work.

By default, an oecore build will have an OpenEmbedded logo; a poky build uses a Yocto Project logo. Layers are free to override the image with whatever logo they want. meta-raspberrypi provides a good example of this.

While I was playing around with psplash the other day, I noticed something peculiar. With qemu images everything works fine, if the init system is systemd everything is fine again. But on my raspberrypi build with sysvinit as the init system, psplash would fail to run on the very first boot. For the first shutdown, and every bootup and shutdown thereafter it works just fine. So why not on the first boot? Note also that currently sysvinit is still the default init system of an OE/Yocto build.

On a real system with real hardware, on the very first boot the filesystem is initially mounted read-only. Only later is the filesystem remounted read-write (and is forevermore read-write). One of the functions of the psplash program is to display a progress bar to give users a vague idea of the progress of the bootup. In order to communicate the progress to the psplash program, when it starts up the psplash program attempts to use, or create if it doesn't exist, a fifo. Processes that want to update the progress send text messages to the psplash program over its fifo.

Due to the fact that the psplash program is one of the very first things started by the init system, at the point where psplash is started on the very first boot, the filesystem is still mounted read-only. The psplash program would try to use a fifo if one existed, but prior to my investigation it isn't easy to place a fifo into a image that is being created. Since there is no pre-existing fifo the psplash program tries to create one. Since the filesystem is currently read-only, it fails at doing so and the psplash program terminates with error.

At some point in the first bootup the filesystem is re-mounted read-write, so all subsequent shutdowns and boots succeed at running psplash.

One of the unfortunate things about missing out on the first boot's psplash is due to the fact that the first boot of most images can often take longer than subsequent boots. Often times an image needs to do some housekeeping chores on the very first boot, but not on any others. For example: unique keys might need to be generated, post-install scripts might need to be run, etc. So having psplash work for all boots is a worthy goal.

My first thought was to get psplash to create its fifo in a part of the filesystem that is read-write from the start. It turns out that the root of the filesystem (i.e. /) is read-write from the start. Reviewers of that patch, however, weren't keen on the idea of messing up the root of the filesystem.

My next approach was to consider what would be involved in trying to add a fifo to an image that is being created. On the one hand, it's not hard at all: simply DEPEND on coreutils-native and call mkfifo to create the fifo in the staging area. In practice, however, I ran into a snag. The build ends up hanging forever until you kill it explicitly.

Turns out, when you perform a build with OpenEmbedded/Yocto, one of the great benefits is that the build includes a number of post-build steps that are run to check over various parts of the system for sanity. These checks and their logic are part of the insane.bbclass. As it turns out, one such check is to look for a shebang in the first line of all the files included in the image. The point of this check is to make sure that anything that is being shebanged (e.g. sh, bash, perl, python) is included in the runtime on-target image. Therefore the first line of every object in the image is checked. Unfortunately if the object happens to be a fifo, reading from it will hang forever waiting for data to appear.

Looking through the code of that bbclass, it wasn't all that hard to find the one and only case where a sanity check reads through the contents of all files. Adding an extra check to make sure that any object whose contents will be examined is not a fifo is all that was required.

Now that it was possible to create an image with an already-existing fifo, it was a simple matter of updating a recipe's install process to add a fifo to the image and point psplash to it.

Turns out others have noticed that adding fifos to an image isn't easily possible, so a bug was filed. Thankfully Richard Purdie remembered seeing the bug and made me aware that in my quest to get psplash working better, I was also helping to close a bug for the project! (yeah!!) Randy MacLeod had suggested that, as part of fixing the bug, an automated unit test should be added to make sure we don't accidentally lose the ability to add fifos to an image. A little bit of back-and-forth with Richard over IRC pointed me in the right direction of how to run the existing tests, and where to look to add a new fifo test.

While I was working on this project I had the opportunity to look through the psplash code itself. One thing I noticed is that in addition to sending messages via the fifo to update the progress, there is another message that can be sent to specify a text string to print immediately above the progress bar. Turns out there's an invisible text field just above the progress bar one can use to show messages to the user. I thought it would be a good addition to have the name of the current boot script displayed above the progress bar. In this way a user can see exactly what is running.

When a system is booting, the progress bar doesn't proceed smoothly; instead it updates in spurts and jumps. Sometimes the progress bar seemingly stops for a noticeable period of time. Showing the user what's currently running lets them know the reason for any pauses and provides for, in my opinion, a better user experience. Therefore, as part of my updates, I also added the plumbing necessary for the sysvinit system to provide not only a progress bar indication of the progress, but also a textual description of which startup/shutdown script is currently running.

Since, up until this point, showing a textual description of the progress hadn't been used, the feedback I was provided suggested that this new feature be added with a knob to turn it on and off, and that it should be off by default. Therefore should you want to enable it in your builds you'll need to:

PACKAGECONFIG_pn-sysvinit = "psplash-text-updates"


I've tried to capture the changes in the following video. Unfortunately the camera struggles to focus, so it's not the best video you'll see. Hopefully it conveys the gist.


The video shows the very first boot of a rasberrypi system. After the system finishes booting up, I then reboot the device. Notice how the sshd module takes a noticeable amount of time to run on the first boot, but is finished almost instantly the second time around.

17 Dec 2020

Graphics with OpenEmbedded/Yocto without X11/Weston

In a previous post I discussed graphics on the RaspberryPi and how one could use the closed binary blob + the userland library to do GLESv2 without a windowing system/compositor. This solution resulted in a system image that only required 36 packages and is 28MB in size. However, this solution is RaspberryPi-specific, 32-bit-specific, GLESv2-specific, and the quality of the binary blob graphics lags noticeably behind the quality of the fully open-source alternatives.

A better, and more generic, solution is to use DRM/KMS, GBM, and EGL directly. This allows fullscreen, "bare-metal" playback of OpenGL or OpenGL ES apps without the weight of x11 or weston/wayland.

Examples of this usage can be found with: kmscube, glmark2, mpv, and kodi. mpv and kodi are huge applications with dozens of dependencies each, so they aren't good to use for this example where I'm trying to show how small a bare-metal graphics system can be.

In this case I'm going to demonstrate on the RaspberryPi, but this solution is generic enough to work with any board whose SoC has a GPU that is supported by Mesa's DRM. I'm using the following layers:

  • bitbake: 71aaac9efa69abbf6c27d174e0862644cbf674ef
  • openembedded-core: c58fcc1379ca5755a5b670f79b75e94370d4943c
  • meta-openembedded: f03ad4971ed0b7cf34550a90ee3c0fa18f964533
  • meta-raspberrypi: 361f42e346e59f3a3fafcfa4ab7c948969d5abf4

The edits I've made to my conf/local.conf are:

1
2
3
4
5
6
7
8
MACHINE = "raspberrypi3-64"

MACHINE_FEATURES_append = " vc4graphics"
DISTRO_FEATURES += "opengl"
CORE_IMAGE_EXTRA_INSTALL += "glmark2 kmscube"
PACKAGECONFIG_append_pn-glmark2 = " drm-gl"

ENABLE_UART = "1"

Note:

  • I'm using "raspberrypi3-64" as my MACHINE, the same works for "raspberrypi3" (i.e. a 32-bit build) as well as a whole bunch of other devices/machines
  • on line 3 I'm specifically requesting the fully open-source graphics stack based on Mesa
  • notice that I'm only adding "opengl" to the DISTRO_FEATURES, and not "x11" (or "wayland")
  • on line 5 I want glmark2 and kmscube added to my image
  • by default, the way the glmark2 recipe is written, it will build drm-gles2 by default, to get drm-gl built as well I've added it to its PACKAGECONFIG
  • I like enabling the UART and using the board via the serial console

Building core-image-minimal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Build Configuration:
BB_VERSION           = "1.49.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "opensuseleap-15.2"
TARGET_SYS           = "arm-oe-linux-gnueabi"
MACHINE              = "raspberrypi3"
DISTRO               = "nodistro"
DISTRO_VERSION       = "nodistro.0"
TUNE_FEATURES        = "arm vfp cortexa7 neon vfpv4 thumb callconvention-hard"
TARGET_FPU           = "hard"
meta-raspberrypi     = "master:361f42e346e59f3a3fafcfa4ab7c948969d5abf4"
meta                 = "master:c58fcc1379ca5755a5b670f79b75e94370d4943c"
meta-oe              = "master:f03ad4971ed0b7cf34550a90ee3c0fa18f964533"

I end up with an image that has 40 packages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
base-files_3.0.14-r89_raspberrypi3.ipk
base-passwd_3.5.29-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox-syslog_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox-udhcpc_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
eudev_3.2.9-r0_cortexa7t2hf-neon-vfpv4.ipk
glmark2_20201114+0+784aca755a-r0_cortexa7t2hf-neon-vfpv4.ipk
init-ifupdown_1.0-r7_raspberrypi3.ipk
initscripts-functions_1.0-r155_cortexa7t2hf-neon-vfpv4.ipk
initscripts_1.0-r155_cortexa7t2hf-neon-vfpv4.ipk
init-system-helpers-service_1.58-r0_cortexa7t2hf-neon-vfpv4.ipk
kbd_2.3.0-r0_cortexa7t2hf-neon-vfpv4.ipk
keymaps_1.0-r31_raspberrypi3.ipk
kmscube_git-r0_cortexa7t2hf-neon-vfpv4.ipk
ldconfig_2.32-r0_cortexa7t2hf-neon-vfpv4.ipk
libblkid1_2.36-r0_cortexa7t2hf-neon-vfpv4.ipk
libc6_2.32-r0_cortexa7t2hf-neon-vfpv4.ipk
libdrm2_2.4.103-r0_cortexa7t2hf-neon-vfpv4.ipk
libegl-mesa_2:20.2.4-r0_cortexa7t2hf-neon-vfpv4.ipk
libexpat1_2.2.10-r0_cortexa7t2hf-neon-vfpv4.ipk
libgbm1_2:20.2.4-r0_cortexa7t2hf-neon-vfpv4.ipk
libgcc1_10.2.0-r0_cortexa7t2hf-neon-vfpv4.ipk
libglapi0_2:20.2.4-r0_cortexa7t2hf-neon-vfpv4.ipk
libgles2-mesa_2:20.2.4-r0_cortexa7t2hf-neon-vfpv4.ipk
libjpeg62_1:2.0.6-r0_cortexa7t2hf-neon-vfpv4.ipk
libkmod2_27-r0_cortexa7t2hf-neon-vfpv4.ipk
libpng16-16_1.6.37-r0_cortexa7t2hf-neon-vfpv4.ipk
libstdc++6_10.2.0-r0_cortexa7t2hf-neon-vfpv4.ipk
libudev1_3.2.9-r0_cortexa7t2hf-neon-vfpv4.ipk
libz1_1.2.11-r0_cortexa7t2hf-neon-vfpv4.ipk
mesa-megadriver_2:20.2.4-r0_cortexa7t2hf-neon-vfpv4.ipk
modutils-initscripts_1.0-r7_cortexa7t2hf-neon-vfpv4.ipk
netbase_1:6.2-r0_all.ipk
packagegroup-core-boot_1.0-r17_raspberrypi3.ipk
run-postinsts_1.0-r10_all.ipk
sysvinit-inittab_2.88dsf-r10_raspberrypi3.ipk
sysvinit-pidof_2.97-r0_cortexa7t2hf-neon-vfpv4.ipk
sysvinit_2.97-r0_cortexa7t2hf-neon-vfpv4.ipk
update-alternatives-opkg_0.4.3-r0_cortexa7t2hf-neon-vfpv4.ipk
update-rc.d_0.8-r0_all.ipk

and my image size is 36MB:

-rw-r--r-- 2 trevor users  36M Dec 16 23:40 tmp-glibc/deploy/images/raspberrypi3/core-image-minimal-raspberrypi3-20201217044019.rootfs.ext3

The image is super-fast to boot to a cmdline, no X11 or Wayland is started, and from the serial console I can run kmscube:


glmark2-es2-drm (OpenGL ES 2):


or glmark2-drm (OpenGL):

15 Dec 2020

userland graphics with OpenEmbedded/Yocto

When the RaspberryPi was first released (Apr 2012), support for its GPU was provided by a binary blob that was accessible via a library called "userland". Originally this glue library was supplied in binary format, but on Oct 24, 2012 the sources for the userland glue library were made available. The userland library exposes support for a number of GPU APIs including: EGL, GLESv2, OpenVG, and others. Although many parts of this graphics stack are open-source, the core GPU code remains closed. Developers can call GLESv2 functions, for example, but aren't able to manage any of the GPU's resources.

Note that this graphics stack (binary blob + userland) is separate from the fully open-source support that has been added to Mesa since release 10.3.

There exist, therefore, two providers of GLES on a RaspberryPi system: the binary blob + userland, and full Mesa with support for vc4.

When using OpenEmbedded/Yocto to put together an image targetting a RaspberryPi device, if the intent is to run GLES applications, one must choose between these two graphics stacks. Otherwise it would be confusing for the build system to know which graphics stack was intended to provide GLES when linking a GLES application (e.g. glmark2).

An application using GLES usually sits atop a large number of libraries such as various X11 libraries, xcb, drm, EGL, GL/GLES, etc. which allow such an application to run in its own window as part of a windowing system. With the blob+userland option, a number of the libraries lower down in the stack are replaced by the dispmanx library. The dispmanx library doesn't sit on top of a window system; it prefers to take over the entire screen. As a result, running a graphics application with the blob+userland requires far fewer packages, doesn't require any x11/weston, and doesn't require any windowing environment.

The original RaspberryPis ran on 32-bit hardware. In an effort to prioritize backwards compatibility, even when the RaspberryPi migrated to 64-bit architectures, the official images continued to be built for 32-bit. As a result, most of the blob+userland code was written for, and assumes, a 32-bit environment.

There isn't very much information available regarding the dispmanx and userland libraries. The userland code comes with a bunch of sample applications that can be optionally built when building the library. A person named Andrew Duncan wrote a bunch of sample dispmanx applications which were published on github in a repository named raspidmx. Also, the popular glmark2 code has an experimental fork with the changes necessary to run on top of dispmanx.

The best-known BSP layer with support for RaspberryPi includes the necessary knobs and buttons to allow you to create an image and select which graphics stack you would like to use. If you choose the blob+userland stack, support is also available for building the optional userland applications, for building raspidmx, as well as a version of glmark2 that runs on dispmanx.

Assuming you're already familiar with most of the basics of setting up an OpenEmbedded/Yocto build, for this test (at a minimum) you'll need:

  • bitbake
  • openembedded-core
  • meta-openembedded
  • meta-raspberrypi

Once you've cloned those repositories and setup your build environment, in order to use the blob+userland graphics stack as well as build all the sample applications mentioned above, my conf/local.conf has the following changes and additions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
MACHINE = "raspberrypi3"
DISABLE_VC4GRAPHICS = "1"

DISTRO_FEATURES += "x11 dispmanx"
CORE_IMAGE_EXTRA_INSTALL += "glmark2 raspidmx"
PACKAGECONFIG_append_pn-userland = " allapps"
PACKAGECONFIG_pn-glmark2 = "dispmanx"

ENABLE_UART = "1"
GPU_MEM = "512"

NOTE: this is not my entire conf/local.conf, but rather just the things that I added or changed.

  • I plan to run this image on a Raspberry Pi 3 Model B V1.2, which has a 64-bit SoC, but I'm setting the MACHINE to "raspberrypi3" (i.e. the 32-bit machine)
  • On line 2 I'm explicitly disabling VC4GRAPHICS (which refers to the fully open-sourced Mesa support), thereby enabling support for the blob+userland graphics stack
  • Although I'm not building or including x11 support, when building glmark2 the code refers to some X11 headers (which might be an oversight on the part of the glmark2 code), in any case we need to add x11 to the DISTRO_FEATURES
  • On line 5 I'm adding the glmark2 (GLES) and raspidmx (dispmanx) sample applications to the image
  • On line 6 I'm enabling the optional build of sample dispmanx samples when building the userland library
  • On line 7 I'm making sure glmark2 is build with support for dispmanx
  • I enable the console UART on the Pi since I prefer working with the device over the console rather than plugging in a keyboard and mouse
  • On line 10 I increase the memory available to the GPU since one of the glmark2 tests fails with the default setting

With my build properly configured, I proceed to build "core-image-minimal" as follows:

Build Configuration:
BB_VERSION           = "1.49.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "opensuseleap-15.2"
TARGET_SYS           = "arm-oe-linux-gnueabi"
MACHINE              = "raspberrypi3"
DISTRO               = "nodistro"
DISTRO_VERSION       = "nodistro.0"
TUNE_FEATURES        = "arm vfp cortexa7 neon vfpv4 thumb callconvention-hard"
TARGET_FPU           = "hard"
meta-raspberrypi     = "master:e4f5c32925fec90ff688e51197cb052fe12af82e"
meta                 = "master:a55b01a3a1faf9a52d7edad074c76327f637aaa2"
meta-oe              = "master:936f2380bb5112721eec2db46eb35b5600ac28de"

Note that bitbake is at checkout: 71aaac9efa69abbf6c27d174e0862644cbf674ef

When my build is done the only packages in my image are:

base-files_3.0.14-r89_raspberrypi3.ipk
base-passwd_3.5.29-r0_cortexa7t2hf-neon-vfpv4.ipk
bash_5.0-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox-syslog_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
busybox-udhcpc_1.32.0-r0_cortexa7t2hf-neon-vfpv4.ipk
eudev_3.2.9-r0_cortexa7t2hf-neon-vfpv4.ipk
glmark2_20201114+0+784aca755a-r0_cortexa7t2hf-neon-vfpv4.ipk
init-ifupdown_1.0-r7_raspberrypi3.ipk
initscripts-functions_1.0-r155_cortexa7t2hf-neon-vfpv4.ipk
initscripts_1.0-r155_cortexa7t2hf-neon-vfpv4.ipk
init-system-helpers-service_1.58-r0_cortexa7t2hf-neon-vfpv4.ipk
kbd_2.3.0-r0_cortexa7t2hf-neon-vfpv4.ipk
keymaps_1.0-r31_raspberrypi3.ipk
ldconfig_2.32-r0_cortexa7t2hf-neon-vfpv4.ipk
libblkid1_2.36-r0_cortexa7t2hf-neon-vfpv4.ipk
libc6_2.32-r0_cortexa7t2hf-neon-vfpv4.ipk
libgcc1_10.2.0-r0_cortexa7t2hf-neon-vfpv4.ipk
libjpeg62_1:2.0.6-r0_cortexa7t2hf-neon-vfpv4.ipk
libkmod2_27-r0_cortexa7t2hf-neon-vfpv4.ipk
libpng16-16_1.6.37-r0_cortexa7t2hf-neon-vfpv4.ipk
libstdc++6_10.2.0-r0_cortexa7t2hf-neon-vfpv4.ipk
libtinfo5_6.2-r0_cortexa7t2hf-neon-vfpv4.ipk
libz1_1.2.11-r0_cortexa7t2hf-neon-vfpv4.ipk
modutils-initscripts_1.0-r7_cortexa7t2hf-neon-vfpv4.ipk
ncurses-terminfo-base_6.2-r0_cortexa7t2hf-neon-vfpv4.ipk
netbase_1:6.2-r0_all.ipk
packagegroup-core-boot_1.0-r17_raspberrypi3.ipk
raspidmx_0.0+git0+e2ee6faa0d-r0_cortexa7t2hf-neon-vfpv4.ipk
run-postinsts_1.0-r10_all.ipk
sysvinit-inittab_2.88dsf-r10_raspberrypi3.ipk
sysvinit-pidof_2.97-r0_cortexa7t2hf-neon-vfpv4.ipk
sysvinit_2.97-r0_cortexa7t2hf-neon-vfpv4.ipk
update-alternatives-opkg_0.4.3-r0_cortexa7t2hf-neon-vfpv4.ipk
update-rc.d_0.8-r0_all.ipk
userland_20201027-r0_cortexa7t2hf-neon-vfpv4.ipk

My entire image size is ~28MB:

-rw-r--r-- 2 trevor users  28M Dec 15 14:23 core-image-minimal-raspberrypi3-20201215192252.rootfs.ext3

Flashing to a µSD card and booting, from the serial console I am able to run the userland sample applications (not all of the sample apps are shown running here, but they all do run):


I can even run 2 of the sample userland applications at the same time:


I can run the raspidmx samples (not all are shown here, but they all do run):




And I can run glmark2-es2-dispmanx: