Creative Coding

CircuitPython Turbo Makes Your Hot Loop 20 to 70 Times Faster Without Leaving Python

Mikey Sklar's turbo compiles only the functions you choose into machine code, installs whichever version benchmarks fastest, and silently falls back to plain Python on boards that can't load it. That's good news for anyone driving LEDs, displays or audio from a microcontroller.

Almost every creative-coding project on a microcontroller runs into the same problem. Python is great until you ask it to draw a fractal, animate 300 NeoPixels or process audio samples, and then the frame rate drops. The usual fix is rewriting the slow part in C, which defeats the point of using CircuitPython.

CircuitPython Turbo, from Adafruit’s Mikey Sklar, is a better fix. You mark the slow function, run one build command, and the board runs that function as machine code. The rest of your program stays readable Python.

Two halves

CircuitPython has long accepted MicroPython’s @micropython.native and @micropython.viper decorators. What most boards lacked was a way to load compiled machine code, and a practical tool to produce it. Turbo provides both.

On the board: firmware built with native loading enabled. It adds just 2–3KB and no compiler on the board. CircuitPython 10.3.1, released Sept 14, allows importing these turbo .mpy files on RP2xxx boards.

On your computer: a CLI that downloads the official mpy-cross compiler for you, compiles your marked module in both modes, benchmarks each one, and installs the winner. There’s no CircuitPython checkout or toolchain to build.

The two speeds

  • @native compiles the function’s instructions but keeps values as Python objects. Expect roughly 3x.
  • @viper requires you to declare integer and pointer types. In exchange it produces far tighter code. On Sklar’s test boards, viper loops ran 19x to 72x faster than bytecode.

His benchmark is a 12-bit fixed-point Mandelbrot. On a Metro RP2040 it took 8,335ms as bytecode, 4,778ms native, and 423ms viper, which is 19.7x faster. The slowest board sees the biggest gain: an old Cortex-M0+ Metro M0 Express improved 71.7x.

The design choices worth copying

Correctness gets checked. Every benchmark run returns a checksum. If a compiled version produces a different result, Turbo treats it as a different program, not a faster one, and won’t install it. That rule is worth adopting in any optimization workflow.

It degrades gracefully. Your source goes in src/, and compiled modules go in lib/turbo/<arch>/. A 49-line shim puts the right folder at the front of the import path when the board boots. Stock firmware, or a board with no compiled build for its architecture, simply imports the Python version: slower, same result. That makes a turbo project safe to share.

It works around an importer quirk. CircuitPython loads name.py before name.mpy if both are in the same folder, so the source would quietly override the fast version. Keeping the source off the import path fixes that without patching the importer. One consequence: code.py itself can’t be accelerated, so move the heavy work into a module.

Where it helps

The Adafruit guide lists the use cases directly: NeoPixel effects, fractals, audio processing. Those are exactly the jobs that push creative projects off CircuitPython today. The demos run on RP2040 boards, including the Feather RP2040 with DVI output. Sklar credits Anthropic’s Claude with helping build the project, and the CLI is headed to PyPI as adafruit-turbo.