Introduction: Psyche-Spirit Influenced Devices, for Paranormal Reseach, Investigations & Fun

How to make psyche influenced, Arduino based, electronic Devices: PKE (Psychokinetic Energy Meter, like from Ghostbusters) & Temperament meters, Magic-8 ball & Ouija simulators, and a Telekinetic monitor.

Any one or an all-in-one device can be made with the devised unique analog input and software provided.

This Instructable concerns itself with unique analog inputs, the software, and related functionality, of electronic versions of these devices.

And not the user Interface electronics (general inputs & outputs, buttons and display).

The hardware platform it is built on top of is Arduino micro-controller based device with 12 LEDs, some buttons and a beeper. It is covered here: In-Line-LED-Display Instructable

[Update] Most the functionality presented here has been ported to the newer hardware: 

"Mini STEM LED Game Platform" see the activities listed under: "Group 3 - Spirited Fun". [now in 2024] Also check out PCB & 3D Case for STEM Game Platform

Step 1: Hardware Requirements

While I used the hardware platform above (due to its suitable I/O ports and wiring) other LED and button configurations would be fine given the display update and button interface code is appropriately implemented.

Given an implementation of the aforementioned hardware project using a Micro-Controller board (e.g. Arduino Nano, Pro Mini, Uno, RP2040-Zero etc.), you need to add a connector going to an additional analog input (I used A3) and ground. I attached a 3.5mm stereo phono-plug, as I had one. However, I would recommend using an RCA connector, as I find it easier to fashion attachments, and shielded leads are not preferred. Alternately, 2 screw or bare terminals, to which alligator clip jumpers could quickly attach whatever (detector or electrodes) was needed or wanted would work well.

Also a pair of 1N4001 diodes needs to be attached to this analog input line, one to +V and one to ground; both reversed so as not to generally conduct (refer to diagram). I have tested many silicon and germanium diodes and transistor junctions for generation of random readings, using establish algorithms, and 1N4001s worked best for me, in this configuration. NOTICE: Be sure not to wire in the diode in the work direction, else you'll effectively short the +Voltage to ground.

The devices that the software implements only utilize three buttons of the 6 total in the Inline LED display project. Of that project, only Btn0 (ESC), Btn1 and Btn4 are needed. Herein these buttons are referred to as btn-A, btn-B(<) and btn-C(>) respectively. If you only want to wire up those three buttons refer to attached button wiring diagram.

In the earlier project I used Led lights of Red, Yellow, Green and Blue going from left to right. For this project I would reverse the color direction. For some of these functionalities, (e.g. Telekinesis detector and Ouija board) LEDs all of the same color may be best.

How you form the body of your device most likely depends on which instrument's functionality you want to replicate or if you want to do them all, in turn. I used my re-purposed in-line display console from the earlier project. I would love to see “I made it” mock-ups of all sorts, especially Halloween inspired.

Step 2: Technical Details and Operational Philosophy

The display and nature operation is driven by a sensor motivated HRNG (Hardware based Random Number Generator) , based on inputs from the analog input connected to a pair of 1N4001, PN junctions. Sporadic breakdowns of these reverse biased diodes is determined by Quantum Mechanics, which is seemingly unpredictable by us, yet this realm is apparently an intersection with the spirit universe.

Here is some details on the internal operation particular to each functionality.

Telekinesis detector, uses direct reading of the analog input. The two least significant bits are used; which are the wafting in the winds of quantum physics; and perhaps your own will.

There is a photo of a purely hardware (discretes + ICs) version I made in the pre-Instructables era (circa 1980s).

P.K.E. (Psycho-Kinetic-Energy) meter, quantifies changes in the degree of randomness of the voltage across the pair of PN junctions. As a naturally random hardware event (the break down of a reverse biased PN junction) if it gets effected from any outside influence, its degree of randomness is directly effected. The relative randomness is what then is used to determine the displayed output level. The source of any apparently detected external influence is for the user to discern.

Temperament meter, maps measured average body impedance & blockages to proposed temperaments. Experience and experiments with the likes of heart felt frowning and griping as compared to smiling and laughing states of myself and others around me was used as a guide line.

Seance (Ouija board simulation): The selections are controlled by changes in bodily impedance which is in turn are driven by psychosomatic reactions and thought processes. However note that changes in hand-electrode and hand to hand contact will have inadvertent effects, and is incompatible with the indented operation.

