Attiny85 UFO Escape Keychain Game

UPDATE: assembly instructions

I finally had some free time to make something fun and decided to write a new game for the Attiny85 ssd1306 keychain.  I reused the sleep, display and interrupts code from the “Breakout” game I created a while ago.

This time I wanted a more addictive game. The game had to be playable using just the two buttons. I thought a racing game might be a good choice but the landscape screen is more suitable for a side-scroller. So I settled for an obstacle avoiding side scroller. Similar games include the recent Flappy Bird, Nyan Cat and many variations of the Helicopter game.

The flying saucer is propelled with the left button while the right button is used to shoot through force fields. The game gets harder as you play:

/* 2015
 * UFO Escape 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


boolean stopAnimate = 0; // this is set to 1 when a collision is detected
int maxObstacles = 1; // this defines the max number of in game obstacles
int obstacleStep = 1; // pixel step of obstacles per frame
int obstacle[9] = {-50,-50,-50,-50,-50,-50,-50,-50,-50}; // x offset of the obstacle default position, out of view
int gapOffset[9] = {0,0,0,0,0,0,0,0,0}; // y offset of the fly-through gap
int gapSize[9]; // y height of the gap
byte maxGap = 60; // max height of the gap
int stepsSinceLastObstacle = 0; // so obstacles are not too close
byte gapBlock[9] = {0,0,0,0,0,0,0,0,0}; // if the fly-through gap is closed
byte blockChance = 0; // this higher value decreases the likelihood of gap being closed 
boolean fire = 0; // set to 1 when the fire interrupt is triggered
byte fireCount = 0; // the shot is persistent for several frames
byte playerOffset = 0; // y offset of the top of the player
byte flames = 0; // this is set to 1 when the move up interrupt is triggered

byte flameMask[2]={B00111111,B11111111}; // this is used to only show the flame part of the icon when moving up


int score = 0; // score - this affects the difficulty of the game
ISR(PCINT0_vect){ // PB0 pin button interrupt			     
   //if (playerOffset >1&&stopAnimate==0){playerOffset-=1;} // for debounce, the movement is in the main loop//
   return;
}
void playerInc(){ // PB2 pin button interrupt
   fire = 1;
   fireCount = 5; // number of frames the shot will persist
}

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,RISING);
}
void loop() { 
      delay(40);
      noInterrupts();
      ssd1306_init();
      ssd1306_fillscreen(0x00);
      ssd1306_char_f6x8(16, 4, "U F O  E S C A P E");
      ssd1306_char_f6x8(20, 6, "webboggles.com");
      beep(200,600);          beep(300,200);          beep(400,300);
      
      delay(2000);

	while (1==1) {

              //update game vars to make it harder to play
              if (score < 500){blockChance = 11-score/50;  maxObstacles=score/70+1;}
              if (score > 130){obstacleStep = 2;}
              if (score < 2000){maxGap = 60-score/100;}
              if (fire == 1){score--;}
              if (fireCount>0){fireCount--;}
              
                if (digitalRead(0)==1){if (playerOffset >0 && stopAnimate==0){playerOffset--; flames = 1; // move player up
                  for (int i = 0; i<2; i++){
                    beep(1,random(0,i*2));
                  }
                }} 
                if (digitalRead(0)==1){if (playerOffset >0 && stopAnimate==0){playerOffset--; flames = 1; // move player up
                  for (int i = 0; i<2; i++){
                    beep(1,random(0,i*2));
                  }
                }}
                if (digitalRead(0)==1){if (playerOffset >0 && stopAnimate==0){playerOffset--; flames = 1; // move player up
                  for (int i = 0; i<2; i++){
                    beep(1,random(0,i*2));
                  }
                }}
                stepsSinceLastObstacle += obstacleStep;
                for (byte i = 0; i<maxObstacles;i++){ // fly obstacles
                  if (obstacle[i] >= 0 && obstacle[i] <= 128 && stopAnimate==0){
                    obstacle[i] -= obstacleStep;
                    if (gapBlock[i]>0 && obstacle[i] < 36  && playerOffset>gapOffset[i] && playerOffset+5<gapOffset[i]+gapSize[i] && fireCount > 0){//
                       gapBlock[i] = 0;
                       score += 5; 
                       for (byte cp = 400; cp>0; cp--){
                         beep(1,cp);
                       }
                    }
                  } 
                  
                  if (obstacle[i]<=4 && stepsSinceLastObstacle>=random(30,100)){ // generate new obstacles
                    obstacle[i] = 123;
                    gapSize[i] = random(25,maxGap);
                    gapOffset[i] = random(0,64-gapSize[i]);
                    if (random(0,blockChance)==0){gapBlock[i] = 1;}else {gapBlock[i] = 0;}
                    stepsSinceLastObstacle = 0;
                    score+=1;
                  }
                }
                
                if (playerOffset < 56 && stopAnimate==0){playerOffset++;} // player gravity
                
                delay(20/maxObstacles); // controls game speed, more obstacles take longer to be sent to the screen
                if (stopAnimate==0){ssd1306_clearscreen();}
              

              // update whats on the screen
                 
                  noInterrupts();
                  // Send Obstacle
                  for (byte i = 0; i<maxObstacles;i++){
                    if (obstacle[i] >= -5 && obstacle[i] <= 128){ // only deal with visible obstacles
                      if (obstacle[i] > 8 && obstacle[i] <16){ // look for collision if obstacle is near the player
                        if (playerOffset < gapOffset[i] || playerOffset+5 > gapOffset[i]+gapSize[i] || gapBlock[i] != 0){
                          // collision!
                          stopAnimate = 1; 
                          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_char_f6x8(32, 3, "Game Over");
                          ssd1306_char_f6x8(32, 5, "score:");
                          char temp[10] = {0,0,0,0,0,0,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(2000);
                          interrupts();
                          system_sleep();
                          resetGame();
                          noInterrupts(); 
                        }
                      }                      
                      
                      for (byte row = 0; row <8; row++){
                        
                          ssd1306_setpos(obstacle[i],row);
                          ssd1306_send_data_start();
                          
                          if (obstacle[i]>0&&obstacle[i] < 128){
                             
                             if ((row+1)*8 - gapOffset[i] <= 8){ // generate obstacle : top and transition
                                byte temp = B11111111>>((row+1)*8 - gapOffset[i]); 
                                byte tempB = B00000000; 
                                if (gapBlock[i]>0){tempB=B10101010;}
                                ssd1306_send_byte(temp);
                                ssd1306_send_byte(temp|tempB>>1);
                                ssd1306_send_byte(temp|tempB);
                                ssd1306_send_byte(temp);
                                
                             }else if (row*8>=gapOffset[i] && (row+1)*8<=gapOffset[i]+gapSize[i]){ // middle gap
                                byte tempB = B00000000; 
                                if (gapBlock[i]>0){tempB=B10101010;}
                                ssd1306_send_byte(B00000000);
                                ssd1306_send_byte(B00000000|tempB>>1);
                                ssd1306_send_byte(B00000000|tempB);
                                ssd1306_send_byte(B00000000);

                             }else if ((gapOffset[i] +gapSize[i]) >= row*8 && (gapOffset[i] +gapSize[i]) <= (row+1)*8){ // bottom transition
                                //}else if ((gapOffset[i] +gapSize[i]) >= row*8 && (gapOffset[i] +gapSize[i]) <= (row+1)*8){ // bottom transition
                                //byte temp = B11111111<<((gapOffset[i] + gapSize[i])%8); 
                                
                                byte temp = B11111111<<((gapOffset[i] + gapSize[i])%8); 
                                byte tempB = B00000000; 
                                if (gapBlock[i]>0){tempB=B10101010;}
                                ssd1306_send_byte(temp);
                                ssd1306_send_byte(temp|tempB>>1);
                                ssd1306_send_byte(temp|tempB);
                                ssd1306_send_byte(temp);
                                
                             }else { // fill rest of obstacle
                                ssd1306_send_byte(B11111111);
                                ssd1306_send_byte(B11111111);
                                ssd1306_send_byte(B11111111);
                                ssd1306_send_byte(B11111111);
                             }
                          ssd1306_send_data_stop();
                          }
                        }
                   
                    }
                  }
                  
                 
                 
                 if (playerOffset%8!=0){ // overflow the player icon into the next screen row if split
                      ssd1306_setpos(8,playerOffset/8);
                      ssd1306_send_data_start();
                        if (stopAnimate==0){
                            ssd1306_send_byte((B00001100&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames])<<playerOffset%8);
                            ssd1306_send_byte((B00001100&flameMask[flames])<<playerOffset%8);
                            if (fireCount >0){
                                for (byte f = 0; f<=24; f++){
                                    ssd1306_send_byte(B00000100<<playerOffset%8);
                                }
                                ssd1306_send_byte(B00010101<<playerOffset%8);
                                ssd1306_send_byte(B00001010<<playerOffset%8);
                                ssd1306_send_byte(B00010101<<playerOffset%8);
                                if (fire==1){beep(50,100);}
                                fire = 0;
                              
                            }
                        }else {
                            ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))<<playerOffset%8);
                            ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))<<playerOffset%8);
                        }
                        
                      ssd1306_send_data_stop();
                      ssd1306_setpos(8,playerOffset/8+1);
                      ssd1306_send_data_start();
                        if (stopAnimate==0){
                            ssd1306_send_byte((B00001100&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames])>>8-playerOffset%8);
                            ssd1306_send_byte((B00001100&flameMask[flames])>>8-playerOffset%8);
                            if (fireCount >0){
                                for (byte f = 0; f<=24; f++){
                                    ssd1306_send_byte(B00000100>>8-playerOffset%8);
                                }
                                ssd1306_send_byte(B00010101>>8-playerOffset%8);
                                ssd1306_send_byte(B00001010>>8-playerOffset%8);
                                ssd1306_send_byte(B00010101>>8-playerOffset%8);
                                if (fire==1){beep(50,100);}
                                fire = 0;
                              
                            }
                        }else {
                            ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                            ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))>>8-playerOffset%8);
                        }
                      ssd1306_send_data_stop();
                      }else {
                          ssd1306_setpos(8,playerOffset/8);
                          ssd1306_send_data_start();
                          if (stopAnimate == 0){
                            ssd1306_send_byte(B00001100&flameMask[flames]);
                            ssd1306_send_byte(B01011110&flameMask[flames]);
                            ssd1306_send_byte(B10010111&flameMask[flames]);
                            ssd1306_send_byte(B01010011&flameMask[flames]);
                            ssd1306_send_byte(B01010011&flameMask[flames]);
                            ssd1306_send_byte(B10010111&flameMask[flames]);
                            ssd1306_send_byte(B01011110&flameMask[flames]);
                            ssd1306_send_byte(B00001100&flameMask[flames]);
                            if (fireCount >0){
                                for (byte f = 0; f<=24; f++){
                                    ssd1306_send_byte(B00000100);
                                }
                                ssd1306_send_byte(B00010101);
                                ssd1306_send_byte(B00001010);
                                ssd1306_send_byte(B00010101);
                                if (fire==1){beep(50,100);}
                                fire = 0;
                              
                            }
                          }else {
                            ssd1306_send_byte(B00001100&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B01011110&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B10010111&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B01010011&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B01010011&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B10010111&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B01011110&flameMask[flames] | random(0,255));
                            ssd1306_send_byte(B00001100&flameMask[flames] | random(0,255)); 
                          }
                          ssd1306_send_data_stop();
                      }
                      
                      // display score
                      
                      //ssd1306_char_f6x8(32, 8, "score:");
                      
                      char temp[10] = {0,0,0,0,0,0,0,0,0,0};
                      itoa(score,temp,10);
                      ssd1306_char_f6x8(92, 0, temp);
                          
                          
                      flames = 0;
          	  interrupts();
                  
             //    
                     
              
              
              
              if (stopAnimate == 1){
                  for (int i = 0; i<1000; i++){
                    beep(1,random(0,i*2));
                  }
                 delay(2000);
                 system_sleep(); 
              }
       }   
}
void resetGame(){
  ssd1306_char_f6x8(16, 4, "U F O  E S C A P E");
  ssd1306_char_f6x8(20, 6, "webboggles.com");
  beep(200,600);          beep(300,200);          beep(400,300);
  delay(2000);

  stopAnimate = 0;
  score = 0;

  maxObstacles = 3;
  obstacleStep = 2;
  for (byte i = 0; i<9; i++){
    obstacle[i] = -50;
    gapOffset[i]=0;
  }
  stepsSinceLastObstacle = 0;
  playerOffset = 0; // y offset of the top of the player
}



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_clearscreen(){

	for(byte 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(byte n=0;n<128;n++){
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        DIGITAL_WRITE_LOW(SSD1306_SDA);
                		DIGITAL_WRITE_HIGH(SSD1306_SCL);
                		DIGITAL_WRITE_LOW(SSD1306_SCL);
                                	
                	        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);
		}
		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);0
}

6 thoughts on “Attiny85 UFO Escape Keychain Game”

  1. Fantastic project, but i can’t compile this program:
    Arduino:1.6.9 (Windows 7), Płytka:”Digispark (Default – 16.5mhz)”

    Opcje projektu zmienione, przeładuj całość
    C:\Users\Adam\Documents\Arduino\sketch_jan17b\sketch_jan17b.ino: In function ‘void loop()’:

    sketch_jan17b:59: error: ‘ssd1306_init’ was not declared in this scope

    ssd1306_init();

    ^

    sketch_jan17b:60: error: ‘ssd1306_fillscreen’ was not declared in this scope

    ssd1306_fillscreen(0x00);

    ^

    sketch_jan17b:61: error: ‘ssd1306_char_f6x8’ was not declared in this scope

    ssd1306_char_f6x8(16, 4, “U F O E S C A P E”);

    ^

    sketch_jan17b:156: error: ‘ssd1306_setpos’ was not declared in this scope

    ssd1306_setpos(obstacle[i],row);

    ^

    sketch_jan17b:157: error: ‘ssd1306_send_data_start’ was not declared in this scope

    ssd1306_send_data_start();

    ^

    sketch_jan17b:165: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(temp);

    ^

    sketch_jan17b:173: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(B00000000);

    ^

    sketch_jan17b:185: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(temp);

    ^

    sketch_jan17b:191: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(B11111111);

    ^

    sketch_jan17b:196: error: ‘ssd1306_send_data_stop’ was not declared in this scope

    ssd1306_send_data_stop();

    ^

    sketch_jan17b:206: error: ‘ssd1306_setpos’ was not declared in this scope

    ssd1306_setpos(8,playerOffset/8);

    ^

    sketch_jan17b:207: error: ‘ssd1306_send_data_start’ was not declared in this scope

    ssd1306_send_data_start();

    ^

    sketch_jan17b:209: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte((B00001100&flameMask[flames])<<playerOffset%8);

    ^

    sketch_jan17b:229: error: 'ssd1306_send_byte' was not declared in this scope

    ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))<>8-playerOffset%8);

    ^

    sketch_jan17b:263: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))>>8-playerOffset%8);

    ^

    sketch_jan17b:274: error: ‘ssd1306_setpos’ was not declared in this scope

    ssd1306_setpos(8,playerOffset/8);

    ^

    sketch_jan17b:275: error: ‘ssd1306_send_data_start’ was not declared in this scope

    ssd1306_send_data_start();

    ^

    sketch_jan17b:277: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(B00001100&flameMask[flames]);

    ^

    sketch_jan17b:297: error: ‘ssd1306_send_byte’ was not declared in this scope

    ssd1306_send_byte(B00001100&flameMask[flames] | random(0,255));

    ^

    sketch_jan17b:306: error: ‘ssd1306_send_data_stop’ was not declared in this scope

    ssd1306_send_data_stop();

    ^

    C:\Users\Adam\Documents\Arduino\sketch_jan17b\sketch_jan17b.ino: In function ‘void resetGame()’:

    sketch_jan17b:336: error: ‘ssd1306_char_f6x8’ was not declared in this scope

    ssd1306_char_f6x8(16, 4, “U F O E S C A P E”);

    ^

    C:\Users\Adam\Documents\Arduino\sketch_jan17b\sketch_jan17b.ino: In function ‘void system_sleep()’:

    sketch_jan17b:592: error: expected ‘;’ before ‘}’ token

    }

    ^

    exit status 1
    ‘ssd1306_init’ was not declared in this scope

    Reply
    • Hi Adam, this will not work on boards other than attiny85, if you are using digispark you will need to modify the code accordingly. You will also need to install the OLED control libraries. Hope this helps.

      Reply

Leave a Comment