Stelian’s electronics projects

Hi. My name is Stelian Saracut and I’ve been an electronics enthusiast since I was born. I like working with both new and old tech. Most of my projects that I’ve made are presented on this website. The content of this website is totally free and Open Source / Open Hardware. I hope you find these projects inspiring.

Contact me : stelian@saracut.ro

Nixie clock (no weird russian chips)

Nixie clocks are really beautiful pieces of equipment, but interfacing high voltage (250 vdc) obsolete components with 5 V microcontrollers is not an easy task. I came across many designs on the internet that use obsolete chips to solve this problem, but these chips are expensive and really hard to find.

1

How it works

Main board

3

The main board is based on the Atmel ATMEGA328P-PU microcontroller. The code was written in Arduino IDE and an Arduino UNO was used for programming the micro. The date and time are set using three buttons (Set, +, -). The clock also has a RTC ( DS3231 ) that keeps running when the clock loses power. It uses a supercapacitor that is charged while the clock is plugged in. (it works about a month without external power)

main_layout

Power supply

The power supply consists of a 9 VAC wall adapter, a small 12 V – 220 V transformer (used to step up), a rectifier bridge and filter capacitor for the anode voltage and another rectifier bridge, filter cap and 7805 regulator for the micro, RTC and the driver.

Nixie driver

The data from the microcontroller comes as BCD (parallel). There are 4 wires that select which digit is to be displayed that go into a BCD decoder, then to the cathodes of the tubes through the cathode output stages that use one NPN transistor each. Since the tubes are multiplexed, all the corresponding cathodes are wired in parallel. The tubes are powered one at a time but because the refresh rate is high enough they look like they are permanently turned on. In order to select which tube is on I used the same principle as for the cathodes, but the output stages are different because the PNP transistors are on the hot side (250 V to the emitters). I used optocouplers to drive the PNP transistors.

nixie_layout

 

The code

Note : The micro runs Arduino code although it does not have an Arduino bootloader on it. (the bootloader is not needed)
The code first checks if there is valid date and time data in the RTC. If data is found, the Time library gets the time from the RTC, then the clock starts running. If no data is found, the tubes display “00” and the user should set the time using the buttons. (time is set on both the micro and the RTC, of course) The micro also updates its time from the RTC once a day because it is less precise than the temperature compensated RTC.
Time is displayed by first turning on one of the NPN (cathode) transistor through the decoder. Then, the corresponding tube is powered for a short period of time by applying 250 V to its anode through the decoder and the anode output stage. Then the algorhytm moves to the next tube and so on and cycles through all tubes in order to make them seem turned on continuously. This is done because you cannot turn multiple tubes on while they’re multiplexed. (google multiplexing for more detail) Once every 20 seconds the time shifts left one digit at a time and then the date shifts in from the right and stays for a few second, then the clock displays time again.

//Nixie clock with RTC
//Written by Stelian Saracut for Arduino 1.0.4
#include <Wire.h>
#include "RTClib.h"
#include <Time.h> 



RTC_DS1307 rtc;

int k;
int del = 200; //wait between digits
int del2 = 400; //wait
boolean cs=HIGH,ps=HIGH,disp=LOW;

void setup() {
  //initializing
  DDRD = B1111; //port D outputs to cathode decoder
  DDRB = B1110; //port B outputs to anode decoder
  PORTC = B110111; //enable pullups for I2C (RTC) and buttons on port C
  Wire.begin();
  rtc.begin();
  if (! rtc.isrunning())
    adjTime(); //manually set time
    else
    getRTCtime(); //get time from RTC
  startup(400); //just because it looks nice
  
}


