Attiny85 Breakout Keychain Game

attiny So, what can you do with Attiny’s 5 i/o pins?

UPDATE: New game, “UFO Escape” side scroller

IMG_20140923_152553

I saw this great Attiny OLED project on http://tinusaur.wordpress.com/ and decided to try out the screen (It will be very handy as a mode display for a remote for my http://orb.photo/ project)

So I set out to make myself familiar with the screen and make something fun in the process. I made a remix of the classic breakout game, here is a video of the gameplay:


Parts required:

  • Attiny85 + dip8 socket
  • SSD1306 OLED screen
  • 2x push buttons
  • 2x resistors (10kOhm optimal)
  • 3x4cm copper clad board
  • Piezo speaker
  • 3V 2032 coin cell battery
  • Paper clip

So first of all you want to create the PCB, to do this just design the layout (printable pcb pdf)

Print the design on a page from a glossy magazine with a laser printer and use an iron or a laminator to transfer the design to the copper. Put the PCB into ferric chloride until the copper is etched away

IMG_20140921_141441

Clean off the toner with acetone:

IMG_20140921_141543

Drill the holes and solder the parts. The speaker is soldered to PB1 pin on the attiny and ground. Connect the large ground pads with a bit of wire. Bend and solder the paper clip to keep the battery in place.

IMG_20140923_150658

3D print the case from Thingiverse

IMG_20140923_133214

Program the attiny85 with the code and enjoy:

/* 2014
 * Breakout game by Ilya Titov. Find building instructions on http://webboggles.com/
 * The code that does not fall under the licenses of sources listed below can be used non commercially with attribution.
 *
 * If you have problems uploading this sketch, this is probably due to sketch size - you need to update ld.exe in arduino\hardware\tools\avr\avr\bin
 * https://github.com/TCWORLD/ATTinyCore/tree/master/PCREL%20Patch%20for%20GCC
 *
 * This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
 * Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
 * 
 * Sleep code is based on this blog post by Matthew Little:
 * http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85
*/
#include <EEPROM.h>
#include "font6x8.h"
#include <avr/sleep.h>
#include <avr/interrupt.h> // needed for the additional interrupt

volatile byte player = 0; //0 to 128-platformWidth  - this is the position of the bounce platform
byte platformWidth = 16; 
byte ballx = 62; // coordinate of the ball
byte bally = 50; // coordinate of the ball
int vdir = -1; // vertical direction and step  distance
int hdir = -1; // horizontal direction and step distance
long lastFrame = 0; // time since the screen was updated last
boolean row1[16]; // on-off array of blocks
boolean row2[16];
boolean row3[16];
int score = 0; // score - counts the number of blocks hit and resets the array above when devisible by 48(total blocks)
ISR(PCINT0_vect){ // PB0 pin button interrupt			     
   if (player >0){player--;} 
   return;
}
void playerInc(){ // PB2 pin button interrupt
  if (player <128-platformWidth){player++;}
}

void setup() {
  resetGame();
  DDRB = 0b00000010;  	// set PB1 as output (for the speaker)
  PCMSK = 0b00000001;	// pin change mask: listen to portb bit 1
  GIMSK |= 0b00100000;	// enable PCINT interrupt 
  sei();			// enable all interrupts
  attachInterrupt(0,playerInc,CHANGE);
  lastFrame = millis();
}
void loop() { 
  delay(40);
      noInterrupts();
      ssd1306_init();
      ssd1306_fillscreen(0x00);
      ssd1306_char_f6x8(16, 4, "B R E A K O U T");
      ssd1306_char_f6x8(20, 6, "webboggles.com");
      beep(200,600);          beep(300,200);          beep(400,300);
      delay(2000);
	while (1==1) {
              // continue moving after the interrupt
              if (digitalRead(2)==1){if (player <128-platformWidth){player++;} if (player <128-platformWidth){player++;} if (player <128-platformWidth){player++;}}               if (digitalRead(0)==1){if (player >0){player--;} if (player >0){player--;} if (player >0){player--;}}

              // bounce off the sides of the screen
              if ((bally+vdir<54&&vdir==1)||(bally-vdir>1&&vdir==-1)){bally+=vdir;}else {vdir = vdir*-1;}
              if ((ballx+hdir<127&&hdir==1)||(ballx-hdir>1&&hdir==-1)){ballx+=hdir;}else {hdir = hdir*-1;}

              // frame actions
              if (lastFrame+10<millis()){                 if(bally>10&&bally+vdir>=54&&(ballx<player||ballx>player+platformWidth)){ // game over if the ball misses the platform
                  int topScore = EEPROM.read(0);
                  topScore = topScore << 8;                   topScore = topScore |  EEPROM.read(1);                                      if (score>topScore){topScore = score; EEPROM.write(1,topScore & 0xFF); EEPROM.write(0,(topScore>>8) & 0xFF); }
                  ssd1306_fillscreen(0x00);                
                  ssd1306_char_f6x8(32, 3, "Game Over");
                  ssd1306_char_f6x8(32, 5, "score:");
                  char temp[4] = {0,0,0,0};
                  itoa(score,temp,10);
                  ssd1306_char_f6x8(70, 5, temp);
                  ssd1306_char_f6x8(32, 6, "top score:");
                  itoa(topScore,temp,10);
                  ssd1306_char_f6x8(90, 6, temp);
                  for (int i = 0; i<1000; i++){
                    beep(1,random(0,i*2));
                  }
                  delay(1000);
                  system_sleep();
                  resetGame();
                }else if (ballx<player+platformWidth/2&&bally>10&&bally+vdir>=54){ // if the ball hits left of the platform bounce left
                  hdir=-1; beep(20,600);
                }else if (ballx>player+platformWidth/2&&bally>10&&bally+vdir>=54){  // if the ball hits right of the platform bounce right
                  hdir=1; beep(20,600);
                }else if (bally+vdir>=54){
                  hdir=1; beep(20,600);
                }

                collisionCheck: // go back to here if a collision was detected to prevent flying through a rigid
                if (floor((bally+vdir)/8)==2){
                  if (row3[ballx/8]==1){row3[ballx/8]=0; score++;  
                      collision(); goto collisionCheck; // check collision for the new direction to prevent flying through a rigid
                  }
                }else if (floor((bally+vdir)/8)==1){
                  if (row2[ballx/8]==1){row2[ballx/8]=0; score++; 
                      collision(); goto collisionCheck;
                  }
                }else if (floor((bally+vdir)/8)==0){
                  if (row1[ballx/8]==1){row1[ballx/8]=0; score++;
                      collision(); goto collisionCheck;
                  }
                }

                // reset blocks if all have been hit
                if (score%48==0){ 
                  for (byte i =0; i<16;i++){
                   row1[i]=1; row2[i]=1; row3[i]=1;
                  } 
                }
              }

              // update whats on the screen
                  noInterrupts();

                  // blocks
                  ssd1306_setpos(0,0);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row1[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,1);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row2[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,2);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row3[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();

                  // clear area below the blocks
                  ssd1306_setpos(0,3);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,4);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,5);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,6);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,7);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();

                  // draw ball
                  ssd1306_setpos(ballx,bally/8);
                  uint8_t temp = B00000001;
                  ssd1306_send_data_start();
                  temp = temp << bally%8+1;
                  ssd1306_send_byte(temp);  
                  ssd1306_send_data_stop();

                  drawPlatform();
          	  interrupts();
             //     

	}
}
void resetGame(){
  ssd1306_char_f6x8(16, 4, "B R E A K O U T");
  ssd1306_char_f6x8(20, 6, "webboggles.com");
  beep(200,600);          beep(300,200);          beep(400,300);
  delay(2000);
  for (byte i =0; i<16;i++){ // reset blocks
   row1[i]=1; row2[i]=1; row3[i]=1;
  } 
  platformWidth = 16;
  ballx = 64;
  bally = 50;
  hdir = -1;
  vdir = -1;
  score = 0;
  player = random(0,128-platformWidth);
  ballx = player+platformWidth/2;
}

void collision(){ // the collsision check is actually done befor this is called, this code works out where the ball will bounce
  if ((bally+vdir)%8==7&&(ballx+hdir)%8==7){ // bottom right corner
      if (vdir==1){hdir=1;}else if(vdir==-1&&hdir==1){vdir=1;}else {hdir=1;vdir=1;}
    }else if ((bally+vdir)%8==7&&(ballx+hdir)%8==0){ // bottom left corner
      if (vdir==1){hdir=-1;}else if(vdir==-1&&hdir==-1){vdir=1;}else {hdir=-1;vdir=1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==0){ // top left corner
      if (vdir==-1){hdir=-1;}else if(vdir==1&&hdir==-1){vdir=-1;}else {hdir=-1;vdir=-1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==7){ // top right corner
      if (vdir==-1){hdir=1;}else if(vdir==1&&hdir==1){vdir=-1;}else {hdir=1;vdir=-1;}
    }else if ((bally+vdir)%8==7){ // bottom side
      vdir = 1;
    }else if ((bally+vdir)%8==0){ // top side
      vdir = -1;
    }else if ((ballx+hdir)%8==7){ // right side
      hdir = 1;
    }else if ((ballx+hdir)%8==0){ // left side
      hdir = -1;
    }else {
      hdir = hdir*-1; vdir = vdir*-1;
  }

  beep(30,300);

}
void drawPlatform(){
 noInterrupts();
 ssd1306_setpos(player,7);
 ssd1306_send_data_start();
 for (byte pw = 1; pw <platformWidth; pw++){ssd1306_send_byte(B00000011);}                
 ssd1306_send_data_stop();  
 interrupts(); 
}
void sendBlock(boolean fill){
  if (fill==1){
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B00000000);
  }else {
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
  } 
}
void beep(int bCount,int bDelay){
  for (int i = 0; i<=bCount; i++){digitalWrite(1,HIGH);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}digitalWrite(1,LOW);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}}
}
#define DIGITAL_WRITE_HIGH(PORT) PORTB |= (1 << PORT)
#define DIGITAL_WRITE_LOW(PORT) PORTB &= ~(1 << PORT)

// Some code based on "IIC_wtihout_ACK" by http://www.14blog.com/archives/1358
#ifndef SSD1306XLED_H
#define SSD1306XLED_H
// ---------------------	// Vcc,	Pin 1 on SSD1306 Board
// ---------------------	// GND,	Pin 2 on SSD1306 Board
#ifndef SSD1306_SCL
#define SSD1306_SCL		PB3	// SCL,	Pin 3 on SSD1306 Board
#endif
#ifndef SSD1306_SDA
#define SSD1306_SDA		PB4	// SDA,	Pin 4 on SSD1306 Board
#endif
#ifndef SSD1306_SA
#define SSD1306_SA		0x78	// Slave address
#endif
// ----------------------------------------------------------------------------
void ssd1306_init(void);
void ssd1306_xfer_start(void);
void ssd1306_xfer_stop(void);
void ssd1306_send_byte(uint8_t byte);
void ssd1306_send_command(uint8_t command);
void ssd1306_send_data_start(void);
void ssd1306_send_data_stop(void);
void ssd1306_setpos(uint8_t x, uint8_t y);
void ssd1306_fillscreen(uint8_t fill_Data);
void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]);
//void ssd1306_char_f8x16(uint8_t x, uint8_t y,const char ch[]);
//void ssd1306_char_f16x16(uint8_t x, uint8_t y, uint8_t N);
void ssd1306_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t bitmap[]);
// ----------------------------------------------------------------------------
#endif
void ssd1306_init(void){
	DDRB |= (1 << SSD1306_SDA);	// Set port as output
	DDRB |= (1 << SSD1306_SCL);	// Set port as output

	ssd1306_send_command(0xAE); // display off
	ssd1306_send_command(0x00); // Set Memory Addressing Mode
	ssd1306_send_command(0x10); // 00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	ssd1306_send_command(0x40); // Set Page Start Address for Page Addressing Mode,0-7
	ssd1306_send_command(0x81); // Set COM Output Scan Direction
	ssd1306_send_command(0xCF); // ---set low column address
	ssd1306_send_command(0xA1); // ---set high column address
	ssd1306_send_command(0xC8); // --set start line address
	ssd1306_send_command(0xA6); // --set contrast control register
	ssd1306_send_command(0xA8);
	ssd1306_send_command(0x3F); // --set segment re-map 0 to 127
	ssd1306_send_command(0xD3); // --set normal display
	ssd1306_send_command(0x00); // --set multiplex ratio(1 to 64)
	ssd1306_send_command(0xD5); // 
	ssd1306_send_command(0x80); // 0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	ssd1306_send_command(0xD9); // -set display offset
	ssd1306_send_command(0xF1); // -not offset
	ssd1306_send_command(0xDA); // --set display clock divide ratio/oscillator frequency
	ssd1306_send_command(0x12); // --set divide ratio
	ssd1306_send_command(0xDB); // --set pre-charge period
	ssd1306_send_command(0x40); // 
	ssd1306_send_command(0x20); // --set com pins hardware configuration
	ssd1306_send_command(0x02);
	ssd1306_send_command(0x8D); // --set vcomh
	ssd1306_send_command(0x14); // 0x20,0.77xVcc
	ssd1306_send_command(0xA4); // --set DC-DC enable
	ssd1306_send_command(0xA6); // 
	ssd1306_send_command(0xAF); // --turn on oled panel 
}

