Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
John Bradnam
Published © GPL3+

Touch Tic-Tac-Toe

A 4x4 Tic-Tac-Toe game using a 4x4 touch pad and a ATtiny3216 CPU

IntermediateFull instructions provided8 hours242
Touch Tic-Tac-Toe

Things used in this project

Hardware components

ATTINY3216
Microchip ATTINY3216
×1
3 wire red/green 3mm LED
×16
16 Channel 4x4 Touch Sensor TTP229 Module
×1
MMBT3904 SOT-23 Transistor
×4
Small passive buzzer
×1
Passive components
Resistors: 4 x 1K 0805, 4 x 180R 0805, 4x 120R 0805; Capacitors: 1 x 0.1uF 0805, 1 x 10uF 1206
×1
Mini USB socket
Though hole variant
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic & PCB in Eagle format

Code

Touch_Tic_Tac_Toe_V2.ino

Arduino
/**
 * TOUCH 4x4 TIC-TAC-TOE
 * Copyright to John Bradnam (jbrad2089@gmail.com)
 * 
 * 2022-03-09 jlb
 *    - Initial coding
*/

#include "Game.h"
#include "Display.h"
#include "Keyboard.h"
#include "Sound.h"

#define FLASH_TIMEOUT 200   //Flash rate in mS
#define FLASH_COUNT 25      //Total number of flashes at end of game

//-------------------------------------------------------------------------
//Initialise Hardware
void setup() 
{
  setupSound();
  setupDisplay();
  setupKeyboard();

  //Have AI start the game
  setupGame(true);
}

//-----------------------------------------------------------------------------------
// Main loop
void loop()
{
  while (!gameOver)
  {
    int key = getKey();
    if (key != 0)
    {
      if (makeMoveForUser(key-1))
      {
        updateDisplay(false);
        beep(5);
        if (evaluateBoard() == USER)
        {
          playWinSound();
          gameOver = true;
        }
        else if (actualMoves == BOARD_SIZE)
        {
          playNoteDecay(440);
          gameOver = true;
        }
        else
        {
          makeMoveForAI();
          updateDisplay(false);
          beep(20);
          if (evaluateBoard() == AI)
          {
            playLoseSound();
            gameOver = true;
          }
          else if (actualMoves == BOARD_SIZE)
          {
            playNoteDecay(440);
            gameOver = true;
          }
        }
      }
    }
    delay(10);
  }

  bool flash = false;
  for (int i = 0; i < FLASH_COUNT; i++)
  {
    updateDisplay(flash);
    delay(FLASH_TIMEOUT);
    flash = !flash;
  }
  setupGame(false);
}

Game.h

C/C++
#pragma once

/*
 Game.h
 Tic-Tac-Toe AI
*/

//Comment out for 4x4 tic-tac-toe
//#define _3X3
//Comment out to store winning table in RAM (reduces CPU cyles in evaulation function)
//#define WINNING_MOVES_IN_RAM
//Comment out to use C version of evaluateBoard function
//#define EVAL_IN_ASSEMBLER

#include "Display.h"

#define FREE 0
#define DRAW 0
#define USER -1
#define AI 1
#define FULL -1
#define UNKNOWN -1

/*
 The winningMoves array lists all possible winning sequences
 Layout of LEDs
 --------------
 00 01 02 03
 04 05 06 07
 08 09 10 11
 12 13 14 15
*/

