Adafruit Blinka: running CircuitPython libraries on a Raspberry Pi under CPython
Add CircuitPython hardware API and libraries to MicroPython & CPython devices
At a glance
- What is it?
- Adafruit Blinka is a compatibility layer that emulates the CircuitPython hardware API, from board and digitalio through busio, pulseio and usb_hid, on machines running CPython or MicroPython. Its recent work is less about new peripherals and more about making the dependency installation safe on a modern Debian based system.
- Who is it for?
- Blinka suits a developer who wants to reuse CircuitPython libraries on a Linux single board computer without writing a new hardware abstraction, and it does not suit someone who is already running CircuitPython, because the documentation states the emulated packages have no value on such a device and would likely conflict.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A compatibility layer, not a driver
Adafruit Blinka exists so that code written for CircuitPython can run on a machine that is not running CircuitPython. The repository description says it adds the CircuitPython hardware API and libraries to MicroPython and CPython devices, and the readme is more precise: it contains a selection of packages emulating the CircuitPython API for devices or hosts running CPython or MicroPython.
The emulated packages are listed individually, and the list reads like a hardware abstraction layer rather than a driver stack. analogio and digitalio provide input and output pins using pin identities that come from board and microcontroller packages. board gives breakout specific pin identities, microcontroller gives chip specific pin identities, and keypad supports scanning keys and key matrices. busio is the hardware driven interface for I2C, SPI and UART, while bitbangio is the software driven version of I2C and SPI for pins that have no peripheral attached.
The rest are specialised. pulseio and pwmio both provide access to basic pulse IO, that is PWM. neopixel_write is a low level interface to NeoPixels, rainbowio provides the colorwheel function, and usb_hid makes the host act as a HID device using the usb_gadget kernel driver.
Two things are worth noting about the list. The first is that board and microcontroller are the abstraction boundary: everything else is generic, and the pin identities come from the platform detection layer. The second is that usb_hid is the outlier, since it uses a kernel driver rather than a GPIO pin, and it is the feature that turns a Raspberry Pi into a USB keyboard or mouse for another machine.
The board import is where the dependency logic lives
Blinka installs a platform independent base package, and the interesting behaviour is in what happens when you import board on CPython.
Importing board checks the detected Raspberry Pi, Jetson or AM33XX dependencies. On a supported board, an interactive terminal offers to install missing or incompatible packages using the running Python interpreter. Other boards retain their existing backend selection and manual dependency setup, and unrelated packages are not installed.
That is a design choice with consequences. The automatic path only applies to the platforms Adafruit knows about, and it deliberately stops short of touching anything that is not the GPIO backend it needs. It is a narrower behaviour than an installer that fixes everything in sight.
The failure handling is the part that shows the most care, and the readme spells out several cases. A missing backend reports its installation guidance when imported. A known incompatible installed version of a required GPIO backend reports an actionable error rather than silently continuing. PWM and NeoPixel backends are optional for board, so declining their installation, or hitting an installation failure, does not block unrelated GPIO use. Installation failures produce a warning, and missing or known incompatible backends are reported when the relevant feature is imported rather than at board import time.
After installation, Blinka rechecks imports and required versions. If they are still unavailable, for example after a first user site installation, it reports restart guidance instead of carrying on as though the installation fixed the running process. Optional only problems warn rather than abort the board import. And there is one case that is easy to miss: if an upgraded backend was already imported, you need to restart Python before using the replacement, even when that backend is optional.
The consequence for a user is that Blinka will not try to repair a running interpreter in place. It tells you to restart, which is correct behaviour but requires an operational habit.
The installer refuses to use sudo, and that is the point
The security stance of the runtime installer is stated in a few sentences and it is worth reading twice.
The runtime installer does not use sudo, does not bypass externally managed environment protections, does not install operating system packages, and does not change GPIO permissions. Native packages may still need a compiler and Python development headers provided by the operating system.
So the split is clean. Anything pip can do inside the environment, Blinka attempts inside the environment. Anything that needs root, such as installing a GPIO library system wide or giving the process access to the GPIO group, is the administrator's job, and the documentation says so rather than trying to escalate.
There is a second rule that follows from the same principle. Non-interactive programs never prompt or run pip. A non-interactive service that imports board with a missing backend gets an error and installation guidance, not a hung prompt waiting for input that will never come. The environment variable BLINKA_SKIP_DEPENDENCY_CHECK=1 exists for applications that manage their own dependencies, and the readme is careful about what it does and does not do: it disables the automatic dependency checks and installation prompts, and it does not install packages or make unsupported backends supported. Missing backend imports and the PWM and NeoPixel feature checks still report errors.
BLINKA_SKIP_DEPENDENCY_CHECK=1 .env/bin/python program.pyThe general advice that comes with all of this is to use a virtual environment where practical, and for services to install the backends they need during setup, before the service starts.
The gpiod version check is a security control, not a formality
Raspberry Pi GPIO in Blinka uses the official gpiod bindings, and the readme devotes unusual attention to which version of that binding is acceptable.
For Raspberry Pi GPIO, Blinka accepts Debian style python3-libgpiod bindings labelled 2.2.0 when they come from maintained libgpiod source of 2.2.1 or later and below 3, and they report a native API version in that range. The explanation given is that Trixie's package uses this older Python label despite containing later fixes. In a virtual environment created with --system-site-packages, Blinka checks that the selected binding and metadata paths belong to the installed system package.
The reason to care is spelled out two paragraphs later. The readme says this trusts the system administrator and package database, and that it is not a file integrity monitor. Security update revisions are not pinned. Importing gpiod before board works normally, and package manager queries are cached per process. And the recommendation is to restart Python after package upgrades rather than reloading native modules.
What makes this passage worth reading is the contrast with the PyPI path. Ordinary PyPI installs retain the conservative gpiod of 2.2.2 or later and below 3, because the old PyPI 2.2.0 has a native use-after-free during line requests, and a check for available API functions cannot detect it. The readme links the upstream fix.
So the same library accepts a lower labelled version from a distribution package and a higher one from PyPI, for a reason that has nothing to do with API compatibility. It is a deliberate, documented exception for distribution packaging, and it is worth knowing about if you maintain the system package yourself.
PWM is a separate package on Pi 5 and earlier
The Raspberry Pi PWM story is split across two packages, and the readme gives the rule for the hardware PWM pins.
pwmio.PWMOut uses the Adafruit Blinka Raspberry Pi PWM package on Pi 5 and earlier models. The package is installed separately, at version 1.0.2 or later, and the same installation discipline as the GPIO backend applies: install it during setup, in the environment the service will run with.
.env/bin/python -m pip install 'Adafruit-Blinka-Raspberry-Pi-PWM>=1.0.2'The plain install command is the other half.
.env/bin/python -m pip install Adafruit-Blinka 'gpiod>=2.2.2,<3'Note the environment prefix. The documentation says to run the service with that same environment's Python, and adds that a virtual environment using --system-site-packages can see operating system packages, but those packages must still meet the required versions. Blinka does not bypass externally managed system Python protections.
The readme also says that configured BCM and RP1 hardware PWM is preferred on verified pin routes. The sentence is cut off in the visible text, so the full rule about other pins is not available here, but the shape of it is clear: on the hardware that supports it, the verified routes give you true hardware PWM rather than a software approximation, and you should check which pins you are using are on that list.
This is the pattern to take away from the whole GPIO section. The base package is one pip install, and each optional feature is another, installed explicitly, with the version floor stated.
The packaging reflects a project with real governance
The repository layout shows an operationally mature project rather than a hobby script.
The top level holds a pyproject.toml and a setup.py, a requirements.txt, a pytest.ini, a .pylintrc, a .pre-commit-config.yaml, a .readthedocs.yaml, a CODE_OF_CONDUCT.md, a scripts/ directory, an examples/ directory with per board example files, both test/ and tests/ directories, and a docs/ directory. There is a .gitmodules entry, and importantly there is a LICENSES/ directory along with README.rst.license and requirements.txt.license files, which is the pattern used for a repository that ships third party text with an explicit licence identifier rather than an implicit one.
The build system is standard modern Python. The pyproject requires setuptools, wheel and setuptools-scm, with the build backend of setuptools.build_meta, and the version comes from git describe --tags --long with a no-local-version local scheme, so the published version follows the repository tags rather than a hand edited string. The setup.py declares python_requires of 3.9.0 or newer, and the packages live under src/ with find_packages.
The interesting part of setup.py is the py_modules list, which names the emulated packages explicitly: analogio, bitbangio, board, busio, digitalio, keypad, neopixel_write, onewireio, pulseio, pwmio, rainbowio and usb_hid. The comment explains why: find_packages only finds packages in directories with __init__.py files, so the top level single file modules have to be listed by hand. It is a small thing, but it is the reason the module names are guaranteed to be importable at the top level, which is the entire point of a compatibility layer.
The dependencies list in requirements.txt is short and telling: Adafruit-PlatformDetect at 3.89.1 or later does the board detection, Adafruit-PureIO at 1.1.7 or later handles the pure Python IO paths, and there is binho-host-adapter, pyftdi, packaging, the circuitpython typing stubs, a toml backport for Python below 3.11, and sysv_ipc for Linux on non MIPS machines.
The licence is MIT, stated in the pyproject SPDX header as well, and the repository carries a LICENSE file.
Where Blinka is the wrong tool
The first limitation is stated in the readme itself and it disqualifies a whole class of use. The emulated packages are intended to provide a CircuitPython like API for devices running CPython or MicroPython, and since the corresponding packages should be built in to any standard CircuitPython image, they have no value on a device already running CircuitPython and would likely conflict in unhappy ways.
So Blinka is only for the case where you have a Linux board, or a CPython or MicroPython host, and you want to run code that was written for CircuitPython. If you are choosing a runtime for a new project, and the board has a CircuitPython build, that build is the better answer, because the same code then runs without a translation layer and without the dependency machinery described above.
The second limitation is the pin identity problem. The board package resolves pin identities from the detected platform, which means the mapping between a physical header pin and a board.D5 style name comes from the platform detection layer and the board specific data that goes with it. If your board is a Raspberry Pi, Jetson or AM33XX, the documentation is specific. If it is something else, the readme says other boards retain their existing backend selection and manual dependency setup, which means you are on your own. Board detection support is a whitelist, and the whitelist is short.
The third is that this is an emulation, so it inherits the limits of the emulated API. The gpio line support on a Linux host is not the same as the timing behaviour on a microcontroller, and any code that assumed CircuitPython specific timing or interrupt behaviour is now running on a different scheduler. The readme does not discuss timing characteristics at all, which is itself informative.
The alternative to weigh against it is a board support package written directly against the Linux GPIO interfaces, or a general purpose library such as RPi.GPIO or smbus. The difference in approach is directness. A native Linux library talks to the hardware with no API translation, which means it is faster, it exposes the real capabilities of the chip, and it is as stable as the kernel bindings underneath. Blinka gives you something different in exchange: your existing CircuitPython code, and any CircuitPython library on PyPI, runs unmodified, which for a device that has a mixed fleet of libraries already written for CircuitPython is worth a translation layer.
On maintenance, the last push was on 2026-10-20, and the recent releases are 9.2.0 on 2026-07-30, 9.1.0 on 2026-04-21 and 9.0.4 on 2026-03-25, whose release note is Fix Olimex LIME2 Import. The 9.1.0 note says Add busio.UART for linux plus more bug fixes and features, and 9.2.0 is Update to PyProject plus many bug fixes. Those notes are a fair description of where the project is: keeping a compatibility layer aligned with the boards and libraries it emulates, rather than adding new hardware abstraction of its own.
Editorial conclusion
Blinka suits a developer who wants to reuse CircuitPython libraries on a Linux single board computer without writing a new hardware abstraction, and it does not suit someone who is already running CircuitPython, because the documentation states the emulated packages have no value on such a device and would likely conflict. Before you rely on it, test the pin mapping for your own board, since board identity is resolved from the detected platform, and read the dependency section of the docs, because the runtime installer will not use sudo, will not install operating system packages and will not change GPIO permissions, so those steps have to be done by an administrator first.
Frequently asked questions
What is Adafruit Blinka and what does it provide?
It is a set of packages emulating the CircuitPython API for devices or hosts running CPython or MicroPython, covering analogio, digitalio, board, microcontroller, busio, bitbangio, keypad, neopixel_write, pulseio, pwmio, rainbowio and usb_hid.
How do I install Adafruit Blinka on a Raspberry Pi?
Create a directory, make a virtual environment with python3 -m venv .env, activate it with source .env/bin/activate, and run pip3 install Adafruit-Blinka. The documentation asks you to use a virtual environment where practical.
Does Blinka need sudo to install GPIO backends?
No. The runtime installer does not use sudo, does not bypass externally managed environment protections, does not install operating system packages and does not change GPIO permissions. Native packages may still need a compiler and Python development headers from the operating system.
What does BLINKA_SKIP_DEPENDENCY_CHECK=1 do?
It disables the automatic dependency checks and installation prompts before importing board, for applications that manage their own dependencies. It does not install packages or make unsupported backends supported, and missing backend imports and the PWM and NeoPixel feature checks still report errors.
Can I use Adafruit Blinka on a device already running CircuitPython?
The readme says not to. The emulated packages have no value on a device already running CircuitPython, since the corresponding packages should be built in, and they would likely conflict in unhappy ways.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/adafruit-adafruit-blinka)Was this useful?