//NO LONGER VALID. MOVED TO ESP32
//
// Basic Quiz buttons
// Richard Salvage 2/1/24
//
//
// DEFINES are not variables. They just give a name tag to values.
// eg it makes things easier to read and the complier just inserts the number
// when it is compiling
/*
Ver 1.0 Original Code
Ver 1.1 Bugfix (a) incorrect # of presses (b) multi-winner switch alg incorrect
*/
#define ver 1.1 //
#define useSerialOutput true //removes all Serial Outputs when FALSE
#define SIMmultiButtonPresses false //if true, simulates button presses. If false, waits for a real button press
#define SIMbuttonData 129 //which buttons are simulated. If zero is random.
bool TMRused=true; //Change this temporarily to turn the timer on and off
unsigned long TMRsecondary=2000; //Number of ms to wait for additional presses. make this 5k to test
// ------------------74HC595 SIPO to drive the LEDS---------------
//we send a byte out to the 74HC595 and each BIT represents an LED.
//for example sending 129 will light up buttons 1 and 8
//
//Sending 1 then 2 then 4 then 8 then 16... will simulate a chasing light.
//thats what makes the 595 such a useful chip to use.
#define latchPin_SIPO 15 // ST_CP pin 12
#define clockPin_SIPO 2 // SH_CP pin 11
#define dataPin_SIPO 4 // DS pin 14
// ------------------74HC165 PISO Driver to read the buttons ---------------
//The 74HC165 is the reverse of the 595. It READS 8 bits at a time
//Therefore, we can read 8 buttons at once, very, very quickly.
//if the result is not zero, someone has pushed a button.
//it is unlikely that two people will hit the button EXACTLY at the same time
//but we will check for that. Maybe pick one of them at random?
//or have a "playoff" between the two players? let me know.
#define PWR_load_PISO 13 // PL pin 1
#define PWR_clockIn_PISO 12 // CP pin 2
#define PWR_dataIn_PISO 14 // Q7 pin 7
#define PWR_clockEnablePin_PISO 27 // CE pin 15
#define btnUnlock 26
#define ledUnlock 25
#define btnRestart 19
// ------------------Global variables ---------------
int buttonResult; //we only need 8 bits BUT we need a flag for "aborted read" so use an INT
byte LEDshowMAX=2;
byte LEDshow [5][17]={ //byte 0 holds the number of bytes in the sequence
{16,128,64,32,16,8,4,2,1,1,2,4,8,16,32,64,128}, //chase up and down
{8,136,68,34,17,17,34,68,136,0,0,0,0,0,0,0,0},
{8,129,66,40,24,24,40,66,129,0,0,0,0,0,0,0,0},
};
byte LEDdelay [5]={250,250,250,250,250}; //the delay in ms for each LED show
String playernames[8]={"player RED A", //The name of each player
"player GREEN B",
"player BLUE C",
"player YELL D",
"player BLACK E",
"player WHITE F",
"player GREY G",
"player RED2 H"
};
String teams[2]={"Team A","Team B"}; //The team name
byte triggered[8] ; //Holds a list of triggered buttons
float TMRtriggered[8] ; //Holds a list of timers for the triggered buttons
bool latch[8]; //Once we get a button, we latch it so we dont record it twice
byte triggeredCount;
unsigned long TMRstart; //hold the time that the UNLOCK happened
unsigned long TMRend; //hold the time that the button was pressed
void setup(){
//Anything that needs to be setup once is placed here
//such as if the ports are input or output etc
Serial.begin(115200); //Provide output to the serial monitor
// ------------------SETUP THE 74HC595 SIPO that drives the LEDs ---------------
pinMode(latchPin_SIPO, OUTPUT);
pinMode(clockPin_SIPO, OUTPUT);
pinMode(dataPin_SIPO, OUTPUT);
// ------------------SETUP THE 74HC165 PISO that reads the buttons ---------------
pinMode(PWR_load_PISO, OUTPUT);
pinMode(PWR_clockEnablePin_PISO, OUTPUT);
pinMode(PWR_clockIn_PISO, OUTPUT);
pinMode(PWR_dataIn_PISO, INPUT);
pinMode(btnUnlock, INPUT);
pinMode(ledUnlock, OUTPUT);
pinMode(btnRestart, INPUT);
readPISO(0); //We just need to do one read and flush the buffer.
randomSeed(analogRead(A0)); //Randomise based on white noise from the analogue port
resetButtons(); //Reset the buttons for the question
#if (useSerialOutput)
Serial.println("---------- READY TO START --------------");
#endif
waitForUnlock();
}
void loop() {
//The Arduino code loops around and around this constantly. When the code
//gets to the bottom, it starts again at the top.
//There's LOTS more to go in this yet. This is just the test loop
waitForButton();
waitForUnlock();
}
void waitForButton () {
byte temp=0;
byte buttonResult=0; //clear the flag. Nothing is pressed
byte lp;
byte lptarget;
byte winner; //The winners button number
byte winnerCount; //Remember the count of 1st capture winners, so we can display
byte firstRead; //Remember the first PISO read
unsigned long TMRfirstRead; //Remember the first timer
unsigned long TMRtargetTime; //Timer target for the secondary presses
float tduration; //we need a float to display 3dp
byte PISOreads[50]; //overkill, but just to get something in there
unsigned long PISOtime[50]; //record the times of each PISO change
byte PISOreadCount; //number of recordings in PISOreads
unsigned long TMRtemp; //Just to hold the time temporarily
byte PISOmaskTarget; //we need to mask out the current button presses.
bool breakout; //a flag to indicate that we need to break out
//now hang here and wait until someone presses a button.
#if (useSerialOutput)
Serial.println("Waiting for button press");
#endif
#if (SIMmultiButtonPresses)
Serial.println("--- SIMULATING DELAY ---");
temp=random(0,12);
delay(temp*250);
#endif
//set as much up outside of the read loop as possible, so we dont burn time
PISOreadCount=0;
triggeredCount=0; //Reset this here, so when it comes out of the
//whichbutton routine, we know how many button
//presses have been captured
//------------------------------------------------------------------
// Get the FIRST button press
//------------------------------------------------------------------
do {
firstRead=readPISO(1); //Read the PISO and wait for a button.
} while (firstRead==0);
TMRfirstRead=millis(); //capture the time that the button was pressed.
/*My original thought was - Dont do any processing at all here, as that takes precious ms -
Just read the data for the capture period + secondary period and process it later.
BUT... that will mean a time delay (eg .5 sec) in lighting the winners button which will look bad.
Therefore, process the initial data, find the winner and light the button but do not send any
screen updates until the end as screen work is very time intensive.
*/
whichButton(firstRead, TMRfirstRead); //We send the PISOread and the press time
//triggeredCount global var hols the number of hits
winner=decideWinner(); //work out the winner, light the LED.
winnerCount=triggeredCount; //Remember the number of initial winners so we can display later
TMRtargetTime=TMRfirstRead+TMRsecondary; //set this up here as it is quicker than calculating every iteration
//target is 500ms (for example) ahead of now.
PISOmaskTarget=firstRead; //We need to mask out the original presses so we only see the differences.
//Now read for the next x milliseconds, capturing the presses
//we must be careful, becuase what is to say that [eg] user 3 does not press the button 5 times?
//So lets capture every change (up to 100 changes), then process it later.
#if (useSerialOutput)
Serial.println("...Starting secondary");
#endif
do {
do {
buttonResult=readPISO(0); //Read the PISO - TURN OFF SIMULATION
TMRtemp=millis();
breakout=(TMRtemp>=TMRtargetTime); //Have we timed out without a button being pressed
} while (((buttonResult==0) || (buttonResult==PISOmaskTarget)) && (breakout==false)); //While the read is not the same as before,
PISOmaskTarget=buttonResult;
if (!breakout) { //we have not timed out, but have a different button press the last time.
//no processing, just look store the raw data for now
#if (useSerialOutput)
Serial.printf("Secondary PISO read # %d=%d",PISOreadCount, buttonResult);
Serial.println();
#endif
if (PISOreadCount<50) { //only collect the first x reads
PISOreads[PISOreadCount]=buttonResult;
PISOtime[PISOreadCount]=TMRtemp;
PISOreadCount++;
} else {
#if (useSerialOutput)
Serial.println("Secondary Read Overflow");
#endif
}
} else {
#if (useSerialOutput)
Serial.println("Secondary Read Timed Out");
#endif
}
} while (millis()<TMRtargetTime);
#if (useSerialOutput)
Serial.println("Secondary Read Completed");
#endif
//We know the number of initial presses (and we have decided a winner).
//now it is not time sensitive, we we now need to decode our secondary data
//and create a master list. They are already in order.
//first, move the winner to the top
if (winnerCount>1) {
temp=triggered[0];
triggered[0]=triggered[winner];
triggered[winner]=temp;
//we dont need to change the times as they are the same
}
for (lp=0;lp<PISOreadCount;lp++) {
//loop through our list of secondary results and add them to the first list, under the winners
#if (useSerialOutput)
Serial.println("Calculating Secondaries "+String(lp));
#endif
whichButton(PISOreads[lp],PISOtime[lp]);
}
#if (useSerialOutput)
Serial.printf("Total button triggers %d",triggeredCount);
Serial.println();
#endif
//Serial.println(playernames[4]);
Serial.println();
Serial.println();
Serial.println();
Serial.println("----------------------------------");
Serial.println(" TFT screen output ");
Serial.println("----------------------------------");
lptarget=2;
if (triggeredCount-1<2) {
lptarget=triggeredCount-1;
}
TFTshowNames(0);
for (lp=1;lp<=lptarget;lp++) { //only show the 1st, 2nd and 3rd here but update the web results page with the full data.
//
#if (useSerialOutput)
Serial.println("LP="+String(lp));
#endif
TFTshowNames(lp);
}
}
void TFTshowNames(byte which) {
float TMRtime;
float TMRtemp;
String prefix;
String suffix;
if (which==0) { //this is the winner
//change colour to Yellow here
TMRtime=(TMRtriggered[0]-TMRstart)/1000; //that gives us the winners time
Serial.println(teams[(triggered[0]<4)]);
prefix=" ";
suffix="";
} else {
//change colour to Blue here
TMRtemp=(TMRtriggered[0]-TMRstart)/1000; //that gives us the winners time
TMRtime=((TMRtriggered[which]-TMRstart)/1000)-TMRtemp; //leaves the difference
prefix="+";
if (TMRtime==0) {
suffix="*";
}
}
Serial.println(playernames[triggered[which]]);
Serial.printf("%s %.3f seconds %s", prefix, TMRtime, suffix);
Serial.println();
}
byte decideWinner() {
byte TMPwinner;
if (triggeredCount==1) { //There is no argument, so just display the winner
LEDsByBit(triggered[0],1);
TMPwinner=0;
} else {
//Now to decide who the winner is and light their LED
TMPwinner=random(0,triggeredCount);
LEDsByBit(triggered[TMPwinner],1);
}
return TMPwinner;
}
void waitForUnlock () {
byte btn=0;
buttonResult=0; //clear the flag. Nothing is pressed
#if (useSerialOutput)
Serial.println("----> Waiting for Unlock");
#endif
digitalWrite(ledUnlock,1);
//now hang here and wait until someone presses a button.
do {
buttonResult=digitalRead(btnUnlock);
} while (buttonResult==0); //wait for the Unlock button.
if (digitalRead(btnRestart)!=0) {
#if (useSerialOutput)
Serial.println("***** RESTARTING *****");
#endif
LEDsByByte(255); //Light up all of the buttons
delay(1000) ; //Keep them alight for a second
resetButtons();
}
LEDsByByte(0);
clearLatch();
delay(500); //half second delay just to stop any bounceback
readPISO(0); //Clear any hits that are in the buffer
digitalWrite(ledUnlock,0);
TMRstart=millis();
}
int readPISO(bool simulate) {
byte dataRead;
//Read the 74HC165 and put the result in dataRead, ready to return
digitalWrite(PWR_load_PISO, LOW); // Write pulse to load pin
delayMicroseconds(5);
digitalWrite(PWR_load_PISO, HIGH);
delayMicroseconds(5);
// Get data from 74HC165
digitalWrite(PWR_clockIn_PISO, LOW);
digitalWrite(PWR_clockEnablePin_PISO, LOW);
dataRead=255-shiftIn(PWR_dataIn_PISO, PWR_clockIn_PISO, LSBFIRST); //Do the read. Always read from right to left.
digitalWrite(PWR_clockEnablePin_PISO, HIGH);
//Serial.println("DR="+String(dataRead));
#if (SIMmultiButtonPresses)
if (simulate) {//simulating multiple button presses
if (SIMbuttonData==0) {
dataRead=random(1,255);
} else {
dataRead=SIMbuttonData;
}
}
#endif
return dataRead;
}
void resetButtons() {
int temp;
temp=random(0,LEDshowMAX); //pick a light sequence
lightShow(temp);
readPISO(0); //Clear any hits that are in the buffer
}
void lightShow(byte whichSequence) {
//we do this in a little routine in case we want to do something
//flashy with the LEDS. For now, lets just set all LEDs to OFF
int lp;
int lpTarget;
lpTarget=LEDshow[whichSequence][0]; //how many in the loop
for (lp=1;lp<=lpTarget;lp++) {
LEDsByByte(LEDshow[whichSequence][lp]);
delay(LEDdelay[whichSequence]);
}
LEDsByByte(0); //Turn all leds off
}
void LEDsByBit(byte whichbit, byte bitvalue) { //this changes the bits in the global
byte temp;
bitWrite(temp, whichbit, bitvalue); //set the lamp
digitalWrite(latchPin_SIPO, LOW); // ST_CP LOW to keep LEDs from changing while reading serial data
shiftOut(dataPin_SIPO, clockPin_SIPO, MSBFIRST, temp); // Shift out the bits
digitalWrite(latchPin_SIPO, HIGH); // ST_CP HIGH change LEDs
}
void LEDsByByte(byte ledToLight) { // Turn all LEDS on or off at once without affecting the global
digitalWrite(latchPin_SIPO, LOW); // ST_CP LOW to keep LEDs from changing while reading serial data
shiftOut(dataPin_SIPO, clockPin_SIPO, MSBFIRST, ledToLight); // Shift out the bits
digitalWrite(latchPin_SIPO, HIGH); // ST_CP HIGH change LEDs
}
void whichButton(byte btnvalue, unsigned long TMRvalue) {
int lp;
byte btn;
//crude but quick. returns the button number rather than the bit value.
for (lp=7;lp>=0;lp--) {
if (bitRead(btnvalue,lp)!=0) {
btn=7-lp;
if (latch[btn]==false) { //only do this if we have not done it before
latch[btn]=true; //record that we have read this button
triggered[triggeredCount]=btn; //holds the number of the activated button
TMRtriggered[triggeredCount]=TMRvalue; //record the capture time
triggeredCount++;
}
}
}
//We dont need to return anything becuase it is held in the global triggeredCount.
}
void clearLatch() {
int lp;
for (lp=7;lp>=0;lp--) {
latch[lp]=0;
}
}