Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 2 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
Hi everyone!
so This is my DIY Oled based Retro Gaming console*
*(actually its not a gaming console, but a setup which connects a generic 0.96inch Oled with an Attiny85)
Let me show you guys how I made this.
watch the video first-
also, the code for this project is pretty common and available online, I just took that code and did a few changes and made PCB for it.
the original post is -https://electronoobs.com/eng_arduino_tut120.php but his version also isn't the original one.
also, Thanks UTSOURCE.net to offer electronic components for this project!you can check UTSOURCE.net, they deal in all kinds of electronics components like for example Attiny84, Resistors, switches, and other discreet electronics components which I'm using in this project.
STEP1
Gather the materials that we need.
- Oled 0.96inch x 1
- Attiny85 x1
- Programmer setup for Programming Attiny85 x 1
(you can use Arduino Uno as Programmer here- https://www.hackster.io/Oniichan_is_ded/learn-how-to-program-attiny85-and-attiny13a-167359 or make your own Custom programmer-https://www.hackster.io/Oniichan_is_ded/multiple-attiny85-13a-programmer-84adf8 )
- Switch x 3
- 10K Resistor SMD 0805 x 2
- 1K Resistor SMD 0603 x 2
- LED 0603 x 1
- M7 diode SMA x 1
- USB micro port x 1
- 7K Resistor SMD 0603 x 1
- Custom PCB (Gerber available for Manufacturing)
- FR4 copper board
- Etched PCB (PDF available for manual PCB making Process via Tone transfer)
watch this if you want to know about how to make PCB via the tone transfer method-
STEP2
"Getting startedBASICS"
This is version 1, which is completely homemade. I etched its PCB from copper Fr board which you can buy from amazon or something.
V2 is kinda the same but with a better PCB and cool silkscreen with a white color solder mask.
originally, I wanted to make this board properly with this PCB which I prepared last month but because of COVID, I was unable to place order because of shipment issue so I decided to etch the PCB by changing the PCB Design a little so I can make it one-sided instead of two-sided PCB.
I used OrCad to design the PCB, and then generated a pdf for printing the board on a photo paper for etching.
if you guys want to make your own, then the schematic, and Gerber data for proper PCB is attached.
Check out V2 of this project https://www.hackster.io/Oniichan_is_ded/atboy-minimal-retro-gaming-console-808bc1
STEP 3"Getting the board Readyand adding components to it"
I first print out the PDF of the board on a photo paper with an inkjet printer, then I cut an Fr4 copper board PCB according to the PCB size.
rest is the generic tone transfer etching procedure, watch my video about this topic.
I'm gonna skip to the finished etched PCB.
Adding components to this PCB is pretty easy and straight Forward, follow this illustration for SMD components
after adding SMD Components, just go ahead and start adding the remaining leaded components, like switches, Oled, and other stuff.
also, because this board is not completely one-sided, I added jumpers on few points to make life a little easy,
after assembling everything, we just need to add the battery to its assigned connector port and power whole thing up.
wait, how to program the attiny85!
STEP 4"Programming the Attiny85"Attiny85 and Attiny13A is my Favorite microcontroller as they are cheap and can be used in a variety of projects which doesn't require overkill hardware and connectivity options, for example- Oled Gameboy!
In order to program the Attiny85 MCU, you gonna need an ISP programmer
I've already made a post about programming the Attiny85 so do check that out-https://www.hackster.io/Oniichan_is_ded/learn-how-to-program-attiny85-and-attiny13a-167359
and also about making your own Arduino Nano based Attiny Programmer-https://www.hackster.io/Oniichan_is_ded/multiple-attiny85-13a-programmer-84adf8
so I'm gonna skip the programming process, let's just say we need to burn the bootloader first and then flash the attiny85 with the provided code.
Note- you need to add the header file provided with the main code in the main code folder crated, then add the whole folder in the Arduino sketch folder in Documents.
"Power and Testing the board"
In order to power this board, I've added a battery connector, a Li-ion battery can be connected which it.
This is still the V1 and the next level of this project will be to make a proper PCB and solder everything onto it.. also, maybe we can add a UI in it so we can select more than 1 game. For now, if you run into some issues just Leave a comment.
ADIOS!
#include <EEPROM.h>
#include "font6x8AJ.h"
#include <avr/sleep.h>
#include <avr/interrupt.h> // needed for the additional interrupt
#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)
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// Defines for OLED output
#define SSD1306XLED_H
#define SSD1306_SCL PORTB4 // SCL, Pin 4 on SSD1306 Board - for webbogles board
#define SSD1306_SDA PORTB3 // SDA, Pin 3 on SSD1306 Board - for webbogles board
#define SSD1306_SA 0x78 // Slave address
// Function prototypes
void resetAliens(void);
void drawPlatform(void);
void sendBlock(int);
void playSpaceAttack(void);
void beep(int,int);
void levelUp(int);
void drawFire(int x, int y);
void doDrawLS(long, byte);
void doDrawRS(long, byte);
void clearAlienArea(int row, int lastActive);
void doNumber (int x, int y, int value);
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_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t bitmap[]);
long firecounter = 0; // Timer for managing fire
long aliencounter = 0; // Timer for alien movement
int mothercounter = 0; // Timer for movement of the mothership
int level = 1; // Game level - incremented every time you clear the screen
int mothershipX = 0; // position of mothership
int mothership = 0; // is mothership active?
int mothershipWidth = 8; // mothership width in pixels
int fireXidx, fireYidx; // mapping of player fire locaiton onto array of aliens
int leftLimit; // furtherst left in x-axis pixels that the aliens are currently
int positionNow = 0; // current position of the alien array (as steps from the left)
boolean alienDirection = 1; // current direction of travel for alien swarm - 1 is right 0 is left
int alienRow = 0; // which alien row are we considering
int alienFire[5][3]; // max 5 lots of alien fire - indices are active, xpos, ypos
int playerFire[3]; // one lot of player fire - indices are active, xpos, ypos
boolean row[4][10]; // on-off array of aliens
int firstAlien = 0; // index of first live alien in the array
int lastAlien = 8; // index of last live alien in the array
int newFirst = firstAlien; // as above when it changes
int newLast = lastAlien; // ...
int aliensDead = 0; // how many aliens have been killed in total on this level?
int lastActiveRow = 2; // what's the lowest row in which we will find live aliens?
int deadOn1 = 0; // how many aliens are dead on row one (middle row)
int deadOn2 = 0; // how many aliens are dead on row two (bottom row)
boolean fire = 0;
int topScoreB = 0;
int player; //0 to 128-platformWidth - this is the position of the player
int platformWidth = 16;
boolean stopAnimate = 0; // this is set to 1 when a collision is detected
boolean mute = 0;
boolean newHigh = 0;
int score = 0; // score - this affects the difficulty of the game
int top = 0;
// Interrupt handlers
ISR(PCINT0_vect){ // PB0 pin button interrupt
if (digitalRead(2)==1) fire = 1;
}
void playerIncSpaceAttack(){ // PB2 pin button interrupt
if (digitalRead(0)==1) fire = 1;
}
// Arduino stuff - setup
void setup() {
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
}
// Arduino stuff - loop
void loop() {
ssd1306_init();
ssd1306_fillscreen(0x00);
// The lower case character set is seriously compromised because I've had to truncate the ASCII table
// to release space for executable code - hence lower case y and w are remapped to h and / respectively.
// There is no z in the table (or h!) as these aren't used anywhere in the text here and most of the
// symbols are also missing for the same reason (see my hacked version of font6x8.h - font6x8AJ.h for more detail)
ssd1306_char_f6x8(0, 1, "A L I E N");
ssd1306_char_f6x8(4, 2, "A T T A C K");
ssd1306_char_f6x8(0, 4, "by A R N O V"); // see comments above !
ssd1306_setpos(85,1);
ssd1306_send_data_start();
sendBlock(1);
sendBlock(0);
sendBlock(1);
ssd1306_send_data_stop();
ssd1306_setpos(85,2);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(2);
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_setpos(85,3);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
sendBlock(1);
sendBlock(0);
sendBlock(1);
ssd1306_send_data_stop();
player = 96;
drawPlatform();
long startT = millis();
long nowT =0;
boolean sChange = 0;
while(digitalRead(0) == HIGH) {
nowT = millis();
if (nowT - startT > 2000) {
sChange = 1;
if (digitalRead(2) == HIGH) {
EEPROM.write(0,0);
EEPROM.write(1,0);
ssd1306_char_f6x8(8, 0, "-HIGH SCORE RESET-");
} else if (mute == 0) { mute = 1; ssd1306_char_f6x8(32, 0, "-- MUTE --"); } else { mute = 0; ssd1306_char_f6x8(23, 0, "-- SOUND ON --"); }
break;
}
if (sChange == 1) break;
}
while(digitalRead(0) == HIGH);
if (sChange == 0) {
for (byte mm = 112;mm>=1;mm--) {
if ( (mm>=32) && (mm<56) ) drawFire(104,mm);
if (mm == 32) {
ssd1306_setpos(100,3);
ssd1306_send_data_start();
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_setpos(100,4);
ssd1306_send_data_start();
sendBlock(0);
ssd1306_send_data_stop();
beep(30,100);
}
ssd1306_setpos(mm,0);
ssd1306_send_data_start();
sendBlock(3);
sendBlock(0);
ssd1306_send_data_stop();
drawPlatform();
delay(20);
}
ssd1306_setpos(0,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_char_f6x8(0, 6, "inspired bh"); // see comments above !
ssd1306_char_f6x8(0, 7, "/ebboggles.com"); // see comments above !
delay(1500);
ssd1306_init();
ssd1306_fillscreen(0x00);
stopAnimate = 0;
score = 0;
playSpaceAttack();
top=topScoreB;
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(11, 1, "----------------");
ssd1306_char_f6x8(11, 2, "G A M E O V E R");
ssd1306_char_f6x8(11, 3, "----------------");
ssd1306_char_f6x8(37, 5, "SCORE:");
doNumber(75, 5, score);
if (!newHigh) {
ssd1306_char_f6x8(21, 7, "HIGH SCORE:");
doNumber(88, 7, top);
}
for (int i = 0; i<1000; i = i+ 50){
beep(50,i);
}
delay(2000);
if (newHigh) {
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(10, 1, "----------------");
ssd1306_char_f6x8(10, 3, " NEW HIGH SCORE ");
ssd1306_char_f6x8(10, 7, "----------------");
doNumber(50,5,top);
for (int i = 700; i>200; i = i - 50){
beep(30,i);
}
newHigh = 0;
delay(2700);
}
}
system_sleep();
}
void doNumber (int x, int y, int value) {
char temp[10] = {0,0,0,0,0,0,0,0,0,0};
itoa(value,temp,10);
ssd1306_char_f6x8(x, y, temp);
}
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)
{
if (y>7) return;
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 (c >0) c = c - 12;
if (c >15) c = c - 6;
if (c>40) c=c-9;
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++;
}
}
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);
}
void beep(int bCount,int bDelay){
if (mute) return;
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");}}
}
/* ------------------------
* SpaceAttack Code
*/
void playSpaceAttack() {
firecounter = 0; // Timer for managing fire
level = 1; // Game level - incremented every time you clear the screen
topScoreB = 0; // highscore
score = 0; // obvious
// Initialisations
for (byte i = 0; i<5;i++) {
alienFire[i][0] = 0;
}
resetAliens();
levelUp(1); // This also does various essential initialisations
attachInterrupt(0,playerIncSpaceAttack,CHANGE);
while (stopAnimate == 0) {
while(1) {
aliencounter++;
firecounter++;
mothercounter++;
// deal with inputs
if(analogRead(0) < 940) fire =1; // this reads the reset pin (pin 1) of the Attiny85 - to use it, see comments section above
if(digitalRead(2)==1) {
if (digitalRead(0)==1) fire = 1;
if(player < 127-platformWidth) {
player++;
}
}
if (digitalRead(0)==1){
if (digitalRead(2)==1) fire = 1;
if (player >1){
player--;
}
}
if ( (mothership == 0) && (random(0,1000) > 998) && (alienRow>0) ) {
mothership = 1;
mothershipX= 127-16;
}
// draw aliens
for (int inc = 0; inc <= lastActiveRow; inc ++) {
ssd1306_setpos((positionNow*8),inc+alienRow);
ssd1306_send_data_start();
for (int bl = firstAlien; bl <=lastAlien; bl++){
if(row[inc][bl]==1){
if (inc == lastActiveRow) {
if(random(0,1000) > (999-level)) { // this alien is going to fire!
byte afIndex = 0;
while (alienFire[afIndex][0] == 1) {
afIndex++; // search for empty alien fire option
if (afIndex == 5) break;
}
if (afIndex < 5) { // we've found a slot
alienFire[afIndex][0] = 1; // activate fire on this slot
alienFire[afIndex][1] = ( (positionNow*8) + ((bl-firstAlien) * 8) + 4); // x position
alienFire[afIndex][2] = (inc+alienRow+1)*8; // Where the fire starts
}
} // end of this alien firing
} // only if we are on the lowest row of live aliens
if ( (inc == 0) || (inc == 2) ) {
sendBlock(1);
} else {
sendBlock(2);
}
}else {
sendBlock(0);
}
}
ssd1306_send_data_stop();
}
// Display the score
doNumber(0,6,score);
// Burn clock cycles to keep game at constant (ish) speed when there are low numbers of live aliens
int burnLimit = (8-(lastAlien-firstAlien));
for (int burn = 0; burn < burnLimit; burn+=2) {
drawPlatform();
}
// Display the mothership
if (mothercounter >= 3) {
mothercounter = 0;
// draw mothership
if (mothership) {
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(3);
sendBlock(0);
ssd1306_send_data_stop();
mothershipX --;
if (mothershipX == 0) {
mothership = 0;
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
}
}
}
// Move the aliens
if (aliencounter >= (92-((level-1)*5)) ) {
aliencounter = 0;
if(alienDirection) { // Moving right
// move down a row
if (positionNow >= 6+(8-(lastAlien-firstAlien))) {
alienDirection = 0;
clearAlienArea(alienRow,lastActiveRow);
alienRow++;
} else {
positionNow++;
}
} else { // Moving left
// move down a row
if (positionNow <= 0) {
alienDirection = 1;
clearAlienArea(alienRow,lastActiveRow);
alienRow++;
} else {
positionNow --;
}
}
clearAlienArea(alienRow,lastActiveRow);
}
// Fire !
if ((fire == 1) && (playerFire[0] == 0)) { // fire has been pressed and we're not currently firing - initiate fire!!
playerFire[0] = 1;
playerFire[1] = player+platformWidth/2; // xposition of new fire!
playerFire[2] = 56;
}
// Handle all firing-related stuff (in both directions!)
if (firecounter >= 2) {
firecounter = 0;
// --- Deal with player Firing ---
if (playerFire[0] == 1) {
drawFire(playerFire[1], playerFire[2]);
if (playerFire[2] == 0) {
ssd1306_setpos(playerFire[1],0);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
playerFire[0] = 0;
fire = 0;
} else {
playerFire[2] = playerFire[2] - 1;
}
}
// aliens are at positionNow * 8 + 8* their index
leftLimit = positionNow*8;
if ((fire == 1)) {
fireXidx = firstAlien + floor((playerFire[1]-positionNow*8)/8);
fireYidx= floor(floor(playerFire[2]/8) - alienRow);
if((mothership == 1) && (playerFire[1] >= mothershipX) && (playerFire[1] <= mothershipX +8) && playerFire[2] <=8) {
long scm = random(1,100);
if (scm<30) {
score +=50;
} else if (scm<60) {
score += 100;
} else if (scm<90) {
score += 150;
} else {
score += 300;
}
beep(30,400);
beep(30,300);
beep(30,200);
beep(30,100);
mothership = 0;
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
}
// Alien has been hit
if ((playerFire[1] >= leftLimit) && (fireYidx>=0) && (fireYidx<=lastActiveRow) && (fireXidx>=0) && (fireXidx<9)) {
if (row[fireYidx][fireXidx] == 1) {
int lastActiveToClear = lastActiveRow; // if we kill the last alien on a row - we still need to clear that row (end of this fn)
if (fireYidx == 2) deadOn2++;
if (fireYidx == 1) deadOn1++;
if (deadOn2 == 5) {lastActiveRow = 1;}
if ((deadOn1 == 4) && (deadOn2 == 5)) {lastActiveRow = 0;}
score = score + (int)((3-fireYidx) * 10);
aliensDead++;
ssd1306_setpos(playerFire[1],alienRow+fireYidx+1);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
beep(30,100);
fire = 0;
playerFire[0] = 0;
playerFire[1] = 0;
playerFire[2] = 7;
row[fireYidx][fireXidx] = 0;
if (fireXidx == firstAlien) { // this is the first Alien - sweep back and reset to new first alien
for (int xi = lastAlien; xi>=firstAlien;xi--) {
if ( (row[0][xi] == 1) || (row[1][xi] == 1) || (row[2][xi] == 1) ) newFirst = xi;
}
positionNow += newFirst - firstAlien;
firstAlien = newFirst;
}
if (fireXidx == lastAlien) { // this is the last Alien - sweep thru and reset to new last alien
for (int xi = firstAlien; xi<=lastAlien;xi++) {
if ( (row[0][xi] == 1) || (row[1][xi] == 1) || (row[2][xi] == 1) ) newLast = xi;
}
lastAlien = newLast;
}
clearAlienArea(alienRow,lastActiveToClear);
}
}
}
// --- Deal with Alien Firing ---
for (byte afIndex = 0; afIndex<5; afIndex++) {
if(alienFire[afIndex][0] == 1) {
drawFire(alienFire[afIndex][1],alienFire[afIndex][2]);
alienFire[afIndex][2] = alienFire[afIndex][2] + 1;
if (alienFire[afIndex][2] >= 56) {
ssd1306_setpos(alienFire[afIndex][1],7);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
alienFire[afIndex][0] = 0; // the fire's got to the end
if( ((alienFire[afIndex][1]) > player) && ( (alienFire[afIndex][1]) < player+platformWidth) ) { // you've been hit!!
stopAnimate = 1;
goto die;
}
}
}
} // end of aliens firing
}
if (aliensDead == 14) {
for (int ai = 0; ai <= 5; ai++) alienFire[ai][0] = 0;
level++;
if (level>15) level = 15;
levelUp(level);
resetAliens();
}
if( ((alienRow == 5) && (lastActiveRow == 2))|| ( (alienRow == 6) && (lastActiveRow == 1)) || ((alienRow == 7) && (lastActiveRow == 0)) || stopAnimate) {
stopAnimate = 1;
break;
}
// draw the player
drawPlatform();
}
}
die:
topScoreB = EEPROM.read(0);
topScoreB = topScoreB << 8;
topScoreB = topScoreB | EEPROM.read(1);
if (score > topScoreB) {
topScoreB = score;
EEPROM.write(1,score & 0xFF);
EEPROM.write(0,(score>>8) & 0xFF);
newHigh = 1;
}
}
void drawPlatform(){
ssd1306_setpos(player,7);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
for (byte pw = 0; pw <5; pw++){ssd1306_send_byte(B11000000);}
for (byte pw = 0; pw <4; pw++){ssd1306_send_byte(B11110000);}
for (byte pw = 0; pw <5; pw++){ssd1306_send_byte(B11000000);}
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
}
void sendBlock(int fill){
if (fill == 1) {
ssd1306_send_byte(B10011000);
ssd1306_send_byte(B01011100);
ssd1306_send_byte(B10110110);
ssd1306_send_byte(B01011111);
ssd1306_send_byte(B01011111);
ssd1306_send_byte(B10110110);
ssd1306_send_byte(B01011100);
ssd1306_send_byte(B10011000);
} else if (fill == 2) {
ssd1306_send_byte(B00110000);
ssd1306_send_byte(B00111110);
ssd1306_send_byte(B10110011);
ssd1306_send_byte(B01011101);
ssd1306_send_byte(B01011101);
ssd1306_send_byte(B10110011);
ssd1306_send_byte(B00111110);
ssd1306_send_byte(B00110000);
} else if (fill == 3) {
ssd1306_send_byte(B00011000);
ssd1306_send_byte(B00111000);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00111000);
ssd1306_send_byte(B00011000);
} else if (fill == 0) {
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 levelUp(int number) {
fire = 0; // make sure no fire
playerFire[0] = 0; // player fire inactive
aliencounter=0;
firecounter=0;
mothercounter=0;
firstAlien = 0;
lastAlien = 8;
newFirst = 0;
newLast = 8;
lastActiveRow = 2;
deadOn1 = 0;
deadOn2 = 0;
aliensDead = 0;
mothership = 0;
alienRow = 0;
positionNow = 0;
alienDirection = 1;
player = 64;
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(16, 3, "--------------");
ssd1306_char_f6x8(16, 4, " L E V E L ");
ssd1306_char_f6x8(16, 5, "--------------");
doNumber(85,4,number);
for (int i = 800; i>200; i = i - 200){
beep(30,i);
}
delay(700);
ssd1306_fillscreen(0x00);
}
void drawFire(int x, int y) {
if (y%8!=0){
ssd1306_setpos(x,y/8);
ssd1306_send_data_start();
doDrawLS(0,y%8);
ssd1306_send_data_stop();
ssd1306_setpos(x,y/8+1);
ssd1306_send_data_start();
doDrawRS(0,8-y%8);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(x,y/8);
ssd1306_send_data_start();
doDrawLS(0,0);
ssd1306_send_data_stop();
}
}
// Drawing routine for the player and fire - with right-shifts
void doDrawRS(long P1, byte P2) {
ssd1306_send_byte((B01111110 | P1)>>P2);
}
// Drawing routine for the player and fire - with left-shifts
void doDrawLS(long P1, byte P2) {
ssd1306_send_byte((B01111110 | P1)<<P2);
}
void clearAlienArea(int row, int lastActive) {
// clear alien area
for (int inc = row; inc <=row+lastActive; inc ++) {
ssd1306_setpos(0,inc);
ssd1306_send_data_start();
for (int bl = 0; bl <16; bl++){
sendBlock(0);
}
ssd1306_send_data_stop();
}
}
void resetAliens(void) {
for (byte i =0; i<10;i++){ // reset aliens
row[0][i]=0; row[1][i]=0; row[2][i]=0;
}
for (byte i =0; i<9;i+=2){ // reset aliens
row[0][i]=1; row[2][i]=1;
}
for (byte i =1; i<9;i+=2){ // reset aliens
row[1][i]=1;
}
}
// ----------------------------------------------------------------------------
#include <avr/pgmspace.h>
// ----------------------------------------------------------------------------
/* Standard ASCII 6x8 font */
static const uint8_t ssd1306xled_font6x8 [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sp
/*
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, // !
0x00, 0x00, 0x07, 0x00, 0x07, 0x00, // "
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14, // #
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12, // $
0x00, 0x62, 0x64, 0x08, 0x13, 0x23, // %
0x00, 0x36, 0x49, 0x55, 0x22, 0x50, // &
0x00, 0x00, 0x05, 0x03, 0x00, 0x00, // '
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, // (
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00, // )
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, // *
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, // +
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00, // ,
*/
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, // -
0x00, 0x00, 0x60, 0x60, 0x00, 0x00, // .
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, // w in place of /
//0x00, 0x20, 0x10, 0x08, 0x04, 0x02, // /
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, // 1
0x00, 0x42, 0x61, 0x51, 0x49, 0x46, // 2
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31, // 3
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, // 4
0x00, 0x27, 0x45, 0x45, 0x45, 0x39, // 5
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
0x00, 0x01, 0x71, 0x09, 0x05, 0x03, // 7
0x00, 0x36, 0x49, 0x49, 0x49, 0x36, // 8
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, // 9
0x00, 0x00, 0x36, 0x36, 0x00, 0x00, // :
/*
0x00, 0x00, 0x56, 0x36, 0x00, 0x00, // ;
0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14, // =
0x00, 0x00, 0x41, 0x22, 0x14, 0x08, // >
0x00, 0x02, 0x01, 0x51, 0x09, 0x06, // ?
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E, // @
*/
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C, // A
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, // B
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, // C
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, // D
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, // E
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, // F
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A, // G
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, // H
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00, // I
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, // J
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, // K
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, // L
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, // M
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, // N
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, // O
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, // P
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, // R
0x00, 0x46, 0x49, 0x49, 0x49, 0x31, // S
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, // T
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, // U
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, // V
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, // W
/*
0x00, 0x63, 0x14, 0x08, 0x14, 0x63, // X
0x00, 0x07, 0x08, 0x70, 0x08, 0x07, // Y
0x00, 0x61, 0x51, 0x49, 0x45, 0x43, // Z
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [
0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55, // 55
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00, // ]
0x00, 0x04, 0x02, 0x01, 0x02, 0x04, // ^
0x00, 0x40, 0x40, 0x40, 0x40, 0x40, // _
0x00, 0x00, 0x01, 0x02, 0x04, 0x00, // '
*/
0x00, 0x20, 0x54, 0x54, 0x54, 0x78, // a
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, // b
0x00, 0x38, 0x44, 0x44, 0x44, 0x20, // c
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, // d
0x00, 0x38, 0x54, 0x54, 0x54, 0x18, // e
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02, // f
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, // g
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C, // y (in place of h)
//0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, // h
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00, // i
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00, // j
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, // l
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, // m
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78, // n
0x00, 0x38, 0x44, 0x44, 0x44, 0x38, // o
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18, // p
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC, // q
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08, // r
0x00, 0x48, 0x54, 0x54, 0x54, 0x20, // s
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20, // t
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C, // u
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, // v
// 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, // w
// 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, // x
// 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C // y
};
// ----------------------------------------------------------------------------
#include <EEPROM.h>
#include "font6x8AJ.h"
#include <avr/sleep.h>
#include <avr/interrupt.h> // needed for the additional interrupt
#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)
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// Defines for OLED output
#define SSD1306XLED_H
#define SSD1306_SCL PORTB4 // SCL, Pin 4 on SSD1306 Board - for webbogles board
#define SSD1306_SDA PORTB3 // SDA, Pin 3 on SSD1306 Board - for webbogles board
#define SSD1306_SA 0x78 // Slave address
// Function prototypes
void resetAliens(void);
void drawPlatform(void);
void sendBlock(int);
void playSpaceAttack(void);
void beep(int,int);
void levelUp(int);
void drawFire(int x, int y);
void doDrawLS(long, byte);
void doDrawRS(long, byte);
void clearAlienArea(int row, int lastActive);
void doNumber (int x, int y, int value);
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_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t bitmap[]);
long firecounter = 0; // Timer for managing fire
long aliencounter = 0; // Timer for alien movement
int mothercounter = 0; // Timer for movement of the mothership
int level = 1; // Game level - incremented every time you clear the screen
int mothershipX = 0; // position of mothership
int mothership = 0; // is mothership active?
int mothershipWidth = 8; // mothership width in pixels
int fireXidx, fireYidx; // mapping of player fire locaiton onto array of aliens
int leftLimit; // furtherst left in x-axis pixels that the aliens are currently
int positionNow = 0; // current position of the alien array (as steps from the left)
boolean alienDirection = 1; // current direction of travel for alien swarm - 1 is right 0 is left
int alienRow = 0; // which alien row are we considering
int alienFire[5][3]; // max 5 lots of alien fire - indices are active, xpos, ypos
int playerFire[3]; // one lot of player fire - indices are active, xpos, ypos
boolean row[4][10]; // on-off array of aliens
int firstAlien = 0; // index of first live alien in the array
int lastAlien = 8; // index of last live alien in the array
int newFirst = firstAlien; // as above when it changes
int newLast = lastAlien; // ...
int aliensDead = 0; // how many aliens have been killed in total on this level?
int lastActiveRow = 2; // what's the lowest row in which we will find live aliens?
int deadOn1 = 0; // how many aliens are dead on row one (middle row)
int deadOn2 = 0; // how many aliens are dead on row two (bottom row)
boolean fire = 0;
int topScoreB = 0;
int player; //0 to 128-platformWidth - this is the position of the player
int platformWidth = 16;
boolean stopAnimate = 0; // this is set to 1 when a collision is detected
boolean mute = 0;
boolean newHigh = 0;
int score = 0; // score - this affects the difficulty of the game
int top = 0;
// Interrupt handlers
ISR(PCINT0_vect){ // PB0 pin button interrupt
if (digitalRead(2)==1) fire = 1;
}
void playerIncSpaceAttack(){ // PB2 pin button interrupt
if (digitalRead(0)==1) fire = 1;
}
// Arduino stuff - setup
void setup() {
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
}
// Arduino stuff - loop
void loop() {
ssd1306_init();
ssd1306_fillscreen(0x00);
// The lower case character set is seriously compromised because I've had to truncate the ASCII table
// to release space for executable code - hence lower case y and w are remapped to h and / respectively.
// There is no z in the table (or h!) as these aren't used anywhere in the text here and most of the
// symbols are also missing for the same reason (see my hacked version of font6x8.h - font6x8AJ.h for more detail)
ssd1306_char_f6x8(0, 1, "A L I E N");
ssd1306_char_f6x8(4, 2, "A T T A C K");
ssd1306_char_f6x8(0, 4, "by A R N O V"); // see comments above !
ssd1306_setpos(85,1);
ssd1306_send_data_start();
sendBlock(1);
sendBlock(0);
sendBlock(1);
ssd1306_send_data_stop();
ssd1306_setpos(85,2);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(2);
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_setpos(85,3);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
sendBlock(1);
sendBlock(0);
sendBlock(1);
ssd1306_send_data_stop();
player = 96;
drawPlatform();
long startT = millis();
long nowT =0;
boolean sChange = 0;
while(digitalRead(0) == HIGH) {
nowT = millis();
if (nowT - startT > 2000) {
sChange = 1;
if (digitalRead(2) == HIGH) {
EEPROM.write(0,0);
EEPROM.write(1,0);
ssd1306_char_f6x8(8, 0, "-HIGH SCORE RESET-");
} else if (mute == 0) { mute = 1; ssd1306_char_f6x8(32, 0, "-- MUTE --"); } else { mute = 0; ssd1306_char_f6x8(23, 0, "-- SOUND ON --"); }
break;
}
if (sChange == 1) break;
}
while(digitalRead(0) == HIGH);
if (sChange == 0) {
for (byte mm = 112;mm>=1;mm--) {
if ( (mm>=32) && (mm<56) ) drawFire(104,mm);
if (mm == 32) {
ssd1306_setpos(100,3);
ssd1306_send_data_start();
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_setpos(100,4);
ssd1306_send_data_start();
sendBlock(0);
ssd1306_send_data_stop();
beep(30,100);
}
ssd1306_setpos(mm,0);
ssd1306_send_data_start();
sendBlock(3);
sendBlock(0);
ssd1306_send_data_stop();
drawPlatform();
delay(20);
}
ssd1306_setpos(0,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
ssd1306_char_f6x8(0, 6, "R E T R O"); // see comments above !
ssd1306_char_f6x8(0, 7, "/GAME CONSOLE"); // see comments above !
delay(1500);
ssd1306_init();
ssd1306_fillscreen(0x00);
stopAnimate = 0;
score = 0;
playSpaceAttack();
top=topScoreB;
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(11, 1, "----------------");
ssd1306_char_f6x8(11, 2, "G A M E O V E R");
ssd1306_char_f6x8(11, 3, "----------------");
ssd1306_char_f6x8(37, 5, "SCORE:");
doNumber(75, 5, score);
if (!newHigh) {
ssd1306_char_f6x8(21, 7, "HIGH SCORE:");
doNumber(88, 7, top);
}
for (int i = 0; i<1000; i = i+ 50){
beep(50,i);
}
delay(2000);
if (newHigh) {
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(10, 1, "----------------");
ssd1306_char_f6x8(10, 3, " NEW HIGH SCORE ");
ssd1306_char_f6x8(10, 7, "----------------");
doNumber(50,5,top);
for (int i = 700; i>200; i = i - 50){
beep(30,i);
}
newHigh = 0;
delay(2700);
}
}
system_sleep();
}
void doNumber (int x, int y, int value) {
char temp[10] = {0,0,0,0,0,0,0,0,0,0};
itoa(value,temp,10);
ssd1306_char_f6x8(x, y, temp);
}
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)
{
if (y>7) return;
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 (c >0) c = c - 12;
if (c >15) c = c - 6;
if (c>40) c=c-9;
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++;
}
}
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);
}
void beep(int bCount,int bDelay){
if (mute) return;
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");}}
}
/* ------------------------
* SpaceAttack Code
*/
void playSpaceAttack() {
firecounter = 0; // Timer for managing fire
level = 1; // Game level - incremented every time you clear the screen
topScoreB = 0; // highscore
score = 0; // obvious
// Initialisations
for (byte i = 0; i<5;i++) {
alienFire[i][0] = 0;
}
resetAliens();
levelUp(1); // This also does various essential initialisations
attachInterrupt(0,playerIncSpaceAttack,CHANGE);
while (stopAnimate == 0) {
while(1) {
aliencounter++;
firecounter++;
mothercounter++;
// deal with inputs
if(analogRead(0) < 940) fire =1; // this reads the reset pin (pin 1) of the Attiny85 - to use it, see comments section above
if(digitalRead(2)==1) {
if (digitalRead(0)==1) fire = 1;
if(player < 127-platformWidth) {
player++;
}
}
if (digitalRead(0)==1){
if (digitalRead(2)==1) fire = 1;
if (player >1){
player--;
}
}
if ( (mothership == 0) && (random(0,1000) > 998) && (alienRow>0) ) {
mothership = 1;
mothershipX= 127-16;
}
// draw aliens
for (int inc = 0; inc <= lastActiveRow; inc ++) {
ssd1306_setpos((positionNow*8),inc+alienRow);
ssd1306_send_data_start();
for (int bl = firstAlien; bl <=lastAlien; bl++){
if(row[inc][bl]==1){
if (inc == lastActiveRow) {
if(random(0,1000) > (999-level)) { // this alien is going to fire!
byte afIndex = 0;
while (alienFire[afIndex][0] == 1) {
afIndex++; // search for empty alien fire option
if (afIndex == 5) break;
}
if (afIndex < 5) { // we've found a slot
alienFire[afIndex][0] = 1; // activate fire on this slot
alienFire[afIndex][1] = ( (positionNow*8) + ((bl-firstAlien) * 8) + 4); // x position
alienFire[afIndex][2] = (inc+alienRow+1)*8; // Where the fire starts
}
} // end of this alien firing
} // only if we are on the lowest row of live aliens
if ( (inc == 0) || (inc == 2) ) {
sendBlock(1);
} else {
sendBlock(2);
}
}else {
sendBlock(0);
}
}
ssd1306_send_data_stop();
}
// Display the score
doNumber(0,6,score);
// Burn clock cycles to keep game at constant (ish) speed when there are low numbers of live aliens
int burnLimit = (8-(lastAlien-firstAlien));
for (int burn = 0; burn < burnLimit; burn+=2) {
drawPlatform();
}
// Display the mothership
if (mothercounter >= 3) {
mothercounter = 0;
// draw mothership
if (mothership) {
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(3);
sendBlock(0);
ssd1306_send_data_stop();
mothershipX --;
if (mothershipX == 0) {
mothership = 0;
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
}
}
}
// Move the aliens
if (aliencounter >= (92-((level-1)*5)) ) {
aliencounter = 0;
if(alienDirection) { // Moving right
// move down a row
if (positionNow >= 6+(8-(lastAlien-firstAlien))) {
alienDirection = 0;
clearAlienArea(alienRow,lastActiveRow);
alienRow++;
} else {
positionNow++;
}
} else { // Moving left
// move down a row
if (positionNow <= 0) {
alienDirection = 1;
clearAlienArea(alienRow,lastActiveRow);
alienRow++;
} else {
positionNow --;
}
}
clearAlienArea(alienRow,lastActiveRow);
}
// Fire !
if ((fire == 1) && (playerFire[0] == 0)) { // fire has been pressed and we're not currently firing - initiate fire!!
playerFire[0] = 1;
playerFire[1] = player+platformWidth/2; // xposition of new fire!
playerFire[2] = 56;
}
// Handle all firing-related stuff (in both directions!)
if (firecounter >= 2) {
firecounter = 0;
// --- Deal with player Firing ---
if (playerFire[0] == 1) {
drawFire(playerFire[1], playerFire[2]);
if (playerFire[2] == 0) {
ssd1306_setpos(playerFire[1],0);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
playerFire[0] = 0;
fire = 0;
} else {
playerFire[2] = playerFire[2] - 1;
}
}
// aliens are at positionNow * 8 + 8* their index
leftLimit = positionNow*8;
if ((fire == 1)) {
fireXidx = firstAlien + floor((playerFire[1]-positionNow*8)/8);
fireYidx= floor(floor(playerFire[2]/8) - alienRow);
if((mothership == 1) && (playerFire[1] >= mothershipX) && (playerFire[1] <= mothershipX +8) && playerFire[2] <=8) {
long scm = random(1,100);
if (scm<30) {
score +=50;
} else if (scm<60) {
score += 100;
} else if (scm<90) {
score += 150;
} else {
score += 300;
}
beep(30,400);
beep(30,300);
beep(30,200);
beep(30,100);
mothership = 0;
ssd1306_setpos(mothershipX,0);
ssd1306_send_data_start();
sendBlock(0);
sendBlock(0);
ssd1306_send_data_stop();
}
// Alien has been hit
if ((playerFire[1] >= leftLimit) && (fireYidx>=0) && (fireYidx<=lastActiveRow) && (fireXidx>=0) && (fireXidx<9)) {
if (row[fireYidx][fireXidx] == 1) {
int lastActiveToClear = lastActiveRow; // if we kill the last alien on a row - we still need to clear that row (end of this fn)
if (fireYidx == 2) deadOn2++;
if (fireYidx == 1) deadOn1++;
if (deadOn2 == 5) {lastActiveRow = 1;}
if ((deadOn1 == 4) && (deadOn2 == 5)) {lastActiveRow = 0;}
score = score + (int)((3-fireYidx) * 10);
aliensDead++;
ssd1306_setpos(playerFire[1],alienRow+fireYidx+1);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
beep(30,100);
fire = 0;
playerFire[0] = 0;
playerFire[1] = 0;
playerFire[2] = 7;
row[fireYidx][fireXidx] = 0;
if (fireXidx == firstAlien) { // this is the first Alien - sweep back and reset to new first alien
for (int xi = lastAlien; xi>=firstAlien;xi--) {
if ( (row[0][xi] == 1) || (row[1][xi] == 1) || (row[2][xi] == 1) ) newFirst = xi;
}
positionNow += newFirst - firstAlien;
firstAlien = newFirst;
}
if (fireXidx == lastAlien) { // this is the last Alien - sweep thru and reset to new last alien
for (int xi = firstAlien; xi<=lastAlien;xi++) {
if ( (row[0][xi] == 1) || (row[1][xi] == 1) || (row[2][xi] == 1) ) newLast = xi;
}
lastAlien = newLast;
}
clearAlienArea(alienRow,lastActiveToClear);
}
}
}
// --- Deal with Alien Firing ---
for (byte afIndex = 0; afIndex<5; afIndex++) {
if(alienFire[afIndex][0] == 1) {
drawFire(alienFire[afIndex][1],alienFire[afIndex][2]);
alienFire[afIndex][2] = alienFire[afIndex][2] + 1;
if (alienFire[afIndex][2] >= 56) {
ssd1306_setpos(alienFire[afIndex][1],7);
uint8_t temp = B00000000;
ssd1306_send_data_start();
ssd1306_send_byte(temp);
ssd1306_send_data_stop();
alienFire[afIndex][0] = 0; // the fire's got to the end
if( ((alienFire[afIndex][1]) > player) && ( (alienFire[afIndex][1]) < player+platformWidth) ) { // you've been hit!!
stopAnimate = 1;
goto die;
}
}
}
} // end of aliens firing
}
if (aliensDead == 14) {
for (int ai = 0; ai <= 5; ai++) alienFire[ai][0] = 0;
level++;
if (level>15) level = 15;
levelUp(level);
resetAliens();
}
if( ((alienRow == 5) && (lastActiveRow == 2))|| ( (alienRow == 6) && (lastActiveRow == 1)) || ((alienRow == 7) && (lastActiveRow == 0)) || stopAnimate) {
stopAnimate = 1;
break;
}
// draw the player
drawPlatform();
}
}
die:
topScoreB = EEPROM.read(0);
topScoreB = topScoreB << 8;
topScoreB = topScoreB | EEPROM.read(1);
if (score > topScoreB) {
topScoreB = score;
EEPROM.write(1,score & 0xFF);
EEPROM.write(0,(score>>8) & 0xFF);
newHigh = 1;
}
}
void drawPlatform(){
ssd1306_setpos(player,7);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
for (byte pw = 0; pw <5; pw++){ssd1306_send_byte(B11000000);}
for (byte pw = 0; pw <4; pw++){ssd1306_send_byte(B11110000);}
for (byte pw = 0; pw <5; pw++){ssd1306_send_byte(B11000000);}
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
}
void sendBlock(int fill){
if (fill == 1) {
ssd1306_send_byte(B10011000);
ssd1306_send_byte(B01011100);
ssd1306_send_byte(B10110110);
ssd1306_send_byte(B01011111);
ssd1306_send_byte(B01011111);
ssd1306_send_byte(B10110110);
ssd1306_send_byte(B01011100);
ssd1306_send_byte(B10011000);
} else if (fill == 2) {
ssd1306_send_byte(B00110000);
ssd1306_send_byte(B00111110);
ssd1306_send_byte(B10110011);
ssd1306_send_byte(B01011101);
ssd1306_send_byte(B01011101);
ssd1306_send_byte(B10110011);
ssd1306_send_byte(B00111110);
ssd1306_send_byte(B00110000);
} else if (fill == 3) {
ssd1306_send_byte(B00011000);
ssd1306_send_byte(B00111000);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00110100);
ssd1306_send_byte(B00111000);
ssd1306_send_byte(B00011000);
} else if (fill == 0) {
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 levelUp(int number) {
fire = 0; // make sure no fire
playerFire[0] = 0; // player fire inactive
aliencounter=0;
firecounter=0;
mothercounter=0;
firstAlien = 0;
lastAlien = 8;
newFirst = 0;
newLast = 8;
lastActiveRow = 2;
deadOn1 = 0;
deadOn2 = 0;
aliensDead = 0;
mothership = 0;
alienRow = 0;
positionNow = 0;
alienDirection = 1;
player = 64;
ssd1306_fillscreen(0x00);
ssd1306_char_f6x8(16, 3, "--------------");
ssd1306_char_f6x8(16, 4, " L E V E L ");
ssd1306_char_f6x8(16, 5, "--------------");
doNumber(85,4,number);
for (int i = 800; i>200; i = i - 200){
beep(30,i);
}
delay(700);
ssd1306_fillscreen(0x00);
}
void drawFire(int x, int y) {
if (y%8!=0){
ssd1306_setpos(x,y/8);
ssd1306_send_data_start();
doDrawLS(0,y%8);
ssd1306_send_data_stop();
ssd1306_setpos(x,y/8+1);
ssd1306_send_data_start();
doDrawRS(0,8-y%8);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(x,y/8);
ssd1306_send_data_start();
doDrawLS(0,0);
ssd1306_send_data_stop();
}
}
// Drawing routine for the player and fire - with right-shifts
void doDrawRS(long P1, byte P2) {
ssd1306_send_byte((B01111110 | P1)>>P2);
}
// Drawing routine for the player and fire - with left-shifts
void doDrawLS(long P1, byte P2) {
ssd1306_send_byte((B01111110 | P1)<<P2);
}
void clearAlienArea(int row, int lastActive) {
// clear alien area
for (int inc = row; inc <=row+lastActive; inc ++) {
ssd1306_setpos(0,inc);
ssd1306_send_data_start();
for (int bl = 0; bl <16; bl++){
sendBlock(0);
}
ssd1306_send_data_stop();
}
}
void resetAliens(void) {
for (byte i =0; i<10;i++){ // reset aliens
row[0][i]=0; row[1][i]=0; row[2][i]=0;
}
for (byte i =0; i<9;i+=2){ // reset aliens
row[0][i]=1; row[2][i]=1;
}
for (byte i =1; i<9;i+=2){ // reset aliens
row[1][i]=1;
}
}
Comments