# Bare-metal Mojo on a $4 microcontroller — a pure-Mojo RP2040 firmware + SDK

**URL:** <https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298>\
**Category:** Community Showcase\
**Tags:** discussion, mojo-compiler, llvm, performance\
**Created:** [July 5, 2026, 8:02am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298 "2026-07-05T08:02:53Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 5, 2026, 8:02am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/1 "2026-07-05T08:02:53Z")

</div>

Mojo is usually pointed at GPUs and AI kernels. I wanted to see how far the other direction goes, so I wrote a bare-metal firmware **and** peripheral SDK for the Raspberry Pi Pico (RP2040, a dual Cortex-M0+) in pure Mojo — no OS, no C application layer. The whole application plus SDK is Mojo; startup is ~60 lines of assembly and one linker script.

Repo: [GitHub - Hundo1018/inmojomni: Bare-metal Raspberry Pi Pico (RP2040) and Pico 2 (RP2350) firmware, written in Mojo. · GitHub](https://github.com/Hundo1018/inmojomni)

 ![hw-tests](https://us1.discourse-cdn.com/flex001/uploads/modular/original/2X/9/9d613489fa9a1baca5e56b79921f9f1e0717a386.svg)  
**[IMAGE 1 — docs/assets/hw-tests.png]** _(hero: the on-hardware test suite, all green)_

A complete blink is **780 bytes** , and it turns out that’s byte-for-byte the size of the same blink compiled with `clang -O2` on the identical rig. Blinking an LED looks like this:

```mojo
import pico
from pico import Pin, pins, sleep_ms

@export("mojo_main")
def start() abi("C"):
    pico.init()

    var led = Pin[pins.LED]() # pin number is a compile-time parameter;
    led.make_output() # Pin[99]() is a compile-time error

    while True:
        led.toggle()
        sleep_ms(250)

```

## The trick that makes it possible

 ![pipeline](https://us1.discourse-cdn.com/flex001/uploads/modular/original/2X/2/20e3589e8762a54a8790d8261ad98ad68c5c6b13.svg)  
**[IMAGE 2 — docs/assets/pipeline.png]** _(the retarget pipeline)_

The bundled Mojo LLVM has no 32-bit ARM backend, so `mojo build` can’t target  
Cortex-M0+ directly. The workaround is a source-to-source-ish pipeline at the  
IR level:

1. `mojo build --emit=llvm` targeting **riscv32** — it shares the exact ILP32  
little-endian data model of ARMv6-M, so the emitted IR is layout-compatible.
2. Rewrite the target triple + datalayout to `thumbv6m-none-eabi`, and downgrade  
the handful of IR constructs the _system_ LLVM (18) doesn’t understand yet  
(`captures(none)`, `#dbg_*` records, `f0x` float literals, single-arg  
lifetime intrinsics).
3. System `opt -O2` / `llc -O2` → Cortex-M0+ object.
4. `arm-none-eabi-gcc` links it with crt0 + linker script + boot2 + libgcc.

The retarget pass is guarded by unit tests, `opt -verify`, and a check that the number of volatile ops is preserved end to end. When a nightly emits new IR syntax, supporting it is usually one more rewrite rule. (The whole pipeline driver is itself a Mojo program, not a shell script.)

## What’s measured

 ![benchmarks](https://us1.discourse-cdn.com/flex001/uploads/modular/original/2X/4/4e61128050f067eda6692c63df8b67103983a727.svg)  
**[IMAGE 3 — docs/assets/benchmarks.png]** _(9 workloads × 4 languages, normalized to clang C)_

I didn’t want to hand-wave the performance claim, so the benchmark builds the same 9 workloads four ways — Mojo, `gcc -O2`, `clang -O2` (same LLVM backend as Mojo, which is the fair yardstick for _language_ overhead), and Rust `opt-level=2` — on the same board, crt0, clocks and 1 MHz timer, three runs each, with checksums verified identical across all four languages.

- Register-loop microbenchmarks: Mojo == clang C **to the microsecond** (the 200k-round xorshift is 102,501 µs in all three of Mojo / clang / Rust).
- Larger workloads (CRC-32, quicksort, 16×16 matmul, recursive fib): Mojo lands within ±13% of clang C, ahead on some, behind on others — ordinary optimizer variance, no systematic “language tax”, no cliff as programs grow.

Full methodology and the honest caveats (Rust’s own compiler-builtins skew the division/float rows; XIP flash alignment adds noise on tight loops) are in the repo’s BENCHMARKS.md.

## What the SDK does today

GPIO (pins as compile-time-checked types), PIO (assembler written as method calls, with side-set, forward labels, and **compile-time assembly** — a PIO program can be a `comptime` value so the instruction words become flash constants, with `comptime assert` turning an invalid program into a build error), PWM, ADC + on-die temperature sensor, UART, NVIC interrupts, RTT  
logging, dual-core launch, SIO hardware spinlocks and inter-core FIFO.

Everything is backed by 26 on-target tests that run on real silicon (results written to a RAM mailbox, read back over SWD) — including a genuinely contended dual-core spinlock test (2×20k increments == exactly 40,000) and full F5 breakpoint debugging validated over the DAP protocol. There’s a scheduled CI job that re-tests against the newest nightly daily.

## The stack, end to end

- **Language & toolchain** : Mojo nightly, managed by **pixi** (pinned lock for reproducibility, plus a daily CI job that re-tests against the _newest_ nightly so breakage surfaces within a day).
- **Build** : `mojo build --emit=llvm` → `tools/retarget.mojo` → system LLVM 18 `opt`/`llc` → `arm-none-eabi-gcc` with a 60-line crt0.S, one linker script, and boot2 assembled from vendored pico-sdk source (checksummed at build time, regression-tested byte-identical to the reference).
- **The tooling is Mojo, too** : the pipeline driver, IR retarget pass, ELF verifier, benchmark driver and chart generator are all Mojo programs — Python survives only as the test orchestrator and DAP protocol client.
- **Debugging** : probe-rs + VS Code — press F5 and you get breakpoints, stepping, variables, CPU registers and live SVD peripheral views _in the `.mojo` sources_, on a $4 MCU. RTT logging over the same probe, no UART wires.
- **Tests & CI** : Mojo host unit tests (retarget rules, PIO encodings, boot2 provenance) + the 26-test on-target suite over SWD; GitHub Actions runs the host suite on every push and the newest-nightly job on a schedule.
- **Packaging** : the SDK installs into any pixi project via the `pixi-build-mojo` backend — `pixi add --git ` [https://github.com/Hundo1018/inmojomni.git`](https://github.com/Hundo1018/inmojomni.git%60) inmojomni`, then `import pico`. (Verified end-to-end from a fresh environment.)

## Where I could use the team’s input (embedded feedback)

A few things came up that I think are relevant to Mojo reaching freestanding / embedded targets, and I’d love to know what’s on the radar:

1. **A 32-bit ARM (thumbv6m/v7m) backend** would remove the entire retarget hack. The riscv32 detour works precisely _because_ the data models match, but it’s a workaround.
2. **Discarded volatile loads.** `_ = read32(mmio_addr)` (a volatile load whose result is unused) is dropped by the elaborator _before_ it reaches LLVM, so the volatile access never happens. On an MCU this is a real footgun — I hit it as a hang draining a hardware FIFO and had to route the value into a branch with a side effect to keep the load alive. Is eliminating an unused-but-volatile load intended? (Happy to file a minimal repro.)
3. **A no-std / freestanding profile.** The bounds-check/assert path pulls in a small printf/formatting machinery; on bare metal I stub those symbols to a `bkpt` trap. A supported freestanding profile (and a way to opt the assert I/O into a user-provided trap) would make this cleaner.

On the flip side, **comptime is a joy here** — folding an entire register map to immediates, and assembling PIO programs at compile time with real assertions, are exactly the ergonomics that make this fun rather than painful.

It’s experimental and pinned to nightly (the language isn’t 1.0), but every claim in the README has a test behind it. Feedback, questions, and “you’re holding LLVM wrong” corrections all very welcome.

* * *

Disclosure of AI-generated content:  
In line with community guidelines, I wish to disclose that this article was entirely written by Claude to convey my intended message.

---

<div class="post-metadata">

**Author:** ![owenhilyard](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/owenhilyard/32/41_2.png) [@owenhilyard](https://forum.modular.com/u/owenhilyard)\
**Post date:** [July 5, 2026, 4:29pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/2 "2026-07-05T16:29:48Z")

</div>

Glad to hear that comptime is as useful for MCUs as it is for GPUs!

I also have a few questions:

- How did you do checksums on GPIO writes?
- Did you avoid the external symbols that the runtime pulls in or just stub them out?

\<Mod hat on\>

While this is a very cool project, I’m seeing a few LLM-isms and we do require disclosure for LLM-generated text.

\<Mod hat off\>

---

<div class="post-metadata">

**Author:** ![clattner](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/clattner/32/201_2.png) [@clattner](https://forum.modular.com/u/clattner)\
**Post date:** [July 5, 2026, 6:08pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/3 "2026-07-05T18:08:12Z")

</div>

This is super cool, I’m thrilled to see this. This will also be so much easier with Mojo OSS, we’re still on track for this year (not long now)!

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 5, 2026, 6:44pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/4 "2026-07-05T18:44:41Z")

</div>

Thanks for the questions, I’ve add the disclosure!

Q1: What I do verify for the GPIO benchmark here is a different thing: does toggle() actually flip the pin? and poll it from the pad, and I found out that GCC -O2 does not unroll the volatile GPIO store loop (4.7× slower in this case). So I didn’t checksum GPIO, but added the same backend (clang) to the benchmark.

Q2: Yes, I simply stub them out to a bkpt(if there is a debugger like a Raspberry Pi debug probe) otherwise hardfault instead.

---

<div class="post-metadata">

**Author:** ![clattner](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/clattner/32/201_2.png) [@clattner](https://forum.modular.com/u/clattner)\
**Post date:** [July 6, 2026, 5:48pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/5 "2026-07-06T17:48:43Z")

</div>

> [@Hundo](#):
>
> 1. **Discarded volatile loads.** `_ = read32(mmio_addr)` (a volatile load whose  
> result is unused) is dropped by the elaborator _before_ it reaches LLVM, so  
> the volatile access never happens. On an MCU this is a real footgun — I hit  
> it as a hang draining a hardware FIFO and had to route the value into a  
> branch with a side effect to keep the load alive. Is eliminating an  
> unused-but-volatile load intended? (Happy to file a minimal repro.)

Is this using `UnsafePointer.load[volatile=True]`? If so, a repro would be greatly appreciated!

-Chris

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 7, 2026, 5:31pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/6 "2026-07-07T17:31:07Z")

</div>

Yes, plain `UnsafePointer[UInt32, MutUntrackedOrigin].load[volatile=True]()`, result discarded via `_ =`  
[Issue here](https://github.com/modular/modular/issues/6755)

At --emit=llvm (before any opt pass), a load[volatile=True] whose result is unused doesn’t appear in the IR at all — the function lowers to { ret void }. The identical load keeps its load volatile the moment the value is used (fed into a store[volatile=True]). Reproduces on both the default host target and riscv32-unknown-none-elf, so it’s in the elaborator, not target lowering.  
Thanks for taking a look!

---

<div class="post-metadata">

**Author:** ![clattner](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/clattner/32/201_2.png) [@clattner](https://forum.modular.com/u/clattner)\
**Post date:** [July 16, 2026, 5:45am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/7 "2026-07-16T05:45:18Z")

</div>

I fixed the “volatile load” issue over the weekend, plz give it a try in a new nightly when you get a chance. Thanks for reporting this!

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 18, 2026, 10:41am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/8 "2026-07-18T10:41:13Z")

</div>

I can’t test your latest fix yet because of another issue I ran into.

Starting with dev2026071105, RISC-V code generation appears to be disabled. riscv32 and riscv64 still show up in `--print-supported-targets`, but any `--emit=llvm`, `--emit=asm`, or `--emit=object` fails with:

```auto
error: no compiler backend is registered for target 'riscv32-unknown-none-elf'; this target is not supported by this build.

```

I bisected the nightlies, and dev2026071006 is the last version where RISC-V emission still works. dev2026071105 is the first one that fails, and every nightly since then behaves the same.

Until that regression is fixed, I can’t build my RP2040 firmware, so unfortunately I can’t verify your latest change.

---

<div class="post-metadata">

**Author:** ![strangemonad](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/strangemonad/32/412_2.png) [@strangemonad](https://forum.modular.com/u/strangemonad)\
**Post date:** [July 19, 2026, 4:57am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/9 "2026-07-19T04:57:23Z")

</div>

@Hundo this is super cool.

On the `no-std` discussion point, I know that’s the direction rust mostly went for embedded and linux kernel work (that and pluggable allocator trait). I’m curious how often you’d see truly bare metal MCU (with no runtime support) in practice vs building on something like FreeRTOS or Zyphir (e.g. on STM32 or ESP32 MCU families). One approach I could imagine exploring might be std lib implementation variants that target these (e.g. AsyncRT mapped on to FreeRTOS scheduling). The std lib already has conditions for things like `sys.is_gpu` and nvidia vs amd vs apple.

Aside from that, I’m curious if you’re hoping to extend this project to other MCU families like SMT, ESP, atmega or is this primarily meant to be a motivating proof of concept?

---

<div class="post-metadata">

**Author:** ![yvv1](https://avatars.discourse-cdn.com/v4/letter/y/8797f3/32.png) [@yvv1](https://forum.modular.com/u/yvv1)\
**Post date:** [July 20, 2026, 12:23pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/10 "2026-07-20T12:23:04Z")

</div>

@Hundo You chose to create a mojo pico SDK from scratch. This is fascinating btw! But would it be possible to re-use the official C/C++ pico SDK inside the mojo project? Could the MLIR infrastructure be useful for this?

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 20, 2026, 6:57pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/11 "2026-07-20T18:57:51Z")

</div>

Bare-metal & RTOS  
In my own surroundings bare-metal actually shows up more often, though that’s probably a bias of where I sit: education and rapid prototyping, small single-purpose applications. I have considered an RTOS, though. The idea of swapping out AsyncRT, replacing Mojo’s runtime with something RTOS-shaped, is genuinely interesting and worth a deep discussion on its own. An allocator trait is also something I’ve wanted for a long time. My hesitation is that I suspect it would be a real burden on the team’s road to 1.0: it likely touches more than one layer at once (MLIR, the compiler, and possibly other factors I can’t see), so I think it fits better as something to plan after the compiler is open-sourced.

Other MCU families  
It started purely as a proof of concept, but along the way I found that Mojo’s parametrization strikes a nice balance between ergonomics and performance. So yes, I’m genuinely interested in expanding. Right now I’m working with the RP2350 / STM / ESP dev boards I have on hand, plus some other hardware, trying to build a more comprehensive automated verification loop.

That said, I’d very much welcome anyone more capable or more specialized in this area to write their own repo, fork this, or contribute directly. The MCU families are enormous, and boards vs. MCUs need to be cleanly separated, so before I commit to that breadth alone I’d want to confirm there’s real demand, and put the effort into writing proper guides and separating the toolchain, rather than trying to maintain all of it by myself.

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [July 20, 2026, 7:02pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/12 "2026-07-20T19:02:22Z")

</div>

Good question. Short version: reusing the C SDK is technically feasible, and the project already does a small piece of it, but MLIR isn’t really the key that makes it work.

Reusing the C/C++ Pico SDK. At the linker level there’s nothing special stopping you. The build already compiles one part of the pico-sdk verbatim (the boot2\_w25q080.S second-stage bootloader) with the Arm toolchain and links it into the image. You could compile more of the C SDK the same way (clang, then object, then link) and then call into the C ABI through Mojo’s FFI. So “reuse the C SDK” is a perfectly workable option.

The reason I didn’t is that the point of this project is the opposite: to show the SDK layer (GPIO, PIO, PWM, ADC, UART, interrupts) can be written entirely in Mojo, with no C application layer, borrowing only the register definitions from upstream (the SVD, plus that one boot2 block). Pulling in the C HAL would defeat that: it becomes “Mojo calling C” rather than “Mojo firmware.” So yes, it’s possible, but you’d still have to steer around AsyncRT.

On MLIR. The retargeting I do is a textual rewrite at the LLVM IR level (triple and data layout), not an MLIR-level transform, and Mojo doesn’t currently expose a way to plug a custom MLIR pass into that pipeline. Bridging to the C SDK wouldn’t go through MLIR at all: it’s just ordinary cross-compilation and linking against C objects. So MLIR is genuinely central to Mojo itself, but it isn’t the mechanism that lets you reuse the C SDK.

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [September 21, 2026, 8:31am UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/13 "2026-09-21T08:31:55Z")

</div>

Good morning everyone,  
If you can help, this is urgent.

Is there a update simplex wiki—to execute pure Mojo code scripts on bare-metal and/or RTOS environments for Cortex-M4, M7, M33, etc. etc. ?

I urgently need read/write access from Mojo scripts to low-level hardware I/O registers (ADC, PWM, GPIO pins) on the MCU (M4/M7/M33 families) in a bare-metal/RTOS context. This requires using API Mojo **UnsafePointer** or similar IO API to write directly to **hardware I/O address registers** (_ **ADC/PWM** _) for deterministic motor control (_hard real-time execution from Mojo_).

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [September 21, 2026, 5:14pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/14 "2026-09-21T17:14:08Z")

</div>

Hi, thanks for the question. There isn’t a simple wiki yet for running pure Mojo on bare metal.

You can take a look at my repo, especially [`src/pico/chips.mojo`](https://github.com/Hundo1018/inmojomni/blob/main/src/pico/chips.mojo) and [`src/pico/mmio.mojo`](https://github.com/Hundo1018/inmojomni/blob/main/src/pico/mmio.mojo), and follow how `UnsafePointer` is used for low-level hardware access. Yes, you will need the register addresses for the specific Cortex-M MCU to load from and store to the memory-mapped peripherals.

Just keep in mind that the repo is still using `dev2026071006`, not `Mojo 1.x`, and the SDK is still WIP. If you try to port the approach, you’ll need to check the current Mojo syntax and inspect the emitted assembly/LLVM IR to make sure everything is being lowered as expected.

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [September 21, 2026, 6:36pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/15 "2026-09-21T18:36:28Z")

</div>

Hi Hundo, thanks.

It’s urgent. Could you help with these specific questions?

1. Does the current MOJO SDK repository for RP2040 also work on **Cortex-M7** (_and the Cortex-Mx family in general_)? Is there a simple installation wiki for the Cortex-Mx family (M4, M7, M33, etc.)?

**IMPORTANT** : Access to **ADC/PWM I/O peripherals** on GPIO pins with **hard-deterministic IRQ** latency\*\* (_see table point 3_):

1. Could Mojo script work on a standard Cortex-M7 FreeRTOS or Zephyr SDK (using current boot/HAL/drivers) to handle the critical code loop for **I/O IRQs**? Is there an example (_FreeRTOS, Zephyr or other RTOS on Cortex-Mx_) of a simple script that loops using the `**UnsafePointer` API for I/O address access\*\* (read/write) with**deterministic** jitter latency (critical loop code)?

2. Actual target jitter (μs) to be achieved with Mojo hardware control for GPIO I/O (GPIO address) on ​​Cortex-Mx (FreeRTOS/Zephyr/bare-metal/RTOS - standard firmware) or PREEMPT\_RT (Linux-RT on isolated core CPU - Rpi4/Rpi5/NXP Linux Board):

- Min Jitter Latency (IRQ → Read/Write IO ): 0–5 μs
- Max Jitter Latency (IRQ → Read/Write IO): 0–15 μs
- Avg Jitter Latency (IRQ → Read/Write IO): 0–10 μs

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [October 8, 2026, 1:11pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/16 "2026-10-08T13:11:02Z")

</div>

Good morning everyone,  
Is there a Hard Real-Time IRQ/I-O update available for the RP2040 board?

For the RP2040 board, is there an RTOS or bare-metal example of a “mojo” script demonstrating hard real-time IRQ/I-O latency, jitter, and determinism as specified in the table (IRQ, PWM/ADC hardware I-O registers)?

- Min Jitter Latency (IRQ → Read/Write IO): 0–5 μs
- Max Jitter Latency (IRQ → Read/Write IO): 0–15 μs
- Avg Jitter Latency (IRQ → Read/Write IO): 0–10 μs

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [October 9, 2026, 4:31pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/17 "2026-10-09T16:31:57Z")

</div>

Hi, can you help,

1. Does the current RP2040 SDK appear to have been updated to the official Mojo version 1.0?

2. Are there any example scripts for testing latency and jitter on low-level hardware IO registers, specifically regarding performance on IRQs with hard real-time determinism?

---

<div class="post-metadata">

**Author:** ![Hundo](https://avatars.discourse-cdn.com/v4/letter/h/c6cbf5/32.png) [@Hundo](https://forum.modular.com/u/Hundo)\
**Post date:** [October 9, 2026, 5:49pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/18 "2026-10-09T17:49:53Z")

</div>

I’ll prioritize upgrading to Mojo 1.1 over the next few days. Once it’s ready, I’ll let you know so you can start following along on your side.

After that, I’ll work on the IRQ-to-GPIO tests.

If you have any requirements or run into any issues along the way, feel free to open an issue! I check issues more often XD, and it’s also easier to keep track of everything there.

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [October 9, 2026, 6:06pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/19 "2026-10-09T18:06:44Z")

</div>

Okay, thanks Hundo,

The latency and jitter values ​​obtained from the Mojo IRQ I/O script on the hardware IO pin should fall within the range suitable for hard real-time systems (determinism), comparable to C-language machine-level performance.

Examples UnsafePointer API Mojo IRQ I/O scripts (that are easy to run) would be preferred, fast test in RP2040, ARM Cortex-M7 platforms, PREEMPT\_RT (linux isolated CPU).

This is controlling motors and ADC actuators (GPIO register I/O).

---

<div class="post-metadata">

**Author:** ![SnappyWalrus34](https://avatars.discourse-cdn.com/v4/letter/s/eb8c5e/32.png) [@SnappyWalrus34](https://forum.modular.com/u/SnappyWalrus34)\
**Post date:** [October 10, 2026, 2:39pm UTC](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298/20 "2026-10-10T14:39:03Z")

</div>

In the updated version of Mojo, the `UnsafePointer` API appears to have been replaced by the `Pointer` API (examble: `var register_1 = Pointer[Float32].unsafe_from_address(address)`):

Could you provide some further clarification or script examples (baremetal RTOS and Linux CPUx PREEMPT\_RT) using the new `Pointer` API, examble tasks accessing motor PIDs or I/O IRQs register (reading/writing values ​​to registers for GPIO, PWM, or ADC pins)?

[Next page](https://forum.modular.com/t/bare-metal-mojo-on-a-4-microcontroller-a-pure-mojo-rp2040-firmware-sdk/3298.md?page=2)
