/**
* TOUCH WHACK-A-MOLE
* Copyright to John Bradnam (jbrad2089@gmail.com)
*
* ATTiny1614 Pins mapped to Ardunio Pins
*
* +--------+
* VCC + 1 14 + GND
* (SS) 0 PA4 + 2 13 + PA3 10 (SCK)
* 1 PA5 + 3 12 + PA2 9 (MISO)
* (DAC) 2 PA6 + 4 11 + PA1 8 (MOSI)
* 3 PA7 + 5 10 + PA0 11 (UPDI)
* (RXD) 4 PB3 + 6 9 + PB0 7 (SCL)
* (TXD) 5 PB2 + 7 8 + PB1 6 (SDA)
* +--------+
*
* BOARD: ATtiny1614/1604/814/804/414/404/214/204
* Chip: ATtiny1614
* Clock Speed: 20MHz
* Programmer: jtag2updi (megaTinyCore)
*/
#include <EEPROM.h>
#include <TimerFreeTone.h> // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home
#define DEBUG //Comment out when ready to release
//My touch switch has a bad 4 key so I am going to ensure that isn't selected as a mole
//Comment out the next line if yours i OK
#define BAD_KEY_MASK 0x0010
#define COL_1 0 //PA4 - Column 1
#define COL_2 1 //PA5 - Column 2
#define COL_4 2 //PA6 - Column 4
#define COL_3 3 //PA7 - Column 3
#define ROW_2 4 //PB3 - Row 2
#define ROW_1 5 //PB2 - Row 1
#define ROW_4 6 //PB1 - Row 4
#define ROW_3 7 //PB0 - Row 3
#define SPEAKER 8 //PA1 - Speaker
#define SDO 9 //PA2 - Touch Switch SDO
#define SCL 10 //PA3 - Touch Switch SCL
#define BUZZER_PORT PORTA.OUT
#define BUZZER_MASK PIN1_bm
struct LED
{
uint8_t row;
uint8_t col;
};
LED leds[] = {
{ROW_1,COL_1},{ROW_1,COL_2},{ROW_1,COL_3},{ROW_1,COL_4},
{ROW_2,COL_1},{ROW_2,COL_2},{ROW_2,COL_3},{ROW_2,COL_4},
{ROW_3,COL_1},{ROW_3,COL_2},{ROW_3,COL_3},{ROW_3,COL_4},
{ROW_4,COL_1},{ROW_4,COL_2},{ROW_4,COL_3},{ROW_4,COL_4}
};
//Refresh timer for LEDs
//ATtiny1614's default clock is 20MHz(RC)
//CLK_PER is 3.333MHz ( default prescaler rate is 6, then 20/6 == 3.3 )
//interrupt interval = 4ms ( 6,667 / 3.333MHz = 0.002sec )
//2ms * 16 LEDs = 32mS or a refresh rate of 31 times / sec
#define TCB_COMPARE 6667
uint16_t ledBuffer = 0;
uint8_t nextLed = 0;
LED lastLed = {0,0};
//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
uint32_t magic;
uint32_t seed;
uint8_t level; //Last level reached
} EEPROM_DATA;
EEPROM_DATA EepromData; //Current EEPROM settings
//Game data
long levelTimeout;
int levelHitCount;
uint16_t lastMoleLitMask;
uint16_t lastKeyMask;
//-------------------------------------------------------------------------
//Initialise Hardware
void setup()
{
pinMode(SPEAKER, OUTPUT);
pinMode(COL_1, OUTPUT);
pinMode(COL_2, OUTPUT);
pinMode(COL_3, OUTPUT);
pinMode(COL_4, OUTPUT);
pinMode(ROW_1, OUTPUT);
pinMode(ROW_2, OUTPUT);
pinMode(ROW_3, OUTPUT);
pinMode(ROW_4, OUTPUT);
digitalWrite(COL_1, LOW);
digitalWrite(COL_2, LOW);
digitalWrite(COL_3, LOW);
digitalWrite(COL_4, LOW);
digitalWrite(ROW_1, HIGH);
digitalWrite(ROW_2, HIGH);
digitalWrite(ROW_3, HIGH);
digitalWrite(ROW_4, HIGH);
//Set up display refresh timer
TCB0.CCMP = TCB_COMPARE;
TCB0.INTCTRL = TCB_CAPT_bm;
TCB0.CTRLA = TCB_ENABLE_bm;
//Enable interrupts
sei();
//To ensure every game from startup is different, store a new random seed for the
//next time the unit starts
readEepromData();
randomSeed(EepromData.seed);
EepromData.seed = EepromData.seed + random(0xffffffff);
writeEepromData();
}
//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each 2mS - output leds
ISR(TCB0_INT_vect)
{
//Turn off last LED displayed
if (lastLed.row != 0)
{
digitalWrite(lastLed.row,HIGH);
digitalWrite(lastLed.col,LOW);
lastLed.row = 0;
}
//Get bit mask of next LED to show
uint16_t mask = 1 << nextLed;
//Show LED if on
if (ledBuffer & mask)
{
lastLed.row = leds[nextLed].row;
lastLed.col = leds[nextLed].col;
digitalWrite(lastLed.row,LOW);
digitalWrite(lastLed.col,HIGH);
}
//Increase nextLed for next interrupt period
nextLed = (nextLed + 1) & 0x0F;
//Clear interrupt flag
TCB0.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB0.CNT)
}
//-----------------------------------------------------------------------------------
// Main loop
void loop()
{
ledBuffer = 0; //Clear all leds
levelHitCount = 0;
lastMoleLitMask = 0;
lastKeyMask = 1;
playStartRound(); //Show user game has started
//Calculate how long player has to hit moles
levelTimeout = millis() + (16 - EepromData.level) * 1000;
while (millis() < levelTimeout && levelHitCount < 16)
{
//Only change mole location if user pressed a button
if (lastKeyMask != 0)
{
//Clear last led lit
ledBuffer &= ~lastMoleLitMask;
//get a random mole location that doesn't match the last one
lastMoleLitMask = getRandomMoleMask(lastMoleLitMask);
//light up new location
ledBuffer |= lastMoleLitMask;
}
//Get user response
lastKeyMask = getKeyMask();
if (lastMoleLitMask != 0 && lastKeyMask == lastMoleLitMask)
{
playHitTone();
levelHitCount++;
}
else if (lastKeyMask != 0)
{
playMissTone();
levelHitCount = max(levelHitCount - 1, 0);
}
}
playEndRound();
//Calculate result (0 to 4 - decrease level, 5 to 12 - no level change, 13 to 16 - increase level
if (levelHitCount > 12)
{
//increase level to a maximum of level 15
EepromData.level = min(EepromData.level + 1, 15);
writeEepromData();
}
else if (levelHitCount <= 4)
{
EepromData.level = max(EepromData.level - 1, 0);
writeEepromData();
}
showResults(levelHitCount);
delay(500);
}
//-------------------------------------------------------------------------------------
//returns key number or 0 if no key pressed
int getKey()
{
int key = 0; //default to no keys pressed
pinMode(SCL, OUTPUT);
digitalWrite(SCL, HIGH);
pinMode(SDO, INPUT);
delay(2); //ensure data is reset to first key
for(int i = 1;i < 17;i++)
{
digitalWrite(SCL, LOW); //toggle clock
digitalWrite(SCL, HIGH);
if (!digitalRead(SDO))
{
key = i; //valid data found
break;
}
}
return key;
}
//-------------------------------------------------------------------------------------
//returns key mask or 0 if no key pressed
uint16_t getKeyMask()
{
uint16_t key = 0; //default to no keys pressed
pinMode(SCL, OUTPUT);
digitalWrite(SCL, HIGH);
pinMode(SDO, INPUT);
delay(2); //ensure data is reset to first key
uint16_t mask = 0x0001;
for(int i = 0;i < 16;i++)
{
digitalWrite(SCL, LOW); //toggle clock
digitalWrite(SCL, HIGH);
if (!digitalRead(SDO))
{
key = mask; //valid data found
break;
}
mask = mask << 1;
}
return key;
}
//-----------------------------------------------------------------------------------
//Returns a different mole square mask from the last one
// last - moles that shouldn't be selected
// returns new mole to light up
uint16_t getRandomMoleMask(uint16_t last)
{
uint16_t mask = 1 << random(16);
#ifdef BAD_KEY_MASK
while (mask == last || mask == BAD_KEY_MASK)
#else
while (mask == last)
#endif
{
mask = 1 << random(16);
}
return mask;
}
//-----------------------------------------------------------------------------------
//Show the results
//Lights up the LEDs with the number of hits.
//Plays losing sound for no hits and winning sound for 16 hits
void showResults(int hits)
{
//Clear all leds
ledBuffer = 0;
//Light up leds reflecting the number of hits
uint32_t mask = 0x0001;
int note = 100;
for (int i = 1; i <= 16; i++)
{
if (i <= hits)
{
//Show LED
ledBuffer |= mask;
}
else
{
//We are done lighting up the LEDs
break;
}
//Get mask for next LED to light up
mask = mask << 1;
//Play an ascending note for each led that lights up
TimerFreeTone(SPEAKER, note, 100);
note += 10;
TimerFreeTone(SPEAKER, note, 100);
note += 10;
}
//Play the win or lose sound
if (hits == 0)
{
playLoseSound();
}
else if (hits == 16)
{
playWinSound();
}
//Flash the LEDS
if (hits > 0)
{
uint16_t temp = ledBuffer;
ledBuffer = 0;
for (int i = 0; i < 10; i++)
{
ledBuffer = ledBuffer ^ temp;
delay(100);
}
ledBuffer = temp;
}
}
//-----------------------------------------------------------------------------------
//Play the sound for the end of a round
void playEndRound()
{
#define MAX_NOTE 4978 // Maximum high tone in hertz. Used for siren.
#define MIN_NOTE 31 // Minimum low tone in hertz. Used for siren.
for (int note = MAX_NOTE; note >= MIN_NOTE; note -= 5)
{
TimerFreeTone(SPEAKER, note, 1);
}
}
//-----------------------------------------------------------------------------------
//Play the sound for the start of a round
void playStartRound()
{
#define MAX_NOTE 4978 // Maximum high tone in hertz. Used for siren.
#define MIN_NOTE 31 // Minimum low tone in hertz. Used for siren.
for (int note = MIN_NOTE; note <= MAX_NOTE; note += 5)
{
TimerFreeTone(SPEAKER, note, 1);
}
}
//-----------------------------------------------------------------------------------
//Play a decaying sound
void playNoteDecay(int start)
{
#define DECAY_NOTE 100 // Minimum delta time.
for (int note = start; note >= DECAY_NOTE; note -= 10)
{
TimerFreeTone(SPEAKER, note, 100);
}
}
//-----------------------------------------------------------------------------------
//Play a attack sound
void playNoteAttack(int start)
{
#define DECAY_NOTE 100 // Minimum delta time.
for (int note = DECAY_NOTE; note <= start; note += 10)
{
TimerFreeTone(SPEAKER, note, 100);
}
}
//------------------------------------------------------------------
void playHitTone()
{
TimerFreeTone(SPEAKER, 300, 150);
}
//------------------------------------------------------------------
void playMissTone()
{
TimerFreeTone(SPEAKER, 50, 150);
}
//------------------------------------------------------------------
//Play a high note as a sign you lost
void playWinSound()
{
//TimerFreeTone(SPEAKER,880,300);
TimerFreeTone(SPEAKER,880,100); //A5
TimerFreeTone(SPEAKER,988,100); //B5
TimerFreeTone(SPEAKER,523,100); //C5
TimerFreeTone(SPEAKER,988,100); //B5
TimerFreeTone(SPEAKER,523,100); //C5
TimerFreeTone(SPEAKER,587,100); //D5
TimerFreeTone(SPEAKER,523,100); //C5
TimerFreeTone(SPEAKER,587,100); //D5
TimerFreeTone(SPEAKER,659,100); //E5
TimerFreeTone(SPEAKER,587,100); //D5
TimerFreeTone(SPEAKER,659,100); //E5
TimerFreeTone(SPEAKER,659,100); //E5
delay(250);
}
//------------------------------------------------------------------------------------------------------------------
//Play wah wah wah wahwahwahwahwahwah
void playLoseSound()
{
delay(400);
//wah wah wah wahwahwahwahwahwah
for(double wah=0; wah<4; wah+=6.541)
{
TimerFreeTone(SPEAKER, 440+wah, 50);
}
TimerFreeTone(SPEAKER, 466.164, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.939)
{
TimerFreeTone(SPEAKER, 415.305+wah, 50);
}
TimerFreeTone(SPEAKER, 440.000, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.662)
{
TimerFreeTone(SPEAKER, 391.995+wah, 50);
}
TimerFreeTone(SPEAKER, 415.305, 100);
delay(80);
for(int j=0; j<7; j++)
{
TimerFreeTone(SPEAKER, 391.995, 70);
TimerFreeTone(SPEAKER, 415.305, 70);
}
delay(400);
}
//---------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData()
{
//This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
EEPROM.put(EEPROM_ADDRESS,EepromData);
}
//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData()
{
//Eprom
EEPROM.get(EEPROM_ADDRESS,EepromData);
if (EepromData.magic != EEPROM_MAGIC)
{
Serial.println("Initialising EEPROM ...");
EepromData.magic = EEPROM_MAGIC;
EepromData.seed = millis();
EepromData.level = 0;
writeEepromData();
}
}
Comments