diff --git a/Cargo.toml b/Cargo.toml
index 816036ea181eecd8de9628a1de38c3652c56a427..25be236c6780f16ad0b05a79d017d8cac92277a9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,12 @@ log = { version = "0.4", optional = true }
 
 embassy-rp = { version = "0.1.0",  features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] }
 
+# TODO: make a feature
+defmt = "0.3"
+defmt-rtt = "0.3"
+
+[dev-dependencies]
+
 [features]
 default = [ "log" ]
 
diff --git a/src/cap.rs b/src/cap.rs
new file mode 100644
index 0000000000000000000000000000000000000000..88fd0aaa679032a7613567bb710ca4b89b060f60
--- /dev/null
+++ b/src/cap.rs
@@ -0,0 +1,162 @@
+use defmt::{debug, info, panic};
+
+use cortex_m::peripheral::SYST;
+use embassy_rp::gpio::{Flex, Pin, Pull};
+use embassy_rp::pac;
+
+///                                         _ still pullup, =up_x
+///         t3      t4       t1      t2    /
+/// |prev    |pulldn |still  | pullup|    /
+/// |iter    |active |pulldn,| active|   / | pulldn active, etc
+/// |        |=t_down|=down_x| =t_up |  o  | measuring next t_down, etc...
+/// |        .       |       |       |     |
+///        .   .     |       |       |     .
+///      .       .   |       |       |   .   .
+///    .           . |       |       | .       .
+///  .---------------.---------------.-----------.------ GPIO threshold. Probably isn't really flat.
+///                    .           .               .
+///                      .       .
+///                        .   .
+///                          .
+///  The GPIO pin is attached to a capacitor to ground. 0.1uF used for experiment.
+///  The GPIO pin is left as input and is driven low/high with pulldown/pullup
+///  resistors. The time taken to reach logical low/high is measured. The pulldown/pullup
+///  resistor is left on for a further time down_x/up_x, which intends to drive
+///  the voltage further so that a useful time can be measured for the next t_up/t_down
+///  cycle.
+///  The down_x/up_x overshoot cycle times are doubled each iteration and add in the measured time,
+///  amplifying noise captured in t_down/t_up measurements.
+///  down_x/up_x time is kept within a sensible range with modulo.
+///  The random output is t_down and t_up time.
+
+/*
+Experimental values:
+0.1uF "monolithic" perhaps from futurlec?
+GPIO6
+0.003717 INFO  initial pulldown del is 346730
+0.003710 INFO  initial pulldown del is 345770
+less than ~13000 overshoot had lots of no-delays
+
+Altronics R8617 • 0.01uF 50V Y5V 0805 SMD Chip Capacitor PK 10
+GPIO10
+0.001292 INFO  initial pulldown del is 43234
+0.001294 INFO  initial pulldown del is 43598
+
+
+Altronics R8629 • 0.047uF 50V X7R 0805 SMD Chip Capacitor PK 10
+GPIO13
+0.002497 INFO  initial pulldown del is 193878
+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.
+
+// Lower limit is necessary because we don't want to hit the threshold immediately
+// on reading.
+const LOW_OVER: u32 = 1000;
+// Power of two for faster modulo
+const HIGH_OVER: u32 = LOW_OVER + 16384;
+
+struct Timer<'t> {
+    syst: &'t mut SYST,
+    t1: u32,
+}
+
+impl<'t> Timer<'t> {
+    fn new(syst: &'t mut SYST) -> Self {
+        syst.clear_current();
+        syst.enable_counter();
+        Self {
+            syst,
+            t1: SYST::get_current(),
+        }
+    }
+
+    /// returns the duration, or failure on overflow
+    fn done(self) -> Result<u32, ()> {
+        let t2 = SYST::get_current();
+        if self.syst.has_wrapped() {
+            return Err(());
+        }
+        self.syst.disable_counter();
+        Ok(self.t1 - t2)
+    }
+}
+
+// `f()` is called on each output `u32`.
+pub fn cap_rand<'d, P: Pin, F>(
+    pin: &mut Flex<'d, P>,
+    pin_num: usize,
+    syst: &mut SYST,
+    n_out: usize,
+    mut f: F,
+) -> Result<(), ()>
+where
+    F: FnMut(u32),
+{
+    syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core);
+    // prescribed sequence
+    syst.set_reload(10_000_000 - 1);
+    syst.clear_current();
+    syst.enable_counter();
+
+    // Seemed to reduce noise (for investigating thermal noise vs other interference).
+    // 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.
+    unsafe {
+        pac::PADS_BANK0
+            .gpio(pin_num)
+            .modify(|s| s.set_schmitt(false))
+    };
+
+    // Measure pulldown time from vcc as a sanity check
+    pin.set_as_output();
+    pin.set_high();
+    // 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);
+
+    // The main loop
+    let mut overshoot = 1u32;
+    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
+            f(meas)
+        } else {
+            // don't produce output, mix measured in
+            overshoot = overshoot * 2 + meas;
+            // modulo to sensible range
+            if overshoot > HIGH_OVER {
+                overshoot = LOW_OVER + (overshoot % (HIGH_OVER - LOW_OVER + 1))
+            }
+        }
+    }
+    Ok(())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 5bf73b7b508816a029c075f24cfb0bb186f80537..526656999122d012609921e29cd215a17b27f039 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,127 +1,9 @@
-use core::num::NonZeroU32;
-use core::cell::RefCell;
-use core::ops::DerefMut;
-
 #[cfg(feature="log")]
 use log::error;
 
-use embassy_rp::gpio::{Flex, Pull, Pin};
-use cortex_m::peripheral::SYST;
-
-use critical_section::Mutex;
-use sha2::{Sha256, Digest};
-use rand_chacha::ChaCha20Rng;
-
-use rand_chacha::rand_core::{RngCore, SeedableRng};
-
-const CHARGE_CYCLES: u32 = 600;
-// 1 bit per iteration is a rough assumption, give a factor of 4 leeway
-const NUM_LOOPS: usize = 1024;
-const MIN_DEL: u32 = 300_000;
-// Assuming at least a 125mhz clock. A slower clock could possibly be used, though
-// we are assuming that the clock is fast enough to pick up variations due to
-// thermal noise.
-// Other constants are also assuming a 125mhz clock.
-const MIN_10MS_CYCLES: u32 = 1_250_000;
-
-// arbitrary constant
-const CAPRAND_ERR: u32 = getrandom::Error::CUSTOM_START + 510132368;
-
-pub fn error() -> getrandom::Error {
-    // OK unwrap: const value is nonzero
-    let c: NonZeroU32 = CAPRAND_ERR.try_into().unwrap();
-    c.into()
-}
-
-
-static RNG: Mutex<RefCell<Option<CapRng>>> = Mutex::new(RefCell::new(None));
-
-pub fn random(buf: &mut [u8]) -> Result<(), getrandom::Error> {
-    critical_section::with(|cs| {
-        let mut rng = RNG.borrow_ref_mut(cs);
-        let rng = rng.deref_mut();
-        if let Some(rng) = rng {
-            rng.0.fill_bytes(buf);
-            Ok(())
-        } else {
-            error!("setup() not called");
-            Err(error())
-        }
-    })
-}
-
-pub(crate) fn setup<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<(), getrandom::Error> {
-    let r = CapRng::new(pin, syst)?;
-
-    critical_section::with(|cs| {
-        let mut rng = RNG.borrow_ref_mut(cs);
-        rng.insert(r);
-    });
-    Ok(())
-}
-
-// TODO: this is another impl of chacha20, can it use chacha20 crate instead? Is the size much?
-// TODO: have some kind of fast erasure RNG instead?
-struct CapRng(ChaCha20Rng);
-
-impl CapRng {
-    /// Disables interrupts, will block. Call this at startup before any time-sensitive code
-    /// `syst` will be modified.
-    fn new<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<Self, getrandom::Error> {
-        let mut h = Sha256::new();
-        for _ in 0..NUM_LOOPS {
-            let del = Self::sample(pin, syst)?;
-            h.update(del.to_be_bytes());
-        }
-        let seed: [u8; 32] = h.finalize().into();
-        Ok(Self(ChaCha20Rng::from_seed(seed)))
-    }
-
-    /// Returns individual samples from the capacitor discharge. Not normally used,
-    /// can be used to analyse the random number generation.
-    pub fn sample<P: Pin>(pin: &mut Flex<P>, syst: &mut SYST) -> Result<u32, getrandom::Error> {
-        syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core);
-        if SYST::get_ticks_per_10ms() != MIN_10MS_CYCLES {
-            error!("System clock not 125mhz");
-            return Err(error())
-        }
-
-        // This sequence is necessary
-        syst.set_reload(0x00ffffff);
-        syst.clear_current();
-        syst.enable_counter();
-
-        pin.set_pull(Pull::Down);
-        pin.set_as_output();
-        pin.set_high();
-        // allow capacitor to charge
-        cortex_m::asm::delay(CHARGE_CYCLES);
-
-        let (t1, t2, wr) = critical_section::with(|_cs| {
-            syst.clear_current();
-            let t1 = SYST::get_current();
-            // allow to drain through pull down
-            pin.set_as_input();
-            while pin.is_high() {}
-
-            let t2 = SYST::get_current();
-            let wr = syst.has_wrapped();
-            (t1, t2, wr)
-        });
-        // Don't leave enabled after completion.
-        syst.enable_counter();
+mod rng;
+mod cap;
 
-        if wr {
-            error!("Timer wrapper, capacitor too large?");
-            return Err(error())
-        }
+pub use rng::{setup, random};
+pub use cap::cap_rand;
 
-        let del = t1 - t2;
-        if del > MIN_DEL {
-            Ok(del)
-        } else {
-            error!("Capacitor seems too small?");
-            Err(error())
-        }
-    }
-}
diff --git a/src/rng.rs b/src/rng.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ed8f9fc64e7ee21c9cea1946d351ca1017418e1c
--- /dev/null
+++ b/src/rng.rs
@@ -0,0 +1,85 @@
+#[cfg(feature = "log")]
+use log::error;
+
+use defmt::{debug, info, warn, panic};
+
+use core::cell::RefCell;
+use core::num::NonZeroU32;
+use core::ops::DerefMut;
+
+use critical_section::Mutex;
+use rand_chacha::ChaCha20Rng;
+use sha2::{Digest, Sha256};
+
+use cortex_m::peripheral::SYST;
+use embassy_rp::gpio::{Flex, Pin};
+
+use rand_chacha::rand_core::{RngCore, SeedableRng};
+
+// arbitrary constant
+pub const CAPRAND_ERR: u32 = getrandom::Error::CUSTOM_START + 510132368;
+
+pub fn error() -> getrandom::Error {
+    // OK unwrap: const value is nonzero
+    let c: NonZeroU32 = CAPRAND_ERR.try_into().unwrap();
+    c.into()
+}
+
+static RNG: Mutex<RefCell<Option<CapRng>>> = Mutex::new(RefCell::new(None));
+
+pub fn random(buf: &mut [u8]) -> Result<(), getrandom::Error> {
+    critical_section::with(|cs| {
+        let mut rng = RNG.borrow_ref_mut(cs);
+        let rng = rng.deref_mut();
+        if let Some(rng) = rng {
+            rng.0.fill_bytes(buf);
+            Ok(())
+        } else {
+            error!("setup() not called");
+            Err(error())
+        }
+    })
+}
+
+/// Call this at early startup. If noisy interrupts or time slicing is happening the caller
+/// should disable interrupts.
+/// `syst` will be modified.
+pub fn setup<P: Pin>(
+    pin: &mut Flex<P>,
+    pin_num: usize,
+    syst: &mut SYST,
+) -> Result<(), getrandom::Error> {
+    let r = CapRng::new(pin, pin_num, syst)?;
+
+    critical_section::with(|cs| {
+        let mut rng = RNG.borrow_ref_mut(cs);
+        let _ = rng.insert(r);
+    });
+    Ok(())
+}
+
+// TODO: this is another impl of chacha20, can it use chacha20 crate instead? Is the size much?
+// TODO: have some kind of fast erasure RNG instead?
+struct CapRng(ChaCha20Rng);
+
+
+impl CapRng {
+    /// Call this at early startup. If noisy interrupts or time slicing is happening the caller
+    /// should disable interrupts.
+    /// `syst` will be modified.
+    fn new<P: Pin>(
+        pin: &mut Flex<P>,
+        pin_num: usize,
+        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(
+            |_| {
+                warn!("Random generation failed");
+                error()
+            },
+        )?;
+        let seed: [u8; 32] = h.finalize().into();
+        Ok(Self(ChaCha20Rng::from_seed(seed)))
+    }
+}