Setting up a Zephyr RTOS development environment on macOS with Vagrant

Following the official guide to set up a development environment to natively compile and run Zephyr application on macOS is a challenging task. Unlike Linux, the Zephyr project does not provide an install package and a toolchain for macOS. You have to build the toolchain by yourself. This step causes quite a headache.

Vagrant is a tool that allows you to compile and run an application in Linux while editing the code in macOS and Windows. It also allows you to provision the virtual machine for the project.

Installing VirtualBox

VirtualBox is a free virtual machine software for macOS, Windows and other operating systems.

  1. Download and install the latest version of VirtualBox from its official website

  2. Download and install the Extension Pack for USB 2.0 and USB 3.0 supports from the same website (if you want to flash a Zephyr application to a microcontroller)

Installing Vagrant

Vagrant has an installer for macOS, so the installing process is trivial.

  1. Download and install the latest version of Vagrant from its official website

Creating a Vagrantfile

Vagrantfile is used to specify the operating system of the virtual machine, provisions and other configuration.

  1. Create an empty project folder and go to the project folder

     mkdir hello_zephyr && cd hello_zephyr

    or go an existing project

     cd <project>
  2. Create a Vagrantfile

     vagrant init

The generated Vagrantfile is a bit verbose, but it is a great starting point to learn about Vagrant.

Specifying a Vagrant Box

Installing an operating system from scratch is a time-consuming process. A box is an image of a pre-built operating system. Vagrant provides a variety of official and third-party boxes. In this case, I choose the Ubuntu 16.04 made by bento.

  1. Change the box from base to bento/ubuntu-16.04

     Vagrant.configure("2") do |config| = "bento/ubuntu-16.04"
       # Other configurations

Provisioning the development environment

The previous step provides a fresh Ubuntu environment. Now we need to install the software and tools. At this point, you can boot up the vagrant and start to install. However, you to set up the environment again when you want to use the same setup on a different machine. Provisioning allows you to define scripts that will set up the environment. When you want to set up the environment on a different machine, you can rerun the script.

The following guide is setting up a development environment for Zephyr 1.9.1 based on the official guide. It might be different if you want to set up for a different version of Zephyr.

  1. Add commands for installing the package with apt-get.

     config.vm.provision "zephyr-dependancies", type: :shell, inline: <<-SHELL
         apt-get update
         apt-get -y upgrade
         apt-get -y install git make gcc g++ ncurses-dev \
             doxygen dfu-util device-tree-compiler python3-ply python3-pip

    Remember to add the -y option apt-get to confirm all prompts since you can not directly interact with the apt-get.

  2. Clone the zephyr project. This will clone and checkout version 1.9.1.

     # apt-get installation provision
     config.vm.provision "zephyr-project", type: :shell, inline: <<-SHELL
       git clone zephyr-project
       cd zephyr-project
       git checkout zephyr-v1.9.1
       cd ..
       echo "export ZEPHYR_BASE=$(pwd)/zephyr-project" >> .profile
       echo "source \\$ZEPHYR_BASE/" >> .profile

    This script will add the location of the local Zephyr repository as an environment variable. It will also source the to the startup script (i.e. ~/.profile), so you don’t need to do it when you launch the virtual machine from sleep.

  3. Install the Zephyr SDK.

     # previous provisions
     config.vm.provision "zephyr-sdk", type: :shell, inline: <<-SHELL
       wget --quiet
       chmod +x
       printf "$(pwd)/zephyr-sdk\n" | ./ --quiet
       echo "export ZEPHYR_GCC_VARIANT=zephyr" >> .profile
       echo "export ZEPHYR_SDK_INSTALL_DIR=$(pwd)/zephyr-sdk" >> .profile

    The most tricky command here is running the setup package. It will prompt to ask you where to install the SDK, but you can not just type in the location. The solution is using printf to input the location for you.

  4. Install the python packages.

     config.vm.provision "shell", privileged: false, inline: <<-SHELL
       pip3 install --quiet --upgrade pip
       pip3 install --quiet --user -r $ZEPHYR_BASE/scripts/requirements.txt

    Setting the privileged to false to tell Vagrant to execute this provision as a non-root user. Otherwise, the packages will be installed in the root user’s directory. The root directory may not be accessible when you run vagrant ssh and login in as a non-root user later.

  5. Add udev rules for the Arduino 101 (optional).

     config.vm.provision "create-dfu-udev-rule", type: :shell, inline: <<-SHELL
       wget --quiet
       chmod +x create_dfu_udev_rule
       sudo ./create_dfu_udev_rule
       rm create_dfu_udev_rule
       echo "export ZEPHYR_FLASH_OVER_DFU=y" >> .profile

