Can putting a processor to sleep make its analog readings less scattered? Build a finite experiment around the Microchip ATtiny85-20PU: capture 32 readings while its processor runs, 32 in its dedicated conversion sleep mode, then 32 active readings again. Transfer each completed block afterward. The result is an active–quiet–active comparison with a checkable data path, rather than a stream of numbers mixed with communication activity.

This is an unbuilt bench design with host-tested data handling. The target firmware has not been compiled, flashed or executed, and no electrical readings have been taken. The plots below are constructed examples. A narrower histogram is a repeatability observation, not a voltage calibration or proof of better absolute accuracy.

What the experiment separates

An analog-to-digital converter (ADC) turns an input voltage into an integer code. Its central processing unit (CPU) executes instructions; switching inside that processor can couple into a sensitive conversion. The ATtiny85 has an ADC Noise Reduction sleep mode that stops selected digital clocks while allowing a conversion to finish. This lesson compares that mode with a busy-waiting CPU using the same input, reference, converter clock, block length and watchdog configuration.

A histogram counts how often each code occurs. We retain the raw 32 values, their mean, minimum, maximum and sample standard deviation, a measure of spread. The second active block helps reveal drift: if the two active blocks differ substantially, treating the middle block as a clean measure of the sleep-mode effect would be premature. Thirty-two samples are a small demonstration, not a statistical qualification.

The Pico collector shares the power rail and ground. Its processor and Universal Serial Bus (USB) interface remain active, so this is not a completely quiet analog system. Communication wires remain static during each capture. That removes one avoidable disturbance without claiming that every other disturbance has disappeared.

Exact hardware and parts

Use the non-automotive Microchip ATtiny85-20PU, the eight-pin plastic dual in-line package (PDIP). This is the normal 2.7–5.5 V device, not an assumption that every ATtiny85 suffix works at 1.8 V. At the chosen 3.3 V, its nominal 1 MHz clock is within the documented operating range. The 20 MHz suffix does not authorize running at 20 MHz on 3.3 V. The other board is the original Raspberry Pi Pico with the RP2040 controller, running official MicroPython 1.26.0 for that board.

PartValue and purpose
U1ATtiny85-20PU, PDIP-8; conversion and capture control
CollectorOriginal Pico; USB power, post-capture clock and data collection
R1, R210 kΩ each, 1%, at least 0.125 W; fixed half-supply divider
R310 kΩ, 1%; pulls RESET high when SW1 is open
R410 kΩ, 1%; gives unused data input PB0 a defined low level
R510 kΩ, 1%; pulls READY low while U1 is in reset and its output is high impedance
C110 nF ceramic, at least 10 V; fixed divider-node filtering in all three blocks
C2100 nF ceramic, at least 10 V; local supply bypass directly between U1 pins 8 and 4
SW1Normally open pushbutton; RESET to common ground

V means volt, Ω means ohm, kΩ means a thousand ohms, nF means nanofarad, W means watt, Hz means cycles per second, kHz and MHz mean thousands and millions of cycles per second, and ms and µs mean milliseconds and microseconds. VCC is U1's positive supply; GND is the common ground return. These are low-voltage bench connections, not inputs for mains, batteries under test or an unknown external voltage.

Original Pico 3V3_OUT physical 36 supplies ATtiny85 VCC pin 8; both grounds join. A 10 kΩ divider with 10 nF feeds ADC2 pin 3. DO pin 6 connects to Pico GP16, USCK pin 7 to GP18, and READY pin 2 to GP17 with a 10 kΩ pull-down. RESET has a 10 kΩ pull-up and grounded button.
Original functional schematic with exact package and board pins. Each ground symbol is the same electrical net. Open full-size drawing · Editable Scalable Vector Graphics (SVG) original.

Trace the wires before applying power

Power the Pico only through its USB connector. Its 3V3_OUT, physical pin 36, feeds U1 VCC pin 8, the upper ends of R1 and R3, and C2. Pico GND physical pin 23 joins U1 GND pin 4, R2, R4, R5, C1, C2 and the grounded terminal of SW1. There is one positive 3.3 V domain; do not connect another regulator output or a 5 V programmer supply to it. Short wires and a local bypass help control the experiment, but do not establish measured noise performance.

