diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..a0957848c05bf8793a654665c3cfc2fe21ddd0cc
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,10 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+ runner = "probe-run --chip RP2040"
+#runner = "elf2uf2-rs -d"
+
+
+[build]
+target = "thumbv6m-none-eabi"        # Cortex-M0 and Cortex-M0+
+
+[env]
+DEFMT_LOG = "trace"
diff --git a/Cargo.toml b/Cargo.toml
index 25be236c6780f16ad0b05a79d017d8cac92277a9..3ea912a947d2d15b1e079f11a097ea56f7683a16 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,25 +5,34 @@ edition = "2021"
 
 [dependencies]
 
-cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
+cortex-m = "0.7"
+embassy-rp = { version = "0.1.0",  features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] }
+embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] }
+defmt = { version = "0.3", optional = true }
 
-getrandom = { version = "0.2", default-features = false, features = ["custom"]}
+getrandom = { version = "0.2", default-features = false }
 critical-section = "1.1"
 sha2 = { version = "0.10", default-features = false }
-rand_chacha = "0.3"
-
-log = { version = "0.4", optional = true }
-
-embassy-rp = { version = "0.1.0",  features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] }
+rand_chacha = { version = "0.3", default-features = false }
 
-# TODO: make a feature
-defmt = "0.3"
-defmt-rtt = "0.3"
+log = { version = "0.4", default-features = false }
 
 [dev-dependencies]
+cortex-m-rt = "0.7.0"
+cortex-m = { version = "0.7", features = ["critical-section-single-core"]}
+embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] }
+panic-probe = { version = "0.3", features = ["print-defmt"] }
+defmt-rtt = { version = "0.3" }
+getrandom = { version = "0.2", default-features = false, features = ["custom"]}
 
 [features]
-default = [ "log" ]
+defmt = [ "dep:defmt" ]
+
+[profile.release]
+debug = 2
 
 [patch.crates-io]
+# embassy isn't released to crates.io yet
 embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" }
+embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" }
+embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "1d6f5493e7764767eb592e0b90d6b07d46b100ca" }
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..afca69b21b81ba4b6e397b5b7e087b6c39d89574
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,36 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+    // Put `memory.x` in our output directory and ensure it's
+    // on the linker search path.
+    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    File::create(out.join("memory.x"))
+        .unwrap()
+        .write_all(include_bytes!("memory.x"))
+        .unwrap();
+    println!("cargo:rustc-link-search={}", out.display());
+
+    // By default, Cargo will re-run a build script whenever
+    // any file in the project changes. By specifying `memory.x`
+    // here, we ensure the build script is only re-run when
+    // `memory.x` is changed.
+    println!("cargo:rerun-if-changed=memory.x");
+
+    println!("cargo:rustc-link-arg-examples=--nmagic");
+    println!("cargo:rustc-link-arg-examples=-Tlink.x");
+    println!("cargo:rustc-link-arg-examples=-Tlink-rp.x");
+    println!("cargo:rustc-link-arg-examples=-Tdefmt.x");
+}
diff --git a/examples/rand.rs b/examples/rand.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f5698a48877c5547131664dc6417587e4e0f089f
--- /dev/null
+++ b/examples/rand.rs
@@ -0,0 +1,40 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+#[cfg(not(feature = "defmt"))]
+use log::{debug, info, warn, error};
+
+#[cfg(feature = "defmt")]
+use defmt::{debug, info, warn, panic, error};
+#[cfg(feature = "defmt")]
+use {defmt_rtt as _, panic_probe as _};
+
+use embassy_rp::gpio::Flex;
+use embassy_executor::Spawner;
+use embassy_time::{Timer, Duration};
+
+use getrandom::register_custom_getrandom;
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = embassy_rp::init(Default::default());
+    let mut cp = cortex_m::peripheral::Peripherals::take().unwrap();
+
+    let mut gpio = (Flex::new(p.PIN_10), 10);
+
+    caprand::setup(&mut gpio.0, gpio.1, &mut cp.SYST).unwrap();
+
+    register_custom_getrandom!(caprand::random);
+
+    loop {
+        let mut mystery = [0u8; 10];
+        getrandom::getrandom(mystery.as_mut_slice()).unwrap();
+
+        info!("mystery bytes!");
+        for m in mystery.iter() {
+            info!("{:x}", *m);
+        }
+        Timer::after(Duration::from_millis(333)).await;
+    }
+}
diff --git a/examples/raw.rs b/examples/raw.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7478e538e1958237da06467992928cc302a124cd
--- /dev/null
+++ b/examples/raw.rs
@@ -0,0 +1,30 @@
+//! Prints raw samples from the capacitor random number generator.
+//! These would not usually be used directly, instead use `CapRng`.
+//! Raw samples are useful to analyse the entropy.
+
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+#[cfg(not(feature = "defmt"))]
+use log::{debug, info, warn, error};
+
+#[cfg(feature = "defmt")]
+use defmt::{debug, info, warn, panic, error};
+#[cfg(feature = "defmt")]
+use {defmt_rtt as _, panic_probe as _};
+
+use embassy_rp::gpio::Flex;
+use embassy_executor::Spawner;
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = embassy_rp::init(Default::default());
+    let mut cp = cortex_m::peripheral::Peripherals::take().unwrap();
+
+    let mut gpio = (Flex::new(p.PIN_10), 10);
+    loop {
+        caprand::cap_rand(&mut gpio.0, gpio.1, &mut cp.SYST, 10_000,
+            |v| info!("{}", v)).unwrap();
+    }
+}
diff --git a/memory.x b/memory.x
new file mode 100644
index 0000000000000000000000000000000000000000..aba861aae94a050c6f3c12b401161ff21f37af0c
--- /dev/null
+++ b/memory.x
@@ -0,0 +1,5 @@
+MEMORY {
+    BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
+    FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
+    RAM   : ORIGIN = 0x20000000, LENGTH = 256K
+}
\ No newline at end of file
diff --git a/src/cap.rs b/src/cap.rs
index 88fd0aaa679032a7613567bb710ca4b89b060f60..4976de20fe1e78c85a14c96b1f9b80a032c2b479 100644
--- a/src/cap.rs
+++ b/src/cap.rs
@@ -1,4 +1,8 @@
-use defmt::{debug, info, panic};
+#[cfg(not(feature = "defmt"))]
+use log::{debug, info, warn, error};
+
+#[cfg(feature = "defmt")]
+use defmt::{error, debug, info, panic};
 
 use cortex_m::peripheral::SYST;
 use embassy_rp::gpio::{Flex, Pin, Pull};
