Adafruit's CircuitPython is an open-source implementation of Python for microcontrollers. It's derived from (also known as, a "fork" of) MicroPython, a ground-breaking implementation of Python for microcontrollers and constrained environments.

CircuitPython ships on many Adafruit products. We regularly create new releases and make it easy to update your installation with new builds.

However, you might want to build your own version of CircuitPython. You might want to keep up with development versions between releases, adapt it to your own hardware, add or subtract features, or add "frozen" modules to save RAM space. This guide explains how to build CircuitPython yourself.

CircuitPython is meant to be built in a POSIX-style build environment. We'll talk about building it on Linux-style systems or on MacOS. It's possible, but tricky, to build in other environments such as CygWin or MinGW: we may cover how to use these in the future.

Install a Real or Virtual Linux Machine

If you don't already have a Linux machine, you can set one up in several different ways. You can install a Linux distribution natively, either on its own machine or as a dual-boot system. You can install Linux on a virtual machine on, say, a Windows host machine. You can also use Windows Subsystem for Linux (WSL), available on Microsoft Windows 10, which allows you to run a Linux distribution with an emulation layer substituting for the Linux kernel.

We recommend using the Ubuntu distribution of Linux or one of its variants (Kubuntu, Mint, etc.). The instructions here assume you are using Ubuntu. The 20.04 LTS (Long Term Support) version is stable and reliable.

Native Linux

You can install Ubuntu on a bare machine easily. Follow the directions (this link is for 20.04) on the Ubuntu website. You can also install Ubuntu on a disk shared with your Windows installation, or on a separate disk, and make a dual-boot installation.

Linux on a Virtual Machine

Linux can also be installed easily on a virtual machine. First you install the virtual machine software, and then create a new virtual machine, usually giving the VM software a .iso file of Ubuntu or another distribution. On Windows, VM Workstation Player and VirtualBox are both free and easily installed.

Raspberry Pi

It may be possible to build on Raspberry Pi OS, but it will be easier if you install Ubuntu on your Raspberry Pi. You will also need to download the aarch64 gcc toolchain. The RPi is not a fast machine: be prepared to wait for a build to complete.

Install Build Tools on Ubuntu

The Ubuntu 20.04 LTS Desktop distribution includes most of what you need to build CircuitPython. You'll need to install some additional packages, including build-essential, if it's not already installed, and also gettext and uncrustify. In a terminal window, do:

sudo apt update
# Try running `make`. If it's not installed, do:
# sudo apt install build-essential
# The version of uncrustify you need is in a PPA:
sudo add-apt-repository ppa:pybricks/ppa
sudo apt install git gettext uncrustify

Cortex-M Builds

Most CircuitPython boards use an ARM Cortex-M processor.  You need to download and unpack the appropriate ARM Cortex-M toolchain. Do not use the obsolete ARM Ubuntu "ppa" (private package archive). The links below point to the x64 versions of the toolchain. If you are building on some other architecture, such as on a Raspberry Pi, download the appropriate toolchain build for your computer.

If you want to build an older version of CircuitPython:

Cortex-A Builds

The broadcom port (Raspberry Pi Linux boards) needs a different toolchain. Use the Cortex-A toolchain:

  • For CircuitPython 7.x and later, use the 10.3-2021.07 version.

You will also need the mtools package:

sudo apt install mtools

And finally, you need a version 4.2 or later of mkfs.fat, which is part of the dosfstools package. Ubuntu 20.04 has version 4.1. You can download and build dosfstools. After you do so, copy mkfs.fat to some place that is or will be on your PATH.

wget https://github.com/dosfstools/dosfstools/releases/download/v4.2/dosfstools-4.2.tar.gz
tar xvf dosfstools-4.2.tar.gz
cd dosfstools-4.2
./configure
make

Installing the Toolchain

Unpack the toolchain in a convenient directory.

# This is an example.
cd ~/bin
tar xvf <name of the .bz2 or .xz file you downloaded>

Next, add a line to your .bash_profile or other startup file to add the unpacked toolchain executables to your PATH. For example:

export PATH=/home/$USER/bin/gcc-arm-none-eabi-10-2020-q4-major/bin:$PATH

Open a new terminal window, and see if you now have the correct executables on your path:

which arm-none-eabi-gcc
/home/halbert/bin/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gcc

Other Builds

Now move to the Build CircuitPython section of this guide. There we will get the CircuitPython source code, install a few more dependencies, and then build!

To build CircuitPython on macOS, you need to install git, python3, and the compiler toolchain. The easiest way to do this is to first install Homebrew, a software package manager for macOS. Follow the directions on its webpages.

Now install the software you need:

brew update
brew install git python3 gettext uncrustify
brew link gettext --force

