npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hdevlop/avrts

v0.1.1

Published

ATmega328P simulator with chip-level timing, peripheral, bootloader, and browser-worker support

Readme

avrts

Realistic ATmega328P simulation in TypeScript.

avrts is built for chip-level fidelity, not for winning every speed chart. It models the MCU underneath an Arduino Uno/Nano: CPU state, IO registers, interrupt vectors, timers, reset sources, fuses, lock bits, sleep modes, flash self-programming, serial buses, and the boot flow. Firmware should observe the same AVR-visible behavior it would observe on the chip, while the simulator still runs comfortably in Node, Bun, and browser workers.

If you want the fastest mature application-level Arduino simulator, avr8js is an excellent choice. If you need the parts avr8js intentionally skips - SPM, bootloaders, fuses, lock bits, sleep/wake clock domains, PRR, Timer2 async, or analog-comparator capture - avrts is the simulator for that layer.

At A Glance

| Need | avrts answer | | --- | --- | | Load firmware | Pass HEX text, a .hex path, or a browser File | | Default chip | ATmega328P, 16 MHz clock | | Main API | AVR(...) facade; no new needed | | Timing model | Fast mode by default, optional cycle-exact mode | | Validation | Bun tests plus native simavr timing/result/Optiboot oracles | | Browser use | Worker runtime, snapshot/restore, pin/PWM/serial adapters | | Best fit | Realistic register-level AVR simulation, bootloaders, low-power firmware, peripheral timing |

Why avrts

  • Chip-level model: not just Arduino API behavior, but the register and interrupt surface real firmware touches.
  • Bootloader capable: stock Optiboot boots, accepts STK500v1 programming over the simulated USART, writes flash through SPM, and runs the uploaded sketch.
  • Native simavr validation: timing, final state, and the Optiboot flow are cross-checked against native simavr where simavr can represent the behavior.
  • Browser friendly: a public AVR(...) facade, snapshot/restore, debugger hooks, component adapters, and a worker-backed browser runtime.
  • Honest speed target: most current fixtures are faster than realtime at 16 MHz, but host-bound bit-banging can fall below realtime and compute-heavy real code can be slower than avr8js. Fidelity is the product priority.

Install

After the package is published:

npm install @hdevlop/avrts
# or: bun add @hdevlop/avrts

For development in this repository:

bun install
bun test

The stable package root contains the function-first facade:

import { AVR } from "@hdevlop/avrts";

Browser/circuit APIs and low-level internals use explicit subpaths so they do not accidentally become part of the minimal root contract:

import { createAVRWorkerRuntime } from "@hdevlop/avrts/browser";
import { CPU, Decoder, PORTB } from "@hdevlop/avrts/advanced";

avrts is released under the MIT License.

Quick Start

Load a local Intel HEX file in Node/Bun. The default chip clock is 16 MHz, matching an Arduino Uno/Nano ATmega328P.

import { AVR } from "@hdevlop/avrts";

const avr = AVR("blink.hex");

avr.pin(13).onChange((high, event) => {
  console.log(event.timeMs.toFixed(3), high ? "LED on" : "LED off");
});

avr.serial.onText((text) => {
  process.stdout.write(text);
});

avr.runFor(1000);
console.log(avr.status());

If you already have HEX text in memory, pass it directly:

const avr = AVR(hexText);

Use clockHz only when you want a non-default clock:

const avr = AVR({ path: "blink.hex", clockHz: 8_000_000 });

Loading Firmware

avrts accepts the common ways firmware appears in apps and tests:

AVR("build/sketch.hex");                    // Node/Bun path
AVR({ path: new URL("./blink.hex", import.meta.url) });
AVR(hexText);                               // direct Intel HEX text
AVR({ hex: hexText, timing: "cycle-exact" });

For browser file uploads, use the async file API:

const input = document.querySelector<HTMLInputElement>("#hex")!;

