Saturday, February 16, 2013

LCD Game Part 7 - Storing Data after Power Down

For now, this will be the final post on the LCD Game Series. Last time we had a fully working version going. I found one minor bug in the code. The youLose()function didn't put the pointer back to zero. You should add this line, anywhere in the procedure:
tick = 0;
I spent the evening messing around with hardware and built up a little board that allows me to plug both the Digispark and my LCD display into it. It includes the two pull up resistors, a push button and the pull down resistor that requires. I soldered in red and black jumpers so that I can plug into a 5V power source and be good to go. Sparky rides higher than the proto board on two headers. The pull up resistors are not visible in the photo because they are underneath the Digispark board. I soldered in a set of four stand up pins that lets me plug my LCD cable into the project.

If you read the original post in this series, you might recall that I had issues with the board being loose in the USB jack. The board you see in this photo is the second one I have programmed. I was really surprised at how tight this one fits! So tight that the first time I had to push it very firmly to get it to enter the slot. Not all Digisarks are created equal, but they are all equally fun!

I had written the Number Flip-Flop game some time ago for my pet Arduino. This project was my first with I2C, which I really like because it only uses two of the I/O pins. There was something else, though, that I wanted to learn to do that I had never done before. I wanted to figure out how to keep the high score from session to session even if the power is removed. I was very pleased to find out that this can indeed be done.

I was able to learn that an Arduino had 512 bytes of EEPROM reserved for storage. It's like a tiny hard drive. I am not sure how much we get on our Digispark, but I know I  only need one byte to store the high score because the high score will never be as high as 255, the largest value you can store in a byte.

EEPROM stands for: Electrically Erasable Programmable Read-Only Memory. Boy is that a mouthfull which is why most people just pronounce it as if it was a word.

If we are going to store data on the chip, we will need to include a special library. Add the highlighted line to your sketch near the other two includes.


#include <TinyWireM.h>             
#include <LiquidCrystal_I2C.h>   
#include <EEPROM.h>


We are going to have to make a few changes to our variable types to pull this off. Although there is a way of storing larger numbers as more than one byte, I wanted to keep this as simple as possible. 

change:

int highScore = 0;              
int thisScore = 120;          

to:

byte highScore = 0;             
byte thisScore = 120;       


We will need to set up two more variables to store our data.  Address, simply holds the location in our EEPROM space. We will set it to zero which will be the first byte. Value will be what we put in or take out of that memory location.


int address = 0;  // points to the first address in the EEPROM memory
byte value;       // stores what we read at that location


We will start by reading this address in setUp() so that we can put it on our display at the beginning of the game. The first time you run this, it should be zero, but if it is some random value, don't worry. We will have a way to reset the high score on start up by holding the button down as the game launches.


  value = EEPROM.read(address); // read the high score left here from earlier
  highScore = value;            // move it into our highScore variable


Add the highlighted line to the youWin() function ...


void youWin(){
  lcd.setCursor(0,1);                   // move to the second row
  if(thisScore > highScore){            // when we get a new high score
    lcd.print("HIGH SCORE!");           
    highScore = thisScore;              // update the new high score
    EEPROM.write(address, highScore);   // store it now for a power down later
   
  }else{                                // when score doesn't beat high score
    lcd.print("WINNER");                
  }
  delay(3000);
  mySpeed = mySpeed - 50;               // lower mySpeed means faster game
  if(mySpeed<400){mySpeed=400;}         // don't go too fast
  playAgain();                        // Do it all again
}

I soon learned that I had a whole new problem. Sparky treats the variable types char and byte as nearly the same thing. So when it came time to print the scores on the display, instead of printing the value, it printed a character as if the number was an ASCII code. There is a real simple solution to this problem. When we send our scores to the LCD, we add one more parameter that tells everyone that we want to display a decimal not a character. Find these lines and add the highlighted text.



  lcd.print(thisScore,DEC);           // show current score


  lcd.print(highScore,DEC);  

Finally, let's check to see if the user is holding the push button down while the program is launching. If she is, we will reset the value we are storing in EEPROM to zero. Add the following lines to setUp().

  if(digitalRead(myButton)){       // If button is down when we power up ...
    EEPROM.write(address,0);       // Change high score stored in EEPROM to zero
    lcd.clear();
    lcd.print("HIGH SCORE RESET");
    highScore=0;                   // reset the high score used by the sketch
    delay(2000);
  }



Sometimes, when I reset the high score, my digits will print out as some odd looking graphic characters. I power down and back up again and all is right again with the high score back down to zero.