void loop() {
  if ((hour() == 0)&&(second()==0)) getRTCtime(); //update time once a day
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW)) 
      adjTime(); //manually adjust time
   ps = cs;
  if (second() % 20 == 0) disp = HIGH;
   if (disp == HIGH) {
     //time shifts out and date shifts in
      if (k<=100) nixDisp(hour() % 10,minute()/10,minute() % 10,second()/10,second() % 10,11);
      if ((k>100) && (k<=200)) nixDisp(minute()/10,minute() % 10,second()/10,second() % 10,11,11); 
      if ((k>200) && (k<=300)) nixDisp(minute() % 10,second()/10,second() % 10,11,11,11); 
      if ((k>300) && (k<=400)) nixDisp(second()/10,second() % 10,11,11,11,11);    
      if ((k>400) && (k<=500)) nixDisp(second() % 10,11,11,11,11,11);     
      if ((k>500) && (k<=600)) nixDisp(11,11,11,11,11,11); //blank display   
      if ((k>600) && (k<=700)) nixDisp(11,11,11,11,11,day()/10);     
      if ((k>700) && (k<=800)) nixDisp(11,11,11,11,day()/10,day() % 10); 
      if ((k>800) && (k<=900)) nixDisp(11,11,11,day()/10,day() % 10,month()/10); 
      if ((k>900) && (k<=1000)) nixDisp(11,11,day()/10,day() % 10,month()/10,month() % 10); 
      if ((k>1000) && (k<=1100)) nixDisp(11,day()/10,day() % 10,month()/10,month() % 10,year() % 100 /10); 
      if ((k>1100) && (k<=1500)) nixDisp(day()/10,day() % 10,month()/10,month() % 10,year() % 100 /10,year() % 10); 
      if (k>1500) {
       disp = LOW; //back to normal operation
       k = 0;
     } 
    k++; 
   }
   else
    
     
      
   nixDisp(hour()/10,hour() % 10,minute()/10,minute() % 10,second()/10,second() % 10); //display time under normal operation
       
}

void getRTCtime() {
  DateTime now = rtc.now();
  setTime(now.hour(),now.minute(),now.second(),now.day(),now.month(),now.year());
  
} 

//function for adjusting time manually and button debouncing
void adjTime() {
  int H=0,M=0,D=1,Mo=1,Y=13;
  boolean cs1=HIGH,cs2=HIGH,ps1=HIGH,ps2=HIGH,cs=HIGH,ps=LOW;
  
 
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=H/10;
  PORTB = B1010;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=H%10;
  PORTB = B1000;
  
  
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         H--;
   if (H <0) H=23;    if ((cs2 != ps2)&&(cs2 == LOW))          H++;    if (H >23) H=0;
   ps1 = cs1;
   ps2 = cs2;
  
   delay(10);
  }
   
 
  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
 
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=M/10;
  PORTB = B110;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=M%10;
  PORTB = B100;

   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         M--;
   if (M < 0) M = 59;    if ((cs2 != ps2)&&(cs2 == LOW))          M++;    if (M > 59) M = 0;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }
  

  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=D/10;
  PORTB = B1010;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=D%10;
  PORTB = B1000;

   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         D--;
   if (D < 0) D = 31;    if ((cs2 != ps2)&&(cs2 == LOW))          D++;    if (D > 31) D = 0;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }
  
  
  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Mo/10;
  PORTB = B110;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Mo%10;
  PORTB = B100;
   
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         Mo--;
   if (Mo < 1) Mo = 12;    if ((cs2 != ps2)&&(cs2 == LOW))          Mo++;    if (Mo > 12) Mo = 1;
   ps1 = cs1;
   ps2 = cs2;
   delay(10);
  }


  
  cs1=HIGH; cs2=HIGH; ps1=HIGH; ps2=HIGH; cs=HIGH; ps=LOW;
  
  while(true) {
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Y/10;
  PORTB = B10;
  delay(10);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=Y%10;
  PORTB = B0;
   
   cs = debounce(ps,A0);
   if ((cs != ps) && (cs == LOW))
      break;
   ps = cs;
   
   cs1 = debounce(ps1,A1); 
   cs2 = debounce(ps2,A2); 
   if ((cs1 != ps1)&&(cs1 == LOW))
         Y--;
   if (Y < 0) Y = 99;    if ((cs2 != ps2)&&(cs2 == LOW))          Y++;    ps1 = cs1;    ps2 = cs2;    delay(10);   }   rtc.adjust(DateTime(Y,Mo,D,H,M,0));   setTime(H,M,1,D,Mo,Y);             }  //button debouncing boolean debounce(boolean last, int pin) {  boolean current = digitalRead(pin);  if (last != current) {    delay(5);    current = digitalRead(pin);  }  return current; } void nixDisp(int a, int b, int c, int d, int e, int f) {   PORTB = B1110;    delayMicroseconds(del2);    PORTD=f;     if (f > 9) PORTB = B1110;
    else
    PORTB = B0; 
  delayMicroseconds(del); 
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=e;
  if (e > 9) PORTB = B1110;
    else
    PORTB = B10;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=d;
  if (d > 9) PORTB = B1110;
    else
    PORTB = B100;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=c;
  if (c > 9) PORTB = B1110;
    else
    PORTB = B110;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD=b;
  if (b > 9) PORTB = B1110;
    else
    PORTB = B1000;
  delayMicroseconds(del);
  PORTB = B1110;
  delayMicroseconds(del2);
  PORTD =a;
  if (a > 9) PORTB = B1110;
    else
    PORTB = B1010;
  delayMicroseconds(del);
}


