Ruduino: reusable register-level components for AVR microcontrollers
A Rust library of generated, MCU-specific cores plus timer, serial and SPI modules for bare-metal Arduino programming.
What Ruduino is
Ruduino is the AVR-Rust project's library of reusable components for programming AVR microcontrollers directly, in Rust, with no Arduino runtime underneath. It describes itself as "Reusable components for AVR microcontrollers," and its README centers on the ATmega328P — the chip at the heart of an Arduino Uno.
The crate is no_std; its examples are no_main. It uses nightly Rust features (asm_experimental_arch, associated_type_defaults, lang_items, proc_macro_hygiene) declared in src/lib.rs. It is dual-licensed under MIT or Apache-2.0 and last released as version 0.4.0.
What you get
The library splits into two layers:
Generated cores. Under src/cores/ sit per-microcontroller modules — atmega328p, atmega168a, atmega88pa, and other ATmega variants. Each defines that chip's registers, pins, SPI ports, USARTs and timers as Rust types:
use ruduino::cores::current::PORTB; // Register
use ruduino::cores::current::PORTB7; // Pin
The current module is selected at build time by the target MCU. The crate's build.rs reads which AVR you are compiling for (via avr_mcu) and emits a cfg like avr_mcu_atmega328p, so the matching core is selected as current (the all-mcus feature can compile all cores). When building docs on a non-AVR host it falls back to atmega328.
Handwritten modules. The register-level API sits under src/modules/ — timers (8-bit and 16-bit), USART serial, and SPI — plus an interrupt helper and a small prelude.
Registers as traits
The foundation is two traits in src/register.rs. RegisterValue constrains what a register can hold (u8 or u16) with the bitwise operators. Register carries the memory address and provides volatile read/write, plus helpers to set and clear individual bits:
fn write<V>(value: V) where V: Into<Self::T> {
unsafe { core::ptr::write_volatile(Self::ADDRESS, value.into()) }
}
RegisterBits represents a set of bits within a register; the README's PORTB7 is a pin.
Timers and serial
Timers are configured through builder-style setup objects rather than raw writes. The README shows a 16-bit timer set to fire at roughly two hertz using a 1024 prescaler:
timer1::Timer::new()
.waveform_generation_mode(timer1::WaveformGenerationMode::ClearOnTimerMatchOutputCompare)
.clock_source(timer1::ClockSource::Prescale1024)
.output_compare_1(Some(INTERRUPT_EVERY_1_HZ_1024_PRESCALER))
.configure();
The interrupt handler is a #[no_mangle] function with the AVR interrupt calling convention, toggling an output pin:
#[no_mangle]
pub unsafe extern "avr-interrupt" fn _ivr_timer1_compare_a() {
let prev_value = read_volatile(PORTB);
write_volatile(PORTB, prev_value ^ PINB5);
}
Serial works the same way — a serial::Serial builder taking a UBRR divisor and options for character size, mode, parity and stop bits, then free functions to transmit and try-receive bytes. The README's echo example transmits the received byte twice:
if let Some(b) = serial::try_receive() {
serial::transmit(b);
serial::transmit(b);
}
Interrupts are managed with without_interrupts, which issues a CLI on entry and restores interrupts via SEI when the guard drops. The prelude re-exports just that one helper.
How cores get generated
The per-chip definitions are not written by hand — they come from a code generator in core_generator/. Its main.rs iterates over every MCU known to avr_mcu, generates register, pin, SPI, USART and timer modules for each into src/cores/, catches panics per chip so one failure doesn't stop the run, deletes broken output, and finally writes a mod.rs wiring up only the successfully generated cores. A shell script drives it:
regenerate-cores.sh
runs cargo run "../src" inside core_generator. The generator is a separate, unpublished workspace member (ruduino-core-generator).
Building and running
The examples show the intended workflow: build with a custom target JSON for the chip, using nightly's -Z build-std=core since there is no standard library on AVR:
cargo build -Z build-std=core --target avr-atmega328p.json --examples --release
simavr -m atmega328p target/avr-atmega328p/release/examples/uart.elf
The uart example prints "Hello, from Rust!" over the serial port, and its output can be viewed with simavr.
Who it is for
This is a low-level library: you work with generated register and bit types rather than hand-writing per-chip definitions. It suits embedded developers who want AVR control without C or an Arduino runtime, and it pairs naturally with the rest of the AVR-Rust toolchain (the avr-config and avr_delay crates appear as dependencies, and avr-mcu appears as a build dependency). The crate is a few years quiet — last commit June 2022 — but its generated-core design keeps per-chip definitions separate from the handwritten modules.
avr-rust/ruduino: 824 KB in 48 files, indexed Nov 9, 2026 after 0.00 hours of indexing. License: Apache-2.0 OR MIT. Source: https://github.com/avr-rust/ruduino.
Ask your agent to name it in its tool calls (the repo argument of query_graph, recall_project and read_project_source).
Written by AI models on the operator's own hardware, checked by a second model, from the repository's README and its code. Back to the shelf