Avalanche Noise Amp: Sampling

The relatively low bandwidth of the amplified noise means two successive samples (measured in Arduino time) will be highly correlated. Rather than putz around with variable delays between the samples, I stuffed the noise directly into the Arduino’s MISO pin and collected four bytes of data while displaying a single row:

unsigned long UpdateLEDs(byte i) {
unsigned long NoiseData = 0ul;
	NoiseData |= (unsigned long) SendRecSPI(~LEDs[i].ColB);				// correct for low-active outputs
	NoiseData |= ((unsigned long) SendRecSPI(~LEDs[i].ColG)) << 8;
	NoiseData |= ((unsigned long) SendRecSPI(~LEDs[i].ColR)) << 16;
	NoiseData |= ((unsigned long) SendRecSPI(~LEDs[i].Row))  << 24;

	analogWrite(PIN_DIMMING,LEDS_OFF);			// turn off LED to quench current
	PulsePin(PIN_LATCH);						// make new shift reg contents visible

	return NoiseData;

The bit timing looks like this:

SPI Sample - noise data - 01
SPI Sample – noise data – 01

The vertical cursors mark the LSB position in the first and last bytes of the SPI clock. The horizontal cursors mark the minimum VIH and maximum VIL, so the sampled noise should produce 0 and 1 bits at the vertical cursors. Note that there’s no shift register on the input: MISO just samples the noise signal at each rising clock edge.

I picked those two bit positions because they produce more-or-less equally spaced samples during successive rows; you can obviously tune the second bit position for best picture as you see fit.

Given a pair of sequential samples, a von Neumann extractor whitens the noise and returns at most one random bit:

#define VNMASK_A 0x00000001
#define VNMASK_B 0x01000000

enum sample_t {VN_00,VN_01,VN_10,VN_11};

typedef struct {
	byte BitCount;					// number of bits accumulated so far
	unsigned Bits;					// random bits filled from low order upward
	int Bias;						// tallies 00 and 11 sequences to measure analog offset
   unsigned SampleCount[4];	// number of samples in each bin
} random_t;

random_t RandomData;

... snippage ...

byte ExtractRandomBit(unsigned long RawSample) {
byte RetVal;

	switch (RawSample & (VNMASK_A | VNMASK_B)) {
	case 0:							// 00 - discard
		RetVal = VN_00;
	case VNMASK_A:					// 10 - true
		RetVal = VN_10;
		RandomData.Bits = (RandomData.Bits << 1) | 1;
	case VNMASK_B:					// 01 - false
		RetVal = VN_01;
		RandomData.Bits  = RandomData.Bits << 1;
	case (VNMASK_A | VNMASK_B):		// 11 - discard
		RetVal = VN_11;
	RandomData.Bias = constrain(RandomData.Bias,-9999,9999);
	RandomData.SampleCount[RetVal] = constrain(RandomData.SampleCount[RetVal],0,63999);

	return RetVal;

The counters at the bottom track some useful statistics.

You could certainly use something more complex, along the lines of a hash function or CRC equation, to whiten the noise, although that would eat more bits every time.

The main loop blips a pin to show when the extractor discards a pair of identical bits, as in this 11 sequence:

SPI Sample - noise flag - 11
SPI Sample – noise flag – 11

That should happen about half the time, which it pretty much does:

SPI Sample - noise flag - 1 refresh
SPI Sample – noise flag – 1 refresh

The cursors mark the same voltages and times; note the slower sweep. The middle trace blips at the start of the Row 0 refresh.

On the average, the main loop collects half a random bit during each row refresh and four random bits during each complete array refresh. Eventually, RandomData.Bits will contain nine random bits, whereupon the main loop updates the color of a single LED, which, as before, won’t change 1/8 of the time.

The Arduino trundles around the main loop every 330 µs and refreshes the entire display every 2.6 ms = 375 Hz. Collecting nine random bits requires 9 x 330 µs = 3 ms, so the array changes slightly less often than every refresh. It won’t completely change every 64 x 8/7 x 3 ms = 220 ms, because dupes, but it’s way sparkly.

In the spirit of “Video or it didn’t happen!”, there’s a low-budget Youtube video about that…

5 thoughts on “Avalanche Noise Amp: Sampling

  1. I’m amused you’re bothering to correct for active-low outputs when operating on random data.

    1. It was easier than standing on my head while building the circuitry: setting the bit makes the light go ON!

      Aaand then I get to mention it in passing, just like I knew what I was doing. [grin]

  2. I have some concerns about this approach, Ed, if the ultimate writeup is targeted at novices.

    Two big lessons for those readers should be, IMHO:

    (1) feeding asynchronous data to a sampled input will violate the setup and hold times sooner or later, and when that happens you have the possibility of metastability weirdness where the part will not act sanely; and

    (2) feeding a CMOS input with a signal in the no-man’s land between Vih and Vil may put the input into a linear conduction mode that can cause latch up or other unwanted effects.

    So feeding the MISO input straight from the op-amp’s output is not quite kosher, it seems to me. Sure, you can argue that the whole purpose is to get random data, so violating normal operating conditions may not matter in this case. But the pedant in me says that rule-breaking should be a privilege earned after a novice has mastered the basics.

    OK, I am getting off my soapbox now :-)

    Did you look at having the ADC convert the raw noise input from Q602’s collector (after level shifting, of course)? Seems like that would get you into the digital domain pretty cleanly, and you could do all sorts of post-processing to whiten and limit the data.

    1. I have some concerns about this approach

      You and me both! I’m not happy with the way the column turned out for other reasons, but I did insert plenty of warnings to RTFM, albeit not in those words.

      All the ATmega inputs have synchronizers that eliminate (well, reduce, because metastable!) the chance of metastability, so they’re more rugged than a bare CMOS input. The I-vs-V graphs for the internal pullup resistor show nice smooth lines across the entire voltage range, which suggests they’ve up-armored the inputs against linear foibles / latchup, too. That probably comes from being a microcontroller; I doubt the same specs apply to, mmmmm, Raspberry Pi inputs, but I’d like to be proven wrong on that.

      I’ve seen some RNGs that feed raw avalanche noise directly to an ADC, then threshold the digital data after sampling. The ATmega doc reminds us that aliasing will occur when the input signal has frequency components over the ADC’s sample-and-hold clock (not just the sampling rate). That clock tops out around 200 kHz (I can’t find where the Arduino runtime sets the control register), which suggests that broadband noise might produce weird effects; I have no idea what aliased OOB noise might look like when you’re digitizing the noise itself.

      I’d prefer an external comparator (or the internal one), with the threshold set by a DAC to make the noise come out right. That seems like a worthwhile thing to discuss, now, doesn’t it? [grin]

Comments are closed.