#ifdef _3X3
  #warning "3x3 board"
  #define BOARD_SIZE 9
  #define BOARD_DEPTH BOARD_SIZE    //Depth that minmax tree can go
  #define WIN_MOVES 8
  #ifdef WINNING_MOVES_IN_RAM
  const uint8_t winningMoves[WIN_MOVES][3] = {
  #else
  const uint8_t winningMoves[WIN_MOVES][3] PROGMEM = {
  #endif
    {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, 
    {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, 
    {0, 4, 8}, {2, 4, 6}
  };
  const uint8_t mapPhysical[BOARD_SIZE] = {0,1,2,4,5,6,8,9,10};
  const uint8_t mapLogical[16] = {0,1,2,16,3,4,5,16,6,7,8,16,16,16,16,16};
#else
  #warning "4x4 board"
  #define BOARD_SIZE 16
  #define BOARD_DEPTH 4         //Depth that minmax tree can go
  #define WIN_MOVES 19
  #ifdef WINNING_MOVES_IN_RAM
  const uint8_t winningMoves[WIN_MOVES][4] = {
  #else    
  const uint8_t winningMoves[WIN_MOVES][4] PROGMEM = {
  #endif    
    {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, {12, 13, 14, 15}, 
    {0, 4, 8, 12}, {1, 5, 9, 13}, {2, 6, 10, 14}, {3, 7, 11, 15}, 
    {0, 5, 10, 15}, {3, 6, 9, 12},
    {0, 1, 4, 5}, {1, 2, 5, 6}, {2, 3, 6, 7},
    {4, 5, 8, 9}, {5, 6, 9, 10}, {6, 7, 10, 11},
    {8, 9, 12, 13}, {9, 10, 13, 14}, {10, 11, 14, 15}
  };
#endif

int8_t board[BOARD_SIZE];     //Current board
int8_t totalMoves;            //Used to hold total moves in minmax tree
int8_t actualMoves;           //The actual number of moves played
int8_t actualDepth;           //The max depth that the minmax tree can go to
int8_t playerStarts;          //0 = computer, -1 = player; used to even out minmax depth
int8_t lastWinningMove;       //The winning move that caused the last win
bool gameOver;                //True when game is over
int8_t result;                //Function result defined as global to reduce local vars

//-------------------------------------------------------------------------
//Forward references

void setupGame(bool makeFirstMove);
void updateDisplay(bool flashOff);
bool makeMoveForUser(uint8_t move);
bool makeMoveForAI();
int8_t evaluteOutcomeForAI();
int8_t evaluteOutcomeForUser();
int8_t evaluateBoard();

//-------------------------------------------------------------------------
//Setup Game
void setupGame(bool makeFirstMove)
{
  //Clear board
  for (int i = 0; i < BOARD_SIZE; i++)
  {
    board[i] = FREE;
  }
  actualMoves = 0;
  gameOver = false;
  playerStarts = (makeFirstMove) ? 0 : -1; //When minmax depth is reduced, make depth even with who starts the game
  if (makeFirstMove)
  {
    makeMoveForAI();
  }
  updateDisplay(false);
}

//-----------------------------------------------------------------------------------
// Transfer board to display buffer
// flashOff - true to not light any winning sequence
void updateDisplay(bool flashOff)
{
  noInterrupts();
  ledBuffer = 0;
  int ix;
  bool w0, w1, w2, w3;
  for (int i = 0; i < BOARD_SIZE; i++)
  {
#ifdef WINNING_MOVES_IN_RAM
    w0 = (lastWinningMove == -1 || i == winningMoves[lastWinningMove][0]);
    w1 = (lastWinningMove == -1 || i == winningMoves[lastWinningMove][1]);
    w2 = (lastWinningMove == -1 || i == winningMoves[lastWinningMove][2]);
#else
    w0 = (lastWinningMove == -1 || i == pgm_read_byte(&winningMoves[lastWinningMove][0]));
    w1 = (lastWinningMove == -1 || i == pgm_read_byte(&winningMoves[lastWinningMove][1]));
    w2 = (lastWinningMove == -1 || i == pgm_read_byte(&winningMoves[lastWinningMove][2]));
#endif    
 
#ifdef _3X3
    ix = mapPhysical[i];
    if (!flashOff || (lastWinningMove != -1 &&  !w0 && !w1 && !w2))
    {
#else
#ifdef WINNING_MOVES_IN_RAM
    w3 = (lastWinningMove == -1 || i == winningMoves[lastWinningMove][3]);
#else
    w3 = (lastWinningMove == -1 || i == pgm_read_byte(&winningMoves[lastWinningMove][3]));
#endif    
    ix = i;
    if (!flashOff || (lastWinningMove != -1 &&  !w0 && !w1 && !w2 && !w3))
    {
#endif
      switch(board[i])
      {
        case USER: ledBuffer = ledBuffer | ((uint32_t)GRN_MASK << (ix << 1)); break;
        case AI: ledBuffer = ledBuffer | ((uint32_t)RED_MASK << (ix << 1)); break;
      }
    }
  }
  interrupts();
}

//-------------------------------------------------------------------------
// Make the users move
// Returns true if move was made, false if square is already taken
bool makeMoveForUser(uint8_t move)
{
#ifdef _3X3
  move = mapLogical[move];
  if (move == 16)
  {
    return false;
  }
#endif  

  bool result = false;
  if (board[move] == FREE)
  {
    board[move] = USER;                   //Make the move
    actualMoves++;                         //Keep count of all moves
    result = true;
  }
  return result;
}

//-------------------------------------------------------------------------
// Make a move
// Returns true if move was made, false if no moves left
// 2m 30sec
bool makeMoveForAI()
{
  int8_t bestMove = UNKNOWN;
  if (actualMoves == 0)
  {
    bestMove = random(BOARD_SIZE);      //Board empty so pick a square
  }
  else if (actualMoves == BOARD_SIZE)
  {
    return false;                       //No moves left
  }
  else
  {
    totalMoves = actualMoves;
    actualDepth = min(actualMoves + playerStarts + BOARD_DEPTH, BOARD_SIZE);   //Depth of minmax tree
    for (int i = 0; i < BOARD_SIZE; i++)
    {
      if (board[i] == FREE)
      {
        board[i] = AI;                    //Make move
        totalMoves++;
        //updateDisplay(false);
        //delay(500);
        //noInterrupts();
        result = evaluteOutcomeForUser(); //Find best USER counter move
        //interrupts();
        board[i] = FREE;                  //Restore board
        totalMoves--;
        if (result == AI)
        {
          bestMove = i;                 //No need to test other squares as this is a winning move
          break;
        }
        else if (result == DRAW)
        {
          bestMove = i;                 //Best outcome is a draw otherwise a loss
        }
      }
      if (bestMove == UNKNOWN)
      {
        bestMove = random(BOARD_SIZE);
        while (board[bestMove] != FREE)
        {
          bestMove = (bestMove + 1) % BOARD_SIZE;
        }
      }
    }
  }
  board[bestMove] = AI;                 //Make the best move we have
  actualMoves++;                        //Keep count of all moves
  return true;
}

//-------------------------------------------------------------------------
// Make a move and evaluate the outcome
// This uses the recursive minmax algorithm with pruning
// Returns DRAW if no winner, USER if user wins, AI if AI wins
// Note: result is defined as a global variable to save stack space during recursion
// Also the minmax function is split into two functions to save passing which player 
// that is being evaulated via a parameter which is the case when using a single function.

//__attribute__((optimize("O0")))
int8_t evaluteOutcomeForAI()
{
  int8_t bestResult = USER;

  result = evaluateBoard();
  if (result != DRAW || totalMoves == actualDepth)
  {
    return result;                        //User or AI has won
  }
  else
  {
    for (int i = 0; i < BOARD_SIZE; i++)
    {
      if (board[i] == FREE)
      {
        board[i] = AI;                    //Make move
        totalMoves++;
        result = evaluteOutcomeForUser(); //Find best AI move for this one
        board[i] = FREE;                  //Restore board
        totalMoves--;
        if (result == AI)
        {
          return AI;                      //No need to test other squares as this is a winning move
        }
        else if (result == DRAW)          //Next best result is a draw
        {
          bestResult = DRAW;
        }
      }
    }
  }
  return bestResult;
}

//__attribute__((optimize("O0")))
int8_t evaluteOutcomeForUser()
{
  int8_t bestResult = AI;

  result = evaluateBoard();
  if (result != DRAW || totalMoves == actualDepth)
  {
    return result;                        //User or AI has won
  }
  else
  {
    for (int i = 0; i < BOARD_SIZE; i++)
    {
      if (board[i] == FREE)
      {
        board[i] = USER;                 //Make move
        totalMoves++;
        result = evaluteOutcomeForAI();  //Find best AI move for this one
        board[i] = FREE;                 //Restore board
        totalMoves--;
        if (result == USER)
        {
          return USER;                   //No need to test other squares as this is a winning move
        }
        else if (result == DRAW)         //Next best result is a draw
        {
          bestResult = DRAW;
        }
      }
    }
  }
  return bestResult;
}

//-------------------------------------------------------------------------
// Tests whether either the user or AI has won
// Returns DRAW if no winner, USER if user won, AI if AI won

#ifdef EVAL_IN_ASSEMBLER

int8_t evaluateBoard()
{
  asm (

    //Backup board pointer for quick restoration
    "movw r6, r26          \n"    //1 cycle
"loop:                     \n"    
    //Copying address of next winningMoves block into Z
    "movw r30, r28         \n"    //1 cycle
    //Get the first board index into tmp
#ifdef WINNING_MOVES_IN_RAM    
    "ld __tmp_reg__, Z+    \n"    //2 cycles
#else    
    "lpm __tmp_reg__, Z+   \n"    //3 cycles
#endif    
    //Restore X pointer and add tmp
    "movw r26, r6          \n"    //1 cycle
    "add r26, __tmp_reg__  \n"    //1 cycle
    "adc r27, __zero_reg__ \n"    //1 cycle
    //store board[winningMoves[y][0]] into r2
    "ld r2, X              \n"    //2 cycles
    //if r2 is 0 (draw) skip this test
    "tst r2                \n"    //1 cycle
    "breq next             \n"    //1 (false), 2 (true) cycles
    //Now test if board[winningMoves[y][1]] into r2
#ifdef WINNING_MOVES_IN_RAM    
    "ld __tmp_reg__, Z+    \n"    //2 cycles
#else    
    "lpm __tmp_reg__, Z+   \n"    //3 cycles
#endif    
    "movw r26, r6          \n"    //1 cycle
    "add r26, __tmp_reg__  \n"    //1 cycle
    "adc r27, __zero_reg__ \n"    //1 cycle
    "ld r3, X              \n"    //2 cycles
    //board[winningMoves[y][1]] is now in r3 so compare with r2
    "cp r2, r3             \n"    //1 cycle
    //if not equal, skip this test
    "brne next             \n"    //1 (false), 2 (true) cycles
    //Now test if board[winningMoves[y][2]] into r2
#ifdef WINNING_MOVES_IN_RAM    
    "ld __tmp_reg__, Z+    \n"    //2 cycles
#else    
    "lpm __tmp_reg__, Z+   \n"    //3 cycles
#endif    
    "movw r26, r6          \n"    //1 cycle
    "add r26, __tmp_reg__  \n"    //1 cycle
    "adc r27, __zero_reg__ \n"    //1 cycle
    "ld r3, X              \n"    //2 cycles
    //board[winningMoves[y][2]] is now in r3 so compare with r2
    "cp r2, r3             \n"    //1 cycle
    //if not equal, skip this test
    "brne next             \n"    //1 (false), 2 (true) cycles
#ifndef _3X3    
    //Now test if board[winningMoves[y][3]] into r2
#ifdef WINNING_MOVES_IN_RAM    
    "ld __tmp_reg__, Z+    \n"    //2 cycles
#else    
    "lpm __tmp_reg__, Z+   \n"    //3 cycles
#endif    
    "movw r26, r6          \n"    //1 cycle
    "add r26, __tmp_reg__  \n"    //1 cycle
    "adc r27, __zero_reg__ \n"    //1 cycle
    "ld r3, X              \n"    //2 cycles
    //board[winningMoves[y][3]] is now in r3 so compare with r2
    "cp r2, r3             \n"    //1 cycle
    //if not equal, skip this test
    "brne next             \n"    //1 (false), 2 (true) cycles
#endif    
    
    //We have found a matching row
    "mov __tmp_reg__, r2   \n"    //1 cycle
    "rjmp done             \n"    //2 cycles
    
"next:                     \n"

#ifdef _3X3    
    //increase winningMoves by 3
    "adiw r28, 3           \n"    //2 cycles
#else    
    //increase winningMoves by 4
    "adiw r28, 4           \n"    //2 cycles
#endif    

    //decrease WIN_MOVES and loop until zero
    "dec %3                \n"    //1 cycle
    "brne loop             \n"    //1 (false), 2 (true) cycles

    //No matches so return DRAW
    "clr __tmp_reg__       \n"    //1 cycle

"done:                     \n"
    //return __tmp_reg__
    "mov %0, __tmp_reg__   \n"    //1 cycle
    
    : "=a" (result) 
    : "x" (board), "y" (winningMoves), "a" (WIN_MOVES)
    : "r2", "r3", "r6", "r7", "r30", "r31"
  );
  
  return result;
} 

#else
#ifdef WINNING_MOVES_IN_RAM    

// C version that the above assembly language routine replaces 
int8_t evaluateBoard(int* row)
{
  const uint8_t* cellPtr;
  lastWinningMove = -1;
  for (uint8_t y = 0; y < WIN_MOVES; y++)
  {
    cellPtr = (const uint8_t*)&winningMoves[y];
    result = board[*cellPtr++];
    if (result != FREE)
    {
      if (result == board[*cellPtr++])
      {
        if (result == board[*cellPtr++])
        {
#ifndef _3X3    
          if (result == board[*cellPtr++])
          {
#endif
            lastWinningMove = y;
            return result;
#ifndef _3X3    
          }
#endif          
        }
      }
    }
  }
  return DRAW;
}

#else

// C version that the above assembly language routine replaces 
int8_t evaluateBoard()
{
  const uint8_t* cellPtr;
  lastWinningMove = -1;
  for (uint8_t y = 0; y < WIN_MOVES; y++)
  {
    cellPtr = (const uint8_t*)&winningMoves[y];
    result = board[pgm_read_byte(cellPtr++)];
    if (result != FREE)
    {
      if (result == board[pgm_read_byte(cellPtr++)])
      {
        if (result == board[pgm_read_byte(cellPtr++)])
        {
#ifndef _3X3    
          if (result == board[pgm_read_byte(cellPtr++)])
          {
#endif          
            lastWinningMove = y;
            return result;
#ifndef _3X3    
          }
#endif          
        }
      }
    }
  }
  return DRAW;
}

#endif
#endif

Display.h

C/C++
#pragma once

/*
 Hardware.h
 LED control

 --------------------------------------------------------------------------
 LedBuffer 32 bits

 bits 31 to 16
 +-------+-------+-------+-------+-------+-------+-------+-------+
 |  15   |  14   |  13   |  12   |  11   |  10   |   9   |   8   |
 | G | R | G | R | G | R | G | R | G | R | G | R | G | R | G | R |
 +-------+-------+-------+-------+-------+-------+-------+-------+

  bits 15 to 0
 +-------+-------+-------+-------+-------+-------+-------+-------+
 |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0   |
 | G | R | G | R | G | R | G | R | G | R | G | R | G | R | G | R |
 +-------+-------+-------+-------+-------+-------+-------+-------+

 Layout of LEDs
 --------------
  00 01 02 03
  04 05 06 07
  08 09 10 11
  12 13 14 15

 --------------------------------------------------------------------------
 
*/
 
#include "Hardware.h"

struct LED
{
  uint8_t red;
  uint8_t grn;
}; 

#define COLS 4
#define ROWS 4
volatile uint8_t rowPins[ROWS] = {ROW_1,ROW_2,ROW_3,ROW_4};
volatile LED colPins[ROWS] = {{COL_1,COL_5},{COL_2,COL_6},{COL_3,COL_7},{COL_4,COL_8}};

//Adjust these to get orange when both red and green are lit
#define GRN_BRIGHT 4              //Brightness level (0 to 15)
#define RED_BRIGHT 15             //Brightness level (0 to 15)

/* Refresh timer for LEDs
 * ATtiny3216'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 )
 * 4ms * 4 Rows = 16mS or a refresh rate of 62.5 times / sec
*/
#define TCB_COMPARE 6667
#define RED_MASK 0x01
#define GRN_MASK 0x02
#define ON_MASK 0x03
volatile uint32_t ledBuffer = 0;
volatile uint8_t nextRow = 0;
volatile int lastRow = -1;
volatile uint8_t bamCounter = 0;           //Bit Angle Modulation variable to keep track of things
volatile uint8_t bamBit;                   //Used to store bit to test against brightness value

//-------------------------------------------------------------------------
//Forward references

void setupDisplay();

//-------------------------------------------------------------------------
//Initialise Hardware
void setupDisplay() 
{
  pinMode(COL_1, OUTPUT);
  pinMode(COL_2, OUTPUT);
  pinMode(COL_3, OUTPUT);
  pinMode(COL_4, OUTPUT);
  pinMode(COL_5, OUTPUT);
  pinMode(COL_6, OUTPUT);
  pinMode(COL_7, OUTPUT);
  pinMode(COL_8, 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(COL_5, LOW);
  digitalWrite(COL_6, LOW);
  digitalWrite(COL_7, LOW);
  digitalWrite(COL_8, LOW);
  digitalWrite(ROW_1, LOW);
  digitalWrite(ROW_2, LOW);
  digitalWrite(ROW_3, LOW);
  digitalWrite(ROW_4, LOW);
 
  //Set up display refresh timer
  TCB0.CCMP = TCB_COMPARE;
  TCB0.INTCTRL = TCB_CAPT_bm;
  TCB0.CTRLA = TCB_ENABLE_bm;

  //Enable interrupts
  interrupts();
}

//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each 2mS - output leds
ISR(TCB0_INT_vect)
{

  //This is 4 bit 'Bit angle Modulation' or BAM, 
  if (bamCounter == (ROWS * 1) || bamCounter == (ROWS * 3) || bamCounter == (ROWS * 7))
  {
    bamBit = bamBit << 1;
  }
  bamCounter++;

  //Turn off last row of LEDs
  if (lastRow != -1)
  {
    //Turn off columns
    for(int i=0; i < COLS; i++)
    {
      digitalWrite(colPins[i].red,LOW);
      digitalWrite(colPins[i].grn,LOW);
    }
    //Turn off row
    digitalWrite(rowPins[lastRow],LOW);
  }

  //Show next row of LEDs
  int row = nextRow * COLS;
  uint32_t mask = ledBuffer >> (row << 1);
  for(int i=0; i < COLS; i++)
  {
    //Show LED if on but only show if bamBit matches brightness level
    if ((mask & RED_MASK) && (bamBit & RED_BRIGHT))
    {
      digitalWrite(colPins[i].red, HIGH);
    }
    if ((mask & GRN_MASK) && (bamBit & GRN_BRIGHT))
    {
      digitalWrite(colPins[i].grn, HIGH);
    }
    mask = mask >> 2;
  }
  digitalWrite(rowPins[nextRow],HIGH);

  //Increase nextRow for next interrupt period
  lastRow = nextRow;
  nextRow = (nextRow + 1) & (ROWS-1);

  //Check if bamCount overflowed, reset if necessary
  if (bamCounter == (ROWS * 15)) 
  {
    bamCounter = 0;
    bamBit = 0x01;
  }

  //Clear interrupt flag
  TCB0.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB0.CNT)
}

Keyboard.h

C/C++
#pragma once

/*
 Keyboard.h
 Touch switch handler
*/
 
#include "Hardware.h"

//-------------------------------------------------------------------------
//Forward references

void setupKeyboard();
int getKey();

//-------------------------------------------------------------------------
//Initialise Hardware
void setupKeyboard() 
{
}

//-------------------------------------------------------------------------------------
//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;
}

Sound.h

C/C++
#pragma once

/*
 Sound.h
 Buzzer handler and game sounds
*/
 
#include "Hardware.h"
#include <TimerFreeTone.h>  // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home

//-------------------------------------------------------------------------
//Forward references

void setupSound();
void beep(int onTime);
void playSirenDown();
void playSirenUp();
void playNoteDecay(int start);
void playNoteAttack(int start); 
void playWinSound();
void playLoseSound();

//-------------------------------------------------------------------------
//Initialise Hardware
void setupSound() 
{
  pinMode(SPEAKER, OUTPUT);
}

//-----------------------------------------------------------------------------------
//Turn on and off buzzer quickly (onTime - 5 short, 20 long)
void beep(int onTime) 
{                                     // Beep and flash LED green unless STATE_AUTO
  BUZZER_PORT |= BUZZER_MASK;   // turn on buzzer
  delay(onTime);
  BUZZER_PORT &= ~BUZZER_MASK;  // turn off the buzzer
}

//-----------------------------------------------------------------------------------
//Play the siren sound
void playSirenDown() 
{
  #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 siren sound
void playSirenUp() 
{
  #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);
  }
}

//------------------------------------------------------------------
//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);
}

Hardware.h

C/C++
#pragma once

/*

 Hardware.h
 Definitions for ATtiny3216 pins
 
 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: 20pin tinyAVR 0/1/2 Series
  Chip: ATtiny3216
  Clock Speed: 20MHz Internal
  Programmer: jtag2updi (megaTinyCore)
  millis()/micros() Timer: TCD0

  ATtiny3216 Pins mapped to Ardunio Pins
                            _____
                    VDD   1|*    |20  GND
   (nSS)  (AIN4) PA4  0~  2|     |19  16~ PA3 (AIN3)(SCK)(EXTCLK)
          (AIN5) PA5  1~  3|     |18  15  PA2 (AIN2)(MISO)
   (DAC)  (AIN6) PA6  2   4|     |17  14  PA1 (AIN1)(MOSI)
          (AIN7) PA7  3   5|     |16  17  PA0 (AIN0/nRESET/UPDI)
          (AIN8) PB5  4   6|     |15  13  PC3
          (AIN9) PB4  5   7|     |14  12  PC2
   (RXD) (TOSC1) PB3  6   8|     |13  11~ PC1 (PWM only on 1-series)
   (TXD) (TOSC2) PB2  7~  9|     |12  10~ PC0 (PWM only on 1-series)
   (SDA) (AIN10) PB1  8~ 10|_____|11   9~ PB0 (AIN11)(SCL)

 */

#define COL_1 9 //PB0 - Column 1
#define COL_2 6 //PB3 - Column 2
#define COL_3 4 //PB5 - Column 3
#define COL_4 2 //PA6 - Column 4
#define COL_5 10 //PC0 - Column 5
#define COL_6 5 //PB4 - Column 6
#define COL_7 3 //PA7 - Column 7
#define COL_8 1 //PA5 - Column 8
#define ROW_1 8 //PB1 - Row 1
#define ROW_2 7 //PB2 - Row 2
#define ROW_3 11 //PC1 - Row 3
#define ROW_4 12 //PC2 - Row 4
#define SPEAKER 0 //PA4 - Speaker
#define SDO 15 //PA2 - Touch Switch SDO
#define SCL 16 //PA3 - Touch Switch SCL

#define BUZZER_PORT PORTA.OUT
#define BUZZER_MASK PIN4_bm

Credits

John Bradnam
147 projects • 181 followers

Comments