FreeBSD now has a number of continuous integration jobs on Jenkins CI to build and test FreeBSD on various architectures, and the newly implemented Tinderbox View presents a high-level, simple dashboard to the real-time FreeBSD CI build status.

This display is so useful that we wanted a physical version in our office to monitor the build status more easily. What started as a side project during my first few weeks of interning at The FreeBSD Foundation, has become a useful LED display of the current FreeBSD CI (continuous integration) build status, and is running 24/7 in the Foundation Kitchener office, proudly running FreeBSD on a BeagleBone Green.


  • A working installation of FreeBSD
  • BeagleBone Green with a 4GB micro-SD card, a serial cable and Internet connection
  • An addressable LED RGB strip. This project uses an APA102 LED strip from Sparkfun


Install FreeBSD on the micro-SD card

1. Build or download a FreeBSD image

To get started, you can download an image from the FreeBSD Snapshot site with filename labeled as BeagleBone. In this case, we download:


then extract it:

$ unxz FreeBSD-12.0-CURRENT-arm-armv6-BEAGLEBONE-20170519-r318502.img.xz

to get the .img image.

You can always choose to build FreeBSD from source code if you want to experience the latest changes for the support of BeagleBone and are comfortable with the process. Crochet is the tool to use, and you can find a detailed guide on GitHub.

2. Install the image

The dd(1) utility is used for raw data copying such as, initializing a disk from a raw image.

dd requires specifying if (input file), of (output file) and bs (copy block size). These arguments should be changed to match the actual file and device name.

$ dd if=FreeBSD-BeagleBone.img of=/dev/da0 bs=8m

Specifying a block size is not necessary, but the default setting will result in very slow operation.

If you are not sure of which device it is, simply run:

$ tail /var/log/messages

right after inserting the micro-SD, or:

$ sudo camcontrol devlist

to see the corresponding device name.

After the operation finishes, you can insert the micro-SD card into the BeagleBone.

Boot the BeagleBone Green

1. Connect the serial cable

A serial cable might not be necessary as you can wait until it boots and try to ssh to it (the system configuration might prevent you from logging in as root with ssh though). However, since BeagleBone Green doesn’t have an HDMI output, you can see what is going on through the whole booting process with a serial cable, making it much easier to diagnose if something goes wrong.

The serial console of BeagleBone Green is exposed on a 6-pin header.

The built-in cu(1) utility can be used for serial communications. cu can only access the /var/spool/lock directory via user uucp and group dialer. Use the dialer group to control who has access to the modem or remote systems by adding user accounts to dialer using pw(8):

$ sudo pw groupmod dialer -m guangyuan # Use your own username

Then log out and log in again to make the above change live.

Connect the USB to TTL cable to BeagleBone and computer, then run the cu utility and specify the line speed of 115200 baud.

$ cu -s 115200 -l /dev/cuaU0 # Or appropriate device

You won’t see any output yet.

Note: Using sudo to use cu is not a good practice, instead you should add the user to the dialer group as above stated, or grant everyone’s access as an alternative by running:

$ chmod 4511 /usr/bin/cu

For more info about serial communications, see FreeBSD Serial Communications.

2. Boot up and log in

The BeagleBone Black can boot from either the onboard eMMC or a micro-SD card. By default it boots from eMMC. To boot from micro-SD, first hold down the boot switch, then apply power. Don’t release the button until you see it starts booting FreeBSD (or count to 5).