//some sort of counter that looks good
void startup(int t) {
  int a=0,b=1,c=2,d=3,e=4,f=5,i,j;
  for (j=1; j<=t; j++) {
    i++;
    if (i % 10 == 0) {
      a++; b++; c++;
      d++; e++; f++;
     }
    if (a == 10) a=0;
    if (b == 10) b=0;
    if (c == 10) c=0;
    if (d == 10) d=0;
    if (e == 10) e=0;
    if (f == 10) f=0;
  
    nixDisp(a,b,c,d,e,f); 
  } 
}

You can post my designs anywhere else as long as you provide a link to the original source or metion me as the author.

Click to download project files.

Contact me if you’re interested in buying fully assembled nixie clocks, driver boards or main boards.
Contact : Stelian Saracut – stelian@saracut.ro

Flappy chip (Arduino game)

Introduction

Flappy Chip is a small game running on the Arduino Uno
board (or standalone atmega328 microcontroller). The purpose of the game is to make the chip pass by as many tubes as you can without hitting them, by pressing the button which makes the chip climb (otherwise it falls). The score represents the number of tube pairs that have been avoided.

Hardware

The graphic LCD display that I used comes from an old Nokia 3310 cellphone. It is connected to the board using 5 resistive voltage deviders (one for each pin) which act as logic level converters. They are needed because the LCD driver works on 3.3 V whereas the microcontroller works on 5 V. The pins used for the display are digital pins 3 to 7 which have the following functions :

  • pin 3 – LCD reset (RST)
  • pin 4 – LCD chip select (CS)
  • pin 5 – Data/Command select (D/C)
  • pin 6 – Serial data out (DIN)
  • pin 7 – Serial clock out (SCLK)

The button is connected to pin 2, which is pulled up internally. When the button is pressed, it pulls the pin to the ground, which triggers an interrupt that changes the position of the chip.

 Software

Each graphic element was drawn in Paint, saved as a monochrome bitmap and converted to hex values using Image2Code. The bitmaps are stored in the Flash memory of the microcontroller. In order to put elements in front of the background, I had to make solid black bitmaps that are dislpayed negatively so that they clear the pixels of the background where the elements need to be displayed.(otherwise the front elements and the background mix) The whole graphic section is based on the Adafruit PCD8544 and GFX libraries, which makes it easy to use the Nokia display.

After displaying the startup image, the MCU waits for the user to press the button which starts the actual game. Then, the ISR that modifies the chip position is attached and the gameloop begins and runs as long as the chip ,,is alive”. Each time the gameloop is executed, the display is cleared, then the backround is displayed, followed by the chip. The maximum number of the tubes is 8 (4 pairs), since 4 sets of variables have been declared for this purpose. The game starts with one pair. The pairs are cycled in and out, gradually decreasing distance between the tubes and gradually adding more tubes until all four pairs are needed.  A series of if statements checks whether one of the tubes have gone offscreen and makes the corresponding variable false. Then if the rightmost tube is at the desired distance from the right margin of the screen, a new pair of tubes is reintroduced by reinitializing their corresponding variables. The vertical position is set at random. Another series of if statements moves the onscreen tubes to the left by one pixel. Afterwards, the tubes and the score are displayed. Then, the coordinates of the chip and the coordinates of the tubes are compared and in case of collision the alive variable is set to false and procedure dead is called which makes the chip fall. The chip is not allowed to hit the margins of the screen so if that happens it is considered ,,dead” as well. Finally, the score is incremented as each pair of tubes is avoided, the chip’s vertical position is incremented (so that it falls unless you press the button) and the delay keeps the game from running at lightning speed (or not really, with only 16 MHz clock speed). After the gameloop, the button interrupt is detached and the score is compared to the highscore (if there is one in the EEPROM). If there is no highscore, or your current score is higher than the highscore, a new higscore is written in the EEPROM so that it will not be lost when the MCU loses power. And just in case you become addicted to this game, you don’t have to worry about EEPROM wear out because a memory wear leveling algorithm writes the highscore at a different adress each time.

 The code