Then install the compiler toolchain. On the Linux Setup page in this guide, you can find pointers to the download pages for different kinds of ARM processors, and also special instructions for other builds (not all are available on macOS). Download and install the appropiate .pkg file for your Mac.

Once the dependencies are installed, we'll also need to make a disk image to clone CircuitPython into. By default the macOS filesystem is case insensitive. In a few cases, this can confuse the build process. So, we recommend creating a case sensitive disk image with Disk Utility to store the source code. Make the image capable of storing at least 10GB, so you won't run out of space if you do extensive development. You can use a sparse bundle disk image which will grow as necessary, so you don't use up all the space at once. The disk image is a single file that can be mounted by double clicking it in the Finder. Once it's mounted, it works like a normal folder located under the /Volumes directory.

That's it! Now you can move on and actually Build CircuitPython. There we will get the CircuitPython source code, install a few more dependencies, and then build!

Windows Subsystem for Linux (WSL) is a feature of Windows 10 that lets you run Ubuntu and other versions of Linux right in Windows. It's real Ubuntu, without the Linux kernel, but with all the software packages that don't need a graphical interface. You can build CircuitPython in WSL easily. It's easier to install than a Linux virtual machine.

Install WSL

The installation procedures for WSL continue to evolve. Rather than provide information here which quickly becomes outdated, we ask that you refer to Microsoft's official instructions: Windows Subsystem for Linux Installation Guide for Windows 10. Enable the WSL 2, which is better for our purposes than WSL 1 in several ways.

One WSL 2 is set up, you need to choose a Linux distribution to install, as described in the document above. Choose Ubuntu 20.04.

Finish Linux Install

Once WSL is set up, just proceed with the regular Linux Setup.

Build CircuitPython

From this point on, you can build CircuitPython just as it's built in regular Ubuntu, described in the Build CircuitPython section of this guide.

Moving Files to Windows

You can copy files to and from Windows through the /mnt/c. For instance, if you want to copy a CircuitPython build to the desktop, do:

cp build-circuitplayground_express/firmware.uf2 /mnt/c/Users/YourName/Desktop
Warning: Don't build in a shared folder (in /mnt/c). You'll probably have filename and line-ending problems.

You might be tempted to clone and build CircuitPython in a folder shared with the Windows filesystem (in /mnt/c somewhere). That can cause problems, especially if you use git commands on the Windows side in that folder. The CircuitPython build assumes case-sensitive filenames, but Windows usually ignores filename case differences. You may also have line-ending problems (CRLF vs.  LF). It's better to clone and build inside your home directory in WSL, and copy files over to a shared folder as needed.

Mounting a CircuitPython Board in WSL

You can mount your ...BOOT or CIRCUITPY drive in WSL. Create a mount point and then mount it. Note that you'll have to remount each time the drive goes away, such as when you restart the board or switch between the BOOT drive and CIRCUITPY. So it's probably more convenient to copy files to the board from Windows instead of WSL.

# You only need to do this once.

# Choose the appropriate drive letter.
sudo mkdir /mnt/d

# Now mount the drive.
sudo mount -t drvfs D: /mnt/d

# Now you can look at the contents, copy things, etc.
ls /mnt/d
cp firmware.bin /mnt/d
# etc.

If the setup instructions above don't work for your particular OS setup, for whatever reason, you can get the ball rolling by installing these tools in whatever way you can and then getting them to work with the Makefile in circuitpython/ports/atmel-samd. (main branch):

Fetch the Code to Build

Once your build tools are installed, fetch the CircuitPython source code from its GitHub repository ("repo") and also fetch the git "submodules" it needs. The submodules are extra code that you need that's stored in other repos.

In the commands below, you're cloning from Adafruit's CircuitPython repo. But if you want to make changes, you might want to "fork" that repo on GitHub to make a copy for yourself, and clone from there.

git clone https://github.com/adafruit/circuitpython.git
cd circuitpython
make fetch-submodules

We are not using git submodule update --init --recursive or git submodule update --init. Instead you run the special Makefile target make fetch-submodules fetches only many commits from each submodule as is necessary. This saves downloading the complete trees of some large submodules.

If you want to build a version other the latest, checkout the branch or tag you want to build. For example:

# Build using the latest code on the main branch.
git checkout main

# Build the latest code on the 7.2.x branch
git checkout 7.2.x

# Build the 7.2.4 version exactly.
git checkout 7.2.4

Note the build process has evolved, and earlier versions will need to be built somewhat differently than how the instructions in this guide specify. If you have trouble, ask on Discord or the forums.

Install Required Python Packages

After you have cloned the repo, you'll need to install some Python packages that are needed for the build process. You only need to do this the first time, though you may want to run this again from time to time to make sure the packages are up to date.

# Install pip if it is not already installed (Linux only)
sudo apt install python3-pip

