John Bradnam
Published © GPL3+

Tiny Space Impact Game

A rebuild of the Space Impact Rocket Game by Mohd Sohail. New hardware, 3D printed case, improved software, battery or externally powered.

IntermediateFull instructions provided8 hours450
Tiny Space Impact Game

Things used in this project

Hardware components

Microchip ATtiny1614 Microprocessor
×1
Standard LCD - 16x2 White on Blue
Adafruit Standard LCD - 16x2 White on Blue
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
7mm Shaft
×5
Passive Components
3 x 10k, 1 x 22k, 1 x330R 0805 resistors 2 x 0.1uF 0805 ceramic capacitors 1 x 10uF 1206 ceramic capacitor 1 x 47uF/10V 3528 tantalum capacitor
×1
LM1117-33
SOT223 3.3V regulator
×1
DC POWER JACK 2.1MM BARREL-TYPE PCB MOUNT
TaydaElectronics DC POWER JACK 2.1MM BARREL-TYPE PCB MOUNT
SMD variant
×1
Single Turn Potentiometer- 10k ohms
Single Turn Potentiometer- 10k ohms
×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

STL files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

SpaceImpactV2.ino

C/C++
/**************************************************************************
 Space Impact Game

 Author: John Bradnam (jbrad2089@gmail.com)
 
 Modified code from Space Impact LCD game by MOHD SOHAIL
 (https://www.youtube.com/channel/UCaXI2PcsTlH5g0et67kdD6g)
 (https://www.hackster.io/mohammadsohail0008/space-impact-lcd-game-ce5c74)
 
 2021-07-21
  - Create program for ATtiny1614
  - Replaced joystick with X-Pad
  - Made fire and movement processing interrupt driven
  - Made LCD backlight and power enabled via code
  - Added processor sleep mode for battery powered systems
  - Added more sounds
  - Added EEPROM functions to store high score 
 
 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 8MHz (if using external power) or 1MHz (if using a battery)
  millis()/micros() Timer: "TCD0 (1-series only, default there)
  Programmer: jtag2updi (megaTinyCore)

  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)
              +--------+
  
 **************************************************************************/

#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <LiquidCrystal.h>
#include <TimerFreeTone.h>  // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home
#include <EEPROM.h>
#include "button.h"

//LCD Screen
#define LCD_RS 0   //PA4
#define LCD_EN 1   //PA5
#define LCD_D4 2   //PA6
#define LCD_D5 3   //PA7
#define LCD_D6 4   //PB3
#define LCD_D7 5   //PB2

//Switches
#define SW_FIRE 6  //PB1
#define SW_XPAD 10 //PA3

//Other
#define SPEAKER 7  //PB0
#define LIGHT 8    //PA1
#define POWER 9    //PA2
 
//Initialize the LCD
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

//Buttons
enum buttonEnum { SW_NONE, SW_LEFT, SW_RIGHT, SW_DOWN, SW_UP };
Button* leftButton;
Button* rightButton;
Button* downButton;
Button* upButton;
Button* fireButton;

//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
  uint32_t magic;
  uint32_t highScore;
} EEPROM_DATA;

EEPROM_DATA EepromData;       //Current EEPROM settings


//Logical screen
volatile uint8_t area[4][15] = 
{
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};

#define CUSTOM_CHARACTERS 8
const byte cc[CUSTOM_CHARACTERS][8] =
{  
  {B11100, B01111, B11100, B00000, B00000, B00000, B00000, B00000},
  {B00000, B00000, B00000, B00000, B11100, B01111, B11100, B00000},
  {B11100, B01111, B11100, B00000, B11100, B10100, B11100, B00000},
  {B11100, B10100, B11100, B00000, B11100, B01111, B11100, B00000},
  {B11100, B10100, B11100, B00000, B11100, B10100, B11100, B00000},
  {B00000, B00000, B00000, B00000, B00100, B10010, B01000, B00000},
  {B00100, B10010, B01000, B00000, B11100, B10100, B11100, B00000},
  {B11100, B10100, B11100, B00000, B00100, B10010, B01000, B00000}
};

#define SLEEP_TIMEOUT 15000
unsigned long sleepTimeOut;

#if (F_CPU == 1000000L)
  //A 1MHz clock uses less battery power when running
  #define MAX_GAME_DELAY 50
  #define MIN_GAME_DELAY 10
  #define STEP_GAME_DELAY 5
#else
  //Assume 8MHz if running via external power
  #define MAX_GAME_DELAY 200
  #define MIN_GAME_DELAY 50
  #define STEP_GAME_DELAY 5
#endif

volatile uint8_t fireLoad = 0;
volatile uint8_t fireConsumption = 0;
volatile bool xPadButtonDown = false;
volatile bool gameover = true;

//-------------------------------------------------------------------------
// Initialise Hardware
void setup() 
{
  pinMode(POWER, OUTPUT);
  digitalWrite(POWER, HIGH);
  pinMode(LIGHT, OUTPUT);
  digitalWrite(LIGHT, HIGH);
  pinMode(SPEAKER, OUTPUT);

  //Initialise buttons
  leftButton = new Button(SW_LEFT, SW_XPAD, 250, 399, false); //22K/10K = 320
  rightButton = new Button(SW_RIGHT, SW_XPAD, 520, 700, false); //22K/30K = 590
  downButton = new Button(SW_DOWN, SW_XPAD, 400, 519, false); //22K/20K = 487
  upButton = new Button(SW_UP, SW_XPAD, 0, 100, false); //GND = 0
  fireButton = new Button(SW_FIRE);
  attachInterrupt(SW_FIRE, fireButtonInterrupt, CHANGE);    //Used to wake up processor and to fire bullet

  //Set up background player control
  TCB1.CCMP = 10000;
  TCB1.INTCTRL = TCB_CAPT_bm;
  TCB1.CTRLA = TCB_ENABLE_bm;

  //Get last high score
  readEepromData();
  
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  //Define custom characters
  for (int i = 0; i < CUSTOM_CHARACTERS; i++)
  {
    lcd.createChar(i, &cc[i][0]);
  }
  lcd.home();
}

//--------------------------------------------------------------------
// Handle pin change interrupt when SW_FIRE is pressed
void fireButtonInterrupt()
{
  if (!gameover && fireButton->State() == LOW && fireLoad >= fireConsumption) {
    fireLoad -= fireConsumption;
    for (uint8_t y=0; y<4; y++) 
    {
      for (uint8_t x=0; x<14; x++) 
      {
        if (area[y][x]==1) // spaceship
        { 
          area[y][x+1] += 4;
        }
      }
    }
  }
}

//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each mS - output segments
ISR(TCB1_INT_vect)
{
  if (xPadButtonDown)
  {
    xPadButtonDown = (leftButton->State() == HIGH || rightButton->State() == HIGH || upButton->State() == HIGH || downButton->State() == HIGH);
  }
  else if (!gameover)
  {
    bool doBreak = false;
    for (uint8_t y=0; y<4; y++) 
    {
      for (uint8_t x=0; x<15; x++) 
      {
        if (area[y][x]==1) 
        {
          doBreak = true;
          if (leftButton->State() == HIGH && x > 0) 
          {
            area[y][x] = 0;
            area[y][x-1] += 1;
            xPadButtonDown = true;
          } 
          else if (rightButton->State() == HIGH && x < 14) 
          {
            if (area[y][x+1]!=4) 
            {
              area[y][x] = 0;
              area[y][x+1] += 1;
              xPadButtonDown = true;
            }
          } 
          else if (upButton->State() == HIGH && y > 0) 
          {
            area[y][x] = 0;
            area[y-1][x] += 1;
            xPadButtonDown = true;
          } 
          else if (downButton->State() == HIGH && y < 3) 
          {
            area[y][x] = 0;
            area[y+1][x] += 1;
            xPadButtonDown = true;
          }
        }
        if (doBreak) 
        {
          break;
        }
      }
      if (doBreak) 
      {
        break;
      }
    }
  }
  
  //Clear interrupt flag
  TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB1.CNT)
}

//-------------------------------------------------------------------------
// Main program loop
void loop()
{
  displayInitialScreens();
  sleepTimeOut = millis() + SLEEP_TIMEOUT;
  while(!fireButton->Pressed())
  {
    if (millis() > sleepTimeOut) 
    {
      Sleep();
      displayInitialScreens();
      sleepTimeOut = millis() + SLEEP_TIMEOUT;
      while (fireButton->State() == LOW); //Wait until fire button is released
    }
  };

  //Setup the game
  lcd.clear();
  delay(500);
  playStartRound(); 

  //Clear playing field
  for (uint8_t y=0; y<4; y++) 
  {
    for (uint8_t x=0; x<15; x++) 
    {
      area[y][x] = 0;
    }
  }
  area[0][0] = 1;         //Put ship in top left corner
  uint8_t sleep = MAX_GAME_DELAY;
  uint8_t junkRisk = 10;
  fireLoad = 0;
  fireConsumption = 9;
  unsigned long score = 0;
  uint8_t life = 3;
  unsigned long count = 0;

  lcd.setCursor(0,0);
  lcd.print(life);
  lcd.setCursor(0,1);
  lcd.print(fireLoad);
      
  draw();
  gameover = false;
  
  while (!gameover) 
  {
    for (uint8_t y=0; y<4; y++) 
    {
      for (uint8_t x=15; x>0; x--) 
      {
        if (area[y][x-1]==4) 
        {
          area[y][x-1] = 0;
          if (x<15) 
          {
            area[y][x] += 4;
          }
        }
      }
    }
    
    if (fireLoad<9) 
    {
      fireLoad++;
      lcd.setCursor(0,1);
      lcd.print(fireLoad);
    }

    draw();
    delay(sleep);
    
    for (uint8_t y=0; y<4; y++) 
    {
      for (uint8_t x=0; x<15; x++) 
      {
        if (area[y][x]==2) 
        {
          area[y][x] = 0;
          if (x>0) 
          {
            area[y][x-1] += 2;
          }
        }
      }
    }

    for (uint8_t y=0; y<4; y++) {
      if (random(100) < junkRisk) {
        area[y][14] += 2;
      }
    }
  
    draw();
    delay(sleep);

    for (uint8_t y=0; y<4; y++) 
    {
      for (uint8_t x=0; x<15; x++) 
      {
        if (area[y][x]==3) // collided ship
        { 
          area[y][x] = 1;
          blinkShip();
          life--;
          lcd.setCursor(0, 0);
          lcd.print(life);
          if(life==0) 
          {
            gameover = true;
            lcd.clear();
            lcd.setCursor(6, 0);
            lcd.print("GAME");
            lcd.setCursor(6, 1);
            lcd.print("OVER");
            playLoseSound();
            for (uint8_t y=0; y<2; y++) 
            {
              for (uint8_t x=0; x<16; x++) 
              {
                lcd.setCursor(x, y);
                lcd.print(char(1));
                delay(100);
                lcd.setCursor(x, y);
                lcd.print(" ");
              }
            }
            lcd.clear();
            lcd.setCursor(0, 0);
            lcd.print("SCORE:");
            lcd.setCursor(7, 0);
            lcd.print(score);
            lcd.setCursor(0, 1);
            lcd.print("BEST:");
            lcd.setCursor(7, 1);
            lcd.print(EepromData.highScore);
            if (score > EepromData.highScore)
            {
              EepromData.highScore = score;
              writeEepromData();
              playWinSound();
            }
            delay(1000);
            for (uint8_t y=0; y<2; y++) 
            {
              for (uint8_t x=0; x<16; x++) 
              {
                lcd.setCursor(x, y);
                lcd.print(char(1));
                delay(100);
                lcd.setCursor(x, y);
                lcd.print(" ");
              }
            }
            delay(1000);
          }
        } 
        else if (area[y][x]>4) 
        {
          for (uint8_t i=0; i<10; i++) 
          {
            digitalWrite(SPEAKER,HIGH);
            delay(3);
            digitalWrite(SPEAKER,LOW);
            delay(3);
          }
          score+=10;
          area[y][x] -= 6;
        }
      }
    }
  
    score++;
    count++;

    if (count % 100 == 0)
    {
      sleep = max(MIN_GAME_DELAY, sleep - STEP_GAME_DELAY);
      junkRisk +=3;
      fireConsumption--;
    }
  }
}