Writing a simple Zephyr application

This will be used to test the development. There is a great application primer on the Zephyr project website

  1. Create a Makefile under the project’s root directory (e.g. hello_zephyr)

     BOARD ?= qemu_x86
     CONF_FILE = prj.conf
     include ${ZEPHYR_BASE}/
  2. Create an empty configuration file named as prj.conf. It does not need to contain anything.

  3. Create a src directory for storing the source code.

  4. Create a main.c file that contains a simple C program

     #include <zephyr.h>
     #include <misc/printk.h>
     void main(void)
         printk("Hello Zephyr!");
  5. Create a Makefile under the src directory

     obj-y = main.o
  6. The project directory should be similar to this.

       - src
           - Makefile
           - main.cpp
       - Makefile
       - prj.conf

Compiling and running the Zephyr application in an emulator

Everything should be ready for compiling and running your zephyr application now.

  1. Start up the virtual machine. This step might take a while since the machine is being booted up for the first time. Vagrant might need to download the image of the box.

     vagrant up
  2. Log into the virtual machine via ssh

     vagrant ssh
  3. Go to the synced directory on the virtual machine

     cd /vagrant

    If you list all the files in /vagrant by calling ls, you will see all files in your macOS that are being synced.

     # Makefile  prj.conf  src  Vagrantfile
  4. Compile the application and run it on the emulator

     make run

    Now, you should see the following in your console output:

     GDT     gdt.o
     BIN     zephyr.bin
     To exit from QEMU enter: 'CTRL+a, x'
     [QEMU] CPU: qemu32
     qemu-system-i386: warning: Unknown firmware file in legacy mode: genroms/multiboot.bin
     Hello Zephyr!
  5. Congratulations, you have successfully set up a development environment for Zephyr with Vagrant

Bonus: Run the application on Arduino 101 (or other microcontrollers)

  1. Find the vendor id and the product id of the microcontroller.

     VBoxManage list usbhost

    The output should be similar to the following. In the case of Arduino 101 (also called Genuino 101), you might need to press the master reset button (between the power jack and the USB port) before calling the list command.

     Host USB Devices:
     UUID:               ...
     VendorId:           0x8087 (8087)
     ProductId:          0x0aba (0ABA)
     Revision:           128.135 (128135)
     Port:               4
     USB version/speed:  0/Full
     Manufacturer:       Intel
     Product:            GENUINO 101
     SerialNumber:       ...
     Address:            ...
     Current State:      Available
  2. Enable the USB filter and add Arduino 101 to the whitelist.

     config.vm.provider "virtualbox" do |vb|
       vb.customize ["modifyvm", :id, "--usb", "on"]
       # Enable USB 2.0
       vb.customize ['modifyvm', :id, '--usbehci', 'on']
       # Enable USB 3.0
       vb.customize ['modifyvm', :id, '--usbxhci', 'on']
       vb.customize ['usbfilter', 'add', '0', '--target', :id, '--name',
                     'GENUINO 101', '--vendorid', '0x8087', '--productid',
  3. List the connected USB device in the virtual machine.


    The vendor id and product id of Arduino 101 should show up in the output similar to the following.

     Bus 002 Device 002: ID 8087:0aba Intel Corp.
  4. Compile and flash the application to the Arduino 101 Sensor Core

     make BOARD=arduino_101_sss flash
  5. Compile and flash the application to the Arduino 101

     make BOARD=arduino_101 flash
  6. The application should be running on your Arduino 101 now.


  1. Zephyr project’s official documentation
  2. Vagrant’s official documentation
  3. Nguyen Sy Thanh Son’s blog