/*Written by Stelian Saracut and Ruxandra Avram

for the Nokia 3310 display driven by Adafruit's

PCD8544 library. Developped in Arduino version 1.5.2 */

#include &lt;Adafruit_GFX.h&gt;

#include &lt;Adafruit_PCD8544.h&gt;

#include &lt;EEPROM.h&gt;

Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);

boolean alive;

//becomes false when the chip touches a tube or the upper/lower margin of the display

int x1,x2,x3,x4,h1,h2,h3,h4,count = 1;

//x1,2... for current horizontal position of each tube

//h1,2... for current vertical position of each tube

//count counts each moment of the game in order to gradually increase difficulty

boolean x1os=true,x2os=false,x3os=false,x4os=false; //true if the coresponding tube is being displayed

//the first tube appears on screen when the game starts

byte dh, dv,score;

//dh - current horizontal distance between any 2 tubes (gradually decreases)

//dv - current vertical distance between any 2 tubes (gradually decreases)

volatile byte v;

//current vertical position of the chip

//decremented in the interrupt routine triggered by the pressed button, incremented once every gameloop execution

//so that the chip falls unless you press the button

#define XCHIP 30 //x position of chip (constant)

#define DHINIT 50 //initial horizontal distance between two tubes

#define DVINIT 35 //initial vertical distance between two tubes

#define DELAY 100 //game speed

//functions that check if the chip hits the tube by comparing coordinates

boolean in1 (int l, int r, int left, int right) {

return ((r&gt;=left)&amp;&amp;(l&lt;= right));

}

boolean in2(int d, int u, int down,int up) {

return ((u&lt;=up)||(d&gt;= down));

}

//returns maximum value of 4 parameters

int maxim (int n1, int n2, int n3, int n4) {

int m;

m = n1;

if (n2&gt;m) m=n2;

if (n3&gt;m) m=n3;

if (n4&gt;m) m=n4;

return m;

}

//bitmaps of the graphic elements

//lower tube contour

static unsigned char PROGMEM tubjos[] = {0xFF,0xF0,

0x80,0x10,

0xDA,0xB0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

};

//solid color lower tube

static unsigned char PROGMEM tubjosplin[] = {0xFF,0xF0,

0xFF,0xF0,

0xFF,0xF0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

};

//upper tube contour

static unsigned char PROGMEM tubsus[] = {0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0x5E,0x20,

0x6D,0xA0,

0x5E,0x60,

0x6D,0x20,

0x5E,0xA0,

0x6D,0x60,

0xDA,0xB0,

0x80,0x10,

0xFF,0xF0,

};

//solid color upper tube

static unsigned char PROGMEM tubsusplin[] = {0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0x7F,0xE0,

0xFF,0xF0,

0xFF,0xF0,

0xFF,0xF0};

//static background

static unsigned char PROGMEM fundal[] = {0x00,0x38,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x03,0xC0,

0x03,0xC7,0x80,0x03,0xF0,0x01,0xC3,0x80,0x00,0x04,0x30,

0x04,0x28,0x60,0x04,0x48,0x06,0x10,0x40,0xFC,0x08,0x80,

0x1A,0x81,0x18,0x18,0x04,0x08,0x00,0x33,0x03,0x14,0x20,

0x62,0x00,0x06,0x22,0x8A,0x10,0x00,0x8C,0x08,0xA0,0x80,

0x88,0x0C,0x21,0x48,0x21,0x68,0x91,0x10,0x84,0x48,0x20,

0x92,0x20,0x00,0x80,0x28,0x82,0x01,0x00,0x01,0x42,0x80,

0x00,0x84,0x20,0x42,0x00,0x00,0x24,0x24,0x28,0x00,0x00,

0x08,0x00,0x04,0x80,0x04,0x14,0x80,0x85,0x4A,0x10,0xA0,

0x60,0x21,0x10,0x10,0x81,0x28,0x00,0x11,0x11,0x15,0x00,

0x02,0x2A,0x02,0x15,0x24,0x00,0x29,0x20,0x40,0x42,0x40,

0x00,0x8C,0x80,0x44,0x40,0x85,0x40,0x40,0x04,0x41,0x00,

0x49,0x82,0x24,0x81,0x00,0x10,0x09,0x24,0xC1,0x02,0x20,

0x02,0x04,0x21,0x02,0x09,0x28,0x41,0x04,0x04,0x12,0x00,

0x09,0x22,0x04,0x91,0x00,0x40,0x08,0x03,0x20,0x90,0x20,

0x00,0x20,0x00,0x10,0x21,0x24,0xD0,0x48,0x41,0x0C,0x80,

0x40,0x19,0x20,0x0C,0x80,0x00,0x01,0x05,0x12,0x21,0x00,

0x82,0x42,0x41,0x21,0x08,0x03,0x32,0x90,0x20,0x14,0x40,

0x08,0x28,0x84,0x10,0x50,0x48,0x40,0x08,0x4A,0x42,0x80,

0x94,0x81,0x4A,0x40,0x81,0x04,0x10,0x00,0x00,0x00,0x00,

};