void ssd1306_xfer_start(void){
	DIGITAL_WRITE_HIGH(SSD1306_SCL);	// Set to HIGH
	DIGITAL_WRITE_HIGH(SSD1306_SDA);	// Set to HIGH
	DIGITAL_WRITE_LOW(SSD1306_SDA);		// Set to LOW
	DIGITAL_WRITE_LOW(SSD1306_SCL);		// Set to LOW
}

void ssd1306_xfer_stop(void){
	DIGITAL_WRITE_LOW(SSD1306_SCL);		// Set to LOW
	DIGITAL_WRITE_LOW(SSD1306_SDA);		// Set to LOW
	DIGITAL_WRITE_HIGH(SSD1306_SCL);	// Set to HIGH
	DIGITAL_WRITE_HIGH(SSD1306_SDA);	// Set to HIGH
}

void ssd1306_send_byte(uint8_t byte){
	uint8_t i;
	for(i=0; i<8; i++)
	{
		if((byte << i) & 0x80) 			DIGITAL_WRITE_HIGH(SSD1306_SDA); 		else 			DIGITAL_WRITE_LOW(SSD1306_SDA); 		 		DIGITAL_WRITE_HIGH(SSD1306_SCL); 		DIGITAL_WRITE_LOW(SSD1306_SCL); 	} 	DIGITAL_WRITE_HIGH(SSD1306_SDA); 	DIGITAL_WRITE_HIGH(SSD1306_SCL); 	DIGITAL_WRITE_LOW(SSD1306_SCL); } void ssd1306_send_command(uint8_t command){ 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA);  // Slave address, SA0=0 	ssd1306_send_byte(0x00);	// write command 	ssd1306_send_byte(command); 	ssd1306_xfer_stop(); } void ssd1306_send_data_start(void){ 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA); 	ssd1306_send_byte(0x40);	//write data } void ssd1306_send_data_stop(void){ 	ssd1306_xfer_stop(); } void ssd1306_setpos(uint8_t x, uint8_t y) { 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA);  //Slave address,SA0=0 	ssd1306_send_byte(0x00);	//write command 	ssd1306_send_byte(0xb0+y); 	ssd1306_send_byte(((x&0xf0)>>4)|0x10); // |0x10
	ssd1306_send_byte((x&0x0f)|0x01); // |0x01

	ssd1306_xfer_stop();
}

void ssd1306_fillscreen(uint8_t fill_Data){
	uint8_t m,n;
	for(m=0;m<8;m++)
	{
		ssd1306_send_command(0xb0+m);	//page0-page1
		ssd1306_send_command(0x00);		//low column start address
		ssd1306_send_command(0x10);		//high column start address
		ssd1306_send_data_start();
		for(n=0;n<128;n++) 		{ 			ssd1306_send_byte(fill_Data); 		} 		ssd1306_send_data_stop(); 	} } void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]){ 	uint8_t c,i,j=0; 	while(ch[j] != '\0') 	{ 		c = ch[j] - 32; 		if(x>126)
		{
			x=0;
			y++;
		}
		ssd1306_setpos(x,y);
		ssd1306_send_data_start();
		for(i=0;i<6;i++)
		{
			ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c*6+i]));
		}
		ssd1306_send_data_stop();
		x += 6;
		j++;
	}
}

// Routines to set and clear bits (used in the sleep code)
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void system_sleep() {
  ssd1306_fillscreen(0x00);
  ssd1306_send_command(0xAE);
  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF
  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 
  sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON  
  ssd1306_send_command(0xAF);
}

References:

31 thoughts on “Attiny85 Breakout Keychain Game”

  1. I’m having trouble compiling this. I keep getting ” ‘ssd1306_xxx’ was not declared in this scope” for all the ssd1306_ commands. I notice that one of the #include is blank up there in your code–which library is supposed to be there? I tried #include but got the same problems. When you have a moment, can you clear up that part of your code? Thank you for this fun project!

    Reply
  2. Thank you for your quick reply! I’m still getting the errors, but I’m trying to figure it out from the tinusaur guide there. We should #include the ssd1306xled libraries too, right? I tried adding ssd1306xled8x16.h and ssd1306xled.h but still have the same problems. Maybe I didn’t put them in the right directory?

    Thanks again 🙂

    Reply
  3. I’m a newbe and I’m having trouble getting the sketch to
    compile. also don’t know if the board choice is correct.
    I’ve used attiny85’s on a couple of simple led dice projects and I don’t know how to code unfortunately. also can you send me your library and hardware files?
    Any help would be greatly appreciated.
    These are the errors I’m getting:
    This report would have more information with
    “Show verbose output during compilation”
    enabled in File > Preferences.
    Arduino: 1.0.6 (Windows NT (unknown)), Board:
    “ATtiny85 @ 8 MHz (internal oscillator; BOD disabled)”
    attiny85_breakout_game_1.ino: In function ‘void loop()’:
    attiny85_breakout_game_1:52: error: ‘ssd1306_char_f6x8’ was not declared in this scope
    attiny85_breakout_game_1.ino: In function ‘void resetGame()’:
    attiny85_breakout_game_1:196: error: ‘ssd1306_char_f6x8’ was not declared in this scope
    attiny85_breakout_game_1.ino: In function ‘void ssd1306_send_command(uint8_t)’:
    attiny85_breakout_game_1:355: error: ‘x’ was not declared in this scope

    Reply
  4. I need your help to fix the code!

    /* 2014
    * Breakout game by Ilya Titov. Find building instructions on http://webboggles.com/
    * The code that does not fall under the licenses of sources listed below can be used non commercially with attribution.
    *
    * If you have problems uploading this sketch, this is probably due to sketch size – you need to update ld.exe in arduino\hardware\tools\avr\avr\bin
    * https://github.com/TCWORLD/ATTinyCore/tree/master/PCREL%20Patch%20for%20GCC
    *
    * This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
    * Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
    *
    * Sleep code is based on this blog post by Matthew Little:
    * http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85
    */
    #include
    #include

    #include
    #include // needed for the additional interrupt

    volatile byte player = 0; //0 to 128-platformWidth – this is the position of the bounce platform
    byte platformWidth = 16;
    byte ballx = 62; // coordinate of the ball
    byte bally = 50; // coordinate of the ball
    int vdir = -1; // vertical direction and step distance
    int hdir = -1; // horizontal direction and step distance
    long lastFrame = 0; // time since the screen was updated last
    boolean row1[16]; // on-off array of blocks
    boolean row2[16];
    boolean row3[16];
    int score = 0; // score – counts the number of blocks hit and resets the array above when devisible by 48(total blocks)
    ISR(PCINT0_vect){ // PB0 pin button interrupt
    if (player >0){player–;}
    return;
    }
    void playerInc(){ // PB2 pin button interrupt
    if (player <128-platformWidth){player++;}
    }

    void setup() {
    resetGame();
    DDRB = 0b00000010; // set PB1 as output (for the speaker)
    PCMSK = 0b00000001; // pin change mask: listen to portb bit 1
    GIMSK |= 0b00100000; // enable PCINT interrupt
    sei(); // enable all interrupts
    attachInterrupt(0,playerInc,CHANGE);
    lastFrame = millis();
    }
    void loop() {
    delay(40);
    noInterrupts();
    SSD1306.ssd1306_init();
    SSD1306.ssd1306_fillscreen(0x00);
    SSD1306.ssd1306_char_f8x16(16, 4, "B R E A K O U T");
    SSD1306.ssd1306_char_f8x16(20, 6, "webboggles.com");
    beep(200,600); beep(300,200); beep(400,300);
    delay(2000);
    while (1==1) {
    // continue moving after the interrupt
    if (digitalRead(2)==1){if (player <128-platformWidth){player++;} if (player <128-platformWidth){player++;} if (player 0){player–;} if (player >0){player–;} if (player >0){player–;}}

    // bounce off the sides of the screen
    if ((bally+vdir1&&vdir==-1)){bally+=vdir;}else {vdir = vdir*-1;}
    if ((ballx+hdir1&&hdir==-1)){ballx+=hdir;}else {hdir = hdir*-1;}

    // frame actions
    if (lastFrame+1010&&bally+vdir>=54&&(ballxplayer+platformWidth)){ // game over if the ball misses the platform
    int topScore = EEPROM.read(0);
    topScore = topScore <topScore){topScore = score; EEPROM.write(1,topScore & 0xFF); EEPROM.write(0,(topScore>>8) & 0xFF); }
    SSD1306.ssd1306_fillscreen(0x00);
    SSD1306.ssd1306_char_f8x16(32, 3, “Game Over”);
    SSD1306.ssd1306_char_f8x16(32, 5, “score:”);
    char temp[4] = {0,0,0,0};
    itoa(score,temp,10);
    SSD1306.ssd1306_char_f8x16(70, 5, temp);
    SSD1306.ssd1306_char_f8x16(32, 6, “top score:”);
    itoa(topScore,temp,10);
    SSD1306.ssd1306_char_f8x16(90, 6, temp);
    for (int i = 0; i<1000; i++){
    beep(1,random(0,i*2));
    }
    delay(1000);
    system_sleep();
    resetGame();
    }else if (ballx10&&bally+vdir>=54){ // if the ball hits left of the platform bounce left
    hdir=-1; beep(20,600);
    }else if (ballx>player+platformWidth/2&&bally>10&&bally+vdir>=54){ // if the ball hits right of the platform bounce right
    hdir=1; beep(20,600);
    }else if (bally+vdir>=54){
    hdir=1; beep(20,600);
    }

    collisionCheck: // go back to here if a collision was detected to prevent flying through a rigid
    if (floor((bally+vdir)/8)==2){
    if (row3[ballx/8]==1){row3[ballx/8]=0; score++;
    collision(); goto collisionCheck; // check collision for the new direction to prevent flying through a rigid
    }
    }else if (floor((bally+vdir)/8)==1){
    if (row2[ballx/8]==1){row2[ballx/8]=0; score++;
    collision(); goto collisionCheck;
    }
    }else if (floor((bally+vdir)/8)==0){
    if (row1[ballx/8]==1){row1[ballx/8]=0; score++;
    collision(); goto collisionCheck;
    }
    }

    // reset blocks if all have been hit
    if (score%48==0){
    for (byte i =0; i<16;i++){
    row1[i]=1; row2[i]=1; row3[i]=1;
    }
    }
    }

    // update whats on the screen
    noInterrupts();

    // blocks
    SSD1306.ssd1306_setpos(0,0);
    SSD1306.ssd1306_send_data_start();
    for (int bl = 0; bl <16; bl++){
    if(row1[bl]==1){
    sendBlock(1);
    }else {
    sendBlock(0);
    }
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,1);
    SSD1306.ssd1306_send_data_start();
    for (int bl = 0; bl <16; bl++){
    if(row2[bl]==1){
    sendBlock(1);
    }else {
    sendBlock(0);
    }
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,2);
    SSD1306.ssd1306_send_data_start();
    for (int bl = 0; bl <16; bl++){
    if(row3[bl]==1){
    sendBlock(1);
    }else {
    sendBlock(0);
    }
    }
    SSD1306.ssd1306_send_data_stop();

    // clear area below the blocks
    SSD1306.ssd1306_setpos(0,3);
    SSD1306.ssd1306_send_data_start();
    for (byte i =0; i<128; i++){
    SSD1306.ssd1306_send_byte(B00000000);
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,4);
    SSD1306.ssd1306_send_data_start();
    for (byte i =0; i<128; i++){
    SSD1306.ssd1306_send_byte(B00000000);
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,5);
    SSD1306.ssd1306_send_data_start();
    for (byte i =0; i<128; i++){
    SSD1306.ssd1306_send_byte(B00000000);
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,6);
    SSD1306.ssd1306_send_data_start();
    for (byte i =0; i<128; i++){
    SSD1306.ssd1306_send_byte(B00000000);
    }
    SSD1306.ssd1306_send_data_stop();
    SSD1306.ssd1306_setpos(0,7);
    SSD1306.ssd1306_send_data_start();
    for (byte i =0; i<128; i++){
    SSD1306.ssd1306_send_byte(B00000000);
    }
    SSD1306.ssd1306_send_data_stop();

    // draw ball
    SSD1306.ssd1306_setpos(ballx,bally/8);
    uint8_t temp = B00000001;
    SSD1306.ssd1306_send_data_start();
    temp = temp << bally%8+1;
    SSD1306.ssd1306_send_byte(temp);
    SSD1306.ssd1306_send_data_stop();

    drawPlatform();
    interrupts();
    //

    }
    }
    void resetGame(){
    SSD1306.ssd1306_char_f8x16(16, 4, "B R E A K O U T");
    SSD1306.ssd1306_char_f8x16(20, 6, "webboggles.com");
    beep(200,600); beep(300,200); beep(400,300);
    delay(2000);
    for (byte i =0; i<16;i++){ // reset blocks
    row1[i]=1; row2[i]=1; row3[i]=1;
    }
    platformWidth = 16;
    ballx = 64;
    bally = 50;
    hdir = -1;
    vdir = -1;
    score = 0;
    player = random(0,128-platformWidth);
    ballx = player+platformWidth/2;
    }

    void collision(){ // the collsision check is actually done befor this is called, this code works out where the ball will bounce
    if ((bally+vdir)%8==7&&(ballx+hdir)%8==7){ // bottom right corner
    if (vdir==1){hdir=1;}else if(vdir==-1&&hdir==1){vdir=1;}else {hdir=1;vdir=1;}
    }else if ((bally+vdir)%8==7&&(ballx+hdir)%8==0){ // bottom left corner
    if (vdir==1){hdir=-1;}else if(vdir==-1&&hdir==-1){vdir=1;}else {hdir=-1;vdir=1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==0){ // top left corner
    if (vdir==-1){hdir=-1;}else if(vdir==1&&hdir==-1){vdir=-1;}else {hdir=-1;vdir=-1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==7){ // top right corner
    if (vdir==-1){hdir=1;}else if(vdir==1&&hdir==1){vdir=-1;}else {hdir=1;vdir=-1;}
    }else if ((bally+vdir)%8==7){ // bottom side
    vdir = 1;
    }else if ((bally+vdir)%8==0){ // top side
    vdir = -1;
    }else if ((ballx+hdir)%8==7){ // right side
    hdir = 1;
    }else if ((ballx+hdir)%8==0){ // left side
    hdir = -1;
    }else {
    hdir = hdir*-1; vdir = vdir*-1;
    }

    beep(30,300);

    }
    void drawPlatform(){
    noInterrupts();
    SSD1306.ssd1306_setpos(player,7);
    SSD1306.ssd1306_send_data_start();
    for (byte pw = 1; pw <platformWidth; pw++){SSD1306.ssd1306_send_byte(B00000011);}
    SSD1306.ssd1306_send_data_stop();
    interrupts();
    }
    void sendBlock(boolean fill){
    if (fill==1){
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B01111110);
    SSD1306.ssd1306_send_byte(B00000000);
    }else {
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    SSD1306.ssd1306_send_byte(B00000000);
    }
    }
    void beep(int bCount,int bDelay){
    for (int i = 0; i<=bCount; i++){digitalWrite(1,HIGH);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}digitalWrite(1,LOW);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}}
    }
    #define DIGITAL_WRITE_HIGH(PORT) PORTB |= (1 << PORT)
    #define DIGITAL_WRITE_LOW(PORT) PORTB &= ~(1 << PORT)

    // Routines to set and clear bits (used in the sleep code)
    #ifndef cbi
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
    #endif
    #ifndef sbi
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
    #endif

    void system_sleep() {
    SSD1306.ssd1306_fillscreen(0x00);
    SSD1306.ssd1306_send_command(0xAE);
    cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
    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
    sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
    SSD1306.ssd1306_send_command(0xAF);
    }

    Reply
    • i don’t really know as well, but i guess if you want to use a 5110 display, then you should switch to a more powerful uController, i guess a AtMega328 would do perfect for that.
      Hope i could help 🙂

      Reply
    • Hi Dylan, I don’t currently have any kits available unfortunately. I’m hoping to make a larger batch at some point, meanwhile you can make one using the files and instructions provided.

      Reply
  5. Hi Ilya, congratulations! It is a outstanding project.
    I have almost same OLED display, but doesn’t have I2C communication, it uses SPI (four communications pins, plus clearscreen and VCC/GND pins).
    My question: there is some easy form to convert the communication sections from your code to adapt to SPI display?
    Once more, congratulations!

    Reply
    • Thank you Jaldomir, unfortunately there are not enough pins on the attiny85 for the SPI screen + 2 buttons + speaker so there is not an east fix. You could look up reference sheet for your screen and use a microcontroller with more pins but you would have to modify the code

      Reply
  6. Excellent work ilya 🙂
    I’m trying to print the pcb from your pdf, but the results aren’t good. My laser printer on Xubuntu print the blak traces in gray, so use the print for toner transfer is almost impossible. Can you provide the kicad files? From Kikad I can print the pcbs in solid black, maybe the cause is the printer driver..

    Thanks a lot, Mirko 🙂

    Reply
    • Hi Mirko, the board was created in a different tool (pcb creator), you should be able to change the print settings via the printer driver. Make sure to turn off any toner saving options.

      Reply
      • Thanks, it was the printer’s driver (sorry Samsung, I’m not so ECO, lol).
        Now I must how to fix the infamous library error:

        “ssd1306_char_f6x8′ was not declared in this scope”

        Reply
  7. Hi, could you please send me the HEX file with the code and programming fuses? I can not make it out of the text … Thank Radek

    Reply
  8. hello Ilya,
    I,ve tried to download a couple of your sketches using the attiny85 as a pocket game. I think they’re very cool.
    First of all I’m not a programmer( I wish), I’m a 69 year tinkerer and love to make things. Anyway I need your help if you don’t mind. here’s the error report for the above sketch. I have installed all the libraries needed.
    This report would have more information with
    “Show verbose output during compilation”
    enabled in File > Preferences.
    Arduino: 1.0.6 (Windows NT (unknown)), Board: “ATtiny85 @ 8 MHz (internal oscillator; BOD disabled)”
    sketch_sep03a.ino: In function ‘void loop()’:
    sketch_sep03a:50: error: ‘ssd1306_init’ was not declared in this scope
    sketch_sep03a:51: error: ‘ssd1306_fillscreen’ was not declared in this scope
    sketch_sep03a:52: error: ‘ssd1306_char_f6x8’ was not declared in this scope
    sketch_sep03a:118: error: ‘ssd1306_setpos’ was not declared in this scope
    sketch_sep03a:119: error: ‘ssd1306_send_data_start’ was not declared in this scope
    sketch_sep03a:127: error: ‘ssd1306_send_data_stop’ was not declared in this scope
    sketch_sep03a:153: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:159: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:165: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:171: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:177: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:186: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a.ino: In function ‘void resetGame()’:
    sketch_sep03a:196: error: ‘ssd1306_char_f6x8’ was not declared in this scope
    sketch_sep03a.ino: In function ‘void drawPlatform()’:
    sketch_sep03a:239: error: ‘ssd1306_setpos’ was not declared in this scope
    sketch_sep03a:240: error: ‘ssd1306_send_data_start’ was not declared in this scope
    sketch_sep03a:241: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:242: error: ‘ssd1306_send_data_stop’ was not declared in this scope
    sketch_sep03a.ino: In function ‘void sendBlock(boolean)’:
    sketch_sep03a:247: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a:256: error: ‘ssd1306_send_byte’ was not declared in this scope
    sketch_sep03a.ino: In function ‘void ssd1306_send_command(uint8_t)’:
    sketch_sep03a:355: error: ‘x’ was not declared in this scope

    Reply
  9. I tried for several days, I am a newb when it comes to programming but I was still able to do most builds through instructions, but this time I am failing and failing and seem that I cant find the right libraries for arduino IDE I really wanted to make this so hard but I give up I am so frustrated right now I cant even describe, I printed the case did everything but I am to dumb to do it.

    Reply
  10. Hi ilya,
    do I need run arduino 1.0 ide to make this work?
    I run 1.8 and I get the sketch to compile and download, but nothing works/shows on the screen???
    all connections have been verified many times per your schematic drwg.
    please advise, much thanks.

    Reply

Leave a Reply to Jacob H Cancel reply