input.addEventListener("change", async () => {
  const file = input.files?.[0];
  if (!file) return;

  const avr = AVR();
  await avr.loadFile(file);
  avr.start();
});

For fluent setup:

const avr = AVR()
  .useClock(16_000_000)
  .useFuses({ low: 0xff, high: 0xde, extended: 0xfd })
  .useHexFile("firmware.hex");

Practical Examples

Digital Input And Output

Drive an input pin from the host and observe firmware-controlled output:

const avr = AVR("button-led.hex");

const led = avr.pin(13);
const button = avr.pin(2);

led.onChange((high) => {
  console.log(`LED is ${high ? "HIGH" : "LOW"}`);
});

button.setInput(true);
avr.runFor(20);
button.setInput(false);
avr.runFor(20);

Use port-level observation when you care about the whole register value:

avr.gpio.port("B").onChange((value, oldValue) => {
  console.log(`PORTB ${oldValue.toString(16)} -> ${value.toString(16)}`);
});

Serial TX And RX

Capture text written by firmware and inject host input back into Serial:

const avr = AVR("serial-echo.hex");

avr.serial.onText((chunk) => {
  process.stdout.write(chunk);
});

avr.serial.write("hello from host\n");
avr.runFor(100);

console.log(avr.serial.getText());

serial.write(...) is frame-time paced through the simulated USART RX path. The firmware does not receive bytes instantly; it observes RXC0, UDR0, FIFO behavior, and overrun status like a chip would.

Analog Input And PWM Output

Feed analogRead(A0) and observe PWM generated by analogWrite(...):

const avr = AVR("analog-pwm.hex");

avr.analog(0).setVoltage(2.5); // A0 at 2.5 V against the default 5 V reference

avr.pwm(9).onChange((signal) => {
  if (signal.enabled) {
    console.log(`pin 9 duty ${(signal.duty * 100).toFixed(1)}%`);
  }
});

avr.runFor(50);

Each timer's PWM handle reports the active compare value and duty. A firmware OCR write updates its CPU-visible buffer first; pwm(...).read() and change listeners reflect the new duty when that mode transfers the buffer at TOP or BOTTOM.

Use setValue(...) when your test already has a raw 10-bit ADC sample:

avr.analog(0).setValue(512);

Virtual I2C Slave

Connect a virtual device to firmware using the AVR TWI master peripheral:

const avr = AVR("wire-master.hex");
const writes: number[] = [];

avr.twi.connect(0x50, {
  start(address, read) {
    console.log(`I2C address 0x${address.toString(16)} ${read ? "read" : "write"}`);
    return true; // ACK address
  },
  write(byte) {
    writes.push(byte);
    return true; // ACK data
  },
  read() {
    return 0x42;
  },
  stop() {
    console.log("I2C STOP", writes);
  },
});

avr.runFor(100);

The same TWI model also exposes avr.twi.master() for host-side tests where firmware is configured as an I2C slave.

SPI Responder

Respond to firmware SPI master transfers:

const avr = AVR("spi-master.hex");

avr.spi.onByte((mosi, meta) => {
  console.log(`MOSI 0x${mosi.toString(16)} ${meta.bitOrder}`);
});

avr.spi.respondWith((mosi) => mosi ^ 0xff);

avr.runFor(20);

When firmware is configured as an SPI slave, drive it from the host:

const master = avr.spi.master();
const miso = master.transfer(0x3c);
console.log(miso);

Snapshot, Rewind, And Deterministic Tests

Snapshots include CPU state, peripherals, pending events, serial buffers, fuses, sleep state, timers, and in-flight transfers:

const avr = AVR("protocol.hex");

avr.runFor(10);
const beforePacket = avr.snapshot();

avr.serial.write("bad packet\n");
avr.runFor(50);

avr.restore(beforePacket);
avr.serial.write("good packet\n");
avr.runFor(50);

Snapshots are plain data and carry a format version, so they can be stored (for example in IndexedDB) and restored by later releases; restore() rejects snapshots from a newer format with a clear error.