//small chip contour

static unsigned char PROGMEM chip[] = {0x55,0x40,

0xFF,0xE0,

0x80,0x20,

0x80,0x60,

0x80,0x20,

0xFF,0xE0,

0x55,0x40};

//small chip solid colour

static unsigned char PROGMEM chipplin[] = {0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0,

0xFF,0xE0};

//startup chip

static unsigned char PROGMEM start[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF0,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x60,

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

};

void setup() {

Serial.begin(9600);

PORTD = B100;

display.begin();

display.setContrast(55);

display.clearDisplay();

display.drawBitmap(0, 0,start, 84, 48, 1);

display.setCursor(6,20);

display.print("FLAPPY CHIP");

display.display();

delay(3000);

display.clearDisplay();

}

void loop() {

gameLoop();

finalScore();

}

void gameLoop() {

v = 20;

x1 = 84;

x2 = 0;

x3 = 0;

x4 = 0;

score = 0;

count = 1;

x1os = true;

x2os = false;

x3os = false;

x4os = false;

dh = DHINIT;

dv = DVINIT;

beginning();

while (digitalRead(2) == HIGH) ; //waits for the button to be pressed

alive = true;

attachInterrupt(0, button, FALLING); //attaching the ISR that makes the chip climb when the button is pressed (on the falling edge of digital pin 2)

while (alive) {

randomSeed(analogRead(A1)); //feeding the random number generator a "random" value by reading an unused analog pin

dispChip(); //displays the chip

//checks if the tubes go offscreen

if (x1&lt;-12) {

x1=0;

x1os=false;

}

if (x2&lt;-12) {

x2=0;

x2os=false;

}

if (x3&lt;-12) {

x3=0;

x3os=false;

}

if (x4&lt;-12) {

x4=0;

x4os=false;

}

//reintroduces tubes

if ((x1os == false) &amp;&amp; ((84-maxim(x1,x2,x3,x4)) == dh)) {

x1=84;

h1 = random(5,35);

x1os = true;

}

if ((x2os == false) &amp;&amp; ((84-maxim(x1,x2,x3,x4)) == dh)) {

x2=84;

h2 = random(5,35);

x2os = true;

}

if ((x3os == false) &amp;&amp; ((84-maxim(x1,x2,x3,x4)) == dh)) {

x3=84;

h3 = random(5,35);

x3os = true;

}

if ((x4os == false) &amp;&amp; ((84-maxim(x1,x2,x3,x4)) == dh)) {

x4=84;

h4 = random(5,35);

x4os = true;

}

//moves the tubes to the left

if (x1os) x1--;

if (x2os) x2--;

if (x3os) x3--;

if (x4os) x4--;

count++;

dispTube();

display.print("           ");

display.print(score);

display.display();

//gradually decreases horizontal and vertical distance between tubes

if (count%50 == 0) {

dv = dv-1;

dh= dh-1;

}

//checks if the chip hits the tubes

if (x1os)

if (in1(XCHIP,XCHIP+11,x1,x1+12))

if (in2(v+7,v,h1+dv,h1)) {

dead(v);

alive = false;

}

if (x2os)

if (in1(XCHIP,XCHIP+11,x2,x2+12))

if (in2(v+7,v,h2+dv,h2)) {

dead(v);

alive = false;

}

if (x3os)

if (in1(XCHIP,XCHIP+11,x3,x3+12))

if (in2(v+7,v,h3+dv,h3)) {

dead(v);

alive = false;

}

if (x4os)

if (in1(XCHIP,XCHIP+11,x4,x4+12))

if (in2(v+7,v,h4+dv,h4)) {

dead(v);

alive = false;

}

if (v &gt; 41) alive = false; //checks if the chip hits the lower screen margin

if ((v &gt;=250) &amp;&amp; (v &lt;=255)) //checks if the chip hits the upper screen margin

dead(0);

//increments the score as you pass by each tube

if ((x1os &amp;&amp; (x1==XCHIP))||(x2os &amp;&amp; (x2==XCHIP))||(x3os &amp;&amp; (x3==XCHIP))

|| (x4os &amp;&amp; (x4==XCHIP))) score++;

v += 1; //makes the chip fall

delay(DELAY);

}

detachInterrupt(0);

}