R1 runs from 3.3 V to the sense node; R2 runs from that node to ground. The node connects to U1 PB4/ADC2, physical pin 3, and to C1's upper terminal. C1's lower terminal goes to ground. R3 connects 3.3 V to RESET/PB5 pin 1; pressing SW1 grounds that pin. R4 connects PB0/data input pin 5 to ground. R5 connects READY/PB3 pin 2 to ground and ensures a reset chip cannot falsely announce that data is ready.

The universal serial interface (USI) is U1's small serial shift peripheral. We use it as a read-only Serial Peripheral Interface (SPI) slave after capture. U1 PB1/data output (DO), pin 6, connects to Pico general-purpose input/output (GPIO) GP16, physical pin 21, used as master-in slave-out (MISO). U1 PB2/USI clock (USCK), pin 7, connects to Pico GP18, physical pin 24, the serial clock (SCK). READY/PB3 pin 2 connects to GP17, physical pin 22. The collector also configures GP19, physical pin 25, as master-out slave-in (MOSI), because its software SPI object needs an output pin; GP19 has no external wire. Do not connect it to the grounded PB0 input.

This is a dedicated point-to-point link without a chip-select wire. Do not attach other slaves or use it as a general shared bus. SCK is idle low; the reader requests SPI mode 0, with sampling on the rising edge. U1 is the slave: its clock terminal is an input, even though an old USI example contains a misleading clock-direction sentence. DO is an output only on the Tiny side. All signal levels belong to the shared 3.3 V domain.

Clock, reference and programming prerequisites

Build the firmware for AVR, Microchip's eight-bit controller architecture, using an appropriate AVR-GCC compiler and avr-libc 2.3.0. This delivered C file includes both portable packet helpers and the target register adapter. A host compiler can test the helpers; it cannot validate the AVR instructions, interrupt timing or actual sleep entry.

Before programming, read and record the device's fuse settings, the persistent configuration bits that select its clock and startup behavior. The oscillator selection must be the internal nominal 8 MHz source. The program sets the clock prescaler register CLKPR to an absolute divide-by-eight setting, producing nominal 1 MHz; that is not another division multiplied by the initial CKDIV8 fuse state. Verify clock output CKOUT is disabled, because PB4 must be ADC2. The forced-on watchdog fuse WDTON must be unprogrammed. Preserve the RESET function, disable debugWIRE debugging, and select a brown-out reset setting compatible with the intended 3.3 V supply. Keep that setting identical across all blocks. Do not guess fuse-byte values.

An in-system programming (ISP) programmer uses some of the same pins as this fixture. Program with the measurement jumpers disconnected and with the programmer's documented voltage compatibility and power arrangement. Disconnect the programmer completely before installing the measurement wiring. Do not leave its clock, reset or power connections attached during the comparison.

avr-gcc -mmcu=attiny85 -Os -std=gnu11 -DF_CPU=1000000UL \
  -Wall -Wextra attiny-adc-quiet-capture.c -o quiet.elf
avr-size quiet.elf
avr-objdump -d quiet.elf > quiet-disassembly.txt
avr-objcopy -O ihex -R .eeprom quiet.elf quiet.hex

Those are build instructions, not a build result. F_CPU is the assumed CPU frequency used by timing macros, not a command that programs fuses or measures the oscillator. Before flashing, inspect the real flash/static random-access memory (SRAM) sizes and leave room for the stack. Check the generated early-reset and CLKPR timed sequences in the disassembly. The block and transmit arrays alone occupy about 143 bytes; that is not a complete memory map. Until a real AVR build and board checks pass, the target adapter remains unqualified.

The ADC uses VCC as its reference and a divide-by-eight ADC clock. Its nominal frequency is therefore 125 kHz, within the datasheet's 50–200 kHz guidance for maximum resolution. One initial active conversion after enabling the ADC is discarded. We never change reference or channel within the triplet. A different reference/channel experiment would need its own settling and discard procedure.

Divider voltage = VCC × R2 / (R1 + R2) = VCC / 2 nominal
Source resistance = R1 || R2 = 5 kΩ nominal
C1 time constant = 5 kΩ × 10 nF = 50 µs nominal
Divider current at 3.3 V = 3.3 V / 20 kΩ = 165 µA nominal
ADC clock = 1 MHz / 8 = 125 kHz nominal
Normal conversion: 13 / 125 kHz = 104 µs nominal
First conversion after enabling: 25 / 125 kHz = 200 µs nominal
Ideal ten-bit code near midpoint = 1024 × 1/2 = 512
One ideal code at 3.3 V reference = 3.3 V / 1024 ≈ 3.223 mV