Debugging And Watchpoints

Use breakpoints and data watchpoints for register-level tests and UI inspectors:

const avr = AVR("firmware.hex");

avr.breakpoint({ pc: 0x0120 });
avr.watchData(0x25, ({ oldValue, value }) => {
  console.log(`PORTB ${oldValue.toString(16)} -> ${value.toString(16)}`);
});

avr.on("breakpoint", (event) => {
  console.log(`stopped at PC=${event.pc?.toString(16)}`);
});

avr.runCycles(200_000);

Chip-Level Setup

Configure fuses and reset behavior when your firmware depends on the chip tier:

const avr = AVR()
  .useFuses({
    // Example only: choose fuse bytes for your target firmware.
    low: 0xff,
    high: 0xde,
    extended: 0xfd,
  })
  .useHexFile("boot-or-app.hex");

avr.reset();
console.log(avr.status().clockHz);

For full bootloader flows, see examples/optiboot/ and the native simavr Optiboot oracle:

bun run oracle:simavr:optiboot

What It Models

| Area | Coverage | | --- | --- | | CPU | AVR instruction set, flags, stack, program/data memory, breakpoints, cycle accounting, fast timing and cycle-exact timing modes | | Registers/vectors | All ATmega328P IO addresses classified; 81 named registers modeled with hooks, 6 verified as plain storage, 0 unmodeled named registers, 26/26 interrupt vectors modeled | | GPIO/interrupts | Ports B/C/D, external interrupts, pin-change interrupts, interrupt flags/masks, vector dispatch and acknowledgement | | Timers | Timer0/1/2, CTC, PWM, Timer1 input capture, comparator capture, Timer2 asynchronous 32.768 kHz TOSC mode | | Serial/buses | Timed USART TX/RX, 2-level RX FIFO, DOR0 overrun, 9-bit/synchronous/MSPIM modes, SPI master/slave, TWI/I2C master/slave | | Analog/peripherals | ADC, internal ADC channels, analog comparator, EEPROM, watchdog, CLKPR clock prescaler, PRR power reduction | | Chip tier | Fuses, lock bits, MCUSR reset sources, CKDIV8, BOOTRST/BOOTSZ, WDTON, EESAVE, IVSEL vector relocation, brown-out reset injection | | Flash/self-programming | SPMCSR, page buffer fill, page erase/write, RWWSB/RWWSRE, boot-lock enforcement, fuse/lock/signature reads through LPM, SPM_READY | | Sleep/wake | Six sleep modes with timer clock-domain gating, ADC noise-reduction entry, async Timer2 in power-save/extended-standby, wake sources and wake latency | | Runtime | Public facade, events, serial text/bytes, pin/PWM/analog handles, EEPROM/SPI/TWI handles, snapshot/restore, debugger/watchpoints, browser worker runtime |

The exact behavioral limits are tracked in docs/limitations.md. That file is intentionally part of the product: if a register or vector is not modeled, it must be documented and the machine-checked matrix must agree.

Fidelity Compared With avr8js

avr8js is fast, widely deployed, and very good for the Arduino-class tier: timers, ADC, GPIO, EEPROM, watchdog, USART, SPI, TWI, and common sketch execution. avrts targets the next layer down: the chip behavior that bootloaders, low-power firmware, self-programming code, and register-level tests depend on.

As of the installed avr8js 0.21.0 dev dependency in this repository:

| Behavior | avr8js 0.21.0 | avrts | | --- | --- | --- | | SPM | Instruction exists, but the CPU core marks it not implemented | Flash page fill/erase/write, RWWSB/RWWSRE, lock bits, SPM_READY, Optiboot programming | | SLEEP | Instruction exists, but the CPU core marks it not implemented | Six sleep modes, clock-domain gating, wake sources, wake latency | | Bootloader flow | No fuse/BOOTRST/SPM tier for stock Optiboot programming | Boots stock Optiboot and flashes a sketch over simulated STK500v1 | | Fuses/lock bits/reset sources | Not modeled as a chip tier | CKDIV8, BOOTRST/BOOTSZ, WDTON, EESAVE, lock bits, MCUSR, external/brown-out/watchdog reset | | Analog comparator | No packaged comparator peripheral | ACSR, interrupts, ACIC to Timer1 input capture, simavr oracle row | | Timer2 async | No ASSR.AS2 / 32.768 kHz TOSC model | Separate two-edge register transfers, busy flags, and long-run drift validation | | PRR | No power-reduction peripheral | Gates modeled peripheral clocks | | USART RX | Application-friendly serial delivery | Frame-time pacing, 2-level FIFO, DOR0, frame/parity status injection | | Validation | Project tests and large real-world ecosystem exposure | Bun tests plus native simavr timing, result, and Optiboot oracles | | Speed | Often faster on compute-heavy real sketches | Usually faster than realtime; host-bound bitbang can fall below it, and arithmetic/string/ISR workloads may trail avr8js |

The honest one-line version: avrts is the more realistic ATmega328P chip simulator; avr8js is the faster and more battle-tested application-level simulator.

Validation

Core checks:

bun run verify

Production package checks:

bun run build:lib       # ESM bundles + declaration maps
bun run package:smoke   # pack/install/import in Node, Bun, and a browser bundle
bun run release:check   # core + browser E2E + package gates

Native simavr oracles:

bun run oracle:simavr:timing    # USART/SPI/TWI timing, Timer2 async, comparator
bun run oracle:simavr:result    # final-state fixtures against native simavr
bun run oracle:simavr:optiboot  # stock Optiboot STK500v1 programming flow

Benchmark and result checks:

bun run bench:result
bun run bench:compare -- --repeats 3 --isolate
bun run bench:revision --baseline /path/to/baseline/checkout --output comparison.json

The current benchmark policy is documented in docs/performance-summary.md and docs/benchmark-plan.md. Speed claims should use isolated runs because production normally runs one firmware per simulator instance.

Browser Runtime

Import worker/circuit helpers from @hdevlop/avrts/browser. The packaged runtime includes browser-worker.js; bundlers that do not preserve package-relative worker URLs can pass an explicitly constructed Worker through the worker option, as the demo does.

The browser example uses only public facade/runtime APIs and runs the core in a Web Worker:

bun run demo

It includes program upload, run/pause/resume/reset, single-step, breakpoints, watchpoints, serial monitor, snapshot/restore, draggable LED/button/PWM widgets, and pin wiring. See examples/browser-simulator/README.md.

Example Fixtures

Checked-in firmware fixtures live under examples/:

| Fixture family | What it proves | | --- | --- | | Arduino Serial / SoftwareSerial | TX text, RX pacing, Serial.available(), host input | | Arduino Wire / SPI | Firmware master paths plus host-side slave/master testing | | Arduino Servo / tone / PWM | Timer-driven output visible through facade handles | | LowPower-style WDT sleep | SLEEP, watchdog wake, sleep-state snapshot/restore | | Timer2 async RTC | 32.768 kHz TOSC drift behavior | | Comparator oracle | ACSR, comparator interrupt, ACIC -> Timer1 capture | | Optiboot | BOOTRST, boot section, USART RX pacing, SPM, flash write | | Benchmark fixtures | Mixed peripherals, ISR churn, strings, fixed DSP, float math, bitbang CRC |

Non-Goals

avrts models AVR-visible logical behavior. It does not try to be a circuit solver or a physics simulator.

Permanent non-goals:

  • debugWIRE and on-chip debug hardware.
  • Electrical analog effects such as rise time, drive strength, bus contention, capacitance, leakage, noise, and temperature drift.
  • Exact current consumption in microamps.
  • Flash wear and exact millisecond erase/write latency.
  • Multi-master physical-wire arbitration beyond documented host injection hooks.

When avrts intentionally approximates something, the row belongs in docs/limitations.md, not in someone's memory.