//ISR that runs each time you press the button

void button ()  {

v -= 4;

delayMicroseconds(5000);

}

//display the chip

void dispChip () {

display.clearDisplay();

display.drawBitmap(0, 33,fundal, 84, 20, 1);

display.drawBitmap(XCHIP, v,chipplin, 11, 7, 0);

display.drawBitmap(XCHIP, v,chip, 11, 7, 1);

}

//display the tubes

void dispTube () {

if (x1os) {

display.drawBitmap(x1, h1+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x1, h1+dv,tubjos, 12, 32, 1);

display.drawBitmap(x1, -32+h1,tubsusplin, 12, 32, 0);

display.drawBitmap(x1, -32+h1,tubsus, 12, 32, 1);

}

if (x2os) {

display.drawBitmap(x2, h2+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x2, h2+dv,tubjos, 12, 32, 1);

display.drawBitmap(x2, -32+h2,tubsusplin, 12, 32, 0);

display.drawBitmap(x2, -32+h2,tubsus, 12, 32, 1);

}

if (x3os) {

display.drawBitmap(x3, h3+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x3, h3+dv,tubjos, 12, 32, 1);

display.drawBitmap(x3, -32+h3,tubsusplin, 12, 32, 0);

display.drawBitmap(x3, -32+h3,tubsus, 12, 32, 1);

}

if (x4os) {

display.drawBitmap(x4, h4+dv,tubjosplin, 12, 32, 0);

display.drawBitmap(x4, h4+dv,tubjos, 12, 32, 1);

display.drawBitmap(x4, -32+h4,tubsusplin, 12, 32, 0);

display.drawBitmap(x4, -32+h4,tubsus, 12, 32, 1);

}

}

//makes the chip fall

void dead (byte vi) {

for (v=vi; v&lt;41; v++) {

dispChip();

dispTube();

display.print("           ");

display.print(score);

display.display();

delay(20);

}

}

//game startup

void beginning () {

display.clearDisplay();

display.drawBitmap(0, 33,fundal, 84, 20, 1);

display.drawBitmap(30, v,chip, 11, 7, 1);

display.print("Press to start");

display.display();

}

//searches highscore in EEPROM

byte getHighScore() {

int i = 0;

byte hs = 0;

boolean found = false;

while ((i&lt;1022) &amp;&amp; (!found)) {

if (EEPROM.read(i) == 'H')

if (EEPROM.read(i+1) == 'S') {

hs = EEPROM.read(i+2);

found = true;

}

i++;

}

return hs;

}

//sets new highscore in EEPROM

void setHighScore (byte hs) {

int i = 0, j=0;

boolean found = false;

while ((i&lt;1022) &amp;&amp; (!found)) {

if (EEPROM.read(i) == 'H')

if (EEPROM.read(i+1) == 'S') {

EEPROM.write(i,0xFF);

j = i+2;

found = true;

}

i++;

}

EEPROM.write(j,'H');

EEPROM.write(j+1,'S');

EEPROM.write(j+2,hs);

}

//displays score at the end

void finalScore() {

display.clearDisplay();

display.drawBitmap(0, 0,start, 84, 48, 1);

if (score &lt;= getHighScore()) {

display.setCursor(4,10);

display.print("Your score:");

display.print(score);

display.setCursor(6,30);

display.print("Highscore:");

display.print(getHighScore());

display.display();

}

else

{

display.setCursor(4,10);

display.print("New Highscore");

display.setCursor(37,22);

display.print(score);

display.display();

setHighScore(score);

}

//do nothing until the button is pressed

while (digitalRead(2) == HIGH) ;

} 

Download project files

Click here to download project files