//-------------------------------------------------------------------------
// Transfer the logical screen to the physical screen
void draw() 
{
  for (uint8_t y=0; y<4; y+=2) 
  {
    for (uint8_t x=0; x<15; x++) 
    {
      lcd.setCursor(x+1, y/2);
      
      if (area[y][x]==1) 
      {
        if (area[y+1][x]==0) 
        {
          lcd.print(char(0));
        } 
        else if (area[y+1][x]==2 || area[y+1][x]==6 || area[y+1][x]==10)  // down obstacle
        {
          lcd.print(char(2));
        }
      } else if (area[y][x]==2 || area[y][x]==6 || area[y][x]==10) 
      {
        if (area[y+1][x]==0) 
        {
          lcd.write(0b11011111); //upper Small box
        } 
        else if (area[y+1][x]==1) 
        {
          lcd.print(char(3));
        } 
        else if (area[y+1][x]==2) 
        {
          lcd.print(char(4));
        }
        else if (area[y+1][x]==4 || area[y+1][x]==6 || area[y+1][x]==10) 
        {
          lcd.print(char(7));
        }
      } 
      else if (area[y][x]==4) 
      {
        if (area[y+1][x]==0) 
        {
          lcd.write(0b11011110); //bullet
        }
        else if (area[y+1][x]==2 || area[y+1][x]==6 || area[y+1][x]==10) 
        {
          lcd.print(char(6)); //bullet + junk
        }
      } 
      else if (area[y][x]==0) // above nothing 
      { 
        if (area[y+1][x]==0) // nothing below
        { 
          lcd.print(" ");
        } 
        else if (area[y+1][x]==1) // below ship
        { 
          lcd.print(char(1));
        } 
        else if (area[y+1][x]==2 || area[y+1][x]==6 || area[y+1][x]==10) 
        {
          lcd.write(0b10100001); //lower Small box
        } 
        else if (area[y+1][x]==4) 
        {
          lcd.print(char(5));
        }
      } else {
        lcd.print(" ");
      }
    }
  }
}

//-------------------------------------------------------------------------
// Flash the ship
void blinkShip() 
{
  for (uint8_t y=0; y<4; y++) 
  {
    for (uint8_t x=0; x<15; x++) 
    {
      if (area[y][x]==1) //Found ship
      {

        for (uint8_t i=0; i<3; i++) 
        {
          lcd.setCursor(x+1, y>1);
          if (y==0 || y==2) 
          {
            if (area[y+1][x]==0) 
            {
              lcd.print(" ");
            } 
            else 
            {
              lcd.write(0b10100001);
            }
          } 
          else 
          {
            if (area[y-1][x]==0) {
              lcd.print(" ");
            } 
            else 
            {
              lcd.write(0b11011111);
            }
          }
          for (uint8_t i=0; i<10; i++) 
          {
            digitalWrite(SPEAKER,HIGH);
            delay(25);
            digitalWrite(SPEAKER,LOW);
            delay(5);
          }
          
          lcd.setCursor(x+1, y>1);
          if (y==0 || y==2) 
          {
            if (area[y+1][x]==0) 
            {
              lcd.print(char(0));
            } 
            else 
            {
              lcd.print(char(2));
            }
          } 
          else 
          {
            if (area[y-1][x]==0) 
            {
              lcd.print(char(1));
            } 
            else 
            {
              lcd.print(char(3));
            }
          }
          for (uint8_t i=0; i<10; i++) 
          {
            digitalWrite(SPEAKER,HIGH);
            delay(25);
            digitalWrite(SPEAKER,LOW);
            delay(5);
          }
        }
      }
    }
  }
}

//--------------------------------------------------------------------
// Display Opening animation, instructions and "Press button to start" message
void displayInitialScreens()
{
  lcd.clear();
  for (uint8_t y=0; y<2; y++) 
  {
    for (uint8_t x=0; x<16; x++) 
    {
      lcd.setCursor(x, y);
      lcd.print(char(1));
      delay(100);
      lcd.setCursor(x, y);
      if (y==0) 
      {
        lcd.print("    SPACE       "[x]);
      } 
      else 
      {
        lcd.print("      IMPACT    "[x]);
      }
    }
  }
  delay(1000);
  
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("3 -> LIFE POINTS");
  lcd.setCursor(0,1);
  lcd.print("9 -> WEAPON LOAD");
  delay(3000);
  
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("  PRESS BUTTON  ");
  lcd.setCursor(0,1);
  lcd.print("    TO START    ");
}

