Advertisements

Avalanche Noise Amp: Arduino Firmware

With the hardware in hand:

Reverse-bias noise amplifier - detail

Reverse-bias noise amplifier – detail

 

Feeding a stream of avalanche noise from a reverse-biased transistor base-emitter junction into the Arduino’s MISO pin without benefit of a shift register, this Arduino source code extracts nine bit chunks of random data to drive the 8×8 RGB LED matrix:

// Random LED Dots - from noise source
// Ed Nisley - KE4ANU - September 2015

//----------
// Pin assignments

const byte PIN_HEARTBEAT = 8;		// DO - heartbeat LED
const byte PIN_SYNC = A3;			// DO - scope sync

const byte PIN_LATCH  = 4;			// DO - shift register latch clock

const byte PIN_DIMMING = 9;			// AO - LED dimming control

// These are *hardware* SPI pins

const byte PIN_MOSI = 11;			// DO - data to shift reg
const byte PIN_MISO = 12;			// DI - data from shift reg - sampled noise input
const byte PIN_SCK  = 13;			// DO - shift clock to shift reg (also Arduino LED)
const byte PIN_SS = 10;				// DO - -slave select (must be positive for SPI output)

//----------
// Constants

#define DISPLAY_MS 10000ul

//----------
// Globals

// Input noise bits can produce one of four possible conditions
//  Use the von Neumann extractor, discarding 00 and 11 sequences
// https://en.wikipedia.org/wiki/Randomness_extractor#Von_Neumann_extractor
// Sampling interval depends on SPI data rate
// LSB arrives first, so it's the earliest sample

#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;

// LED selects are high-active bits and low-active signals: flipped in UpdateLEDs()
// *exactly* one row select must be active in each element

typedef struct {
	const byte Row;
	byte ColR;
	byte ColG;
	byte ColB;
} leds_t;

// altering the number of rows & columns will require substantial code changes...
#define NUMROWS 8
#define NUMCOLS 8

leds_t LEDs[NUMROWS] = {
	{0x80,0,0,0},
	{0x40,0,0,0},
	{0x20,0,0,0},
	{0x10,0,0,0},
	{0x08,0,0,0},
	{0x04,0,0,0},
	{0x02,0,0,0},
	{0x01,0,0,0},
};

byte RowIndex;

#define LEDS_ON 0
#define LEDS_OFF 255

unsigned long MillisNow;
unsigned long DisplayBase;

//-- Helper routine for printf()

int s_putc(char c, FILE *t) {
  Serial.write(c);
}

//-- Useful stuff

// Free RAM space monitor
//  From http://playground.arduino.cc/Code/AvailableMemory

uint8_t * heapptr, * stackptr;

void check_mem() {
  stackptr = (uint8_t *)malloc(4);          // use stackptr temporarily
  heapptr = stackptr;                     // save value of heap pointer
  free(stackptr);      // free up the memory again (sets stackptr to 0)
  stackptr =  (uint8_t *)(SP);           // save value of stack pointer
}

void TogglePin(char bitpin) {
	digitalWrite(bitpin,!digitalRead(bitpin));    // toggle the bit based on previous output
}

void PulsePin(char bitpin) {
	TogglePin(bitpin);
	TogglePin(bitpin);
}

//---------
//-- SPI utilities

void EnableSPI(void) {
	digitalWrite(PIN_SS,HIGH);					// make sure this is high!
	SPCR |= 1 << SPE;
}

void DisableSPI(void) {
	SPCR &= ~(1 << SPE);
}

void WaitSPIF(void) {
	while (! (SPSR & (1 << SPIF))) {
//		TogglePin(PIN_HEARTBEAT);
		continue;
	}
}

byte SendRecSPI(byte DataByte) {					// send one byte, get another in exchange
	SPDR = DataByte;
	WaitSPIF();
	return SPDR;									// SPIF will be cleared
}

//---------------
// Update LED shift registers with new data
// Returns noise data shifted in through MISO bit

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
	analogWrite(PIN_DIMMING,LEDS_ON);

	return NoiseData;
}

