Introduction: Build Ghostly, Psyche Influenced, Devices

Picture of Build Ghostly, Psyche Influenced, Devices

How to make Psychokinetic Devices: PKE & 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: Single-Line-LED-Display Instructable

Step 1: Hardware Requirements

Picture of Hardware Requirements

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

Given an implementation of the aforementioned hardware project using an Atmega328P (e.g. Arduino Nano, Pro Mini, Uno 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, quantify changes in the degree the 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) is 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 to proposed temperaments. Experience and experiments with the likes of heart felt frowning and gripping 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

Picture of 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 buttons '<B' and 'C>' 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

Picture of 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

Picture of 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

Picture of 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

Picture of 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. One connects to ground and one to the analog input.

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

Step 9: Seance / Ouija Simulation Device

Picture of 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 (1AB thru 12WXYZ), 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.

Psyche_Ometers Ron Miller Oct 2017 Instructable: ************************************/ // 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] = { {10,8,9}, {10,11,9}, {10,8,11}, {11,9,8} }; // # 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) { myMillis++; oneMilliUpdate(); 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); break; 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) break; 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 break; } 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.begin(115200); 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); func=1; nextFunc=0; //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 delay(10); 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"); delay(2000); //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 clearDisp(); // select a function 1-5 dim[1]=dim[2]=dim[3]=dim[4]=dim[5]=true; brightOne=func; scanBtns(); while(Btn) {scanBtns();} // wait for no button if (nextFunc) { brightOne = nextFunc; nextFunc = 0; } else { while (! btn0) { delay(1); scanBtns(); if (btn1Pressed) brightOne = (brightOne==1)? 5 : brightOne-1; // -- if (btn4Pressed) brightOne = (brightOne%5) + 1; // ++ } } func = brightOne; clearDisp(); 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); delay(50); 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; ackd=false; nextFunc = 0; Serial.print(" FUNC: "); Serial.println(func); switch (func) { case 1: // =========================================== Crazy-8-Ball while (func==1) { clearDisp(); dim[1]=true; flashLed=1; while(!Btn) { scanBtns(); refreshWait(1); } if (btn0) break; t0 = millis(); // start timing on Button pushed dim[1]=false; flashLed=0; while(btn1 || btn4) { // stop on release scanBtns(); if (allColor>0) cnt+=3; // colors lasts 1/4 long as dark if(cnt++>cntMax) { cnt=0; cntMax= 200 + 50 * random(10); allColor = (allColor)? 0 : random(0,5); } refreshWait(1); } keyCode = priorBtn; clearDisp(); 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 allColor=ans+1; /***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) { scanBtns(); refreshWait(1); if (btn0) {func=0; break;} // btn0 when done if (btn1 || btn4) break; // go again } } break; case 2: // ============================================= Telekeniss pos=6; clearDisp(); while(1) { cnt=0; maxSum=0; for (int i=0; i<4; i++) dir[i]=0; while (cnt++<400) { // consider influence over time refreshWait(1); x=HRNG(1); dir[x%4]++; 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; pos=pos%12; brightOne = pos+1; scanBtns(); if (btn0Pressed) break; } 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++) { dispPKE(i); refreshWait(100); } for (int i=11; i>=1; i--) { dispPKE(i); refreshWait(100); } // 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 beep(40); clearDisp(); 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 refreshWait(50); // 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); } dispPKE(PKE_level); 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 scanBtns(); // 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 break; } if (btn4Pressed) { // recalabrate allColor=5; 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 } } break; case 4: // =============== Temperament / Affection / Strength Meter for (int i=1; i<=12; i++) dim[i]=true; refreshWait(1000); 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; sumX=ravgX; n=1; // delays locking an average in cnt=0; } 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; refreshWait(50); x=HRNG(1); if (ravgX == 0.0) ravgX = x; ravgX = (5.0 * ravgX + x) / 6.0; } sumX += ravgX; n++; x = sumX/n; if (debugPrt) {Serial.print(" sumX/n = X: "); Serial.println(x);} litLed=0; 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); cnt=0; threshold++; minChg+=0.1; } lastX = x; if (lastLED !=litLed) { cnt=0; if (debugPrt) {Serial.print("ravgX: "); Serial.println(ravgX);} } lastLED =litLed; scanBtns(); if (btn0Pressed) break; if (btn1Pressed) { // prehaps current pos should be maintained nextFunc = func; // set to restart this func break; } if (btn4Pressed) { // implement a Hold state while(Btn) scanBtns(); while(!Btn) refreshBtns(); } } break; } // end of switch structure } void dispPKE(byte level) { clearDisp(); if (level==0) { dim[6]=dim[7]=true; return; } // set bright the level LEDs if (level<=6) { brightOne=6+level; brightTwo=7-level; } else { // level>=7 overflows and form a trail of dim leds brightOne= 12-(level-6); brightTwo=level-5; 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 delay(1); msec--; } } void refreshBtns() { delay(1); scanBtns(); } // ----------- Every One Milli-Second update the Display void oneMilliUpdate() { bool on[13]; int ledSet, ledn, Lite, ledTime; int i; bool litOne, flashTime; msCnt++; // 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); litOne=false; for (int i=0; i<3; i++) { ledn = (3 * serviceBank) + i; if (on[ledn+1]) { Lite = lites[serviceBank][i]; pinMode(Lite, OUTPUT); digitalWrite(Lite,LOW); litOne=true; } } if (litOne) { pinMode(bank[serviceBank], OUTPUT); digitalWrite(bank[serviceBank],HIGH); } } // ------------------ void clearDisp() { allColor=0; brightOne = 0; brightTwo = 0; flashLed=0; for (int i=0; i<=12; i++) { dim[i] =false; lit[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; delayMicroseconds(250); aVal = analogRead(Ain); // double check the read if (4 < abs(lastAVal - aVal)) return; // -------------------------- a new Reading has been accepted lastUsedAVal=aVal; // Which Button(s) are Pressed k=0; 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; btnChanged=true; //if (debugPrt) Serial.println(aVal); b0=b1=b4=false; // determin the state of the buttons and update the variables: switch(currState) { case 0 : // no key pressed b0 = true; break; case 1 : b1 = b4 = true; // both buttons are pressed break; case 2 : b4 = true; break; case 3 : b1 = true; break; case 4 : // Open, no key pressed break; } 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 pinMode(BEEPPIN, OUTPUT); for (int i=...


About This Instructable




Bio: Professional Software, Hardware, Systems Engineer for more than 40 years. Amateur Radio HAM (KI7NEW)
More by RonM9:Build Ghostly, Psyche Influenced, DevicesMake G25 FAIRY Light BULBs for HalloweenSignal Code Communicators (RFM69)
Add instructable to: