|
// Random LED Dots |
|
// Based on Entropy library using watchdog timer jitter |
|
// https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library |
|
// Ed Nisley – KE4ANU – August 2016 |
|
|
|
#include <Entropy.h> |
|
|
|
//———- |
|
// 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 (unused) |
|
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 UPDATE_MS 5 |
|
|
|
//———- |
|
// Globals |
|
|
|
// 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; |
|
} LED_BYTES; |
|
|
|
// altering the number of rows & columns will require substantial code changes… |
|
#define NUMROWS 8 |
|
#define NUMCOLS 8 |
|
|
|
LED_BYTES 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 MillisThen; |
|
|
|
//– 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 Dbyte) { // send one byte, get another in exchange |
|
SPDR = Dbyte; |
|
WaitSPIF(); |
|
return SPDR; // SPIF will be cleared |
|
} |
|
|
|
void UpdateLEDs(byte i) { |
|
|
|
SendRecSPI(~LEDs[i].ColB); // low-active outputs |
|
SendRecSPI(~LEDs[i].ColG); |
|
SendRecSPI(~LEDs[i].ColR); |
|
SendRecSPI(~LEDs[i].Row); |
|
|
|
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); |
|
|
|
} |
|
|
|
//————— |
|
// Set LED from integer |
|
// On average, this leaves the LED unchanged for 1/8 of the calls… |
|
|
|
void SetLED(unsigned long 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("Random LED Dots – Watchdog Entropy\r\nEd Nisley – KE4ZNU – August 2016\r\n"); |
|
|
|
Entropy.initialize(); // start up entropy collector |
|
|
|
//– Set up SPI hardware |
|
|
|
SPCR = B01110001; // Auto SPI: no int, enable, LSB first, master, + edge, leading, f/16 |
|
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 |
|
|
|
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++) { |
|
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 |
|
|
|
digitalWrite(PIN_HEARTBEAT,LOW); |
|
|
|
uint32_t rn = Entropy.random(); |
|
printf("Preloading LED array with seed: %08lx\r\n",rn); |
|
randomSeed(rn); |
|
|
|
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; // random(2) returns 0 or 1 |
|
LEDs[Row].ColG |= random(2) << Col; |
|
LEDs[Row].ColB |= random(2) << Col; |
|
} |
|
UpdateLEDs(Row); |
|
} |
|
|
|
check_mem(); |
|
printf("SP: %u HP: %u Free RAM: %u\r\n",stackptr,heapptr,stackptr – heapptr); |
|
|
|
printf("Running…\r\n"); |
|
|
|
MillisThen = millis(); |
|
|
|
} |
|
|
|
//—————— |
|
// Run the test loop |
|
|
|
void loop() { |
|
|
|
unsigned long Hash; |
|
uint32_t rn; |
|
|
|
MillisNow = millis(); |
|
|
|
// Re-seed the generator whenever we get enough entropy |
|
|
|
if (Entropy.available()) { |
|
digitalWrite(PIN_HEARTBEAT,HIGH); |
|
|
|
rn = Entropy.random(); |
|
// printf("Random: %08lx ",rn); |
|
randomSeed(rn); |
|
digitalWrite(PIN_HEARTBEAT,LOW); |
|
} |
|
|
|
// If it's time for a change, whack a random LED |
|
|
|
if ((MillisNow – MillisThen) > UPDATE_MS) { |
|
MillisThen = MillisNow; |
|
SetLED(random()); |
|
} |
|
|
|
// Refresh LED array to maintain the illusion of constant light |
|
|
|
UpdateLEDs(RowIndex++); |
|
if (RowIndex >= NUMROWS) { |
|
RowIndex = 0; |
|
PulsePin(PIN_SYNC); |
|
} |
|
|
|
} |
|
|