That is about it for the LCD game. I will include the full sketch at the end of this post. Please let me know if you are able to get it working on your own little Sparky. And come back again soon. I have just completed a hardware project that gives me a real cool power supply that plugs right into a standard breadboard. I will look forward to sharing the photos and the how to's in the next post. C U then ...


/* ATtiny85 as an I2C Master   Ex2        BroHogan                           1/21/11
 * Modified for Digistump - Digispark LCD Shield by Erik Kettenburg 11/2012
 * SETUP:
 * ATtiny Pin 1 = (RESET) N/U                      ATtiny Pin 2 = (D3) N/U
 * ATtiny Pin 3 = (D4) to LED1                     ATtiny Pin 4 = GND
 * ATtiny Pin 5 = SDA on DS1621  & GPIO            ATtiny Pin 6 = (D1) to LED2
 * ATtiny Pin 7 = SCK on DS1621  & GPIO            ATtiny Pin 8 = VCC (2.7-5.5V)
 * NOTE! - It's very important to use pullups on the SDA & SCL lines!
 * PCA8574A GPIO was used wired per instructions in "info" folder in the LiquidCrystal_I2C lib.
 * This ex assumes A0-A2 are set HIGH for an addeess of 0x3F
 * LiquidCrystal_I2C lib was modified for ATtiny - on Playground with TinyWireM lib.
 * TinyWireM USAGE & CREDITS: - see TinyWireM.h
 */

// Number Flip-Flop by Budd Churchward, WB7FHC, @barnacleBudd 

/* Place a push button on your board
 * Put a 1K resistor between one pin on the button and ground
 * Place a jumper between pin 5 and a point between the button and resistor
 * Place a jumper between 5V and the other side of the button
 */

#include <TinyWireM.h>                  // I2C Master lib for ATTinys which use USI - comment this out to use with standard arduinos
#include <LiquidCrystal_I2C.h>          // for LCD w/ GPIO MODIFIED for the ATtiny85
#include <EEPROM.h>
#define GPIO_ADDR     0x27              // (PCA8574A A0-A2 @5V) typ. A0-A3 Gnd 0x20 / 0x38 for A - 0x27 is the address of the Digispark LCD modules.

int address = 0;                        // points to the first address in the EEPROM memory
byte value;                             // stores what we read at that location

LiquidCrystal_I2C lcd(GPIO_ADDR,16,2);  // set address & 16 chars / 2 lines

String winner ="0123456789";            // a test string to easily see if game is over
String workingString = "0123456789";    // these get scrambled
int rnd;                                // will hold a random number for us.
int myButton = 5;                       // hook button to pin 5 with pull down resistor see notes above
int btnPress;                           // is it down or is it up
byte tick = 0;                          // counts the digits we plan to flip
unsigned long myTime;                   // used with mySpeed to set tick rate

int mySpeed = 1000;                     // start with a 1 second interval
byte highScore = 0;                     // so the first score will always beat it
byte thisScore = 120;                    // start with 120 points

void setup(){
  
  TinyWireM.begin();                    // initialize I2C lib - comment this out to use with standard arduinos
  lcd.init();                           // initialize the lcd 
  lcd.clear();                          // clear the screen
  lcd.backlight();                      // turn on the backligth
  pinMode (myButton, INPUT);            // pin 5
  pinMode(1, OUTPUT); //LED on Model A  // we light the LED when the button is pressed
  value = EEPROM.read(address);         // read the high score left here from earlier
  highScore = value;                    // move it into our highScore variable
  
  if(digitalRead(myButton)){            // If button is down when we power up ...
    EEPROM.write(address,0);            // Change high score stored in EEPROM to zero
    lcd.clear();
    lcd.print("HIGH SCORE RESET");
    highScore=0;                        // reset the high score used by the sketch
    delay(2000);
  }
    
  lcd.clear();
  delay(1000);
  lcd.print("NUMBER FLIP-FLOP");
  scramblePrompt();                     // Go pick random numbers until user presses button
  scrambleString();                     // puts digits in random order
  displayWorkingString();               // prints digits
  lcd.setCursor(12,1);                  // move to bottom right corner
  lcd.print(thisScore,DEC);             // show starting score
                                        // because thisScore is type 'byte' you need to show it as decimal
  delay(1000);
  myTime = millis();                    // start timer
  
  
  
}