@@ -30,8 +34,8 @@ use embassy_rp::pac;
 ///  The random output is t_down and t_up time.
 
 /*
-Experimental values:
-0.1uF "monolithic" perhaps from futurlec?
+Experimental capacitor values:
+0.1uF "monolithic" through hole, perhaps from futurlec?
 GPIO6
 0.003717 INFO  initial pulldown del is 346730
 0.003710 INFO  initial pulldown del is 345770
@@ -49,8 +53,6 @@ GPIO13
 0.002547 INFO  initial pulldown del is 200170
 */
 
-/// Extra iterations prior to taking output, so that `overshoot` is nice and noisy.
-const WARMUP: usize = 8;
 
 // Range of cycle counts for overshoot. These values are somewhat dependent on the
 // cpu frequency and capacitor values.
@@ -61,12 +63,22 @@ const LOW_OVER: u32 = 1000;
 // Power of two for faster modulo
 const HIGH_OVER: u32 = LOW_OVER + 16384;
 
-struct Timer<'t> {
+// Assume worst case from rp2040 datasheet.
+// 3.3v vdd, 2v logical high voltage, 50kohm pullup, 0.01uF capacitor, 125Mhz clock.
+// 0.5 * 50e3 * 0.01e-6 * 125e6 = 31250.0
+// Then allow 50% leeway for tolerances.
+const MIN_CAPACITOR_DEL: u32 = 15000;
+
+/// Extra iterations prior to taking output, so that `overshoot` is nice and noisy.
+const WARMUP: usize = 16;
+
+/// Wraps timing with SYST. The clock source must already be configured.
+struct SyTi<'t> {
     syst: &'t mut SYST,
     t1: u32,
 }
 