//---------------
// Extract random data from sampled noise input
//  ... tuck it into the global bit structure
// Returns von Neumann status of the sample

byte ExtractRandomBit(unsigned long RawSample) {
	
byte RetVal;

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

	return RetVal;
}

//---------------
// Set LED from random bits
// Assumes the Value contains at least nine low-order random bits
// On average, this leaves the LED unchanged for 1/8 of the calls...

void SetLED(unsigned Value) {
	
byte Row =   Value        & 0x07;
byte Col =   (Value >> 3) & 0x07;
byte Color = (Value >> 6) & 0x07;
	
byte BitMask = (0x80 >> Col);
	
//	printf("%u %u %u %u\r\n",Row,Col,Color,BitMask);

	LEDs[Row].ColR &= ~BitMask;
	LEDs[Row].ColR |= (Color & 0x04) ? BitMask : 0;
	
	LEDs[Row].ColG &= ~BitMask;
	LEDs[Row].ColG |= (Color & 0x02) ? BitMask : 0;
	
	LEDs[Row].ColB &= ~BitMask;
	LEDs[Row].ColB |= (Color & 0x01) ? BitMask : 0;
	
}

//------------------
// Set things up

void setup() {
	
	pinMode(PIN_HEARTBEAT,OUTPUT);
	digitalWrite(PIN_HEARTBEAT,HIGH);		// show we arrived
	
	pinMode(PIN_SYNC,OUTPUT);
	digitalWrite(PIN_SYNC,LOW);

	pinMode(PIN_MOSI,OUTPUT);				// SPI-as-output is not strictly necessary
	digitalWrite(PIN_MOSI,LOW);

	pinMode(PIN_SCK,OUTPUT);
	digitalWrite(PIN_SCK,LOW);

	pinMode(PIN_SS,OUTPUT);
	digitalWrite(PIN_SS,HIGH);				// OUTPUT + HIGH is required to make SPI output work
	
	pinMode(PIN_LATCH,OUTPUT);
	digitalWrite(PIN_LATCH,LOW);
	
	Serial.begin(57600);
	fdevopen(&s_putc,0);					// set up serial output for printf()

	printf("Noisy LED Dots\r\nEd Nisley - KE4ZNU - September 2015\r\n");
	
//-- Set up SPI hardware
// LSB of SPCR set bit clock speed:
//  00 = f/4
//  01 = f/16
//  10 = f/64
//  11 = f/128
	
	SPCR = B01110011;						// Auto SPI: no int, enable, LSB first, master, + edge, leading, speed
	SPSR = B00000000;						// not double data rate

	EnableSPI();							// turn on the SPI hardware
	
	SendRecSPI(0);							// set valid data in shift registers: select Row 0, all LEDs off
	
//-- Dimming pin must use fast PWM to avoid beat flicker with LED refresh rate
//   Timer 1: PWM 9 PWM 10

	analogWrite(PIN_DIMMING,LEDS_OFF);		// disable column drive (hardware pulled it low before startup)

	TCCR1A = B10000001; 					// Mode 5 = fast 8-bit PWM with TOP=FF
	TCCR1B = B00001001; 					// ... WGM, 1:1 clock scale -> 64 kHz
	
//-- lamp test: send a white flash through all LEDs
//	 collects noise data to get some randomness going

	printf("Lamp test begins: white flash each LED...");

	digitalWrite(PIN_HEARTBEAT,LOW);		// turn off while panel blinks
	
	analogWrite(PIN_DIMMING,LEDS_ON);		// enable column drive

	for (byte i=0; i<NUMROWS; i++) {
		for (byte j=0; j<NUMCOLS; j++) {
			LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0x80 >> j;
			for (byte k=0; k<NUMROWS; k++) {
				ExtractRandomBit(UpdateLEDs(k));
				delay(25);
			}
		LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0;
		}
	}
	UpdateLEDs(NUMROWS-1);					// clear the last LED
	
	printf(" done!\r\n");

//-- Preload LEDs with random values
//   We take whatever number of random bits arrived in RandomData during lamp test

	digitalWrite(PIN_HEARTBEAT,LOW);
	printf("Preloading LED array\r\nRandom bits %04x\r\n",RandomData.Bits);
	
	randomSeed(RandomData.Bits);
	
	for (byte Row=0; Row<NUMROWS; Row++) {
		for (byte Col=0; Col<NUMCOLS; Col++) {		// Col runs backwards, but we don't care
			LEDs[Row].ColR |= random(2) << Col;
			LEDs[Row].ColG |= random(2) << Col;
			LEDs[Row].ColB |= random(2) << Col;
		}
		UpdateLEDs(Row);
	}
	
	RandomData.BitCount = 0;
	RandomData.Bits = 0;
	RandomData.Bias = 0;
   for (byte i=0; i<4; i++) {
	   RandomData.SampleCount[i] = 0;
   }
	
	check_mem();
	printf("SP: %u HP: %u Free RAM: %u\r\n",stackptr,heapptr,stackptr - heapptr);

	printf("Running...\r\n");
	
	DisplayBase = millis();

}

//------------------
// Run the test loop

void loop() {
	
byte ThisBit;
	
	MillisNow = millis();
	
	if (RowIndex >= NUMROWS) {							// set up LED row index for this pass
		RowIndex = 0;
		PulsePin(PIN_SYNC);
	}
	
	if ((MillisNow - DisplayBase) >= DISPLAY_MS) {
		analogWrite(PIN_DIMMING,LEDS_OFF);				// turn off LED to prevent bright glitch
		printf("Bias: %5d of %5u - %5u %5u %5u %5u\r\n",
         RandomData.Bias,
         RandomData.SampleCount[VN_00] + RandomData.SampleCount[VN_11],
         RandomData.SampleCount[0],
         RandomData.SampleCount[1],
         RandomData.SampleCount[2],
         RandomData.SampleCount[3]
         );
		RandomData.Bias = 0;
      for (byte i=0; i<4; i++) {
         RandomData.SampleCount[i] = 0;
      }
 //		check_mem();
//		printf("SP: %u HP: %u Free RAM: %u\r\n",stackptr,heapptr,stackptr - heapptr);
		DisplayBase = MillisNow;
	}
		
// Update one LED row per pass, get at most one random bit

	ThisBit = ExtractRandomBit(UpdateLEDs(RowIndex++));
	
// Update the heartbeat LED to show bit validity

	switch (ThisBit) {
		case VN_00:
		case VN_11:
			digitalWrite(PIN_HEARTBEAT,HIGH);
			break;
		case VN_01:
		case VN_10:
			digitalWrite(PIN_HEARTBEAT,LOW);
			break;
	}
	
// If we have enough random data, twiddle one LED
	
	if (RandomData.BitCount >= 9) {
//		analogWrite(PIN_DIMMING,LEDS_OFF);				// turn off LED array to prevent bright glitch
		
		SetLED(RandomData.Bits);
		RandomData.BitCount = 0;
		RandomData.Bits = 0;
	}

	digitalWrite(PIN_HEARTBEAT,LOW);

}

Now I can point folks at the whole thing…

Advertisements

  1. #1 by pbsweb on 2015-10-07 - 09:47

    The best companion to this, that I have found is:
    Random Sequence Generator based on Avalanche Noise (http://holdenc.altervista.org/avalanche/)

    Meanwhile, up there on the right it says “Askimet’s spam filtering ”

    Methinks ’tis kismet – the software is “auto kismet” – Akismet.

    • #2 by Ed on 2015-10-07 - 12:55

      Good reference: I like the full-frontal analog design using a handful of discrete parts, even though I’m tinkering with the ATmega’s internal Analog Comparator instead. Hard to verify that it works without seeing the output; this may call for a firmware read-and-display loop just for the obligatory scope shot.

      As nearly as I can tell, I cannot type Akismet correctly without watching. every. keystroke. Thanks for pointing that out.

  1. Got My Picture On The Cover Of Circuit Cellar | The Smell of Molten Projects in the Morning