// Mini Game Platform Library - Hardware Support functions // 4 Button + 8 LED module + speaker out (Piezo) // STEM : Arduino, electronic and software ('C'), with interesting Math used to solve interesting issues // see: https://www.instructables.com/member/RonM9 // license: Attribution-NonCommercial-ShareAlike // // 2022 Added RP2040 support // July 2024 Update // !!!! enable your hardware configuration here ... //#define RP2040 // else by default its an AVR Nano //#define ENCASED // else on Open breadboard //#define DEBUG false bool DEBUG = false; void setColor(byte); // ---------------- Function prototypes (pre definitions, for the compiler) void gPlatform_setup(); // ----------------- setup the hardware for the Mini STEM/Game platform void refreshDisp(); // Display Update - Should be called ~ every One-two Milli-Seconds void clearDisp(); // Clear the Display and all related variables void refreshWait(int msec); // Wait for #msecs while keeping the LEDs status behavior refreshed void scanBtns(); // Process HW Button states and update related global status variables void btnsNow(); // gets an updated state without scanBtns's 'debounce' interference void beep(int msecs, int delayms=0); // works OK with Buzzers, best with Speakers (1KHz) void bop(int msecs, int delayms); // a lower frequency ~500Hz tone than does 'beep()' void boop(int msecs, int delayms=0); // produces a lower frequency ~250Hz tone than does 'beep()' void buZZ(int msecs, int delayms=0); // produces a lower frequency tone than does 'beep()' //================================================================= // Edit within this section if your device is not wired as a // standard "Mini Game Platform" see: instructables.com/.... // ---------------- hardware dependent section ------------------ // constants won't change, unless the hardware changes. // ---------- 8 LED configuration data const int nleds=8; #define ON_STATE 0 bool Loud = false; // enables AC driven audio #ifdef LED_BUILTIN const int ledOnBrd=LED_BUILTIN; // the number of the LED 'L' on MCU board #endif //=========================================================================================================== #ifndef RP2040 // so must be AVR Nano #ifndef ENCASED // ------------ open prototype STEM Educational platform (arduino Nano) // (could be on a breadboard, top-side of PCB or otherwise directly attached) const int button[] = {16, 17, 18, 19}; // The four button input pins are oriented in this order const int lites[] = {9, 8, 7, 6, 5, 4, 3, 2}; // the LED pin assignments const int RedSet=4, YelSet=3, GrnSet=1; // used by readySetGo() to cycle Red/Yellow/!GREEN const byte AUDIO_OUT = 13; const byte AUDIO_NEG = 12; // PSUDO... defs are only needed when modules are directly attached to MCU or via a Breadbord, not when using a PCB #define PSUDOGND 15 // (aka A1) used by 4 Key module as needed #define PSUDOVCC 10 // needed when powering LED module with DIO line #define EXTRNVCC 10 // ext wire supplies +V to 8 LED module #else // ------------ enclosed Boxed version (also aka Lefthand version, all reversed right to left) // enCased STEM Prj 1-3 with a Nano MCU const int button[] = {19, 18, 17, 16}; //The four button input pins const int lites[] = {2, 3, 4, 5, 6, 7, 8, 9}; //the LED pins const int RedSet=1, YelSet=2, GrnSet=4; // used by readySetGo() to cycle Red/Yellow/!GREEN const byte AUDIO_OUT = 13; const byte AUDIO_NEG = 12; #define PSUDOVCC 10 #endif #else // so the MCU is an RP2040 #ifndef ENCASED // ------------ Plug-N-Play (held together with just an elastic band) - - - - - - - - - - WaveShare RP2040-Zero // (could be on a breadboard, or soldered them directly together) const int button[] = {27, 26, 15, 14}; //NOTE: button input pins need pullup Rs const int lites[] = {1, 2, 3, 4, 5, 6, 7, 8}; //the LED pins const int RedSet=4, YelSet=3, GrnSet=1; // used by readySetGo() to cycle Red/Yellow/!GREEN const byte AUDIO_OUT = 9; //12; const byte AUDIO_NEG = 10; //for TESTING 9; #define PSUDOVCC 0 // supplies +V to 8 LED module #define PSUDOGND 28 // used by 4 Key module as needed const int ledOnBrd=16; // for the WS2812 LED of Waveshare RP2040-Zero #else // ------------ enCased STEM Prj 1 2 or 3 with WaveShare RP2040-Zero const int button[] = {14, 15, 26, 27}; //NOTE: button input pins need pullup Rs const int lites[] = {8, 7, 6, 5, 4, 3, 2, 1}; //the LED pins const int RedSet=1, YelSet=2, GrnSet=4; // used by readySetGo() to cycle Red/Yellow/!GREEN const byte AUDIO_OUT = 12; const byte AUDIO_NEG = 10; // #define PSUDOVCC 0 // supplies Vcc to 8 LED module ... PCB does it //#define PSUDOGND 28 // used by 4 Key module as needed. was 28 (ADC2) const int ledOnBrd=16; // for the WS2812 LED of Waveshare RP2040-Zero #endif #endif //=========================================================================================================== // ---------- Analog inputs used by some #ifdef RP2040 byte ADC_a = A2; byte ADC_b = A3; // this line does not support interal pull-up resistor byte SENSOR = A2; #else byte ADC_a = A6; byte ADC_b = A7; byte SENSOR = A0; #endif byte DETECTOR = ADC_b; bool debugPrt=false; //================================================================= // ---------- button config. data #define PRESSED_STATE 0 // ----------------------- // 4-Button module support bool btn1 = false; // reflects the state of button #1 bool btn2 = false; bool btn3 = false; bool btn4 = false; bool Btn=false; // reflects Buttons collectively bool btn[5]; // array of btn1-4 states bool btns=false; // true is multiple buttons are pressed bool ESC = false; // a psuedo button, true is btn4 is held down for 2 seconds unsigned int escCnt; int secsIdle=0; bool btn1Changed = false; // true if btn1 just changed bool btn2Changed = false; bool btn3Changed = false; bool btn4Changed = false; bool BtnChanged = false; // true if any button changes //unsigned long lastChg; // millis() when last button change occured unsigned int lastChg; // my 'msCnt' millisec cnt when last button change occured byte btnNum; // btn number pressed 1-4, lower num takes precedence static byte btnState = 0, currState, priorBSt; #define btn1Pressed (btn1Changed && btn1) #define btn2Pressed (btn2Changed && btn2) #define btn3Pressed (btn3Changed && btn3) #define btn4Pressed (btn4Changed && btn4) #define BtnPressed (BtnChanged && Btn) // ----------------------------------------------------------------- // 8 LED display bar support unsigned int msCnt; // pseudo MSecs, incremented every time 'refreshDisp()' is called #define myMillis msCnt unsigned int mySecs=0; // NOTE! in the arrays below, elements [1]-[8] relate to LED 1-8 respectively // [0] & [9] are for overflow protection bool dim[8+2]; // set true which LEDs you want to be dim (~10% on) bool lit[8+2]; // " lit (~33% on) bool brt[8+2]; // " bright (100%) bool arc[8+2]; // " sparky variable brightness int brightOne; // sets indicate (1-8) LED Bright. Useful as a cursor or sprite int litLED; // " Lit " int dimLED; int flashLed; // the number of the LED to Flash int dimFlash; // only works over otherwise off or dim LEDs int blinkLed; int spriteOne, spriteTwo; bool onSet; // if true LEDs set as is, upd disabled byte levelOn[nleds+2]; // for lighting 0-16 Levels bool glow[nleds+2]; // used to implement a cyclic brightness 'glow' lighting effect bool glowEnable=false; int dimIt; // the led to be 'dim'd 50% of its otherwise brightness //==================== Support functions ======================== void gPlatform_setup() { // ----------------- setup the hardware for the Mini STEM/Game platform // ------------ setup LED pins as outputs #ifdef PSUDOVCC pinMode(PSUDOVCC, OUTPUT); // provide +v for LED bar module digitalWrite(PSUDOVCC, HIGH); #endif #ifdef EXTRNVCC pinMode(EXTRNVCC, INPUT); // let it be externally driven, for my blackBox proj. #endif for (int x = 0; x < nleds; x++) { pinMode(lites[x], OUTPUT); digitalWrite(lites[x], ON_STATE); // on startup have LEDs momentarially On } // ------------ setup the four button module for (int x = 0; x < 4; x++) { // pinMode(button[x], INPUT); // button pins are inputs // digitalWrite(button[x], HIGH); // enable internal pullup; buttons start in high position; logic reversed pinMode(button[x], INPUT_PULLUP); } #ifdef PSUDOGND if (PSUDOGND == ADC_a) ADC_a = ADC_b; // work-around (RP2040-zero) as pin cannot do double duty pinMode(PSUDOGND, OUTPUT); // provide Ground (-V) for the button module digitalWrite(PSUDOGND, LOW); #endif // ------------ setup and init the Audio pinMode(AUDIO_OUT, OUTPUT); digitalWrite(AUDIO_OUT, LOW); pinMode(AUDIO_NEG, OUTPUT); digitalWrite(AUDIO_OUT, LOW); clearDisp(); // initialize display LEDs scanBtns(); // " button statuses } // ------------------------------------------------------------ // Display Output Processing // --------------------- Display Update - Should to be called ~ every One-two Milli-Seconds // takes ~16-50us with an RP2040, ~210us with Arduino Nano void refreshDisp() { // Display Update - Should be called ~ every One-two Milli-Seconds bool on[13]; int ledn, Lite; unsigned int ledTime; int i, t; bool flashTime; byte gs, gsCnt; static byte dimitCnt; static unsigned long lastMS; msCnt++; if (DEBUG && msCnt%10000==0) { // check on display refresh rate and service consistancy Serial.print(millis()-lastMS); Serial.print(" "); lastMS=millis(); } if (msCnt%1000==0) { mySecs++; secsIdle++; } if (onSet) return; // override: LEDs are set in fixed On pattern flashTime = ((msCnt%600) < 250); // 200ms out of 500ms turn it On for (i=1; i<=nleds; i++) on[i]=0; // init working array // -------------- determine which Leds its time to illuminate ledTime = msCnt; if (ledTime%15 == 1) { // 1/x cadence time to show 'dim' for (i=1; i<=nleds; i++) {on[i]=dim[i];} on[dimLED] = true; on[blinkLed] = true; } else if (ledTime%3 == 1) { // ~27% (1/3-20%) (=4/15) of the time as 'lit' for (i=1; i<=nleds; i++) on[i]=lit[i]; on[litLED] = true; } // -------- glow support if (glowEnable) { gsCnt=(ledTime/30)%31+1; // cycles 1-31 gs=(gsCnt>16)? 32-gsCnt : gsCnt; // goes 1-16...15-1 for (i=1; i<=nleds; i++) { if(glow[i] && gs>(ledTime%16)) on[i]=true; } } // -------- handle Brt & lighting levels for (i=1; i<=nleds; i++) { if (brt[i]) on[i]=true; if (levelOn[i]>(ledTime%16)) on[i]=true; // provide for lighting of 0-16 levels } // implement a sparkly arc option for (i=1; i<=nleds; i++) if (arc[i]) { t=(ledTime/101)%4; if (t==0 && (ledTime%15==0)) on[i]=true; else if (t==1 && (ledTime%3==0)) on[i]=true; // spark else if (t==2 && (ledTime%11==0)) on[i]=true; else if (t==3 && (ledTime%7==0)) on[i]=true; } //if (flashTime) on[dimFlash] = (dim[dimFlash])? 0:(ledTime%15==0); // for 200ms make it dim, if dim make it off if (flashTime) on[dimFlash] = (dim[dimFlash] || arc[dimFlash] || glow[dimFlash])? 0:(ledTime%15==0); // for 200ms make it dim, if dim make it off on[brightOne] = true; on[spriteOne] = true; on[spriteTwo] = true; if (flashTime && flashLed) on[flashLed] = (brt[flashLed] || glow[flashLed])? false:true; if ((msCnt%250) < 80) on[blinkLed] = (dim[blinkLed] || dimLED); if (on[dimIt]) { if (dim[dimIt]) on[dimIt]=msCnt&1; // dim 1/2 else if ((++dimitCnt%3)>0) on[dimIt]=false; // 2/3 Dimming for any and all other LED lite level } // -------------- drive the resulting Leds of interest for (i=0; i0) { t0=micros(); // improve accuracy & contistancy so as not to modulate audio refreshDisp(); //delay(1); delayMicroseconds((1000+t0)-micros()); msec--; } } // ------------------------------------------------------------ // Button Input Processing // Optimized for games, minimizing button press detection time, // while practically elliminating false state changes (due to button contact noise) void scanBtns() { // Process HW Button states and update related global status variables // static byte btnState = 0, currState, priorBSt; Now public vars static byte lastState; bool b1,b2,b3,b4; byte i,k; unsigned int st; // state time static unsigned long lastChgMs; static byte sscnt=0; // steady state cnt if (secsIdle>4*60) { // every 4 mins. secsIdle=0; setColor(5); // lite up the LEDs digitalWrite(ledOnBrd, HIGH); beep(100,20); digitalWrite(ledOnBrd, LOW); beep(100); // say HEY! Don't forget me onSet = false; } if (BtnChanged) delay(2); // let new state settle BtnChanged = btn1Changed = btn2Changed = btn3Changed = btn4Changed = false; b1 = btn[1] = ! digitalRead(button[0]); b2 = btn[2] = ! digitalRead(button[1]); b3 = btn[3] = ! digitalRead(button[2]); b4 = btn[4] = ! digitalRead(button[3]); currState = int(b1) + 2*int(b2) + 4*int(b3) + 8*int(b4); // combined button states if (currState != lastState) { // little debounce: a new state must repeat 2 scans in a row // do not increase this, else sprite hit detection will sufer lastState=currState; if (currState==btnState) lastChgMs=millis(); // so set new begin timing sscnt=0; return; } sscnt++; st=millis()-lastChgMs; if (sscnt>14 && currState==0) st=99; // set to fix false button state if ((st>14 || currState!=priorBSt) && currState != btnState) { // the 'button state' has changed & not too soon ESC = false; lastChg = msCnt; // was: millis(); lastChgMs = millis(); escCnt = msCnt; priorBSt = btnState; btnState = currState; BtnChanged=true; secsIdle=0; sscnt=0; Btn = b1 || b2 || b3 || b4; btns = (b1 + b2 + b3 + b4) > 1; if (debugPrt) {Serial.print(" B#s: "); Serial.print(button[3]);Serial.print(" "); Serial.print(button[2]);Serial.print(" "); Serial.print(button[1]);Serial.print(" "); Serial.print(button[0]); } if (debugPrt) {Serial.print(" Btns: "); Serial.print(b4); Serial.print(b3); Serial.print(b2); Serial.print(b1); } if (debugPrt) {Serial.print(" CurrState Btn State: "); Serial.println(currState);} btnNum = (b1)? 1 : ((b2)? 2 : ((b3)? 3 : ((b4)? 4 : 0))); // logical button # (1-4) (multiple presses not supported) Btn = b1 || b2 || b3 || b4; if(b1 != btn1) {btn1 = b1; btn1Changed = true;} if(b2 != btn2) {btn2 = b2; btn2Changed = true;} if(b3 != btn3) {btn3 = b3; btn3Changed = true;} if(b4 != btn4) {btn4 = b4; btn4Changed = true;} } else { if (currState!=btnState) {lastChgMs=millis();} // update minimum Chg timing } if (btn4) ESC = (msCnt-escCnt)>2000; // generally 1000 msCnt = 1200 millis else ESC = false; if (btn1 && btn4) ESC = true; // will not be ok if b1+4 is valid in current activity } // ------------- void btnsNow() { // gets an updated state without scanBtns's 'debounce' interference scanBtns(); if (BtnChanged || (currState == btnState)) return; scanBtns(); // test again, due to debounceDelays if (BtnChanged || (currState == btnState)) return; // else likly change in progress delayMicroseconds(100); scanBtns(); } // ------------------------------------------------------------ // Audio support void beep(int msecs, int delayms){ // works OK with Buzzers, best with Speakers (1KHz) for (int i=0; i