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, 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

  1. Highlights

    • embedded_bedrock

      Rust · not on crates.io yet

      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
  2. Wire format and APIs

    • 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

    • ww_lsp

      Rust

      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

  3. 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
    • embedded_bedrock

      Rust · not on crates.io yet

      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 here
      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 here
    • logring

      Rust

      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 Lun trait, 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

      Rust · renamed from mcan, not on crates.io yet

      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],
      );
  4. Drivers and HALs

    • A bare-metal no_std Rust driver for the OTM8009A LCD controller. It sends the controller’s init sequence through a MIPI DSI host, using the embedded-display-controller traits. 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-hal I2C implementation works, and the crate is no_std without 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

    • bq769x2

      Rust

      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

      Rust coming in the future

      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

      Rust coming in the future

      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.

  5. Host tools and instruments

    • 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()?;
      // 16
      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()?;     // 16
    • gw121

      Rust

      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

    • m2k

      Rust

      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-7
      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-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 saleae command 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 shared vc_* 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

    • vhrd_egui

      Rust · not on crates.io yet

      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,
              };
          });
    • can_bus_cli

      Rust coming in the future

      A command line for CAN bus bridges.

    • fpga_tools

      Verilog, Rust coming in the future

      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

      Rust coming in the future

      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

      Rust coming in the future

      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 popov command. FPGA, GPU and plotting crates are ideas so far, and the book has its sample page.

  6. 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);
    • 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

      Rust · not on crates.io yet

      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 theory checks 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 choreographd
      cargo run -p choreograph_cli -- check examples/bench.toml
      cargo run -p choreograph_cli -- top --socket /run/choreographd/api.sock
          # a live view of choreographd
  7. EDA and hardware tools

    • 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

      Rust coming in the future

      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.

  8. Templates

    • 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

      Rust coming in the future

      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

      Rust coming in the future

      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

      Rust coming in the future

      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

      Rust coming in the future

      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

      Rust coming in the future

      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.