Get started

Requirements first, honestly

Firn makes an early bet on C++26 reflection — the same kind of bet that put C++17 on Cortex-M before the field thought it reasonable. Here is exactly what that costs you today.

Requirements — stated plainly

Consume via xpm (the xPack ecosystem, same as µOS++)

// package.json of your firmware project
{
  "xpack": {
    "dependencies": {
      "@firnware/firn": "…",   // registry or git URL once published
      "@micro-os-plus/micro-os-plus-iii": "github:micro-os-plus/micro-os-plus-iii#v7.1.0"
    }
  }
}
xpm install
g++ -std=c++26 -freflection -I xpacks/@firnware/firn/include ...
#include <firn/topology.hpp>

Or plain: clone, add -I firn/include, done. There is nothing to build.

The 60-second taste (works today, host-only)

git clone https://github.com/henris42/firn && cd firn
tests/check.sh          # run tests + asm proof + compile-fail diagnostics
examples/build.sh       # the demos: generated boards, dynamic device tree, bridge
examples/build/genboard

What genboard prints — a real run, showing the device surface generated from board.hpp, the single authored file:

  [hw] cfg pin 13 OUT
  [hw] cfg pin 32 OUT
  [hw] cfg pin 11 IN pull-up
  [hw] cfg pin 4 IN
  [hw] i2c7 init @ 400 kHz

== firn::dev generated from board.hpp (the single authored file) ==
  dev.LedRed.pin   = 13
  dev.LedGreen.pin = 32
  dev.Button3.pin  = 11
  dev.Light.pin    = 4
  dev.I2C_7.index  = 7          (generated bus handle)
  dev.AmbientLight = addr 0x55 on i2c7  (generated sensor handle)

  dev.LedRed.write(true) -> hw line 0  (active-low)
  dev.Light.getPhysical() = 500.0 lux  (analog handle, also generated)
  [event] Button3 edge via GENERATED handle: pin 11, level 1

No handle was written by hand -- add a row to board.hpp and a new
dev.* handle appears, typed, validated, and topic-wired. The table
is the only artifact.

What a board file looks like

The one file you author. Ports and buses are physical devices; pins and sensors are logical channels that reference a device by name — a keyref the compiler enforces.

#include <firn/topology.hpp>

namespace firn {

inline constexpr Port kPorts[] = {
    { "P0", 0  },
    { "P1", 32 },
};

inline constexpr Pin kPins[] = {
    { "LedRed",   "P0", 13, Direction::OUT, Signal::DIGITAL, true,  Pull::NONE },
    { "LedGreen", "P1",  0, Direction::OUT, Signal::DIGITAL, true,  Pull::NONE },
    { "Button3",  "P0", 11, Direction::IN,  Signal::DIGITAL, false, Pull::UP,
                  0, 1, 0.0, 1.0, "ButtonTopic" },   // publishes edge events
    { "Light",    "P0",  4, Direction::IN,  Signal::ANALOG,  false, Pull::NONE,
                  0, 65535, 0.0, 1000.0 },        // 0..65535 counts -> 0..1000 lux
};

inline constexpr Bus kBuses[] = {
    { "I2C_7", 7, 400 },
};

inline constexpr Sensor kSensors[] = {
    { "AmbientLight", "I2C_7", 0x55 },            // bus -> I2C_7
};

} // namespace firn

Binding your silicon

Firn's generic peripheral drivers — shift-register chains (74HC165/595), quadrature decoding, mux'd pot scanning with IIR + deadband, HD44780 LCDs, LED banks, SPI/I2C buses — are written once, board-independent. Config arrives as compile-time specs joined from your topology; your silicon arrives as a tiny static-function Io policy:

// The ENTIRE project-side cost of Firn's generic drivers:
struct Io {
    static void gpio_write(firn::PinRef p, bool v) { hal::gpio_write(p, v); }
    static std::uint8_t spi_xfer(std::uint32_t i, std::uint8_t b)
                                                   { return hal::spi_xfer(i, b); }
    // delay_us, adc_read, mux_select ...
};

using Chain       = firn::ShiftReg<firn::SpiBus<kSpiSr, Io>, Io, kSrLoad, kSrRclk>;
using EncoderBank = firn::QuadDecoder<kEncSpecs>;   // masks joined from the board
using Lcd         = firn::Hd44780<Io>;              // role pins joined from the board

Running on µOS++

The case-study firmware documents everything a CMSIS-startup + µOS++ build needs, each one a hard-won fact: working .init_array linker sections, the interrupt-stack watermark fill, memory-resource setup on cross builds, thread objects in static storage, ≥1 KB thread stacks. A "Running on µOS++" docs page will collect these as the material is adapted for publication.

License: MIT. Package: @firnware/firn (xpm / npm, or plain -I include). Status: the framework is real and regression-tested; the public repository and package registry entry are being prepared — until then, the case study is the proof it runs.