mV means millivolt. The 5 kΩ source resistance is below the approximately 10 kΩ source guidance, and 50 ms settling before each block is much longer than the calculated filter time constant. Real resistor mismatch, ADC error, input loading and reference behavior remain. Because the divider and reference share VCC, a supply change can cancel in their ratio; a steady code is not proof of a steady supply voltage.

Why quiet capture does not start with ADSC

For the active block, the firmware starts a conversion with the ADC start-conversion bit ADSC, enables interrupts and waits with its CPU running. For the quiet block it enables the ADC completion interrupt, selects ADC Noise Reduction, then follows the documented interrupt-safe sleep sequence. It does not set ADSC beforehand: with the enabled converter idle in single-conversion mode, entering this sleep mode starts the conversion. Starting it first would already spend part of the conversion with an active CPU.

The completion interrupt reads the low result byte before the high byte and sets a flag. It does not toggle a test pin. A pin-change interrupt on PB2, called PCINT2, records any unwanted readout-clock activity during capture. This interrupt can wake the processor; that block is marked invalid rather than quietly described as a successful quiet block. At the closing boundary, with interrupts disabled, the firmware turns off the pin-change masks and samples the pending hardware flag as well as the serviced software flag. A late unserviced edge is not lost. Edges after that closing boundary belong to the post-capture interval.

The watchdog timer (WDT) is an independent reset guard configured identically in both modes. Its nominal one-second setting is not a calibrated one-second measurement or an ADC deadline measured by a running Timer0: that timer's clock stops in ADC Noise Reduction. The code refreshes the watchdog only when starting the next conversion or loading the next output byte. It never refreshes it inside a wait loop. A stuck conversion or absent readout clock therefore resets the chip rather than producing a partial successful block.

At boot, the early initialization hook saves the reset cause, clears the watchdog reset flag before disabling the watchdog, and only then continues startup. A watchdog reset produces a fault frame and a terminal fault state, not an automatic replacement triplet. The collector rejects the entire pending run. Restart the collector and manually begin a fresh run after resolving the cause. Watchdog oscillator tolerance, brown-out resets and physical communication faults require target experiments; the host tests cannot establish their timing.

Constructed active, ADC Noise Reduction and active sequence: each 50 ms READY-low settling interval precedes 32 conversions, and Pico SCK pulses occur only during READY-high readout. Three explicitly synthetic 32-sample histograms show code counts around 512, without a measured accuracy claim.
Original phase diagram and synthetic distributions. Phase widths are illustrative; conversion and transfer durations are nominal calculations, not measurements. The quiet histogram is an example, not an expected improvement. Open full-size drawing · Editable SVG.

A finite triplet, protected before statistics

Each block has 32 raw ten-bit samples, where a bit is a binary digit. Its magic bytes use ASCII, the American Standard Code for Information Interchange. READY stays low while settling and capturing. U1 loads the first byte into the serial output before raising READY. Only then does the Pico clock out 77 bytes. U1 returns READY low, settles, and captures the next block. The requested 2 kHz software SPI clock needs about 308 ms for 616 bits, excluding interpreter and protocol overhead. It is not a measured bus rate.

Byte offsetsMeaning
0–3 / 4ASCII magic Q85! / protocol version 1
5 / 6 / 7Mode 0, 1 or 2 / status / exact sample count 32
8–9 / 10Little-endian block sequence 0, 1 or 2 / saved reset flags
11–7432 little-endian unsigned sample values
75–76Little-endian cyclic redundancy check (CRC), a corruption-detection checksum of bytes 0–74

The CRC-16/CCITT-FALSE settings are polynomial 0x1021, initial value 0xFFFF, no bit reflection and no final exclusive-or. The check value for the ASCII bytes 123456789 is 0x29B1. The collector rejects the wrong length, magic, version, checksum, status, sample count, range, watchdog-reset flag or order. It prints statistics only after all three blocks pass. This checksum detects many accidental corruptions; it is neither authentication nor proof against every possible corruption. Sequence numbers identify order within this manually bounded session, not unique hardware runs or measurement timestamps.