The Magic-8 ball's response is directly determined by the user, via the amount of time they hold down the button when they ask their question. All be it not consciously deliberate. This makes it a sort of Psyche-Ometer. Alternatively the software could be changed to make the response driven by the included HRNG (Hardware based Random Number Generator) code to function randomly, arguably more similar to the original physical magic-8 ball.

Let it be said, that in the end what I have created here in is foremost simply intended for entertainment only.

Step 3: Sensor Attachments

For the PKE meter it is recommended you attach, a low frequency sensitive collector, (having long conductive paths) like a steel wool pad to the detector analog line (A3). For The Telekinesis device, alternately, an unshielded wire (simple jumper wire) should do.

The Crazy-8 ball operates independently of anything attached.

For both Seance / Ouija board simulation and Temperament meter operation, two conducting electrodes are needed. One connected to detector analog line and the other connected to ground. I have found that paper toilet cores wrapped with aluminum foil (shiny side out) serve very well. Paper towel cores would be sturdier. I have also had good luck with short (4.5” long) galvanized 1/2” pipe as well as table spoons (with the bottoms making good contact in the palm of the hands). The pipes however may leave a ding if they hit the floor.

Note that the PKE meter is quite sensitive, so putting anything very near the detector or the presence of fluorescing devices or plasma material (& but of course Ectoplasm) will cause significant reads or suppress there of. As the indicated levels are on a logarithmic scale, even a change of one, and certainly two, could be significant.

Step 4: General Operational Use

The software, as it is, supports all 5 types of “Psyche-Ometers”. The software is available for download below.

On startup a menu phase is entered. Five LEDs are lit #1-5, one of them will be extra bright. You use the main buttons to move to the one for the function you want.

  1. The Magic-8 ball
  2. Telekinesis device
  3. P.K.E. (Psycho-Kinetic-Energy) meter (aka ghost detector)
  4. Temperament meter
  5. Seance

Then hit button-A to select.

The appropriate operation will commence, usually after a brief intro display.

To exit any one operation hold down button-A. Note, for some operations it may take a few seconds for it to respond.

If you choose to fashion a device dedicated to a single functionality, remove menu code and the 'case' code sections you're not using, all of which is in the “loop()” function.

Step 5: Magic 8-ball

The Magic-8: You propose a question; You press the B-button; ponder your request; let go of the button and your answer will be divined. This process can be repeated over and over. The answers and functionality is modeled on the well known Magic-8 ball. It has been simplified to give four answers (without any “in other words”). To better model the original, the software can easily be changed to give 1 of 12 responses (see Wikipedia for the full set of responses to pick from).

Step 6: Telekinesis

Telekinesis challenge device: The LED indicator left to its own, randomly wanders with no true will to go right or left. It will only continuously hold still or continuously move left or right if an external will (goal driven influence) takes control of it. Challenge yourself, friends, and especially anyone who believes they can or have mastered such powers.

Or if you think you have predictive, prognostication abilities, or really good intuition, you can try your hand at predicting which way it will go next.

I sometimes like to simply watch the Telekinesis device operation left to its own, wafting in the wind, as a soothing meditative agent.

Step 7: Psycho-Kinetic-Energy Meter

P.K.E. (Psycho-Kinetic-Energy) meter: Inspired by Ghost-Busters. With greater and greater PKE measurement the LEDs fans out from the center. The consecutive levels reflect a logarithmic scale. If & when the LEDs reach the ends, higher levels will be indicated by additional LEDs lit in conjunction with them. When your environment has changed, you can reset the base reference level (& thus sensitivity) by exiting and re-selecting this instrument.

Step 8: Temperament Meter

Temperament meter, the momentary nature of ones temperament is purportedly reflected by the settled on LED at the end of a test period which is acknowledged by a double beep. The person under examination holds study the two electrodes. Not squeezed, as it would than tend to be a strength meter. One connects to ground and one to the analog input. The test can be repeated by pressing button 1.

This operation could be considered and displayed as a Love/Affinity scale or other human nature measurement.

Step 9: Seance / Ouija Simulation Device

Seance / Ouija board simulation, two or more hold hands, with the electrodes held by the opposite end free hands. The operation is like that of the Ouija board; the group proposes and agrees on what sort of answer they are looking for. Then watches as something, to be interpreted by them, is 'spelt' out.