-impl<'t> Timer<'t> {
+impl<'t> SyTi<'t> {
     fn new(syst: &'t mut SYST) -> Self {
         syst.clear_current();
         syst.enable_counter();
@@ -80,6 +92,7 @@ impl<'t> Timer<'t> {
     fn done(self) -> Result<u32, ()> {
         let t2 = SYST::get_current();
         if self.syst.has_wrapped() {
+            error!("SYST wrapped");
             return Err(());
         }
         self.syst.disable_counter();
@@ -99,7 +112,7 @@ where
     F: FnMut(u32),
 {
     syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core);
-    // prescribed sequence
+    // prescribed sequence for setup
     syst.set_reload(10_000_000 - 1);
     syst.clear_current();
     syst.enable_counter();
@@ -108,8 +121,8 @@ where
     // XXX Somehow disabling ROSC breaks SWD, perhaps signal integrity issues?
     // unsafe{ pac::ROSC.ctrl().modify(|s| s.set_enable(pac::rosc::vals::Enable::DISABLE)) };
 
-    // Not necessary, but output has a cleaner more understandable linear response
-    // with Schmitt Trigger disabled.
+    // Disabling the Schmitt Trigger gives a clearer correlation between "overshoot"
+    // and measured values.
     unsafe {
         pac::PADS_BANK0
             .gpio(pin_num)
@@ -122,35 +135,48 @@ where
     // Long enough to drive the capacitor high
     cortex_m::asm::delay(10000);
     pin.set_as_input();
-    pin.set_pull(Pull::Down);
-    syst.clear_current();
-    let t = Timer::new(syst);
-    // Get it near the threshold to begin.
-    while pin.is_high() {}
-    let del = t.done()?;
-    info!("initial pulldown del is {}", del);
+    let del = critical_section::with(|_cs| {
+        pin.set_pull(Pull::Down);
+        let t = SyTi::new(syst);
+        // Get it near the threshold to begin.
+        while pin.is_high() {}
+        t.done()
+    })?;
+    info!("Initial pulldown del is {}", del);
+
+    if del < MIN_CAPACITOR_DEL {
+        error!("Capacitor seems small or missing?");
+        return Err(())
+    }
 
     // The main loop
     let mut overshoot = 1u32;
+
+    // After warmup we sample twice at each "overshoot" value.
+    // One sample is returned as random output, the other is mixed
+    // in to the overshoot value.
     let n_iter = WARMUP + 2 * n_out;
+
     for i in 0..n_iter {
         // Pull up until hit logical high
-        pin.set_pull(Pull::Up);
-        while pin.is_low() {}
-        // Keep pulling up for `overshoot` cycles
-        cortex_m::asm::delay(overshoot);
-
-        // Pull down, time how long to reach threshold
-        pin.set_pull(Pull::Down);
-        let t = Timer::new(syst);
-        while pin.is_high() {}
-        let meas = t.done()?;
-
-        if i > WARMUP && (i - WARMUP) % 2 == 1 {
-            // produce output, don't use internally
+        let meas = critical_section::with(|_cs| {
+            pin.set_pull(Pull::Up);
+            while pin.is_low() {}
+            // Keep pulling up for `overshoot` cycles
+            cortex_m::asm::delay(overshoot);
+
+            // Pull down, time how long to reach threshold
+            pin.set_pull(Pull::Down);
+            let t = SyTi::new(syst);
+            while pin.is_high() {}
+            t.done()
+        })?;
+
+        if i > WARMUP && (i - WARMUP) % 2 == 0 {
+            // real output
             f(meas)
         } else {
-            // don't produce output, mix measured in
+            // don't produce output, mix measured sample in
             overshoot = overshoot * 2 + meas;
             // modulo to sensible range
             if overshoot > HIGH_OVER {
diff --git a/src/lib.rs b/src/lib.rs
index 526656999122d012609921e29cd215a17b27f039..44e5bfc7f5d7efdedabb20fa49c973537d82d5f8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
+#![no_std]
+
 #[cfg(feature="log")]
 use log::error;
 
diff --git a/src/rng.rs b/src/rng.rs
index ed8f9fc64e7ee21c9cea1946d351ca1017418e1c..02bab827815e971bc4e5bd5297a46323bb954eda 100644
--- a/src/rng.rs
+++ b/src/rng.rs
@@ -1,7 +1,8 @@
-#[cfg(feature = "log")]
-use log::error;
+#[cfg(not(feature = "defmt"))]
+use log::{debug, info, warn, error};
 
-use defmt::{debug, info, warn, panic};
+#[cfg(feature = "defmt")]
+use defmt::{debug, info, warn, panic, error};
 
 use core::cell::RefCell;
 use core::num::NonZeroU32;
@@ -62,8 +63,9 @@ pub fn setup<P: Pin>(
 // TODO: have some kind of fast erasure RNG instead?
 struct CapRng(ChaCha20Rng);
 
-
 impl CapRng {
+    const SEED_SAMPLES: usize = 1024;
+
     /// Call this at early startup. If noisy interrupts or time slicing is happening the caller
     /// should disable interrupts.
     /// `syst` will be modified.
@@ -73,7 +75,7 @@ impl CapRng {
         syst: &mut SYST,
     ) -> Result<Self, getrandom::Error> {
         let mut h = Sha256::new();
-        crate::cap::jumble(pin, pin_num, syst, 256 * 4, |v| h.update(v.to_be_bytes())).map_err(
+        crate::cap::cap_rand(pin, pin_num, syst, Self::SEED_SAMPLES, |v| h.update(v.to_be_bytes())).map_err(
             |_| {
                 warn!("Random generation failed");
                 error()