void loop()  {
  btnPress = digitalRead(myButton);     // read pin 5
  if (btnPress==1){                     // When button is down ...
    digitalWrite(1,1);                  // light up on-board LED
    flip();                             // go flip some digits
    delay(1000);
  }else{                                // When button is up ...
    digitalWrite(1,0);                  // turn off on-board LED
  }
  if(millis() - myTime > mySpeed ){     // when we have waited long enough
    shiftLeft();                        // move the current digit left
    myTime = millis();                  // reset interval timer
  }
}

void flip(){
  int i=0;                              // a counter
  if(tick==1){tick=10;}                 // power play, flip all if only 1 digit
  tick--;                               // undo the last increment, we don't need it
  do {  
    swap(i,tick-i);                     // swap two digits
    i++;                                // increment counter
  } while (i<=(tick/2));                // do half of them and we're done
  //lcd.clear();
  displayWorkingString();               // put the new order on the screen
  tick= 0;                              // reset the digit counter
  myTime = millis();                    // reset the interval timer
}


void shiftLeft(){
  if (tick < 10){                       // keep us in the range...
    lcd.setCursor(tick + 2,0);          // move to spot in front of our digit
    lcd.print(workingString[tick]);     // put a copy of digit in the new spot
    lcd.print(' ');                     // erase the original digit
    tick++;                             // increment the digit counter
    thisScore--;                        // subtract a point
    int x = 12;                         // cursor location for score when its 3 digits
    if(thisScore<100){x++;}             // nudge location for 2 digit score
    if(thisScore<10){x++;}              // nudge again if only 1 digit
    lcd.setCursor(x,1);                 // set the cursor location
    lcd.print(' ');                     // erase digit if we are dropping from 100's to 10's
    lcd.print(thisScore,DEC);           // show current score
    
    if(thisScore==0){                   // When points are all gone ...
      youLose();                        // Tell the user he lost
      return;                           // go back to loop()
    }

  }else{                                // if user doesn't press the button...
    tick=0;                             // start again at the beginning
    displayWorkingString();             // move the digits back in place
    }
  
  }

void scramblePrompt(){
  lcd.noCursor();                       // some LCDs want to flash at you
  lcd.print("NUMBER FLIP-FLOP");        // show the title on the top line
  lcd.setCursor(0,1);                   // move to the bottom line
  lcd.print("Press Button Now");        // prompt the user to press the button

  do {
    rnd=random(9);                      // pick random numbers over and over
  } while (!digitalRead(myButton));     // until user presses the button
 }


void scrambleString() {
  lcd.clear();                          // clear the screen
  lcd.print("NUMBER FLIP-FLOP");        // print the title again
  
  for (int i=0; i<10; i++){             // do this once for each digit
    rnd=random(9);                      // pick a random spot
    swap(i,rnd);                        // swap two of the digits
  }
  delay(1000);
  lcd.clear();                          // clear the screen again
  lcd.setCursor(0,1);                   // move to bottom row
  lcd.print("HS=");                     // print the high score
  lcd.print(highScore,DEC);  
}

void swap(int x, int y){
  byte hold=workingString[x];           // store the first digit
  workingString[x]=workingString[y];    // copy the second digit to the first spot
  workingString[y]=hold;                // put the first digit in the second spot
}
  

void displayWorkingString(){
  lcd.setCursor(2,0);                   // move to top row
  lcd.print(' ');                       // erase the first digit
  lcd.print(workingString);             // put the whole string back in place
  
  if (workingString==winner){           // if the digits are all in order ...
    youWin();                           // go do the cool stuff for the winner
   }
}

void youWin(){
  lcd.setCursor(0,1);                   // move to the second row
  if(thisScore > highScore){            // when we get a new high score
    lcd.print("HIGH SCORE!");           
    highScore = thisScore;              // update the new high score
    EEPROM.write(address, highScore);   // store it now for a power down later
   
  }else{                                // when score doesn't beat high score
    lcd.print("WINNER");                
  }
  delay(3000);
  mySpeed = mySpeed - 50;               // lower mySpeed means faster game
  if(mySpeed<400){mySpeed=400;}         // don't go too fast
  playAgain();                          // Do it all again
}

void youLose(){
  lcd.setCursor(0,1);                   // move to the second row
  lcd.print("YOU LOSE");
  delay(3000);
  mySpeed = mySpeed + 200;              // higher mySpeed means slower game
  if(mySpeed>1000){mySpeed=1000;}       // never slower than once a second
  tick = 0;                             // reset pointer
  playAgain();                          // do it all again
}

void playAgain(){
  lcd.clear();                          // clear the screen
  scrambleString();                     // go mix up the digits again
  displayWorkingString();               // show us the new order
  thisScore = 120;                      // start again with 120 points
}
  




No comments:

Post a Comment