The collector must observe READY low before waiting for the first high. The external pull-down makes that low defined while SW1 holds U1 in reset. Each handshake wait is bounded to three seconds using MicroPython's wrapping tick difference. Service must stay within that clock's half-period; a debugger suspension is outside this contract. Do not connect a logic analyzer that drives the clock or send exploratory pulses during capture. If you deliberately do, the whole affected triplet must be rejected.

Both complete programs

Download complete ATtiny C firmware. The portable block and packet helpers below are exactly the ones exercised by the host tests. The register, interrupt and sleep branch is delivered in full but has not been target-built.

/* ATtiny85-20PU / 1MHz / avr-libc2.3.0. Target branch NOT built here.
 * Host tests compile only shared capture/frame helpers with HOST_TEST.
 * No UART, bus clocks or READY toggles during a sample block.
 */
#include <stdint.h>
#define Q85_N 32u
#define Q85_LEN 77u
#define Q85_CLOCK_EDGE 1u
#define Q85_BAD_SAMPLE 2u
#define Q85_WATCHDOG_BOOT 128u

typedef struct {uint16_t value[Q85_N]; uint8_t count,status;} q85_block;
void q85_init(q85_block *b) {b->count=0; b->status=0;for(uint8_t i=0;i<Q85_N;i++)b->value[i]=0;}
uint8_t q85_add(q85_block *b,uint16_t value,uint8_t tainted) {
    if(tainted)b->status|=Q85_CLOCK_EDGE;
    if(value>1023u||b->count>=Q85_N){b->status|=Q85_BAD_SAMPLE;return 0;}
    b->value[b->count++]=value;return 1;
}
uint16_t q85_crc(const uint8_t *data,uint8_t size) {
    uint16_t crc=0xffffu;
    while(size--){crc^=(uint16_t)*data++<<8;for(uint8_t j=0;j<8;j++)crc=(crc&0x8000u)?(uint16_t)((crc<<1)^0x1021u):(uint16_t)(crc<<1);}
    return crc;
}
void q85_frame(uint8_t *f,const q85_block *b,uint8_t mode,uint16_t seq,uint8_t reset_flags) {
    f[0]='Q';f[1]='8';f[2]='5';f[3]='!';f[4]=1;f[5]=mode;f[6]=b->status;f[7]=b->count;
    f[8]=(uint8_t)seq;f[9]=(uint8_t)(seq>>8);f[10]=reset_flags;
    for(uint8_t i=0;i<Q85_N;i++){f[11+2*i]=(uint8_t)b->value[i];f[12+2*i]=(uint8_t)(b->value[i]>>8);}
    uint16_t crc=q85_crc(f,75);f[75]=(uint8_t)crc;f[76]=(uint8_t)(crc>>8);
}

#ifndef HOST_TEST
#ifndef F_CPU
#define F_CPU 1000000UL
#endif
#if F_CPU != 1000000UL
#error This fixture requires verified 1MHz and F_CPU=1000000UL
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <util/delay.h>
static uint8_t boot_flags __attribute__((section(".noinit")));
void early_reset(void) __attribute__((naked,section(".init3")));
void early_reset(void) {boot_flags=MCUSR;MCUSR=0;wdt_disable();}
static volatile uint8_t adc_done,clock_taint;
static volatile uint16_t adc_value;
static q85_block block;
static uint8_t frame[Q85_LEN];
ISR(ADC_vect){uint8_t low=ADCL;adc_value=((uint16_t)ADCH<<8)|low;adc_done=1;}
ISR(PCINT0_vect){clock_taint=1;}

