Open source
Open source
Every crate and repository, from register to GUI: what it does, a code sample and where to read more.
This page is all of VHRD’s open-source work. The highlights come first: the crates the rest is built on, the API, its wire format, units, the firmware base and the event counters. After them come the other repositories, by area: wire format and APIs, firmware, drivers, host tools and instruments, GUI, science, hardware tools and templates; the highlights appear there again. In each area the crates on crates.io come first, then the other public repositories, then work in progress and the ones not public yet (“coming in the future”), by name within each. draft
-
Highlights
-
WireWeaver designs no_std APIs for devices: RPC, streams and properties, built from composable API traits and compatible backwards and forwards. It generates the server and the client, blocking or async, for Rust and Python, over USB, Ethernet and CAN bus. Introspection gives dynamic Python clients, and a standard library of traits covers GPIO, CAN, firmware update, logs and more. It is approaching its alpha release.
#[ww_trait] #[claim(exclusive)] pub trait Uart { #[counted] stream!(rx: RxChunk<'i>); #[credit] sink!(tx: MaxLen<RefVec<'i, u8>, UART_TX_MAX>); fn capabilities() -> Capabilities<'i>; }#[ww_trait] #[claim(exclusive)] pub trait Uart { #[counted] stream!(rx: RxChunk<'i>); #[credit] sink!(tx: MaxLen<RefVec<'i, u8>, UART_TX_MAX>); fn capabilities() -> Capabilities<'i>; } -
ShrinkWrap is the binary wire format under WireWeaver. It packs data to 1-bit, 4-bit and 1-byte alignment without compression, so useful messages fit into a CAN frame. Structs, enums with data, strings and vectors of any size work without an allocator, and reading is zero-copy. An older device and newer software read each other’s data, and the other way round.
// 1 + 7 or 1 + 10 bits #[derive_shrink_wrap(ww_repr = u1)] enum I2cAddress { Seven(U7), Ten(U10) } #[derive_shrink_wrap] struct I2cWrite<'i> { // 0x50: 0 1010000 addr: I2cAddress, stop: bool, // 1 bit fast: bool, // 1 bit retries: Nibble, // 3: 0011 // Some(500): 1 0111110100 timeout_ms: Option<U10>, data: RefVec<'i, u8>, // ab cd } // 50 83 be 80 ab cd 02: 7 bytes, the // count (02) at the back. repr(C): 20.// 1 + 7 or 1 + 10 bits #[derive_shrink_wrap(ww_repr = u1)] enum I2cAddress { Seven(U7), Ten(U10) } #[derive_shrink_wrap] struct I2cWrite<'i> { // 0x50: 0 1010000 addr: I2cAddress, stop: bool, // 1 bit fast: bool, // 1 bit retries: Nibble, // 3: 0011 // Some(500): 1 0111110100 timeout_ms: Option<U10>, data: RefVec<'i, u8>, // ab cd } // 50 83 be 80 ab cd 02: 7 bytes, the count (02) at the back. repr(C): 20. -
ExactUnits puts a number’s unit, scale and storage into its type and checks them at compile time. A conversion is exact or refused, never rounded through a float, and the type is the same bytes as the plain integer, no_std and no alloc. It covers the cases other unit crates leave out: a temperature is a point against an origin, and a voltage gain is a different kind from a power gain.
// exact or refused, never rounded let t: Micros<u32> = Fixed::new(1500); assert_eq!(t.to::<Milli, u16>(), None); // 1500 µs is not a whole number of ms // V/V and W/W are different kinds, // and the kind picks the dB formula type V = Per<Volt, Volt>; type W = Per<Watt, Watt>; Fixed::<V, One, f64>::new(2.0).to_db(); // 6.0206 dB: 20·lg 2 Fixed::<W, One, f64>::new(2.0).to_db(); // 3.0103 dB: 10·lg 2 // a threshold in dB: an integer, exact, // 501 mV/V but 251 mW/W let v: Fixed<V, Milli, u16> = db!(-6); let p: Fixed<W, Milli, u16> = db!(-6); // 0.5 duty is not -3 dB: a Ratio has no dB // Fixed::<Ratio, One, f64>::new(0.5) // .to_db(); // error[E0080]: decibels are for W/W or // V/V, A/A, Pa/Pa: a plain Ratio has no // level form// exact or refused, never rounded let t: Micros<u32> = Fixed::new(1500); assert_eq!(t.to::<Milli, u16>(), None); // 1500 µs is not a whole number of ms // V/V and W/W are different kinds, and the kind picks the dB formula type V = Per<Volt, Volt>; type W = Per<Watt, Watt>; Fixed::<V, One, f64>::new(2.0).to_db(); // 6.0206 dB: 20·lg 2 Fixed::<W, One, f64>::new(2.0).to_db(); // 3.0103 dB: 10·lg 2 // a threshold in dB: an integer, exact, 501 mV/V but 251 mW/W let v: Fixed<V, Milli, u16> = db!(-6); let p: Fixed<W, Milli, u16> = db!(-6); // 0.5 duty is not -3 dB: a Ratio has no dB // Fixed::<Ratio, One, f64>::new(0.5).to_db(); // error[E0080]: decibels are for W/W or V/V, A/A, Pa/Pa: a plain Ratio has no // level form -
A bare-metal firmware template with ruggedizing, debugging and logging built in, and optional embassy, RTIC and HAL support. The template is an agent skill that generates a firmware for a chip, with its errata notes, blink codes on the status LED when it cannot start, and a clock a host keeps in step across boards. Most STM32s and a few MCUs from other vendors are covered.
use bedrock_led::code::*; use bedrock_sync::{Config, Sync}; use cortex_m::asm::delay; // A firmware that will not start // says why on its status LED, then // waits in the ROM bootloader. if !guard.runs_board() { let pause = |n| delay(n * KHZ); blink(Code::UnknownRework, led, pause); reboot_into_rom_bootloader(); } // One clock on all boards, steered // by the host over WireWeaver: // Acquiring, Locked within 2 ms. // `ww sync` locks the boards in // 1.2-1.6 s. On Donguru it costs // about 1.5 KB of flash. static SYNC: Sync = Sync::new(Config::DEFAULT); // clock.steer, once a second let r = SYNC.with(|clock, now| { server.steer(clock, now, &p) }); // all boards act at the same µs SYNC.wait_until_shared(t).await?;use bedrock_led::code::*; use bedrock_sync::{Config, Sync}; use cortex_m::asm::delay; // A firmware that will not start says why on its status LED, then // waits in the ROM bootloader. if !guard.runs_board() { let pause = |n| delay(n * KHZ); blink(Code::UnknownRework, led, pause); reboot_into_rom_bootloader(); } // One clock on all boards, steered by the host over WireWeaver: // Acquiring, Locked within 2 ms. `ww sync` locks the boards in // 1.2-1.6 s. On Donguru it costs about 1.5 KB of flash. static SYNC: Sync = Sync::new(Config::DEFAULT); // clock.steer, once a second let r = SYNC.with(|clock, now| { server.steer(clock, now, &p) }); // all boards act at the same µs SYNC.wait_until_shared(t).await?; -
When logging is not an option, count. Each count statement gets its own counter in a RAM array, through a linker trick like defmt’s, without a critical section, so it is safe in any interrupt; on Cortex-M3 and up a counter shared with an interrupt never loses an update. The cnt tool reads the counters over the debug probe, once, as a watch or in a TUI, with the defmt log beside them.
// one RAM counter per call site, // no critical section, safe in an IRQ fn high_frequency_irq() { let r = do_something(); cnt::cnt_if!(r.is_err(), unpack_errors: u64); } fn process_packet() { cnt::cnt!(packet_count: u32); } // on the PC, over the debug probe: // $ cnt tui --chip STM32H533RE// one RAM counter per call site, // no critical section, safe in an IRQ fn high_frequency_irq() { let r = do_something(); cnt::cnt_if!(r.is_err(), unpack_errors: u64); } fn process_packet() { cnt::cnt!(packet_count: u32); } // on the PC, over the debug probe: // $ cnt tui --chip STM32H533RE
-
-
Wire format and APIs
-
ShrinkWrap is the binary wire format under WireWeaver. It packs data to 1-bit, 4-bit and 1-byte alignment without compression, so useful messages fit into a CAN frame. Structs, enums with data, strings and vectors of any size work without an allocator, and reading is zero-copy. An older device and newer software read each other’s data, and the other way round.
// 1 + 7 or 1 + 10 bits #[derive_shrink_wrap(ww_repr = u1)] enum I2cAddress { Seven(U7), Ten(U10) } #[derive_shrink_wrap] struct I2cWrite<'i> { // 0x50: 0 1010000 addr: I2cAddress, stop: bool, // 1 bit fast: bool, // 1 bit retries: Nibble, // 3: 0011 // Some(500): 1 0111110100 timeout_ms: Option<U10>, data: RefVec<'i, u8>, // ab cd } // 50 83 be 80 ab cd 02: 7 bytes, the // count (02) at the back. repr(C): 20.// 1 + 7 or 1 + 10 bits #[derive_shrink_wrap(ww_repr = u1)] enum I2cAddress { Seven(U7), Ten(U10) } #[derive_shrink_wrap] struct I2cWrite<'i> { // 0x50: 0 1010000 addr: I2cAddress, stop: bool, // 1 bit fast: bool, // 1 bit retries: Nibble, // 3: 0011 // Some(500): 1 0111110100 timeout_ms: Option<U10>, data: RefVec<'i, u8>, // ab cd } // 50 83 be 80 ab cd 02: 7 bytes, the count (02) at the back. repr(C): 20. -
WireWeaver designs no_std APIs for devices: RPC, streams and properties, built from composable API traits and compatible backwards and forwards. It generates the server and the client, blocking or async, for Rust and Python, over USB, Ethernet and CAN bus. Introspection gives dynamic Python clients, and a standard library of traits covers GPIO, CAN, firmware update, logs and more. It is approaching its alpha release.
#[ww_trait] #[claim(exclusive)] pub trait Uart { #[counted] stream!(rx: RxChunk<'i>); #[credit] sink!(tx: MaxLen<RefVec<'i, u8>, UART_TX_MAX>); fn capabilities() -> Capabilities<'i>; }#[ww_trait] #[claim(exclusive)] pub trait Uart { #[counted] stream!(rx: RxChunk<'i>); #[credit] sink!(tx: MaxLen<RefVec<'i, u8>, UART_TX_MAX>); fn capabilities() -> Capabilities<'i>; } -
A Rust attribute macro for structs made with bitfield-struct. A field takes #[value(0 = “off”)] attributes that say what each raw value means, and the macro turns them into doc lines such as “0 = off” on that field. It is an early 0.1.0 crate without a README.
example: TBD
-
The WireWeaver definition of a CAN bus bridge’s API and data types, for a CAN FD to USB adapter. It describes classic and FD frames with standard or extended ids, bit timing, acceptance filters, bus modes, SD card logging settings and bus error notifications. The API has properties that switch CAN and termination on, a stream of received frames and a method to send one. The repo has no README yet.
example: TBD
-
A language server for WireWeaver APIs that runs beside rust-analyzer. It shows the ExactUnits types of API items as inlay hints, such as
mV · i16 · ±32.767 V, and on hover the unit, exact scale, step, range and size on the wire. Setups for Neovim, Helix, Kakoune, Emacs, VS Code, JetBrains IDEs and Zed come with it. It is a prototype.example: TBD
-
-
Firmware and embedded
-
When logging is not an option, count. Each count statement gets its own counter in a RAM array, through a linker trick like defmt’s, without a critical section, so it is safe in any interrupt; on Cortex-M3 and up a counter shared with an interrupt never loses an update. The cnt tool reads the counters over the debug probe, once, as a watch or in a TUI, with the defmt log beside them.
// one RAM counter per call site, // no critical section, safe in an IRQ fn high_frequency_irq() { let r = do_something(); cnt::cnt_if!(r.is_err(), unpack_errors: u64); } fn process_packet() { cnt::cnt!(packet_count: u32); } // on the PC, over the debug probe: // $ cnt tui --chip STM32H533RE// one RAM counter per call site, // no critical section, safe in an IRQ fn high_frequency_irq() { let r = do_something(); cnt::cnt_if!(r.is_err(), unpack_errors: u64); } fn process_packet() { cnt::cnt!(packet_count: u32); } // on the PC, over the debug probe: // $ cnt tui --chip STM32H533RE -
A bare-metal firmware template with ruggedizing, debugging and logging built in, and optional embassy, RTIC and HAL support. The template is an agent skill that generates a firmware for a chip, with its errata notes, blink codes on the status LED when it cannot start, and a clock a host keeps in step across boards. Most STM32s and a few MCUs from other vendors are covered.
use bedrock_led::code::*; use bedrock_sync::{Config, Sync}; use cortex_m::asm::delay; // A firmware that will not start // says why on its status LED, then // waits in the ROM bootloader. if !guard.runs_board() { let pause = |n| delay(n * KHZ); blink(Code::UnknownRework, led, pause); reboot_into_rom_bootloader(); } // One clock on all boards, steered // by the host over WireWeaver: // Acquiring, Locked within 2 ms. // `ww sync` locks the boards in // 1.2-1.6 s. On Donguru it costs // about 1.5 KB of flash. static SYNC: Sync = Sync::new(Config::DEFAULT); // clock.steer, once a second let r = SYNC.with(|clock, now| { server.steer(clock, now, &p) }); // all boards act at the same µs SYNC.wait_until_shared(t).await?;use bedrock_led::code::*; use bedrock_sync::{Config, Sync}; use cortex_m::asm::delay; // A firmware that will not start says why on its status LED, then // waits in the ROM bootloader. if !guard.runs_board() { let pause = |n| delay(n * KHZ); blink(Code::UnknownRework, led, pause); reboot_into_rom_bootloader(); } // One clock on all boards, steered by the host over WireWeaver: // Acquiring, Locked within 2 ms. `ww sync` locks the boards in // 1.2-1.6 s. On Donguru it costs about 1.5 KB of flash. static SYNC: Sync = Sync::new(Config::DEFAULT); // clock.steer, once a second let r = SYNC.with(|clock, now| { server.steer(clock, now, &p) }); // all boards act at the same µs SYNC.wait_until_shared(t).await?; -
A minimal exFAT for microcontroller SD cards, no_std and without allocation. It formats a card with SD Association geometry, mounts and checks a volume, finds and reads files, and creates preallocated contiguous files that their owner writes by raw sector address. A crafted card gives an error, never a panic or a hang. Checked with fsck.exfat and a Linux mount; Windows and macOS checks are pending.
let mut vol = Volume::mount(dev).await?; // validates boot sector, checksum, // bitmap, up-case vol.create(&NewFile::new("README.txt") .with_data(b"hello\n")).await?; let ring = NewFile::new("logring.wlr") .size(32 << 20) // preallocated, contiguous, // FAT untouched .align_sectors( geometry.boundary_unit_sectors) // starts on an allocation unit // boundary .attributes(attr::READ_ONLY | attr::SYSTEM | attr::HIDDEN); let ring = vol.create(&ring).await?; let lba = vol.info() .cluster_lba(ring.first_cluster); // write it by raw sector address // from herelet mut vol = Volume::mount(dev).await?; // validates boot sector, checksum, bitmap, up-case vol.create(&NewFile::new("README.txt").with_data(b"hello\n")).await?; let ring = NewFile::new("logring.wlr") .size(32 << 20) // preallocated, contiguous, FAT untouched .align_sectors(geometry.boundary_unit_sectors) // starts on an allocation unit boundary .attributes(attr::READ_ONLY | attr::SYSTEM | attr::HIDDEN); let ring = vol.create(&ring).await?; let lba = vol.info().cluster_lba(ring.first_cluster); // write it by raw sector address from here -
A power-cut-safe log store for embedded Rust, no_std and without alloc. Many tracks of any ShrinkWrap type are written into a ring file on an SD card, with sessions, recordings and markers. A PC tool and a Python module read a card image and export CSV, MCAP and MF4. It is tested on card images so far, not yet on a real card, and showing the log as a USB disk is planned.
import logring card = logring.open("card.img") df = card.track("can1_rx").to_pandas() -
Donguru is a USB-C dongle that turns one USB-C port into a small test bench: a USB hub, UART, I2C, GPIO and ADC, a power switch and a power meter. The
donguru(dg) command line drives all of it, with JSON output and fixed exit codes for scripts and CI. A Python package does the same through the same Rust library. Rev B boards for testers are planned for the end of 2026.example: TBD
-
A USB Mass Storage device class for embassy-usb, which today has only the host side of MSC. It is planned to do Bulk-Only Transport with SCSI commands, several LUNs on one interface, an async
Luntrait, media change and write protect. It is built for logring’s two-LUN USB view. So far the repo is a scaffold: the class itself is not written yet.example: TBD
-
canzone work in progress
Stand-alone Rust driver for the Bosch M_CAN controller, aimed at STM32 G0, G4, H5, H7 and L5. Goals: no panics, no blocking waits without a timeout, optional async.
let mut can = unwrap!(can.into_config_mode()); can.set_nominal_bit_timing(unwrap!( NominalBitTiming::new(1, 55, 8, 1) )); unwrap!(can.set_layout(layout_fdcan1)); can.set_automatic_bus_off_recovery(true); let mut can = unwrap!(can.into_normal()); let id = unwrap!(StandardId::new(0x123)); can.write_tx_buffer_pend( tx_buffers.idx1, TxFrameHeader::fd_brs(Id::Standard(id)), &[0xAA, 0xBB, 0xCC], );let mut can = unwrap!(can.into_config_mode()); can.set_nominal_bit_timing(unwrap!(NominalBitTiming::new(1, 55, 8, 1))); unwrap!(can.set_layout(layout_fdcan1)); can.set_automatic_bus_off_recovery(true); let mut can = unwrap!(can.into_normal()); let id = unwrap!(StandardId::new(0x123)); can.write_tx_buffer_pend( tx_buffers.idx1, TxFrameHeader::fd_brs(Id::Standard(id)), &[0xAA, 0xBB, 0xCC], );
-
-
Drivers and HALs
-
A bare-metal
no_stdRust driver for the OTM8009A LCD controller. It sends the controller’s init sequence through a MIPI DSI host, using theembedded-display-controllertraits. Frame rate, portrait or landscape, RGB or BGR order and the size are settings.example: TBD
-
A Rust HAL for the SLG46824 and SLG46826 mixed-signal matrix chips. It reads and programs their RAM and NVM over I2C, and rewires the matrix while it runs. Any
embedded-halI2C implementation works, and the crate isno_stdwithout its default features.example: TBD
-
A Rust HAL for the GPIO pins and the I2C master of Microchip’s USB4604 USB hub. It reaches them through the hub’s feature controller with USB control transfers (nusb), and its I2C bridge implements the embedded-hal 1.0 I2c trait. Eight PIO pins are verified to work; SPI and UART are not supported.
example: TBD
-
Register definitions in no_std Rust for the TI BQ76952, BQ76942 and BQ769142 battery monitor AFEs. It covers the direct command and subcommand registers as bitfield structs, with optional defmt. So far it is register definitions only, without a README or a driver on top.
example: TBD
-
i2c_periph
A portable async no_std I2C driver for microcontrollers, with one API across I2C IPs. It adds what a test adapter needs and HAL drivers often lack: repeated starts per operation, NACK reports with the byte, a clock-stretch watchdog, bus recovery through GPIO and the actual SCL frequency. The DesignWare I2C of the RP2350 is the only backend so far; STM32 is an idea.
-
pd_bench
A no_std driver for the onsemi FUSB302 USB PD PHY over embedded-hal-async, with batched I2C transactions and a register-level simulator for host tests. Next to it are a USB PD sink library on usbpd’s policy engine, with policies for testing PD sources, and a PD message log that both ends of a link write the same way. The driver has had one hardware run; the rest is host-tested only.
-
-
Host tools and instruments
-
Reads an EEVblog 121GW multimeter over Bluetooth LE. A Rust library gives typed readings and a stream of packets, a CLI scans, reads and logs to CSV, and a Python module serves notebooks and test benches. An agent skill sits on top. It only reads the meter, mode and range stay on its knob, and not every knob position is checked on a real meter yet.
let meter = gw121::Meter::open( None, Duration::from_secs(10) ) .await?; println!( "{}", meter .read(Duration::from_secs(5)) .await? .main()? ); // -0.0021 V let mut packets = meter.packets().await?; // a futures Stream meter.disconnect().await?;let meter = gw121::Meter::open(None, Duration::from_secs(10)).await?; println!("{}", meter.read(Duration::from_secs(5)).await?.main()?); // -0.0021 V let mut packets = meter.packets().await?; // a futures Stream meter.disconnect().await?; -
A client library meant to read and drive GPIO, I2C, USART, SPI and ADC on a device over USB, Ethernet or CAN, built on WireWeaver. It is an early start: the API has one method that switches an LED, the client connects over USB, and the Python module is a stub.
example: TBD
-
The Analog Devices ADALM2000 (M2K) from Rust, the shell or Python: scope, signal generator, power supplies, voltmeter, logic analyzer and calibration. It talks to the device over the network with its own pure-Rust iiod client, without libiio, libm2k or C. Every command can answer in JSON, and the iiod client also works for other IIO devices. It is not on crates.io yet.
m2k info # model, serial, firmware, calibration, # outputs, supplies m2k cal run # self-calibration, ~1.5 s; kept until # power-off m2k scope -n 10k -r 1M --csv cap.csv # both channels: stats per channel, # all samples as CSV m2k gen -c 1 --for 10s sine -f 1k -a 1 # W1: 1 kHz, ±1 V; off again after # 10 s (or Ctrl+C) m2k voltmeter --range 2.5 # DC and AC rms on 1+/1- and 2+/2- m2k psu set --pos 3.3 --neg -3.3; m2k psu off m2k logic -n 100k -r 10M -p 0-7m2k info # model, serial, firmware, calibration, # outputs, supplies m2k cal run # self-calibration, ~1.5 s; # kept until power-off m2k scope -n 10k -r 1M --csv cap.csv # both channels: stats per channel, # all samples as CSV m2k gen -c 1 --for 10s sine -f 1k -a 1 # W1: 1 kHz, ±1 V; # off again after 10 s (or Ctrl+C) m2k voltmeter --range 2.5 # DC and AC rms on 1+/1- and 2+/2- m2k psu set --pos 3.3 --neg -3.3; m2k psu off m2k logic -n 100k -r 10M -p 0-7 -
A Rust library for the RP2350 and RP2040 boot ROM in BOOTSEL mode, on nusb without libusb. It erases, writes and verifies flash from UF2, ELF or raw images and reboots the chip, so a host can update a device without a button press or picotool. A simulated boot ROM tests the whole sequence without hardware. OTP and the RP2040 EXEC commands are not implemented yet.
use picoboot_host::{Image, Picoboot, usb}; let image = Image::from_uf2( &std::fs::read("app.uf2").unwrap(), &[picoboot_host::image::family ::RP2350_ARM_S] )?; let mut pb = usb::open(None).await?; // the only BOOTSEL device connected pb.flash(&image, &mut |stage, done, total| { eprintln!("{stage:?}: {done}/{total}"); }) .await?; pb.reboot2( picoboot_host::proto::Reboot2::Normal, picoboot_host::proto::REBOOT_DELAY ) .await?;use picoboot_host::{Image, Picoboot, usb}; let image = Image::from_uf2( &std::fs::read("app.uf2").unwrap(), &[picoboot_host::image::family::RP2350_ARM_S] )?; let mut pb = usb::open(None).await?; // the only BOOTSEL device connected pb.flash(&image, &mut |stage, done, total| { eprintln!("{stage:?}: {done}/{total}"); }) .await?; pb.reboot2( picoboot_host::proto::Reboot2::Normal, picoboot_host::proto::REBOOT_DELAY ) .await?; -
Drives Saleae logic analyzers through Saleae’s headless Logic 2 automation server, without the Logic 2 GUI. It is a Rust library, the
saleaecommand line and a Python module for notebooks and test benches. One command captures, runs an I2C, SPI or UART analyzer and prints a short summary. Saleae’s simulated devices work without hardware.import saleae_rs as saleae s = saleae.Session(sim_only=True) # starts the server in the background # on first use sim = next(d for d in s.devices() if d["simulated"]) # F4241 / F4244 / F4243 without real # hardware rec = s.capture(device=sim["id"], digital=[0, 1], duration=0.5) analyzer = s.add_analyzer( rec["capture"], "I2C", settings={"SDA": 0, "SCL": 1}) print(s.summarize(rec["capture"], analyzer, kind="i2c")["text"]) s.close(rec["capture"])import saleae_rs as saleae s = saleae.Session(sim_only=True) # starts the server in the background on first use sim = next(d for d in s.devices() if d["simulated"]) # F4241 / F4244 / F4243 without real hardware rec = s.capture(device=sim["id"], digital=[0, 1], duration=0.5) analyzer = s.add_analyzer(rec["capture"], "I2C", settings={"SDA": 0, "SCL": 1}) print(s.summarize(rec["capture"], analyzer, kind="i2c")["text"]) s.close(rec["capture"]) -
A UX guide for Rust command-line tools: completion, help, colour, output, errors and exit codes, progress, prompts, configuration and
--version. The small sharedvc_*crates that are to do these parts are planned and not written yet. With them, a new CLI gets the parts by adding a dependency instead of copying code.example: TBD
-
can_bus_cli
A command line for CAN bus bridges.
-
fpga_tools
FPGA tools for lab benches on the Efinix Xyloni board (Trion T8). A signal glitcher injects needles, double edges, dropped pulses and stuck levels into a passing logic signal, also on an MCU’s reset line or a power switch. A bus generator drives 8 lines with cycle-exact squares, pulses, PWM, UART and I2C, and reports the cycles it used, to check logic analyzers. The host CLIs and Python modules are in Rust.
-
mdcheck
Compile-checks the Rust code blocks in Markdown files. Each block becomes its own scratch crate with a Cargo.toml inferred from the nearest heading and the crates the code names, looked up on crates.io. cargo check runs over all of them in parallel with one shared build cache, so a dependency used by many blocks compiles once. rustdoc block attributes such as rust,no_run work as they are.
-
popov
Radio in Rust: software-defined radio and signal processing, taught by the Radio Room, a book of short stories set on a Moon prospecting mission. It has FIR filters and NCOs in float and Q15, SigMF recordings, a seeded channel simulator with noise and frequency offset, and the
popovcommand. FPGA, GPU and plotting crates are ideas so far, and the book has its sample page.
-
-
GUI
-
Fast table viewer and editor that only draws the visible rows. Data from vectors, files, databases or live bus traces.
let mut table = VariantTable::new([ ColumnDef::new("Name", VariantTy::Str) ]); let opts = TableViewOptions::default(); let mut view = TableView::new(opts); let mut config = TableViewConfig::default(); // Every frame: let output = view.show( ui, &mut table, &mut VariantCellUi, &mut config );let mut table = VariantTable::new([ColumnDef::new("Name", VariantTy::Str)]); let opts = TableViewOptions::default(); let mut view = TableView::new(opts); let mut config = TableViewConfig::default(); // Every frame: let output = view.show(ui, &mut table, &mut VariantCellUi, &mut config); -
A dynamically typed value for Rust, inspired by Qt’s QVariant: a Variant plus a type descriptor with an SI unit, enum variants or a currency. Text is parsed strictly and printed so it reads back: 4k7 with an ohm type is 4700 Ω. Conversions never lose information silently, and a vendor-text parser reads distributor and BOM values like 13.7 kOhms. Used for table cells, CSV import and stored parameters.
use rvariant::{ Variant, VariantTy, NumberTy, si_dynamic::{Unit, BaseUnit} }; let ohm = VariantTy::si(NumberTy::F32, Unit::base(BaseUnit::Ohm)); let r = Variant::try_from_str("4k7", &ohm)?; // 4700 Ω assert_eq!(r.to_string(), "4700 Ω"); // Display is parseable back assert_eq!(Variant::try_from_str("-1", &VariantTy::u32()).is_err(), true); let n: u16 = Variant::str("0x10").try_into()?; // 16use rvariant::{Variant, VariantTy, NumberTy, si_dynamic::{Unit, BaseUnit}}; let ohm = VariantTy::si(NumberTy::F32, Unit::base(BaseUnit::Ohm)); let r = Variant::try_from_str("4k7", &ohm)?; // 4700 Ω assert_eq!(r.to_string(), "4700 Ω"); // Display is parseable back assert_eq!(Variant::try_from_str("-1", &VariantTy::u32()).is_err(), true); let n: u16 = Variant::str("0x10").try_into()?; // 16 -
egui building blocks: the widget frame every panel sits in, a dock that moves widgets between windows, an HDR phosphor scope.
// min, nom and wide sizes, and a tall form let hints = SizeHints::new( [90.0, 30.0], [200.0, 80.0], [360.0, 80.0] ) .tall(200.0); WidgetFrame::new("log") .sizes(hints) .show_area(ui, &mut state, |ui, area| { let rows = match area.height { Height::Min => 1, Height::Nom => 5, Height::Tall => 15, }; });// min, nom and wide sizes, and a tall form let hints = SizeHints::new([90.0, 30.0], [200.0, 80.0], [360.0, 80.0]).tall(200.0); WidgetFrame::new("log") .sizes(hints) .show_area(ui, &mut state, |ui, area| { let rows = match area.height { Height::Min => 1, Height::Nom => 5, Height::Tall => 15, }; });
-
-
Science
-
A Rust parser for SI quantities and units in text, at run time. It reads every SI base and derived unit and prefix, exponents, unit expressions such as m/s² or W/(m·K), percent and ppm. It also reads the resistance shorthand of schematics and BOMs, such as 4k7 and 499kR. Each unit carries its dimension and scale, and units beyond SI, such as rpm, are registered at run time.
use si_dynamic::{ BaseUnit, OhmF32, Prefix, Quantity, Unit }; let q = Quantity::parse("4.7 uF")?; assert_eq!( q.unit.as_unit(), Some(&Unit::new( Prefix::Micro, BaseUnit::Farad, 1 )) ); assert_eq!(q.base_decimal()?, "0.0000047"); let q = Quantity::parse("10 V/us")?; // a unit expression assert_eq!(q.unit.to_string(), "V/μs"); let r = OhmF32::parse("4k7")?; // 4700 Ω assert_eq!(r.0, 4700.0);use si_dynamic::{BaseUnit, OhmF32, Prefix, Quantity, Unit}; let q = Quantity::parse("4.7 uF")?; assert_eq!( q.unit.as_unit(), Some(&Unit::new(Prefix::Micro, BaseUnit::Farad, 1)) ); assert_eq!(q.base_decimal()?, "0.0000047"); let q = Quantity::parse("10 V/us")?; // a unit expression assert_eq!(q.unit.to_string(), "V/μs"); let r = OhmF32::parse("4k7")?; // 4700 Ω assert_eq!(r.0, 4700.0); -
ExactUnits puts a number’s unit, scale and storage into its type and checks them at compile time. A conversion is exact or refused, never rounded through a float, and the type is the same bytes as the plain integer, no_std and no alloc. It covers the cases other unit crates leave out: a temperature is a point against an origin, and a voltage gain is a different kind from a power gain.
// exact or refused, never rounded let t: Micros<u32> = Fixed::new(1500); assert_eq!(t.to::<Milli, u16>(), None); // 1500 µs is not a whole number of ms // V/V and W/W are different kinds, // and the kind picks the dB formula type V = Per<Volt, Volt>; type W = Per<Watt, Watt>; Fixed::<V, One, f64>::new(2.0).to_db(); // 6.0206 dB: 20·lg 2 Fixed::<W, One, f64>::new(2.0).to_db(); // 3.0103 dB: 10·lg 2 // a threshold in dB: an integer, exact, // 501 mV/V but 251 mW/W let v: Fixed<V, Milli, u16> = db!(-6); let p: Fixed<W, Milli, u16> = db!(-6); // 0.5 duty is not -3 dB: a Ratio has no dB // Fixed::<Ratio, One, f64>::new(0.5) // .to_db(); // error[E0080]: decibels are for W/W or // V/V, A/A, Pa/Pa: a plain Ratio has no // level form// exact or refused, never rounded let t: Micros<u32> = Fixed::new(1500); assert_eq!(t.to::<Milli, u16>(), None); // 1500 µs is not a whole number of ms // V/V and W/W are different kinds, and the kind picks the dB formula type V = Per<Volt, Volt>; type W = Per<Watt, Watt>; Fixed::<V, One, f64>::new(2.0).to_db(); // 6.0206 dB: 20·lg 2 Fixed::<W, One, f64>::new(2.0).to_db(); // 3.0103 dB: 10·lg 2 // a threshold in dB: an integer, exact, 501 mV/V but 251 mW/W let v: Fixed<V, Milli, u16> = db!(-6); let p: Fixed<W, Milli, u16> = db!(-6); // 0.5 duty is not -3 dB: a Ratio has no dB // Fixed::<Ratio, One, f64>::new(0.5).to_db(); // error[E0080]: decibels are for W/W or V/V, A/A, Pa/Pa: a plain Ratio has no // level form -
A deterministic no_std electric motor simulator in pure Rust, for testing motor controllers in software. It models a PMSM in dq with an average inverter, rotor mechanics and loads. The true state is kept apart from what a controller measures, with sensor offsets, noise and faults. The same parameters and seed give the same run on every target, and libm is the only dependency.
use motor_sim::{ Drive, Load, Motor, presets }; let mut motor = Motor::new( presets::vesc_hobby_outrunner(), 42 ); motor.set_load(Load::Fan { k_nms2_per_rad2: 2e-7 }); let dt = 1.0 / 25_000.0; for _ in 0..25_000 { let _s = motor.sensors(); // i_abc_a, vdc_v, encoder_count, hall let drive = Drive::Duty([0.5; 3]); // your controller here motor.step(&drive, dt); }use motor_sim::{Drive, Load, Motor, presets}; let mut motor = Motor::new(presets::vesc_hobby_outrunner(), 42); motor.set_load(Load::Fan { k_nms2_per_rad2: 2e-7 }); let dt = 1.0 / 25_000.0; for _ in 0..25_000 { let _s = motor.sensors(); // i_abc_a, vdc_v, encoder_count, hall let drive = Drive::Duty([0.5; 3]); // your controller here motor.step(&drive, dt); } -
choreograph early stage
A scheduler for shared resources: instruments, connections, RAM, cores and machines, with priorities, release times and deadlines. A job gets all it needs at once or nothing, nothing goes over capacity, cleanup always runs, and it says why each waiting job waits. The same core runs a hardware-in-the-loop test bench and a merge queue on build machines. It is pure, with no IO and no clock, so the simulator, the tests and a real executor drive the same code, and
choreograph theorychecks recordings of real history against queueing theory.cargo run -p choreograph_cli -- \ check examples/bench.toml cargo run -p choreograph_cli -- \ top --socket \ /run/choreographd/api.sock # a live view of choreographdcargo run -p choreograph_cli -- check examples/bench.toml cargo run -p choreograph_cli -- top --socket /run/choreographd/api.sock # a live view of choreographd
-
-
EDA and hardware tools
-

Schematic checks beyond the ERC built into an EDA tool: they know what an I2C bus, a pull-up, a MOSFET or a power rail is. It reads KiCad projects, OrCAD and Altium netlists and Altium Smart PDFs, and runs from the shell or CI with text, JSON, SARIF or Markdown output. berc textconv makes git diffs of KiCad files readable and is ready for daily use. The project is early; the desktop app is a first prototype.
berc check hw/board # findings worst first, the checks # that could not run and why berc explain I2C-3 # what a check catches, why it # matters, how to fix it berc checks list --project hw/board # every catalogue check: ran, not run # (and why), plannedberc check hw/board # findings worst first, # the checks that could not run and why berc explain I2C-3 # what a check catches, why it matters, # how to fix it berc checks list --project hw/board # every catalogue check: ran, # not run (and why), planned -
VHRD’s KiCad library: symbols, footprints and 3D models, drawing sheets in A4 and A3, and a project template with net class colours. Power symbols are coloured by kind, supplies orange and grounds grey. Board templates for a Raspberry Pi HAT, an STM32 Nucleo-64, a Hammond case and others come with it. The README explains KiKit panelization setup.
example: TBD
-
A GNU Radio graph for radiated immunity pre-tests in the style of EN 61000-4-3, with a USRP B210 SDR and a desktop TEM cell. It sweeps a carrier from 80 to 1000 MHz by default with adjustable AM, at about 18 dBm out of the B210. It is a cheap bench that keeps the device under test easy to reach, not a test house setup.
example: TBD
-
eda_lens
A planned reader of EDA artwork: schematic PDFs, screenshots, scans and hand drawings into netlists first, Gerbers and PCB photos later. What exists today is the test harness: a corpus of 48 KiCad projects whose ground truth kicad-cli renders, checked end to end. The readers and their scoring are not built yet.
-
-
Templates
-
A firmware template for the B129A CANnify Micro, a one-channel CAN FD board with an STM32G0B1. One WireWeaver API definition gives the firmware, a Rust client library, the
cannifycommand line and a Python module. The firmware starts with firmware info, crash records, a label and settings in flash, event counters, a kept log and a host-steered clock. It is built, not yet run on a board, and does not drive CAN yet.import cannify_micro with cannify_micro.Device() as dev: # Device(probe_serial="...", # elf="firmware/target/.../ # cannify_micro") print(dev.firmware()) dev.label = "bench 1" dev.blink(count=3, period_ms=400) print(dev.board(), dev.variant(), dev.uid()) print(dev.last_reset()) print(dev.counters()) # {"RAM/loop_iterations": 139, ...} print("\n".join(dev.log()))import cannify_micro with cannify_micro.Device() as dev: # Device(probe_serial="...", elf="firmware/target/.../cannify_micro") print(dev.firmware()) dev.label = "bench 1" dev.blink(count=3, period_ms=400) print(dev.board(), dev.variant(), dev.uid()) print(dev.last_reset()) print(dev.counters()) # {"RAM/loop_iterations": 139, ...} print("\n".join(dev.log())) -
A firmware template for the ST Nucleo-G0B1RE board (STM32G0B1RE). From one WireWeaver API definition it gives the firmware, a typed Rust client, a command line and a Python module. It talks to the PC through the board’s own ST-LINK, so only the Nucleo and its USB cable are needed. The firmware starts with device info, crash records, a stored log, settings in flash and a shared clock.
import nucleo_g0b1re with nucleo_g0b1re.Device() as dev: # Device(probe_serial="...", # elf="firmware/target/.../ # nucleo_g0b1re") print(dev.firmware()) dev.label = "bench 1" dev.blink(count=3, period_ms=400) print(dev.board(), dev.variant(), dev.uid()) print(dev.last_reset()) print(dev.counters()) # {"RAM/loop_iterations": 139, ...} print("\n".join(dev.log()))import nucleo_g0b1re with nucleo_g0b1re.Device() as dev: # Device(probe_serial="...", elf="firmware/target/.../nucleo_g0b1re") print(dev.firmware()) dev.label = "bench 1" dev.blink(count=3, period_ms=400) print(dev.board(), dev.variant(), dev.uid()) print(dev.last_reset()) print(dev.counters()) # {"RAM/loop_iterations": 139, ...} print("\n".join(dev.log())) -
A starting point for a WireWeaver device on USB. One API crate is shared by the firmware and the host. Example firmware runs on Nucleo H743ZI2 and H753ZI boards and an STM32G0 board, and any board with embassy-usb should work. A Rust driver comes with a simple client example and a REPL with history and auto-complete. A rename script and a virtual device are still to do.
example: TBD
-
b135_template
A firmware template for the B135B, an isolated USB high-speed to CAN FD adapter with an STM32H725. One WireWeaver API definition gives the firmware, a Rust client library, a command line and a Python module. The firmware starts with firmware info, crash records, settings in flash, event counters and a kept log. It is built but not yet run on a board.
-
donguru_template
A firmware template for the B153A Donguru board (STM32G0B1). One API definition gives the embassy firmware, a typed WireWeaver API, a Rust client library, a command line and a Python module. The firmware starts with device info, crash records, a settings store in flash, a stored defmt log and reboots into the ROM bootloader. It is meant to be copied and renamed for a new device.
-
pico2_template
A firmware template for the Raspberry Pi Pico 2 (RP2350). From one WireWeaver API definition it gives the firmware, a typed Rust client, a command line and a Python module. The board talks to the PC over its own micro-USB, and BOOTSEL gives drag-and-drop UF2 flashing without a probe. The firmware starts with device info, crash records, a log kept across a reset and settings in flash.
-
pico_template
A firmware template for the Raspberry Pi Pico (RP2040). One API definition gives the embassy firmware, a typed WireWeaver API, a Rust client library, a command line and a Python module. The firmware starts with device info, crash records, a settings store in flash, a stored defmt log and reboots into BOOTSEL. It is meant to be copied and renamed for a new device.
-
report_template
A firmware template for the rePort board (RP2350B). From one WireWeaver API definition it gives the firmware, a typed Rust client, a command line and a Python module. The board talks to the PC over its own USB-C, which also powers it. The firmware starts with device info, crash records, a log kept across a reset, settings in flash and reboots into the boot ROM.
-