The linux kernel is responsible for managing all the hardware. Which brings us to the question: how does it know what hardware there is to manage? On traditional PC platforms, the kernel can read out a lot of information from the BIOS. But on embedded devices such as the BeagleBone (Black), there is no BIOS to read from.

The answer is that you simply tell the kernel what hardware is present. The way you do that, is by providing a device tree during boot. This tree lists all the hardware that is present on the system, along with the needed parameters. For example:

memory {
  device_type = "memory";
  reg = <0x80000000 0x10000000>;

This tells the kernel that it has 256MB (0x10000000 bytes) of memory, mapped from 0x80000000 onwards. The device tree has such an entry for every hardware part. These nodes are organised in a tree, so that the mailbox for PRU0 is located under the mailboxes node, so it can reuse lots of parameters. Update: I’ve found a really good explanation of the device tree for the Raspberry Pi. The device tree is exactly the same, but the way to load overlays is different.

The kernel needs a flattened version of this device tree to boot, aptly called the flattened device tree (fdt). The boot-loader should provide this when booting a kernel (much like the initramdisk). The device tree compiler can be used to compile device trees in to flattened device trees (and the other way around, but you lose some symbolic references in that direction).


The device tree is only read at boot, which means it’s not possible to dynamically add/change hardware after boot. To solve this problem, BeagleBone has developed the device tree overlay, a changeset that can be applied (and un-applied!) to the device tree. BeagleBone needed this to support their Capes, hence the name cape manager.

The device tree also checks for exclusive usage of hardware resources. It makes sure that you can’t allocate a single PIN to 2 different functions at once.

This device tree fragment will enable Pin 8-11 as a Dallas 1-wire master:


/ {
  compatible = "ti,beaglebone", "ti,beaglebone-black";

  part-number = "DS18B20-IO";

  exclusive-use =
    "P8.11", "gpio1_13";

  fragment@0 {
    target = <&am33xx_pinmux>;
    __overlay__ {
      ds18b20_pinmux: pinmux_ds18b20 {
        pinctrl-single,pins = < 0x034 0x37 // P8_11 (GPIO1_13): mode 7 (gpio), pull up, Rx enabled >;

  fragment@1 {
    target = <&ocp>;
    __overlay__ {
      onwire@0 {
        compatible = "w1-gpio";
        pinctrl-names = "default";
        pinctrl-0 = <&ds18b20_pinmux>;
        status = "okay";

        gpios = <&gpio1 13 0>


In order to use GPIO pins from the PRU, you need to load a device tree fragment, at least to let the kernel know that you intend to use hardware components. And while you’re at it, you can ask the kernel to configure them for you as well. I needed 4 GPIO pins for my PRU logic. They were located on GPIO2, which is otherwise unused. Without the device tree, the whole GPIO-hardware was put in shutdown by the kernel, which took me a while to figure out. The following device tree overlay enables these parts.


/ {
  compatible = "ti,beaglebone", "ti,beaglebone-black";

  part-number = "DHT22-PRU-IO";  // Arbitrary name used for reference

  exclusive-use =
    "P8.7", "gpio2_2",
    "P8.8", "gpio2_3",
    "P8.9", "gpio2_5",
    "P8.10", "gpio2_4";

  fragment@0 {
    target = <&am33xx_pinmux>;
    __overlay__ {
      dht22_pru_pinmux: pinmux_dht22_pru {
        pinctrl-single,pins = <
          0x090 0x37 // P8_07 (TIMER4): mode 7 (gpio), pull up, Rx enabled
          0x094 0x37 // P8_08 (TIMER7): mode 7 (gpio), pull up, Rx enabled
          0x09c 0x37 // P8_09 (TIMER5): mode 7 (gpio), pull up, Rx enabled
          0x098 0x37 // P8_10 (TIMER6): mode 7 (gpio), pull up, Rx enabled

  fragment@1 {
    target = <&ocp>;
    __overlay__ {
      dht22_pru_helper {
        compatible = "gpio-of-helper";
        pinctrl-names = "default";
        pinctrl-0 = <&dht22_pru_pinmux>;
        status = "okay";

        P8_07 {
          gpio-name = "P8_07";
          gpio = <&gpio2 2 0>;
        P8_08 {
          gpio-name = "P8_08";
          gpio = <&gpio2 3 0>;
        P8_09 {
          gpio-name = "P8_09";
          gpio = <&gpio2 5 0>;
        P8_10 {
          gpio-name = "P8_10";
          gpio = <&gpio2 4 0>;

Next step is to compile this overlay, and apply it to the device tree:

dtc -O dtb -o DHT22-PRU-IO-00A0.dtbo -b 0 -@ overlay.dts
cp DHT22-PRU-IO-00A0.dtbo /lib/firmware/.
echo "DHT22-PRU-IO" > /sys/devices/platform/bone_capemgr/slots
cat /sys/devices/platform/bone_capemgr/slots

You can unload the overlay by echo’ing -$slotnumber to the slots file.

I chose the BeagleBone Black (amongst other things) for the embedded microcontrollers (PRUs): Besides your usual ARM Cortex processor, you can put two “Programmable Real-time Units” to work. These are 2 RISC-cores clocked at 200MHz, optimised for real-time processing:

  • Most instructions take 1 cycle (external memory access takes longer)
  • No async interrupt handling, but provisions to efficiently check for pending interrupts
  • Enhanced GPIO mode, that allows some pins to be directly controlled by the PRU, bypassing the normal GPIO-logic. This boosts performance down to 5ns latencies.

Enabling the PRU

There are two methods for accessing the PRUs from the Linux kernel. The “old” method, “pruss” or “uio”, exposes the entire PRU memory region to userspace. This allows for maximal flexibility, but you need to do everything yourself, such as communications.

The “new” method, “rproc”, works differently: The kernel assumes responsibility for the remote processor, and protects it from userspace. Additional drivers are needed to allow userspace to communicate with the PRU, and these drivers define what is and is not allowed.

I chose to use the rproc-method, but most of what is written below applies to both methods. Newer kernels (4.9.36 at least) don’t start the PRU’s automatically on boot. You need to start the manually by echo start > /sys/class/remoteproc/remoteproc1/state. Stopping the PRU is left as an exercise for the reader 😉 Note that on my setup, PRU0 was assigned remoteproc1 (the M3 power management processor is assigned remoteproc0).

Writing code for the PRU

TI provides an assembler, and even a full IDE suite, but I wanted to stay with my known tools. There is a GCC-variant that compiles to PRU-opcodes. It’s even available as a pre-compiled Debian package called “gcc-pru” from Robert Nelson’s repo. Unfortunately, Robert only has a Jessie-build available, but it seems to work fine on Stretch as well.

As with most embedded projects, the first thing to get working is the blinking LED. I learned a lot from the blinking LED “functionality” I found on GitHub, especially about the rproc-part. It took me countless hours to figure everything out, but in the end it worked.

Getting a blinking LED

GPO 21 of GPIO bank 1 is wired to the blue USR0 LED, normally showing the heartbeat. You can use this output to test the PRU without adding any additional hardware. Start by disabling the heartbeat, so it doesn’t interfere: echo "none" >/sys/class/leds/beaglebone:green:usr0/trigger.

The PRU code is super basic. main.S:

#define CONST_PRUCFG 4
#define GPIO1 0x4804c000
#define GPIO_SETDATAOUT 0x194

    .section .init0, "x"
    .global __start
    // Enable OCP master port to access external memory
    // otherwise the PRU will stall when trying
    LBCO      r0, CONST_PRUCFG, 4, 4    // Load SYSCFG in to r0
    CLR       r0, r0, 4         // Clear SYSCFG[STANDBY_INIT] to enable OCP master port
    SBCO      r0, CONST_PRUCFG, 4, 4    // Write r0 to SYSCFG

    LDI32 r2, 1<<21
    SBBO r2, r3, 0, 4    // write 1<<21 in to GPIO1 SETDATAOUT

    // Delay loop, count down r0 to zero
    LDI32 r0, 0x08000000
    SUB r0, r0, 1
    QBNE DEL1, r0, 0

    LDI32 r2, 1<<21
    SBBO r2, r3, 0, 4    // write 1<<21 in to GPIO1 CLEARDATAOUT
    // Delay loop, count down r0 to zero
    LDI32 r0, 0x08000000
    SUB r0, r0, 1
    QBNE DEL2, r0, 0

    // repeat
    JMP main

    // unreachable
    LDI r31.b0, 35 // interrupt to host

    /* Dummy data, required by remoteproc loader */
    .section .resource_table,"aw",@progbits
    .word 1, 0, 0, 0 /* struct resource_table base */
    .word 0 /* uint32_t offset[1] */

You can compile this by using:

pru-gcc -g -Os -Wall -Wextra -minrt -mmcu=am335x.pru0 -nostdlib -nodefaultlibs -nostartfiles main.S -o am335x-pru0-fw

The pru_rproc kernel driver loads two firmware files: /lib/firmware/am335x-pru0-fw and am335x-pru1-fw in the same directory, one for each PRU. Copy the generated ELF-binary there, and start the PRU by:

echo start > /sys/class/remoteproc/remoteproc1/state

The firmware files contain, besides the actual code to run, a resource table. This table is used to ask the kernel to set up some things before starting the PRU, such as interrupt mappings, communication channels, …

While having pre-configured devices is super-fun for getting started quickly, I usually want to know exactly what is installed on them. So I spent some time installing my BeagleBone from scratch.

U-Boot – the bootloader

First step is the boot-loader. The ROM bootloader loads the 2nd stage bootloader in to on-chip RAM and executes it. On-chip RAM is limited to just over 100kB, not enough for a full fledged bootloader. So U-boot is itself split in 2 stages, typically called MLO and u-boot.img.

To build these, I downloaded the git repository of U-Boot, applied Robert Nelson’s patches (you’ll recognise that name if you did some BeagleBone Googling before) and did:

patch -p1 < 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch
make distclean
make am335x_boneblack_config

I have no idea what these patches do, but everyone seems to apply them…

You can probably cross-compile these on your x86-based workstation, but I just run the compile on the BeagleBone itself. I copied the resulting files to the SD-card: MLO and u-boot.img. Booting with S2 pressed greeted me with:

U-Boot SPL 2017Trying to boot from MMC1
reading u-boot.img
reading u-boot.img

U-Boot 2017.07-00043-g8822c8f (Jul 11 2017 - 19:34:01 +0000)

CPU  : AM335X-GP rev 2.1
I2C:   ready
DRAM:  512 MiB
No match for driver 'omap_hsmmc'
No match for driver 'omap_hsmmc'
Some drivers were not found
Reset Source: Power-on reset has occurred.
Using default environment

Board: BeagleBone Black
 not set. Validating first E-fuse MAC
Net:   eth0: MII MODE
Press SPACE to abort autoboot in 2 seconds

A minimal Debian image

I used debootstrap to generate a minimal Debian environment:

debootstrap --verbose --variant=minbase stretch /mnt

I bootstrapped in to a loop-mounted file, but you can do that straight to an SD-card if you’re brave.

I had trouble getting this to work from the shipped Wheezy. debootstrap stopped with:

I: Extracting liblzma5...
I: Extracting zlib1g...
E: no . cannot create devices

The problem seems to be that Wheezy doesn’t use devfs. I tar’ed up /dev and export DEVICES_TARGZ=/mnt/dev.targz to get through this point, then removed the extracted files from the target.

Since minbase is really minimal, I added the following:

  • apt-get install --no-install-recommends openssh-server
  • apt-get install vim iproute2 iputils-ping kmod less ifupdown init udev
  • echo "deb stretch main" >> /mnt/etc/apt/sources.list; apt-get update
  • apt-get install linux-image-4.9.36-ti-r45
  • echo "/dev/mmcblk0p2 / ext4 defaults 0 1" > /mnt/etc/fstab
  • cp /etc/hosts /mnt/etc/hosts
  • configure /etc/network/interfaces or /etc/network/interfaces.d/

OK, well, to be honest, the above list was compiled from several trail-and-error attempts to get the image to boot… Who could have guessed you need a kernel to actually boot 😉 If you’re lazy, you can just extract my tarball, and try to log in on via USB with SSH as root (password admin).

Now you can either manually boot the kernel in U-Boot:

load mmc 0:2 ${loadaddr} /boot/vmlinuz-4.9.36-ti-r45
load mmc 0:2 ${fdtaddr} /boot/dtbs/4.9.36-ti-r45/am335x-boneblack.dtb
set bootargs console=tty0 console=ttyS0,115200n8 root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait fixrtc init=/lib/systemd/systemd
bootz ${loadaddr} - ${fdtaddr}

Or put the equivalent commands in uEnv.txt on the FAT partition: (I copied the kernel & Flattened Device Tree file to the FAT partition (mmc 0:1) for easier debugging).

bootargs=console=tty0 console=ttyS0,115200n8 root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait fixrtc init=/lib/systemd/systemd
uenvcmd=load mmc 0:1 ${loadaddr} kernel.img; load mmc 0:1 ${fdtaddr} fdt; bootz ${loadaddr} - ${fdtaddr}

Some notes:

  • The boot takes ~3 minutes longer than it should, because systemd waits for eth0 to come up (which it won’t because it’s not connected), only giving up after a few minutes.
  • If you have trouble getting the serial console to work because systemd can’t find dev-ttyS0.device, make sure you installed udev


Booting up a modern microprocessor is more like a journey than a recipe. The Texas Instruments Sitara AM3358 processor, powering the BeagloBone Black is no different. The journey starts when the Power Management IC brings up the different voltage lines of the CPU in the correct order. The CPU initialises itself and starts executing its ROM bootloader code.

The ROM bootloader reads in the status of the 16 SYSBOOT pins. According to the BeagleBone Black’s System Reference Manual (section 6.7.1, local copy), the board has these pins wired to 0b01 00 00 0 0 00 1 11100, but pin SYSBOOT[2] can be pulled down by pushing S2. The Technical Reference Manual (section, local copy) will tell you the important difference between these 2 options is mainly the boot sequence:

  • Normal mode: MMC1, MMC0, UART0, USB0
  • With S2 pushed: SPI0, MMC0, USB0, UART0

Since the BeagleBone’s eMMC is connected to MMC1 (cfr BBB SRM section 6.4.2), and the micro SD card slot to MMC0, S2 effectively allows you to skip the eMMC when booting, and boot straight from SD.

The bootloader will (cfr section of the TRM) read 4 sectors (sector nr 0, 256, 512 and 768) and look for a TOC structure. If found, it will load the next bootloader from there. If this fails, the bootloader looks for an active primary FAT12/16 or FAT32 partition in the MBR partition table, and tries to load a file named MLO (which seems to stand for Mmc LOader) from the root directory. This next bootloader, usually U-Boot’s Second Program Loader, then takes over. The ROM bootloader provides some information about the boot (e.g. which device is currently booted from), but it’s up to this new bootloader to use this info.

What actually happens

You can see what actually happens by connecting to the BBB with a TTL-level (3.3V) RS232 adapter. The J1 connector (6 pins, above the P9 header) is connected to UART0 as follows:

  • pin 1 (marked with a dot): GND
  • pin 4: Rx (i.e. to BBB)
  • pin 5: Tx (i.e. from BBB)

The (default) parameters are 115200 baud, 8 databits, no parity, 1 stopbit. I use kermit to provide a terminal emulator on a second device (but other options are possible).

The boot-up of the build-in eMMC starts off like this:

U-Boot SPL 2014reading args
spl_load_image_fat_os: error reading image args, err - -1
reading u-boot.img
reading u-boot.img

This looks like the SPL loading the 3rd stage bootloader (located in u-boot.img). The 2nd stage bootloader (the MLO file) needs to fit in on-chip RAM (slightly over 100kB usable), which is not enough for a full fledged boot-loader, hence this split in 2 (additional) stages. The boot goes on:

U-Boot 2014.04-00014-g47880f5 (Apr 22 2014 - 13:23:54)

I2C: ready
DRAM: 512 MiB
*** Warning - readenv() failed, using default environment

Net: <ethaddr> not set. Validating first E-fuse MAC
cpsw, usb_ether
Hit any key to stop autoboot: 0
gpio: pin 53 (gpio 53) value is 1
Card did not respond to voltage select!
mmc0(part 0) is current device
Card did not respond to voltage select!
gpio: pin 56 (gpio 56) value is 0
gpio: pin 55 (gpio 55) value is 0
gpio: pin 54 (gpio 54) value is 0
mmc1(part 0) is current device
gpio: pin 54 (gpio 54) value is 1
SD/MMC found on device 1
reading uEnv.txt
1709 bytes read in 7 ms (238.3 KiB/s)

This looks like U-Boot tries to boot of MMC0 (the SD-card slot) first, even though the ROM-bootloader choose MMC1 first. Since there is no SD-card inserted, this fails, and U-Boot continues on to MMC1 (the on-board eMMC), where it reads the uEnv.txt file.

Note that in this setup, U-Boot runs its compiled-in boot script. So overriding the bootcmd variable in uEnv.txt is useless, since it’s loaded too late. The default boot script loads this uEnv.txt file, and executes the uenvcmd variable, if defined. So you can use that variable to customise your boot.

My BeagleBone Black came pre-installed with a 2-year-old Debian Wheezy on it’s eMMC chip (image, hashes). The out-of-the-box experience is actually quite impressive: the BeagleBone presents itself over the USB bus as a mass storage device with the needed drivers and some documentation. Once you install the driver (only the RNDIS is needed, the Black does not have a serial-over-USB anymore), you can just surf to the BeagleBone and control USER-LEDs from your web browser. Impressive!

But I don’t want nor need these fancy features eating up CPU, disk space and RAM, so I went looking for a minimal Debian image to use. Besides the bare minimum, I wanted the Ethernet-over-USB to work, and SSH.

  1. Get a copy of the BeagleBoard image builder
  2. Build a rootfs tarball for the desired config. I used, which matched my requirements quite nicely. You can see the (additional) packages that will be installed listed in the corresponding config file.
    ./ -c
  3. From the generated directory inside deploy, run
    ./ --img-1gb ~/disk-image.img --dtb beaglebone

    I wanted to keep the FAT boot partition, which is no longer needed, so I reverted that part of the commit before running the above command.

  4. dd the generated image (hash) onto an SD card.

To boot from the SD card, press the boot button (S2) at power-on and release when the 4 User-LEDs are on. Be patient: it took my board ~90 seconds to get SSHd up and running.

Note: sometimes, udhcpd would refuse to start. Rebooting (i.e. press the power button (S3) briefly, wait for the LEDs to go off, press-and-hold S2, press S3, release S2 when 4 LEDs are on) helped. I’m guessing it’s a race condition on bootup that sometimes prevent udhcpd from starting. Manually assigning your side also works.

You can use the /opt/scripts/tools/ script to grow the root partition to the actual size of the SD card instead of the 1GB it originally has.

I have an Epson WiFi multifuncional, and was very happy to see that I needed no additional software at all to make it work. OS X had everything built in. For scanning, I use Image Capture. It’s fairly basic, but it does the job of scanning a batch of duplex pages in to a single PDF.

Sometimes, however, Image Capture hangs on “Waiting for scanner…”. Restarting Image Capture doesn’t help; restarting the multifunctional neither, nor did turning the Mac’s WiFi off and on again. Restarting OS X entirely did solve the issue, but I don’t call that a solution.

Today, I came across a post of Packetrider on an HP forum. He figured out that killing the Image Capture Extension process solves the problem as well:

killall "Image Capture Extension"

When I buy electronic devices, I always keep an eye on their power consumption. Especially the power drain that you’ll get 24/7. For most devices, this is their “standby” power consumption, but some devices are left on all day long. My broadband router, a TP-Link Archer C7 running OpenWRT 15.05 Chaos Calmer, is in this last category.

Continue reading ‘TP-Link Archer C7 power consumption’ »

When I buy electronic devices, I always keep an eye on their power consumption. Especially the power drain that you’ll get 24/7. For most devices, this is their “standby” power consumption, but some devices are left on all day long. The b-box 3 of Proximus is in this last category.

Continue reading ‘B-box 3 power consumption’ »

Akismet recently decided to revoke my API key, so I got overwhelmed with spam comments again. So I’m trying something new: a quiz before you can comment. Let’s see how this works out.

My ISP, Skynet Belgacom Proximus, has been rolling out IPv6 since 2013. However, you need a B-Box 3 in order to get it. Recently, my B-Box 2 decided to stop working, so I got a (free) upgrade to a B-Box 3 (but see below).

Continue reading ‘Native IPv6 over Proximus DSL’ »