static void setup(void) {
    cli();
    /* CKSEL must already select internal8MHz. CLKPR is an absolute /8 setting,
       not an extra divide applied on top of CKDIV8. Never write fuses here. */
    CLKPR=_BV(CLKPCE);CLKPR=_BV(CLKPS1)|_BV(CLKPS0);
    PORTB=_BV(PB1);DDRB=_BV(PB1)|_BV(PB3); /* DO idleHIGH; READY LOW; SCK input */
    TCCR0A=0;TCCR0B=0;TCCR1=0;TIMSK=0;PLLCSR=0;
    USICR=0;GIMSK=0;PCMSK=0;ACSR=_BV(ACD);
    ADMUX=_BV(MUX1); /* ADC2, VCC reference, right-adjusted */
    ADCSRB=0;DIDR0=_BV(ADC2D);
    ADCSRA=_BV(ADEN)|_BV(ADIE)|_BV(ADPS1)|_BV(ADPS0); /* /8 */
    wdt_enable(WDTO_1S); /* RESET guard; same configuration active and quiet */
}
static uint16_t one_sample(uint8_t quiet) {
    cli();adc_done=0;ADCSRA|=_BV(ADIF); /* write-one clears stale completion */
    wdt_reset(); /* real progress: next conversion, never inside wait loop */
    if(quiet){
        set_sleep_mode(SLEEP_MODE_ADC);sleep_enable();
        sei();sleep_cpu(); /* conversion starts on entering sleep, not ADSC */
        sleep_disable();
    } else {ADCSRA|=_BV(ADSC);sei();}
    /* Unexpected PCINT wake can leave conversion running: block stays tainted.
       Lost completion eventually RESETs; no partial block is accepted/exported. */
    while(!adc_done){}
    cli();return adc_value;
}
static void capture_close(void) {
    /* cli already held. Closing boundary: masks OFF, then sample the latched
       PCIF as well as serviced ISR taint; a last-edge pending ISR is not lost. */
    GIMSK=0;PCMSK=0;
    if(GIFR&_BV(PCIF))clock_taint=1;
    if(clock_taint)block.status|=Q85_CLOCK_EDGE;
}
static void capture(uint8_t quiet) {
    cli();PORTB&=(uint8_t)~_BV(PB3);USICR=0;PORTB|=_BV(PB1);
    _delay_ms(50); /* fixed LOW/settling interval, same for both modes */
    q85_init(&block);clock_taint=0;PCMSK=_BV(PCINT2);GIFR=_BV(PCIF);GIMSK=_BV(PCIE);
    if(PINB&_BV(PB2))clock_taint=1; /* capture requires idle-LOW SCK */
    for(uint8_t i=0;i<Q85_N;i++){
        uint16_t v=one_sample(quiet);
        q85_add(&block,v,clock_taint);
    }
    capture_close();
}
static void export_frame(void) {
    cli();GIMSK=0;PCMSK=0;
    /* Slave: DO output, USCK input. USI positive-edge shift, both-edge counter.
       Pico owns clock. This is a dedicated point-to-point read-only link. */
    USICR=_BV(USIWM0)|_BV(USICS1);
    for(uint8_t i=0;i<Q85_LEN;i++){
        USIDR=frame[i];USISR=_BV(USIOIF);
        wdt_reset(); /* next byte loaded; no reset while waiting for clocks */
        if(i==0)PORTB|=_BV(PB3); /* first byte ready BEFORE READY rises */
        while(!(USISR&_BV(USIOIF))){}
    }
    PORTB&=(uint8_t)~_BV(PB3);USICR=0;PORTB|=_BV(PB1);
}
int main(void) {
    setup();
    if(boot_flags&_BV(WDRF)){
        q85_init(&block);block.status=Q85_WATCHDOG_BOOT;
        q85_frame(frame,&block,255,0,boot_flags);export_frame();
        wdt_disable();for(;;){} /* terminal fault, requires manual fresh run */
    }
    /* First conversion after ADEN can take25ADCclocks; discard before triplet. */
    (void)one_sample(0);
    for(uint8_t mode=0;mode<3;mode++){
        capture(mode==1);q85_frame(frame,&block,mode,mode,boot_flags);export_frame();
    }
    cli();wdt_disable();PORTB&=(uint8_t)~_BV(PB3);
    for(;;){} /* finite acquisition complete: no automatic second triplet */
}
#endif

Download complete Pico collector. It buffers the three validated blocks, then reports each histogram and sample spread. It does not assign a fictitious measurement timestamp to data read later.

"""Original Pico / MicroPython1.26.0; read3postcapture frames, print only after.
All target adapter behavior remains unexecuted; host tests use injected buses.
"""
import math
FRAME_LEN=77

def crc16(data):
    crc=0xffff
    for x in data:
        crc^=x<<8
        for _ in range(8):crc=((crc<<1)^0x1021)&65535 if crc&0x8000 else(crc<<1)&65535
    return crc