# Install needed Python packages from pypi.org.
pip3 install --upgrade -r requirements-dev.txt
pip3 install --upgrade -r requirements-doc.txt

The --upgrade flag will force installation of the latest packages, except where a version is explicitly specified in the requirements-*.txt files.

Install pre-commit

We are using the pre-commit system to check submitted code for problems before it gets to GitHub. For more information, see this Learn Guide page. To add pre-commit to your repository clone, do:

cd <your repository clone directory>
# You only need to do this once in each clone.
pre-commit install

Build mpy-cross

Build the mpy-cross compiler first, which compiles Circuitpython .py files into .mpy files. It's needed to include library code in certain boards.

(If you get a make: msgfmt: Command not found error, you have not installed gettext. Go back to the Setup page for your operating system.)

Normally you do not need to rebuild mpy-cross on every pull or merge from the circuitpython repository or for your own changes. The .mpy format does not change very often. But occasionally when we merge from MicroPython, the format changes. You will find that your old .mpy files or frozen libraries give an error, and you will need to rebuild mpy-cross.

make -C mpy-cross

Build CircuitPython

Now you're all set to build CircuitPython. If you're in the main branch of the repo, you'll be building the latest version. Choose which board you want to build for. The boards available are all the subdirectories in ports/atmel-samd/boards/.

cd ports/atmel-samd
make BOARD=circuitplayground_express

By default the en_US version will be built. To build for a different language supply a TRANSLATION argument.

cd ports/atmel-samd
make BOARD=circuitplayground_express TRANSLATION=es

Run Your Build!

When you've successfully built, you'll see output like:

Create build-circuitplayground_express/firmware.bin
Create build-circuitplayground_express/firmware.uf2
python2 ../../tools/uf2/utils/uf2conv.py -b 0x2000 -c -o build-circuitplayground_express/firmware.uf2 build-circuitplayground_express/firmware.bin
Converting to uf2, output size: 485888, start address: 0x2000
Wrote 485888 bytes to build-circuitplayground_express/firmware.uf2.

Copy firmware.uf2 to your board the same way you'd update CircuitPython: Double-click to get the BOOT drive, and then just copy the .uf2 file:

# Double-click the reset button, then:
cp build-circuitplayground_express/firmware.uf2 /media/yourname/CPLAYBOOT

The board will restart, and your build will start running.

If you're using a board without a UF2 bootloader, you'll need to use bossac and the firmware.bin file, not the .uf2 file. Detailed instructions are here.

Use All Your CPUs When Building

Most modern computers have CPU chips with multiple cores. For instance, you may have a 2-core, 4-core, or 6-core or more CPU. Your CPU may also allow 2 "threads" per core, so that it appears to have even more cores. You can run much of the build in parallel by using the make -j flag. This is will speed up the build noticeably.

If you don't know how many cores or threads your CPU has, on Linux you can use this command:

getconf _NPROCESSORS_ONLN
12
# This CPU has 6 cores and 12 threads.

Then, when you run make, add the -j<n> option to use as many cores or threads as possible. For example:

make -j12 BOARD=trinket_m0

When to make clean

After you make changes to code, normally just doing make BOARD=... will be sufficient. The changed files will be recompiled and CircuitPython will be rebuilt.

However, there are some circumstance where you must do:

make clean BOARD=...

If you have changed the #include file structure in certain ways, or if you have defined QSTR's (a way of defining constants strings in the CircuitPython source), then you must make clean before rebuilding. If you're not sure, it's always safe to make clean and then make. It might take a little longer to build, but you'll be sure it was rebuilt properly.

Updating Your Repo

When there are changes in the GitHub repo, you might want to fetch those and then rebuild. Just "pull" the new code (assuming you haven't made changes yourself), update the submodules if necessary, and rebuild:

git pull
git submodule sync
git submodule update --init
# Then make again.

Those are the basics. There's a lot more to know about how to keep your forked repo up to date, merge "upstream" (Adafruit's) changes into your code, etc. We cover this in the Contribute to CircuitPython with Git and GitHub guide

Normally, all imported Python modules in CircuitPython are loaded into RAM in compiled form, whether they start as .mpy or .py files. Especially on M0 boards, a user program can run out of RAM if too much code needs to be loaded.

To ameliorate this problem, a CircuitPython image can include compiled Python code that is stored in the image, in flash memory, and executed directly from there. These are "internal frozen modules". The circuitplayground_express  builds use this technique, for example.

If you would like to build a custom image that includes some frozen modules, you can imitate how it's done in the circuitplayground_express build. Look at boards/circuit_playground_express/mpconfigboard.mk:

USB_VID = 0x239A
USB_PID = 0x8019
USB_PRODUCT = "CircuitPlayground Express"
USB_MANUFACTURER = "Adafruit Industries LLC"

CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21

SPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "S25FL216K, GD25Q16C"
LONGINT_IMPL = MPZ

# Turn off displayio to make room for frozen libs.
CIRCUITPY_DISPLAYIO = 0

# Now we actually have a lot of room. Put back some useful modules.
CIRCUITPY_BITBANGIO = 1
CIRCUITPY_COUNTIO = 1
CIRCUITPY_BUSDEVICE = 1

# Include these Python libraries in firmware.
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_CircuitPlayground
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HID
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_LIS3DH
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Thermistor

Notice the FROZEN_MPY_DIRS lines in the file. Pick the mpconfigboard.mk file for the board you are using, and add one or more similar lines. You will need to do add directories for the libraries you want to include. If these are existing libraries in GitHub, you can add them as submodules. For instance, suppose you want to add the Adafruit_CircuitPython_HID library to the feather_m0_express build. Add this line to boards/feather_m0_express/mpconfigboard.mk:

FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HID

Then add the library as a submodule:

cd circuitpython/frozen
git submodule add https://github.com/adafruit/Adafruit_CircuitPython_HID
Set the submodule to a commit that is a release tag. If you try to freeze a module that is at untagged commit, you'll get a git error when building.

When you add the submodule it will be cloned into the frozen/ directory.

Note that there is limited unused space available in the images, especially in the non-Express M0 builds, and you may not be able to fit all the libraries you want to freeze. You can of course try to simplify the library code if necessary to make it fit.

CircuitPython supports external SPI/QSPI flash chips for the CIRCUITPY filesystem. Each board build is setup to support only one or a few flash chips. The chips are not identical in how they are accessed, so you can't just substitute without rebuilding. The chips that CircuitPython currently supports are listed in a GitHub repository of chip data, https://github.com/adafruit/nvm.toml, along with the settings needed for each.

The chip(s) that are supported in a particular board are specified in the mpconfigboard.mk file for that board, in the line that defines EXTERNAL_FLASH_DEVICESSo change or add to EXTERNAL_FLASH_DEVICES if you want to use a different supported chip. We don't support a entire list of chips for each build because the table of data for all possible chips would take significant space in the CircuitPython build.

Here's an example:

EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"

You may want to include a particular module that is not included by default for the board for your board. You can customize which modules to include in your CircuitPython build by adding or changing settings in the mpconfigboard.mk file for your board.

For example, here is the standard circuitpython/ports/atmel-samd/boards/trinket_m0/mpconfigboard.mk file:

USB_VID = 0x239A
USB_PID = 0x801F
USB_PRODUCT = "Trinket M0"
USB_MANUFACTURER = "Adafruit Industries LLC"

CHIP_VARIANT = SAMD21E18A
CHIP_FAMILY = samd21

INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

Suppose you wanted to turn on pulseio, but you did not need MIDI support. There is not enough room for pulseio in this build, but if you turn other modules, you can make room. Sometimes this will work only for certain languages due to the size of the translation. In this case, for the en_US translation, turning off usb_midi frees up enough space to include pulseio. Add these lines to the file above:

CIRCUITPY_PULSEIO = 1
CIRCUITPY_USB_MIDI = 0

You can figure out which modules are on or off by looking at the module support matrix in the CIrcuitPython documentation. It also helps to study the files circuitpython/py/circuitpy_mpconfig.mk and circuitpympconfig.h, and circuitpython/ports/<your-board's-port>/mpconfigport.mk and mpconfigport.h.

Warning: Don't put Makefile comments on the same line as Makefile assignments. I causes the variable value to include the trailing spaces before comment character

The ports/espressif build setup is a rather involved process. Please read the README.md in that directory. It has instructions that also refer to further instructions in the ESP-IDF documentation.

On MacOS, you will need to install cmake and ninja:

brew install cmake
brew install ninja

On Linux you probably need to install ninja-build and cmake.

sudo apt install ninja-build cmake

The ESP-IDF expects there to be a python command which runs python3. On Ubuntu, there is no plain python by default, so install this simple package which links python to python3.

sudo apt install python-is-python3

Once you have the prerequisites installed, change to the ports/espressif directory, and run the install.sh script. You only need to do this once.

cd circuitpython/ports/espressif
esp-idf/install.sh

After this, in each fresh terminal window in which you are doing builds, you need to use esp-idf/export.sh in order to set up the correct PATH and other environment variables.

# Do this in each new terminal.
cd circuitpython/ports/espressif
source esp-idf/export.sh

export.sh will suggest that you use idf.py to do a build after you run it. But we don't use idf.py. Instead, use make, for example:

make BOARD=adafruit_magtag_2.9_grayscale

This guide was first published on Apr 26, 2018. It was last updated on Apr 26, 2018.