//--------------------------------------------------------------------
// Set all LCD pins LOW or HIGH
void setLcdPins(int state) 
{
  static char Outputs[] = {LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7};
  for (int i=0; i<6; i++) 
  {
    pinMode(Outputs[i], state);
  }
}

//--------------------------------------------------------------------
// Put the processor to sleep
void Sleep() 
{
  TCB1.CTRLA = TCB1.CTRLA & ~TCB_ENABLE_bm; //Switch off background timer
  setLcdPins(INPUT);
  digitalWrite(LIGHT, LOW);
  digitalWrite(POWER, LOW);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
  sleep_enable();
  sleep_mode();                         // System actually sleeps here
  sleep_disable();                      // System continues execution here when watchdog timed out
  // Continue after sleep
  pinMode(POWER, OUTPUT);
  digitalWrite(POWER, HIGH);
  pinMode(LIGHT, OUTPUT);
  digitalWrite(LIGHT, HIGH);
  setLcdPins(OUTPUT);
  lcd.begin(16, 2);
  TCB1.CTRLA = TCB1.CTRLA | TCB_ENABLE_bm; //Switch on background timer
}

//-----------------------------------------------------------------------------------
//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 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)
  {
    EepromData.magic = EEPROM_MAGIC;
    EepromData.highScore =0;
    writeEepromData();
  }
}

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 10

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50

class Button
{
	public:
	//Simple constructor
	Button(int pin);
	Button(int name, int pin);
	Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true);

  //Background function called when in a wait or repeat loop
  void Background(void (*pBackgroundFunction)());
	//Repeat function called when button is pressed
  void Repeat(void (*pRepeatFunction)());
	//Test if button is pressed
	bool IsDown(void);
	//Test whether button is pressed and released
	//Will call repeat function if one is provided
	bool Pressed();
	//Return button state (HIGH or LOW) - LOW = Pressed
	int State();
  //Return button name
  int Name();

	private:
		int _name;
		int _pin;
		bool _range;
		int _low;
		int _high;
		bool _activeLow;
		void (*_repeatCallback)(void);
		void (*_backgroundCallback)(void);
};

Button.cpp

C/C++
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"

Button::Button(int pin)
{
  _name = pin;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin)
{
  _name = name;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow)
{
  _name = name;
  _pin = pin;
  _range = true;
  _low = analogLow;
  _high = analogHigh;
  _activeLow = activeLow;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
  _backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
  _repeatCallback = pRepeatFunction;
}

  bool Button::IsDown()
{
	if (_range)
	{
		int value = analogRead(_pin);
		return (value >= _low && value < _high);
	}
	else
	{
		return (digitalRead(_pin) == LOW);
	}
}

//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
  bool pressed = false;
  if (IsDown())
  {
    unsigned long wait = millis() + DEBOUNCE_DELAY;
    while (millis() < wait)
    {
      if (_backgroundCallback != NULL)
      {
        _backgroundCallback();
      }
    }
    if (IsDown())
    {
  	  //Set up for repeat loop
  	  if (_repeatCallback != NULL)
  	  {
  	    _repeatCallback();
  	  }
  	  unsigned long speed = REPEAT_START_SPEED;
  	  unsigned long time = millis() + speed;
      while (IsDown())
      {
        if (_backgroundCallback != NULL)
        {
          _backgroundCallback();
        }
    		if (_repeatCallback != NULL && millis() >= time)
    		{
    		  _repeatCallback();
    		  unsigned long faster = speed - REPEAT_INCREASE_SPEED;
    		  if (faster >= REPEAT_MAX_SPEED)
    		  {
    			  speed = faster;
    		  }
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		int value = analogRead(_pin);
		if (_activeLow)
		{
			return (value >= _low && value < _high) ? LOW : HIGH;
		}
		else 
		{
			return (value >= _low && value < _high) ? HIGH : LOW;
		}
	}
	else
	{
		return digitalRead(_pin);
	}
}

//Return current button name
int Button::Name()
{
	return _name;
}

Credits

John Bradnam

John Bradnam

141 projects • 167 followers
Thanks to MOHD SOHAIL.

Comments