def decode(frame):
    if len(frame)!=FRAME_LEN:raise ValueError('incomplete frame')
    if frame[:5]!=b'Q85!\x01':raise ValueError('magic/version')
    if crc16(frame[:75])!=(frame[75]|frame[76]<<8):raise ValueError('CRC')
    mode,status,count=frame[5],frame[6],frame[7]
    sequence=frame[8]|frame[9]<<8
    reset_flags=frame[10]
    if status&128 or reset_flags&8:raise ValueError('WATCHDOG_BOOT: manual fresh run required')
    if status or count!=32 or mode not in(0,1,2):raise ValueError('invalid capture block')
    values=[frame[11+2*i]|frame[12+2*i]<<8 for i in range(32)]
    if any(x>1023 for x in values):raise ValueError('ADC range')
    return {'mode':mode,'sequence':sequence,'values':values,'reset_flags':reset_flags}

def statistics(values):
    if len(values)<2 or any(type(x)is not int or not 0<=x<=1023 for x in values):raise ValueError('need at least2 valid integer ADC codes')
    n=len(values);total=sum(values);squares=sum(x*x for x in values)
    variance=(n*squares-total*total)/(n*(n-1))
    histogram={}
    for x in values:histogram[x]=histogram.get(x,0)+1
    return {'n':n,'raw_codes':list(values),'mean_code':total/n,'minimum':min(values),'maximum':max(values),
            'sample_sd_code':math.sqrt(variance),'histogram':histogram,
            'not_voltage_calibration':True}

class Triplet:
    def __init__(self):self.blocks=[];self.invalid=False
    def add(self,frame):
        if self.invalid:raise ValueError('run already invalid')
        try:
            block=decode(frame);expected=len(self.blocks)
            if expected>=3 or block['mode']!=expected or block['sequence']!=expected:raise ValueError('triplet order/session mismatch')
            self.blocks.append(block)
        except BaseException:
            self.invalid=True;self.blocks=[];raise
    def result(self):
        if self.invalid or len(self.blocks)!=3:raise ValueError('no complete valid triplet')
        return [statistics(b['values'])for b in self.blocks]

def collect(read_ready,read_frame,ticks_ms,ticks_diff,sleep_ms):
    """Require observed LOW/HIGH handshakes; no SCK before HIGH. Bounded waits.

    Adjacent ticks_diff assumes servicing within its half-period. A long host
    suspension can violate this; no arbitrary-debugger-pause guarantee.
    """
    result=Triplet()
    def wait_level(wanted):
        start=ticks_ms()
        while True:
            now=ticks_ms();age=ticks_diff(now,start)
            if type(age)is not int or age<0 or age>=3000:raise TimeoutError('READY phase timeout: restart run')
            if read_ready()==wanted:return
            sleep_ms(1)
    try:
        wait_level(0) # reader starts BEFORE operator presses Tiny RESET
        for _ in range(3):
            wait_level(1)
            raw=read_frame(FRAME_LEN) # only place that produces SCK clocks
            wait_level(0)           # falling READY observed before next frame
            result.add(raw)
        return result.result()
    except BaseException:
        result.invalid=True;result.blocks=[];raise

def main():
    from machine import Pin,SoftSPI
    from time import ticks_ms,ticks_diff,sleep_ms
    sck=Pin(18,Pin.OUT,value=0);mosi=Pin(19,Pin.OUT,value=0)
    ready=Pin(17,Pin.IN)
    spi=SoftSPI(baudrate=2000,polarity=0,phase=0,sck=sck,mosi=mosi,miso=Pin(16,Pin.IN))
    try:
        print('Reader waiting: press Tiny RESET now; no programmer connected')
        completed=collect(ready.value,lambda n:spi.read(n,0),ticks_ms,ticks_diff,sleep_ms)
    finally:
        spi.deinit();sck.init(Pin.OUT,value=0);mosi.init(Pin.OUT,value=0)
    for name,stats in zip(('ACTIVE_BEFORE','ADC_NOISE_REDUCTION','ACTIVE_AFTER'),completed):
        print(name,stats)
    return completed

Save the collector under the exact filename shown above on the Pico. From the read–evaluate–print loop (REPL), the interactive Python prompt, run these three lines once. The file does not start itself on boot. Begin with SW1 held down, launch the collector, then release SW1 after its waiting prompt.

scope = {"__name__": "quiet_capture_reader"}
exec(open("attiny-adc-quiet-capture-reader.py").read(), scope)
scope["main"]()