It takes some amount of time for an individual selection to be settled on. You should note the setting (1:A:B thru 12:WX:YZ), then press btn-B to start moving in on the next selection. Once enough character sets have been selected, Folks can work out possible spellings, and get an idea of what the spirit forces in the room are trying to give them.

Seances are not often clear and concise, but instead are yearning for interpretation.

Step 10: The Software 'Code'

Reading the comments in the code can shed more light on the internal design as well as operational behaviors, I may not have explained.

Please forgive the crudeness of the code organization and optimization. It was assembled on a shoe string budget.


Ron Miller Oct 2017

// constants won't change. They're used here to set pin numbers:

#define DETECTOR A3
const bool debugPrt=false;
const int ledOnBrd = 13; // the number of the LED 'L' on the board
const byte Ain = A2;
const byte Din = 16; // the digital equivalent to the above analog pin
const byte BEEPPIN = PD3; // using D3 on Nano & Uno

const uint8_t bank[4] = {11,8,9,10};

const uint8_t lites[4][3] = {

// # define LED_ON(n) {DDRB = 0; PORTB = led[n][0]; DDRB = led[n][1];}

const unsigned char led[12][2] = {
{8, 8 | 4}, {8, 8 | 1}, {8, 8 | 2},
{1, 1 | 4}, {1, 1 | 8}, {1, 1 | 2},
{2, 2 | 4}, {2, 2 | 1}, {2, 2 | 8},
{4, 4 | 8}, {4, 4 | 2}, {4, 4 | 1}

const uint8_t bankBit[4] = {8,1,2,4};
int serviceBank;

bool lit[13], dim[13];
int brightOne, brightTwo;
int litLed;
int flashLed;
byte allColor=0; // 0: none, 1:RED, 2:YEL, 3:GRN, 4:BLUE

unsigned int msCnt;
unsigned long t0,t1;

bool btnChanged = false;
bool btn0Changed = false;
bool btn1Changed = false;
bool btn4Changed = false;

#define btn0Pressed (btn0Changed && btn0)
#define btn1Pressed (btn1Changed && btn1)
#define btn4Pressed (btn4Changed && btn4)

byte func, nextFunc=0;
int algo;
float aveErr = 0.0;
int lbd[16], ubd[16]; // Lower & Upper bit distribution buckets

int aVal, lastAVal, lastUsedAVal, avAdj;
byte btnState = 0, priorBtn;
byte currState;
bool btn1 = false;
bool btn4 = false;
bool btn0, Btn=false; // some backwards support
int threshold;
float minChg;
bool ESC = false;
int escCnt=0;

volatile unsigned long myMillis=0;
unsigned long usecs;
//Timer2 Overflow Interrupt Vector, called every 1ms
ISR(TIMER2_OVF_vect) {
TCNT2 = 130; //Reset Timer to 130 out of 255
TIFR2 = 0x00; //Timer2 INT Flag Reg: Clear Timer Overflow Flag

// ----------- Hardware based Random Number Generator -----------------
int HRNG(byte algo) {
int sensorValue;
byte y, yRev, s; // use of a byte restricts results to 8 bits (0-255)
char c;
unsigned int x;

// read Ao voltage
sensorValue = analogRead(DETECTOR);

switch (algo) {
case 0: // ------------ use the random function ---------------
// calculated Pi: 3.15 mean: 127.12 Avg Bits Chg'd: 4.00 dist: 1.07 Random Mark missed by: 0.95 % Running avg Err: 0.94 %
x = random(256);

case 1: // Use a direct Analog Read
// calculated Pi: 3.00 mean: 130.35 Avg Bits Chg'd : 3.09 dist: 1.19 Random Mark missed by: 20.23 % Running avg Err: 20.88 %
x = sensorValue; // direct Analog Read (uses lower 8 bits)

case 2: // reverse read bit order and add in 2 more reads
// with + length of wire looks close to as good as case #0
// w/ 2 readings, Pi: 3.12 mean: 127.70 Avg Bits Chg'd: 3.91 dist: 1.06 Random Mark missed by: 2.65 % Running avg Err: 2.96 %
case 3: // Combine Hardware RNG value and Pseudo SW RandomNumber

y = sensorValue;
// reverse the bits
yRev = 0;
for (int i=0; i<=7; i++) {
yRev = yRev << 1;
yRev += y % 2; y = y >> 1;

sensorValue = analogRead(DETECTOR);
// with double reading Avg Bits Chg'd: 3.44 -> 3.65
sensorValue = sensorValue + analogRead(DETECTOR); // ' ' & improved err (-2%)

y = yRev + sensorValue; // Avg Bits Chg'd: 3.44 w/no wire, to 4.0 with wire
x = y; // the byte 'y' kept results to 8 bits


if (algo==3) { // use for best of both worlds
y = y + random(256);
x = y;

return(x); // x is an Integer, but in most cases only has 8 bits in it

// the setup routine runs once on power up or when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.println("Psyche ~ Ometers ");

pinMode(ledOnBrd, OUTPUT);
digitalWrite(ledOnBrd, HIGH);

pinMode(Din, INPUT);
digitalWrite(Din,HIGH); // inact the internal pull-up resister

pinMode(bank[0], INPUT); pinMode(bank[1], INPUT); pinMode(bank[2], INPUT); pinMode(bank[3], INPUT);
digitalWrite(bank[0],LOW); digitalWrite(bank[1],LOW); digitalWrite(bank[2],LOW); digitalWrite(bank[3],LOW);

//nextFunc=2; // if you want to automatically start as a given device set its # here

pinMode(BEEPPIN, OUTPUT); // declare BEEPPIN to be an output:
beep(60); beep(60); beep(60); // not "beep()" has built-in pause interval

if (debugPrt) Serial.println("Ideals... mean: 127.50 GeoMean: 113.2 Seq.Delta: 85.33 Avg Bits Chg'd: 4.00");
if (debugPrt) Serial.println(" ------ ----- ----- ----");
if (debugPrt) Serial.println("\n");

//Setup Timer2 to fire every 1ms
TCCR2B = 0x00; //Disbale Timer2 while we set it up
TCNT2 = 130; //Reset Timer Count to 130 out of 255
TIFR2 = 0x00; //Timer2 INT Flag Reg: Clear Timer Overflow Flag
TIMSK2 = 0x01; //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
TCCR2A = 0x00; //Timer2 Control Reg A: Wave Gen Mode normal
TCCR2B = 0x05; //Timer2 Control Reg B: Timer Prescaler set to 128

// the loop routine runs over and over again forever:
// ==================================================
void loop() {
byte y,s;
char c;
unsigned int x, lastX =127;
int pos;
int seqDelta, geoMean;
long meanDevSum =0;
long DeltaSum =0;
long bitChgSum=0;
float runningMean=127.5, runningGMean=113.2, runningDelta=85.33, runningBChg=4.0, rateRNG, lastRNG;
float runningXMean=512; // for running mead of read Analog value (range 0-1023);
float sumX;
int n=0, cnt=0, cntMax=0;
const int xvals[] = {0,26,75,119,178,246,375,525,646,742,805,853,889,1024};
byte chgd;
float baseline, PKE_level;
int sensorAvg=512;
int userRx=0;
float ravgX=0.0;
int lastLED;
byte ans, keyCode;
byte lSide, rSide;
int dir[4];
int maxDir, maxSum;
bool ackd;

// ------ Diag code ...
// delay(10);
// t0=myMillis;
// delay(1000);
// Serial.print(" # of ms ticks during delay(1000): "); Serial.println(myMillis - t0);

digitalWrite(DETECTOR,0); // disable pull-up

// select a function 1-5

while(Btn) {scanBtns();} // wait for no button

if (nextFunc) {
brightOne = nextFunc;
nextFunc = 0;
} else {
while (! btn0) {
if (btn1Pressed) brightOne = (brightOne==1)? 5 : brightOne-1; // --
if (btn4Pressed) brightOne = (brightOne%5) + 1; // ++

func = brightOne;
if (func==4 || func==5) {
// engage pullup ~38K
digitalWrite(DETECTOR,1); // prepare to read input impedance on analog input

for (int i=0; i<10; i++) { // establish some reference points (this block is not critical)
if (func==3) x=HRNG(2);
else HRNG(1);
runningXMean = (5.0*runningXMean + x)/6.0;
x = x & 0xFF; // hold to a byte
// Determin a running randomness rate
chgd = x ^ lastX;
bitChgSum = 0;
for (int i=0; i<8; i++) {bitChgSum += (1 & (chgd >> i));}
seqDelta = abs((int)lastX - (int)x);
geoMean = sqrt(x * lastX); // tracks geometric mean
lastX = x;

runningMean = (5.0*runningMean + x)/6.0;
runningGMean = (5.0*runningGMean + geoMean)/6.0;
runningDelta = (5.0*runningDelta + seqDelta)/6.0;
runningBChg = (5.0*runningBChg + bitChgSum)/6.0;
if (debugPrt) {Serial.print(" X mean: "); Serial.print(runningXMean);}
if (debugPrt) {Serial.print(" mean: "); Serial.print(runningMean);}
if (debugPrt) {Serial.print(" GeoMean: "); Serial.print(runningGMean);}
if (debugPrt) {Serial.print(" Seq.Delta: "); Serial.print(runningDelta);}
if (debugPrt) {Serial.print(" Avg Bits Chg'd: "); Serial.print(runningBChg);}

rateRNG = abs((runningMean/127.5)-1.0);
rateRNG += abs((runningGMean/113.2)-1.0);
rateRNG += abs((runningDelta/85.33) - 1.0);
rateRNG += abs((runningBChg/4.0) - 1.0);
if (debugPrt) {Serial.print(" Non-Randomn Percent: "); Serial.println(100 * rateRNG /4);}
baseline = rateRNG;
Serial.print(" Non-Randomn Percent: "); Serial.println(100 * rateRNG /4);
while(btn0) scanBtns(); // wait for button release

cnt = 0;
nextFunc = 0;
Serial.print(" FUNC: "); Serial.println(func);
switch (func) {
case 1:
// =========================================== Crazy-8-Ball
while (func==1) {
while(!Btn) {
if (btn0) break;
t0 = millis(); // start timing on Button pushed
while(btn1 || btn4) { // stop on release
if (allColor>0) cnt+=3; // colors lasts 1/4 long as dark
if(cnt++>cntMax) {
cntMax= 200 + 50 * random(10);
allColor = (allColor)? 0 : random(0,5);
keyCode = priorBtn;
refreshWait(200 + random(800)); // make them wait alittle
if (keyCode==2) { // btn4 was used
ans = (millis() - t0) %12;
brightOne = ans+1;
} else { // was btn1
ans = (millis() - t0) %4; // answer is determined by their action
/***Serial.print("The answer is: "); Serial.print(ans);
if (ans==0) Serial.println(" NO");
else if(ans==1) Serial.println(" Maybe");
else if(ans==2) Serial.println(" YES");
else Serial.println(" Ask again, later."); ***/
while(1) {
if (btn0) {func=0; break;} // btn0 when done
if (btn1 || btn4) break; // go again

case 2:
// ============================================= Telekeniss
while(1) {

for (int i=0; i<4; i++) dir[i]=0;
while (cnt++<400) { // consider influence over time
if (dir[x%4] > maxSum) {
maxSum = dir[x%4];
maxDir= x%4;
if (maxDir==0) pos++; // go right
else if (maxDir==3) pos--; // go left
if (pos<0) pos=11;
brightOne = pos+1;
if (btn0Pressed) break;

case 3:
// ======================== ghostbuster's type P.K.E. meter
// psycho-kinetic scope Detecting Psychokinetic Energy

// start off with a display sweep to Max level & back to level 1
for (int i=1; i<=11; i++) {
for (int i=11; i>=1; i--) {

// set level 1 = to a standard level
// baseline = rateRNG; set above
baseline = (10.0)/100.0; // set empirical (for 10%)
lastRNG = baseline;
Serial.print(" Non-Randomn baseline: "); Serial.println(100 * baseline);

// sweep from max to level 1 at start
while(func == 3) {
// using HRNG(2) which is about twice as solidly random as is HRNG(1)
// and therefore less sensitive. I think HRNG(1) results in to much signal noise.
x=HRNG(2) & 0xFF; // take an 8bit reading
// Determin a running randomness rate
chgd = x ^ lastX;
bitChgSum = 0;
for (int i=0; i<8; i++) {bitChgSum += (1 & (chgd >> i));}
seqDelta = abs((int)lastX - (int)x);
geoMean = sqrt(x * lastX); // geometric mean
lastX = x;

runningMean = (9.0*runningMean + x)/10.0;
runningGMean = (9.0*runningGMean + geoMean)/10.0;
runningDelta = (9.0*runningDelta + seqDelta)/10.0;
runningBChg = (9.0*runningBChg + bitChgSum)/10.0;
rateRNG = abs((runningMean/127.5)-1.0);
rateRNG += abs((runningGMean/113.2)-1.0);
rateRNG += abs((runningDelta/85.33) - 1.0);
rateRNG += abs((runningBChg/4.0) - 1.0);
rateRNG = (rateRNG + 3*lastRNG) /4; // smooth it out a little
lastRNG = rateRNG;
if (debugPrt && (cnt++ % 10)==0) {
Serial.print(" mean: "); Serial.print(runningMean);
Serial.print(" GeoMean: "); Serial.print(runningGMean);
Serial.print(" Seq.Delta: "); Serial.print(runningDelta);
Serial.print(" Avg Bits Chg'd: "); Serial.print(runningBChg);
Serial.print(" Non-Randomn Percent: "); Serial.println(100 * rateRNG/4);

PKE_level = (rateRNG/4)/baseline;
if (PKE_level>=2) {
// given logb(x) = ln(x)/ln(b)
// PKE_level = log(PKE_level)/log(1.4);
litLed = PKE_level;

// with the next line's analog read of a line with a pull-up on it, the DETECTOR line's Rness is:
// Non-Randomn Percent: ~40, while w/o it is: ~15
// could, for more TRNG, instead use a digitalRead(Ain), and detect b0 only; or simply comment it out for maximum sensitivity

if (btn0Pressed) break; // done with this func
if (btn1Pressed) {
nextFunc = func; // set to restart this func
if (btn4Pressed) { // recalabrate
refreshWait(1000/3); // note allColor=5; takes longer to refresh
baseline = (baseline+rateRNG)/2; // set level 1 halfway to current level
if (digitalRead(Ain)==0) {
scanBtns(); // get public vars refrecting button state
break; // btn0 (aka ESC) to exit this func

case 4:
// =============== Temperament / Affection / Strength Meter
for (int i=1; i<=12; i++) dim[i]=true;
lSide=0; rSide=12;
ravgX = runningXMean;
litLed = ravgX/100+2; // put it in the ball park
// sumX = 0.0;
while(1) {
if (lSide<=rSide) { // initially sweep Leds in on the 'x' pos
if ((litLed-lSide) > (rSide-litLed)) dim[lSide++] = false;
else dim[rSide--] = false;
if (lSide<=rSide) litLed = 0;
n=1; // delays locking an average in
} else {
if (cnt>4 && !ackd) { // tell em that's it
ackd = true;
beep(50); beep(50);
for (int i=0; i<10; i++) {
brightOne = litLed;
if (ravgX == 0.0) ravgX = x;
ravgX = (5.0 * ravgX + x) / 6.0;
sumX += ravgX;
x = sumX/n;
if (debugPrt) {Serial.print(" sumX/n = X: "); Serial.println(x);}
while (xvals[++litLed](ravgX+minChg)) ++userRx;
if (x<(ravgX-minChg)) --userRx;

// provide opertunity, in the form of a dwindling to & fro
// defer, this appears not to be needed

// user reaction with righer impedence is taken as approval; which reinforce prevailing tendancy
if (userRx > threshold) {
litLed=(litLed+1) % 12;
userRx = 0;
ravgX = (ravgX+x)/2; // re-adjust running average
// use ravgX=x; for less reactivness

// user reaction with lowwer impedence is taken as dis-approval; which counter acts prevailing tendancy
if (userRx < -threshold) {
litLed=(litLed+11) % 12; // effectively subtracts 1
userRx = 0;
ravgX = (ravgX+x)/2; // re-adjust running average
// use ravgX=x; for less reactivness

// detect lingering state, as a selection
if (cnt>40) { // if no change for ~2secs increase threshold that throttles change
//diag: beep(30);

lastX = x;
if (lastLED !=litLed) {
if (debugPrt) {Serial.print("ravgX: "); Serial.println(ravgX);}
lastLED =litLed;
if (btn0Pressed) break;
if (btn1Pressed) {
// prehaps current pos should be maintained
nextFunc = func; // set to restart this func
if (btn4Pressed) { // implement a Hold state
while(Btn) scanBtns();
while(!Btn) refreshBtns();
} // end of switch structure

void dispPKE(byte level) {
if (level==0) {
// set bright the level LEDs
if (level<=6) {
} else { // level>=7 overflows and form a trail of dim leds
brightOne= 12-(level-6);
for (int i=brightOne; i<=12; i++) dim[i]=true;
for (int i=1; i<=brightTwo; i++) dim[i]=true;

void refreshWait(int msec) {
while (msec>0) {
// oneMilliUpdate(); it is now consistantly updated via a call in a One MilliSecond interurpt service routine

void refreshBtns() {

// ----------- Every One Milli-Second update the Display
void oneMilliUpdate() {
bool on[13];
int ledSet, ledn, Lite, ledTime;
int i;
bool litOne, flashTime;


// Update Display
for (int b=0; b<4; b++) {
pinMode(bank[b], INPUT);
if (allColor != 0) {
if (allColor<=4) {
// set the appropriate bank control line High and all four lines as outputs
DDRB = 0; PORTB = bankBit[allColor-1]; DDRB = 15;
} else { // =5 for light them all (which takes an extra 1.5 ms
// cycle thru all banks of lights
DDRB = 15;
PORTB = bankBit[0]; delayMicroseconds(500); //delay(1);
PORTB = bankBit[1]; delayMicroseconds(500); //delay(1);
PORTB = bankBit[2]; delayMicroseconds(500); //delay(1);
PORTB = bankBit[3];
return; // 'allColor' settings over ride other led pixel settings

ledSet = msCnt%4;
ledTime = msCnt/4;
if (ledTime%6 == 0) { // 1/6 cadence time to show 'dim'
for (i=1; i<=12; i++) on[i]=dim[i];
} else if (ledTime%2 == 1) { // 50% of the time process 'lit'
for (i=1; i<=12; i++) on[i]=lit[i];
} else {
for (i=1; i<=12; i++) on[i]=0;
on[brightOne] = true;
on[brightTwo] = true;
flashTime = ((msCnt%500) < 300); // 300ms out of 500ms turn it off
if (flashTime) on[flashLed] = false;

// drive the hardware output for the (1 of 4) 3 led set of interest
serviceBank = ledSet;
ledn = (3 * serviceBank);
for (int i=0; i<3; i++) {
ledn = (3 * serviceBank) + i;
if (on[ledn+1]) {
Lite = lites[serviceBank][i];
pinMode(Lite, OUTPUT);
if (litOne) {
pinMode(bank[serviceBank], OUTPUT);

// ------------------
void clearDisp() {
brightOne = 0;
brightTwo = 0;
for (int i=0; i<=12; i++) {
dim[i] =false;

// Btn 0, all, 1&4, 2&4, 3&4, 4, 1&3, 2&3, 1&2, 1, 3, 2, fncKey, open
// --- --- --- --- ----
// expected reading: 0, 410, 510, 680, 1020
const int kVals[] = { 200, 460, 590, 850, 1024};

void scanBtns() {
bool b0,b1,b4;
byte i,k;

btnChanged = btn0Changed = btn1Changed = btn4Changed = false;
if (btn0) escCnt++;
else escCnt=0;
ESC = (escCnt>1500); // takes <2secs given Btns check ~ every msec

lastAVal = aVal;
aVal = analogRead(Ain);

if (5 >= abs(lastUsedAVal- aVal)) return; // it's nothing new

if (5 < abs(lastAVal - aVal)) { // minimal debouncing
return; // check again later, s.b. in ~1ms
// the higher the test value the more likely err in button decerning
// the lower the more likly noise could cause delays and that delaying refreshes thus causing led flicker
lastAVal = aVal;
aVal = analogRead(Ain); // double check the read
if (4 < abs(lastAVal - aVal)) return;

// -------------------------- a new Reading has been accepted

// Which Button(s) are Pressed
while (aVal > kVals[k]) {k++;}
currState = k;

if (currState != btnState) { // the 'button state' has changed
if (debugPrt) {Serial.print("aVal, currState: "); Serial.print(aVal); Serial.print(", "); Serial.println(currState);}
priorBtn = btnState;
btnState = currState;
//if (debugPrt) Serial.println(aVal);
// determin the state of the buttons and update the variables:
switch(currState) {
case 0 : // no key pressed
b0 = true;
case 1 :
b1 = b4 = true; // both buttons are pressed
case 2 :
b4 = true;
case 3 :
b1 = true;
case 4 :
// Open, no key pressed

Btn = b0 || b1 || b4;
if(b0 != btn0) {btn0 = b0; btn0Changed = true;}
if(b1 != btn1) {btn1 = b1; btn1Changed = true;}
if(b4 != btn4) {btn4 = b4; btn4Changed = true;}


void beep(int delayms){ // works for both Buzzers & Speakers
for (int i=...
Halloween Contest 2017

Participated in the
Halloween Contest 2017