rp2040-project-template: a starting point for Rust firmware on the RP2040
A basic rp2040-hal project with blinky and rtt logging example code. With this you can quickly get started on a new rp2040 project
At a glance
- What is it?
- This template is a working rp2040-hal project with a blinky example, defmt logging over RTT, a panic probe and a stack overflow checker, set up so that cargo run flashes and runs on a Raspberry Pi Pico. The build profiles are the part most worth reading, and the answer for anyone without a debug probe is in the runner configuration rather than in a separate guide.
- Who is it for?
- Adopt rp2040-project-template if you are starting embedded Rust on an RP2040 and want the knurling-rs development tooling already wired up, since the defmt, defmt-rtt, panic-probe and flip-link combination is the part that takes real setup to reproduce.
- Can I use it commercially?
- Yes. Apache-2.0 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 180 days ago.
- What is it written in?
- Mainly Rust, 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 template that already works, not a tutorial to follow
The rp2040-project-template is intended as a starting point for developing your own firmware based on the rp2040-hal, and it is described as a basic project with blinky and RTT logging example code, so you can quickly get started on a new rp2040 project.
The decision that shapes everything else is that it includes the whole knurling-rs tooling as showcased in the knurling-rs app-template: defmt, defmt-rtt, panic-probe and flip-link. The point of that set is that the development loop works on the hardware, with logs over RTT rather than over a serial console, a panic handler that prints the panic message through the same channel, and a linker-based check for stack overflows.
The default runner is probe-rs, which is what makes the one command build and run work. The README states that with probe-rs configured as the default runner you can start your program with cargo run --release.
The quickstart assumes specific hardware: a first-generation Raspberry Pi Pico containing the RP2040 MCU, a Raspberry Pi Debug Probe, and flashing the Pico with probe-rs. The README is careful to say you do not have to use this setup, that it is just the most common and well-supported one, and that the rest of the document covers other hardware and software arrangements.
That framing matters for evaluation. This is a template that arrives with decisions made, and the useful question is not whether the example is interesting but whether the decisions baked in match the board you have.
The four tool installs a new project needs
The dependency set is short and each piece has a job.
The cross-compilation toolchain for the RP2040's processor is the first, and it is a rustup target rather than a package:
rustup target install thumbv6m-none-eabiStack overflow protection comes next, and it is a separate tool rather than a crate:
cargo install flip-linkThe flashing tools follow, installed with the locked flag so the pinned versions are used:
cargo install --locked probe-rs-toolsAnd the project generator itself, which is what turns the template into your project:
The generator is installed with cargo install cargo-generate, and the project is then created from the template with the generator pointed at the repository:
cargo generate --git https://github.com/rp-rs/rp2040-project-templateThat is the form the README gives in the project creation section, and the generator runs a wizard that produces a new project directory.
The requirements list makes the boundaries explicit. You need the standard Rust tooling, cargo and rustup. You need toolchain support for the cortex-m0+ processors in the rp2040, which is the thumbv6m-none-eabi target. You need flip-link, and the README is specific about what it buys: it allows you to detect stack overflows on the first core, which is the only supported target for now. And by default you need a probe-rs installation and a probe-rs compatible debug probe, though a second Pico can be used as a CMSIS-DAP debug probe, and the details on other supported probes are in debug_probes.md.
That single sentence about the first core being the only supported target is the kind of detail that a README normally omits, and it is the one that bites.
Build profiles tuned for embedded debugging, not for speed
The Cargo manifest is where the template's real opinions live, and the four profiles are unusual enough to be worth reading line by line.
The dev profile sets codegen-units to 1, debug to 2, debug-assertions to true, incremental to false, opt-level to 3 and overflow-checks to true. That combination is deliberate and unusual: optimising at level 3 with full debug info, no incremental compilation, and overflow checks left on. The usual advice is the opposite, that a dev build should be unoptimised for speed of compilation. Here the goal is that the code you debug is the code that runs, at close to release speed.
The release profile switches off debug assertions and overflow checks, turns on fat LTO, and keeps opt-level at 3 with codegen-units at 1. Fat LTO across a whole program graph is expensive to build, and on a microcontroller it buys a smaller binary, which is the trade being made.
The test and bench profiles are copies of the two above with the appropriate assertions flipped, and both build-override profiles set opt-level to 0 with codegen-units at 8. The comment explains why: do not optimize proc-macro crates, which is faster builds from scratch. That is a build-time cost saving, and it is the kind of detail a template gets right precisely because the people who wrote it build firmware every day.
The dependencies are equally minimal. cortex-m and cortex-m-rt at 0.7 for the runtime and the core, embedded-hal 1.0.0 for the hardware abstraction layer, defmt and defmt-rtt for logging, panic-probe with the print-defmt feature, and rp-pico at 0.9 for the board support package, with a comment noting it is a Pico by default and that any other board support package can be substituted, with the pro_micro_rp2040 alternative left commented out. The manifest also shows the alternative route for people who do not want a board support package at all, which needs rp2040-hal with the rt and critical-section-impl features plus rp2040-boot2.
The log level is the knob you will actually use
Most of the README after the quickstart is about one environment variable, and it is worth reading because the behaviour is not what a Rust developer expects.
If you do not specify a DEFMT_LOG level, it is set to debug, which means println, info and debug statements are printed. The default is overridable in the cargo configuration file:
[env]
DEFMT_LOG = "off"Or inline on Linux and macOS:
DEFMT_LOG=trace cargo runOr as an environment variable for the session, and the README gives the bash form on Linux, macOS and unix:
export DEFMT_LOG=traceOn Windows the README notes that users can only override DEFMT_LOG through config.toml or by setting the environment variable as a separate step before calling cargo run, with a cmd form and a PowerShell form.
That Windows restriction is a real friction point and the documentation is upfront about it. The reason is structural rather than a preference: the .cargo/config.toml file is what makes the override apply to every cargo run, and the inline form relies on shell behaviour that differs between the two environments.
For a reader who has not worked with defmt before, the notable thing is that this is not the standard logging crate. defmt formats at compile time, so a log statement carries no formatting cost at runtime, and defmt-rtt delivers the output over the RTT channel rather than a UART. The cost of that is that the log format is fixed at build time, which is exactly why the level is an environment variable rather than a runtime switch.
Without a debug probe, the answer is a runner change
The alternative runners section exists because the debug probe is the most common reason a new user cannot run the template, and the fix is a configuration change rather than a different project.
The runner is set in .cargo/config.toml, and the README points at the section for other options. The first listed is cargo embed, described as basically a more configurable version of probe-rs run, the default runner, with a link to the cargo-embed documentation. The section also mentions that if you want to use picotool instead, you install a picotool binary for your system, with a link to the pico-sdk-tools releases.
There is also a note about a specific installation failure. If you get the error about the binary cargo-embed already existing during installation of probe-rs, you run cargo uninstall cargo-embed to remove the older version before trying again. That is the kind of collision that happens when a tool has been renamed and the old binary is still on the path, and it is worth knowing before you spend time on it.
The repository layout is consistent with a template rather than a library. At the top level there is a Cargo.toml, a build.rs, a memory.x, an Embed.toml, a cargo-generate.toml and a cargo-generate/ directory, plus a .cargo/ directory holding the configuration that selects the runner, a .vscode/ directory for the debug launch configuration, src/, debug_probes.md and two licence files.
The two licence files, LICENSE-APACHE-2.0 and LICENSE-MIT, and the licence field in the manifest reading MIT OR Apache-2.0, mean you can use the template's code under either. The presence of a .github/ directory and a CODE_OF_CONDUCT.md is also expected for a project of this kind, and the README's table of contents lists a roadmap section, so the template is expected to evolve rather than sit still.
Where a template is the wrong answer
The first limitation is that a template fixes decisions you may not want. The manifest ships rp-pico as the board support package, with the alternative left commented out, and it assumes a single core, since flip-link supports only the first core for now. Both are correct defaults and both are wrong for some hardware, and the cost of switching is small but real, because a board support package is where the pin mapping and the peripheral configuration live.
The second limitation is the size of the machinery. Four separate tools to install, a linker-based stack overflow check, a custom log format, and a runner configured in a TOML file is a fair amount of setup to accept before your first blink. For a first project on a board you already know, that cost is amortised over many projects. For a one-off, it is not, and the alternative of writing a minimal Cargo project against rp2040-hal directly, with defmt-rtt as the only extra crate, gets you to a running program sooner.
The third limitation is that the template says nothing about the rest of your firmware. There is no USB, no PIO state machine, no DMA, no multicore, no interrupt priorities, none of the things an actual product needs. It is a starting point for a project, not a library of board drivers.
The real alternative is the other two templates in the same family, and the difference in approach is instructive. The knurling-rs app-template, which this one follows for its tooling, is the more configurable of the two and lets you pick your own logging, runner and board support package. A bare rp2040-hal project, by contrast, gives you the hal with nothing else and lets you add what you need.
The choice is about how much opinion you want to inherit. The rp2040-project-template is the batteries-included option for embedded Rust on the RP2040, and it is the right starting point precisely when you want the debug and logging loop to work on the first try. If you already know the knurling-rs tooling and have a board you are comfortable with, the app-template is a better base, and if you are writing your own CI or have a non-standard probe setup, the bare hal project may be the only thing that does not fight you.
Editorial conclusion
Adopt rp2040-project-template if you are starting embedded Rust on an RP2040 and want the knurling-rs development tooling already wired up, since the defmt, defmt-rtt, panic-probe and flip-link combination is the part that takes real setup to reproduce. Do not adopt it if you need a second core for stack overflow detection, because flip-link currently supports only the first core, and do not copy its build profiles blindly, since the debug profile is deliberately unusual and the release profile is where the interesting settings live. Before you start, confirm you have a debug probe or plan to switch runners in .cargo/config.toml, and read the build.rs and memory.x files in the template root, since they are where the RP2040-specific build configuration lives.
Frequently asked questions
What is the rp2040-project-template?
It is a starting point for developing firmware on the rp2040-hal, shipped as a basic project with blinky and RTT logging example code. It bundles the knurling-rs tooling, which means defmt, defmt-rtt, panic-probe and flip-link, and it configures probe-rs as the default runner so that cargo run flashes and runs on the board.
How do I create a new project from the rp2040 template?
Install cargo-generate with cargo install cargo-generate, then run cargo generate rp-rs/rp2040-project-template, or cargo generate --git https://github.com/rp-rs/rp2040-project-template, and follow the wizard. You can also download the repository as a zip and edit Cargo.toml, .cargo/config.toml and the .vscode files by hand.
What do I need to install before building for the RP2040?
The rustup cross-compilation target thumbv6m-none-eabi, flip-link for stack overflow detection, and the flashing tools installed with cargo install --locked probe-rs-tools. You also need a probe-rs compatible debug probe, although a second Raspberry Pi Pico can act as a CMSIS-DAP probe.
How do I change the log level in the rp2040 template?
The default is debug, so println, info and debug statements are printed. You can set DEFMT_LOG in the [env] section of .cargo/config.toml, set it inline as DEFMT_LOG=trace cargo run on Linux and macOS, or export it for the session. On Windows the README says the override is only possible through config.toml or by setting the environment variable as a separate step before cargo run.
Can I use the rp2040 template without a debug probe?
Yes. The runner is selected in .cargo/config.toml, and the README lists alternative runners including cargo embed, described as a more configurable version of probe-rs run, and picotool, for which you install a picotool binary for your system. There is also a note about uninstalling an older cargo-embed if the installation of probe-rs reports that the binary already exists.
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/rp-rs-rp2040-project-template)Was this useful?