Donguru Coming Q1–Q2 2027
A USB-C dongle with a USB hub, a serial port, a power switch and a power meter.
- Hub
- USB, on board
- Serial
- TX/RX flip
- Power
- switch + meter
Boards · Rust firmware · Open source
Boards, Rust firmware and the open-source software that ties them together, from Norway.
draft Copy not yet read by Marketing and PM. Board renders follow.
01Products
Three tools for bring-up, debugging and test, each with a Rust driver, a CLI and a GUI.
A USB-C dongle with a USB hub, a serial port, a power switch and a power meter.
Digital and analog I/O over USB. No programming needed.
USB and Ethernet bridges to isolated CAN FD, with SD card logging, and the adapters around them.
02Donguru · B153
Donguru sits between your computer and a board with a USB-C port. One cable carries USB, a serial console and power you can switch and measure.
A USB hub and a UART on the same cable. TX and RX are flipped for you.
Electronic switches on VBUS and the USB data lines, a power meter, and a mechanical switch for the boot mode.
A fast CLI, donguru or dg, with a Rust driver and Python behind it.
For development, debugging and production flashing of devices with a USB-C port.
03Software
IOWeaver plots what your device says. RockFace debugs the firmware that says it. WireWeaver keeps both sides speaking the same API.
// The API, once, for both sides (ww_uart):
#[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 device with several ports lists them as one array:
ww_impl!(uart[]: ww_uart::Uart)
Protocol analyzer and grapher. Record, decode, plot and send. It ships with rePort and Donguru.
Firmware debugger: logs, stack and CPU use, zero-cost profiling, logs from a fleet. First paid release planned for January 2027.
One API definition for device and host: RPC, streams and properties, backwards and forwards compatible. Open source.
04From the lab
Selected boards and machines from my own work: optics, power, motor control and precision analog.

Optical inspection of solder paste and placed parts with a scanning green laser line.



Analog front end that rides on the shaft and talks through a slip ring.
05Open source
The products stand on open crates, all Rust. Each layer works on its own, and together they reach from the M_CAN registers to the table on your screen.
The desktop side: IOWeaver and RockFace are built from these.
egui building blocks: the widget frame every panel sits in, a dock that moves widgets between windows, an HDR phosphor scope.
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,
};
});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 mut view = TableView::new(TableViewOptions::default());
let mut config = TableViewConfig::default();
// Every frame:
let output = view.show(ui, &mut table, &mut VariantCellUi, &mut config);Runs the jobs that use the boards.
Plans and runs jobs over shared instruments and resources, with priorities and deadlines. Says why each waiting job waits.
[[resource]]
name = "supply"
kind = "supply"
states = ["off", "3V0", "3V3", "3V6"]
safe = "off"
[[job]]
name = "a-3V3"
needs = [{ kind = "probe", state = "fw-a" }, { kind = "supply", state = "3V3" }]
run = "10s"One definition for the device and the host, and the bytes between them.
API description language and code generator: RPC, streams and properties, backwards and forwards compatible, no_std and no alloc.
#[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>;
}Compact zero-copy wire format for microcontrollers. No allocator, dynamic types, packed down to the bit.
let mut buf = [0u8; 256];
let mut wr = BufWriter::new(&mut buf);
wr.write_bool(true).unwrap();
wr.write_u8(0xaa).unwrap();
let bytes = wr.finish().unwrap();
assert_eq!(bytes, &[0x80, 0xaa]); // the bool takes one bitUnits of measure checked at compile time, with exact scales. The same bytes as the plain integer, no_std and no alloc.
// exact or refused, never rounded through a float
let t: Micros<u32> = Fixed::new(1500);
assert_eq!(t.to::<Milli, u16>(), None); // 1500 µs is not a whole number of ms
// zero cost: the type is the integer
assert_eq!(size_of::<Micros<u32>>(), size_of::<u32>());Bare metal, next to the registers.
Bare-metal firmware template with ruggedizing, debugging and logging built in. Optional embassy, RTIC and HAL support.
use bedrock_led::code::{blink, Code};
// A firmware that won't start says why on its status LED,
// then waits in the ROM bootloader to be flashed.
if !guard.runs_board() {
blink(Code::UnknownRework, |on| led_pin_set(on), |ms| cortex_m::asm::delay(ms * BOOT_CLOCK_KHZ));
reboot_into_rom_bootloader();
}Stand-alone Rust driver for the Bosch M_CAN controller on STM32 G0, G4, H5, H7 and L5. No panics, no blocking waits without a timeout.
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());
can.write_tx_buffer_pend(
tx_buffers.idx1,
TxFrameHeader::fd_brs(Id::Standard(unwrap!(StandardId::new(0x123)))),
&[0xAA, 0xBB, 0xCC],
);06Stay in the loop
Donguru launches on Crowd Supply. Its sign-up list opens with the pre-launch page; until then, send a line and you hear first. draft
Contact
Questions about the boards, the software or a project of your own: hello@vhrd.tech.
Engineering services are rare right now. What I do and how I work