A reproducible bench procedure

  1. With all power removed, inspect U1 orientation and trace every connection against the pin table and schematic. Verify R1–R5 and the READY pull-down. Confirm no programmer or second positive supply is connected.
  2. Complete the actual AVR build, fuse readback, size and disassembly checks. Program separately, then remove the programmer. Check the intended clock and operating supply on the real fixture before treating capture timing as known.
  3. Power through Pico USB. With SW1 held, confirm U1 RESET is low and READY is low. Check the shared supply and midpoint without moving probes during capture; do not attach an external voltage to ADC2.
  4. Start the collector once from the REPL while holding SW1. Release SW1 after the waiting prompt. Wait for one complete three-block result. Keep wires, capacitor, USB connection, software and instruments unchanged across all three blocks.
  5. Copy the three raw block results and reported statistics to the blank observation worksheet. Record real clock/supply checks, component values, fuse readback, tool versions, reset flags and any rejected run. Do not fill the blank log with the constructed histogram.
  6. Repeat complete sessions, restarting the reader and manually resetting U1 each time. Compare active-before with active-after before interpreting the middle block. Retain outliers and failures rather than averaging them away.
  7. For a controlled failure test, prevent the readout clock after READY rises. The watchdog should reset U1; a subsequent watchdog fault or timeout must reject the session. For a separate contamination test, stop the normal collector and use a dedicated test stimulus on the same Pico GP18, which remains the sole SCK driver. Use an input-only analyzer to confirm that the transition falls inside the capture window. Never parallel a second push-pull clock output with GP18. The contaminated session must be rejected. These are proposed physical tests, not results already observed.

Do not infer an ADC failure merely because both modes look alike. The input filter, source ratio, reference coupling and external noise may dominate. Conversely, a narrower quiet distribution can coexist with a shifted mean or with a stable systematic error. Record the actual distribution and conditions before drawing a conclusion.

Interpret spread without inventing accuracy

n = 32; S = sum(code); Q = sum(code²)
mean = S / n
sample variance = (n × Q − S²) / (n × (n − 1))
sample standard deviation = sqrt(sample variance)

The reader uses integer sums before the final division to avoid unnecessary cancellation in this small dataset. A constant sequence of 500 has zero sample standard deviation, yet is 12 codes from the ideal midpoint 512. That is the simplest counterexample to “zero spread means accurate.” Conversion to volts requires the actual reference and an appropriate calibrated error model; multiplying the code by an assumed 3.3 V does not provide either. Quantization, where continuous inputs fall into finite code bins, also changes how small noise appears.

What was actually checked

Host checks compile the supplied portable C helpers with a desktop compiler and execute the supplied Python parser, statistics and collector with injected buses and clocks. They exercise all 1,024 constant-code round trips, all 616 single-bit frame corruptions, incomplete and reordered triplets, watchdog/status rejection, constant and extreme distributions, wrapping clocks, missing READY edges and transport failures. A separate check executes the actual closing-boundary function body with fake registers to challenge pending pin-change flags. These are software checks, not an AVR build, sleep-current measurement or electrical verification of the target interrupt path.

The reviewed manufacturer document is Microchip's linked 2586Q full datasheet. The live product/document pages were checked; that does not establish that no later advisory can exist. Its ATtiny85 errata section lists an EEPROM-read issue for revision A; this experiment does not use electrically erasable programmable read-only memory (EEPROM). Record the actual silicon revision and recheck applicable advisories before a physical build. The absence of a used feature in that erratum is not a universal “no errata” claim.

Challenge and answer

Challenge: The active blocks center on code 512, while the quiet block repeats code 500 exactly. Has the quiet mode made the voltage measurement more accurate? What should happen if one clock edge occurs just after the last conversion but before the capture masks close?

Show the answer

No accuracy conclusion follows. The quiet block has lower observed spread but a different mean; its zero standard deviation does not establish the true voltage or explain the shift. Repeat the controlled comparison and investigate reference, timing and coupling. The pending pin-change flag at the defined closing boundary must taint the block, even when its interrupt handler has not run. The collector then rejects the complete triplet instead of comparing a contaminated “quiet” result.

For the underlying ideas, use the ADC resolution and quantization experiment, the loaded-divider lesson, and the full electronics course.

Primary technical sources