(image from

The boot switch is just above the micro-SD slot.

After booting, log in as root (the default password is “root” as well).

Tip: Making a BeagleBone Black Always Boot From the Micro-SD
The AM335x chip on board actually boots from the first partition that has the active flag set. After using the “holding the boot button” method described above to boot FreeBSD and log in as root, you will be able to turn off the bootable flag of the onboard eMMC to make it always boot from the micro-SD:

 $ gpart unset -a active -i 1 mmcsd1

To restore this change and make the eMMC available again do:

 $ gpart set -a active -i 1 mmcsd1

Alternatively, you can copy the FreeBSD image to eMMC so no pressing the button is needed.

3. Sync system clock

The system may refuse to proceed on some commands if the system clock is wrong.

In FreeBSD, it is recommended to use both ntpdate and ntpd. ntpdate will set the clock when you first boot so it’s close enough that ntpd will work with it. Add the following to /etc/rc.conf:


Then read through /etc/ntp.conf. It’s pretty well documented so it should be obvious what to set.

4. Enable root login via ssh

Open /etc/ssh/sshd_config and change this line:

 #PermitRootLogin no


 PermitRootLogin yes

then, restart the ssh daemon:

 $ /etc/rc.d/sshd restart

and you will be able to login as root via ssh.

Test the GPIO on board

Let us start from mastering the control of an external LED.

1. GPIO wiring

First let’s take a look at Beaglebone Green’s pin map:

(image from

Now we connect a LED and a 200Ω resistor using jumper wires.

(image from

The top two connections on the BeagleBone expansion header are both GND. The other lead is connected to a pin of your choice.

2. Send test signals

No programming is required at this moment, as FreeBSD provides us with the gpioctl(8) utility which could be used to list available pins and manage GPIO pins from userland.

Let’s list all the available pins defined by device /dev/gpioc0:

 $ gpioctl -f /dev/gpioc0 -l

By default, all the IO pins are set to be inputs. This does not work for our LED. Instead, we need the pin it is connected to be an output, so we configure that:

 $ gpioctl -f /dev/gpioc0 -c 3 OUT # Assuming pin 3 is the one used

The pin should be output mode now, but the LED should still be off. To turn it on, type:

 $ gpioctl -f /dev/gpioc0 3 1 # Assuming pin 3 is the one used

Now we have set the logical value of pin 3 to be 1, and the LED is on! To turn it off again, type:

 $ gpioctl -f /dev/gpioc0 3 0 # Assuming pin 3 is the one used

You can try blinking the LED by writing a bash script with a simple loop.

SPI bit banging

Awesome! GPIO is working well with BeagleBone, it’s time to start using the addressible LED strip.

The LED RGB strip we used is packed with 60 APA102s and can be controlled with a standard SPI interface. However, at this moment, FreeBSD has no userland support for SPI devices. We used Bit banging to simulate the SPI Protocol as a workaround.

1. Wire LED strip to the BeagleBone

(image from

Using the pin map, we connected:
CI -> GPIO of your choice
DI -> GPIO of your choice

2. Install Python development environment

We used Python and the fbsd_gpio python bindings for the code. Install Python and pip first, and then cffi and fbsd_gpio libraries via PyPI.

 $ pkg install python py27-pip
 $ pip install --user cffi fbsd_gpio

Note: You may encounter an error when using `pip` which will give an error like:

unable to execute '/nxb-bin/usr/bin/cc': No such file or directory

This is because FreeBSD uses some cross-compile tools on some embedded platforms (mips, arm, aarch64, etc.) which aren’t used in this setup and will cause build errors. The bug has not been fixed yet (Bug 208282), but in the meantime, we could just change all references in /usr/local/lib/python2.7/ as a workaround:

 $ sed -i '' 's/\/nxb-bin\/usr\/bin\/cc/\/usr\/bin\/cc/g' /usr/local/lib/python2.7/

3. Write SPI bit banging functions

There is a SPI bit banging abstraction in the fbsd_gpio package used below but has not been documented yet. You can use that abstraction and skip this step, or you can still choose to follow it as a good learning practice.

Import the library and create a controller:

 from fbsd_gpio import GpioController
 gpioc = GpioController(0) # Using gpio controller unit 0 (/dev/gpioc0)

Set which pins we are using:

 SCLK = 2 # CI (Blue)
 MOSI = 3 # DI (Green)

SCLK: Serial Clock (output from master)
MOSI: Master Output Slave Input (data output from master), or DI from LED

Provide SPI init and write functions (it’s better to use bitwise operators when working with bits):

 def spi_init():
     gpioc.pin_set(SCLK, 0)
     gpioc.pin_set(MOSI, 0)

 def spi_write_byte(b):
     for i in xrange(7, -1, -1):
         gpioc.pin_set(SCLK, 0)
         gpioc.pin_set(MOSI, (b >> i) & 1)
         gpioc.pin_set(SCLK, 1)

 def spi_write(buf):
     for i in buf:

A complete description of fbsd_gpio can be found in the fbsd_gpio documentation on PyPI.

4. Work with APA102 LEDs

Once we set up the SPI functions, we were ready to send SPI data, but first we needed to figure out what to send in order to light up the LEDs we want. Follow the APA102 Manual to find out the data format:

(image from

Each update consists of a start frame of 32 zeroes, 32 bits for every LED, and an end frame of 32 ones. So our send function will most likely to work as follows:

 # Start Frame
 spi_write([0b00000000, 0b00000000, 0b00000000, 0b00000000])

 # LED Frames
 spi_write([0b11111111, 0b00000001, 0b00000000, 0b00000000]) # First LED, brightness full, blue

 # End Frame
 spi_write([0b11111111, 0b11111111, 0b11111111, 0b11111111])

Read this article if you want to Understand the APA102 “Superled” better.

Display the status

1. Get data from Jenkins

Many objects of Jenkins provide remote access APIs. We used the provided Python one to get status of all jobs:

 import ast
 import urllib
 JENKINS_URL = "[name,color]"
 data = ast.literal_eval(urllib.urlopen(JENKINS_URL).read())["jobs"]

This is how the data will look like:

   "_class" : "hudson.model.Hudson",
   "jobs" : [
       "_class" : "hudson.model.FreeStyleProject",
       "name" : "FreeBSD-doc-head",
       "color" : "blue"
       "_class" : "hudson.model.FreeStyleProject",
       "name" : "FreeBSD-doc-head-igor",
       "color" : "blue_anime"

And the latest status could be extracted from color attribute in data["jobs"] and stored in a dictionary called status.

The Jenkins API manual can be found in the Jenkins CI API reference.

2. Light up the LEDs

Recalling the APA102 data format, we wrote some predefined data frames:

 # Variables
 BRT = 224 + 16 # Brightness, 0~31 decimal

 # Predefined data frames
 START_FRAME = [0, 0, 0, 0]
 END_FRAME = [255, 255, 255, 255]
 BLUE_LED_FRAME = [BRT, 1, 0, 0]
 GREEN_LED_FRAME = [BRT, 0, 1, 0]
 RED_LED_FRAME = [BRT, 0, 0, 1]
 YELLOW_LED_FRAME = [BRT, 0, 1, 1]
 OFF_LED_FRAME = [224, 0, 0, 0]

and some send functions:

def led_send_start():  

def led_send_end():  

def led_send(status):  
    if status in ["blue", "blue_anime"]:  
    elif status in ["red", "red_anime"]:  
    elif status in ["dne"]:  

def led_send_all(jobs):  
    for job in jobs:  

so that once we updated the status dictionary, we were able to use led_send(status) to update the display:

 if __name__ == "__main__":
     while True:
         status = ... # Fetch the data
         led_send_all(status) # Update
         time.sleep(10) # or any other interval

3. Let them blink!

Now the display works really well with the static states of the job. However, we noticed that the blue_anime and red_anime colours in Jenkins, which indicate a project is in the process of building (and should be blinking), were treated as static status in the code.

How can we make the LEDs blink while keeping their current status? We added a blink_flag boolean inside the loop, reversed it each time, and decided if we should turn the lights off based on that.

Add the flag to the LED updating loop:

 blink_flag = False
 while True:
     status = ... # Fetch the data
     blink_flag = not blink_flag
     led_send_all(status, blink_flag) # Update
     time.sleep(10) # or any other interval

and reflect changes in the send function:

 def led_send_all(jobs, blink_flag):
     for job in jobs:
         if "anime" in job["status"]:
             if blink_flag:

We considered splitting the data fetching and the LED updating process, since blinking requires updating the LEDs every 0.5s, but fetching data should be every 20s or even longer. This could be achieved by simply adding a nested loop (for example, 40 led updates, 1 data updates, loop), but I chose to use threading so both jobs will not affect each other if one gets stuck.

Move the LED updating process into a controller class and run it separately:

import threading
class Led_controller(threading.Thread):
    def run(self):
        blink_flag = False
        while True:
            blink_flag = not blink_flag
            led_send_all(status, blink_flag)

if __name__ == "__main__":
    led_controller = Led_controller()

while True:
    status = ... # Fetch the data
    time.sleep(10) # or any other interval

And the LEDs should be able to blink at an interval of 0.5s.

Add some final touches

We cut the strip to parts and stuck them inside a picture frame.

Looking good!

Further reading

My implementation of this project: yzgyyang/freebsd-ci-ledstrip

FreeBSD’s support for BeagleBone: FreeBSD/arm/BeagleBoneBlack

A guide of building, installing and updating FreeBSD on a BeagleBone:
Getting Started with FreeBSD on BeagleBone Black

Official BeagleBone Green Document: BeagleBone Green

APA102 Manual

Understanding the APA102 “Superled”


I would like to thank my supervisor Ed Maste for his guidance and support on my work. I would also like to thank Siva Mahadevan, my colleague and friend, for the help and useful suggestions.

– Contributed by Guangyuan (Charlie) Yang