Sunday, February 17, 2013

External Power Supply for Digispark Projects

While searching Google for info about an LM567 tone decoder chip I wanted to use in an Amateur Radio project, I came across a vendor's site that had some interesting kits.

NightFire Electronics has a great little 5V power supply that is built to plug right into your breadboard. It sells for just under $8, but for just a few dollars more you can get one that is fully built and tested. I really didn't need another unfinished project staring at me, so I chose to spend the $12 and get the assembled one.

I couldn't resist the temptation to purchase a few more items from NightFire. I got two more breadboards, several small protoboards for when I what to make my projects permanent. And a huge bag of miscellaneous parts including a speaker, a microphone, transistors and ICs for only $10. Shipping wasn't free, so it was better to get more items. Everything arrived at my mailbox about a week later.

A few of the components were bent over when I removed it from the mailing packet, but they were easy to straighten. 

The underside has two sets of pins that plug into the side rails on a solderless breadboard putting regulated 5V and GND right where you need them.

There are two screw down terminals for the input voltage and ground. The spec sheet that came with the board says you can input either DC or AC through these terminals. It warns you to use no more than 25V DC and no more than 15V AC.

Knowing that, I dug through my junk box and selected one of the many old 'wall wart' transformers that I have accumulated over the years. I chose one that was labeled as 12V DC. It was a nice hefty one that could handle a good load. If you are planning to do the same thing, make sure you pay attention to the polarity diagram usually printed on the case. Most of these use a barrel connector with the inside connector + and the outside -. But there are some out there that do the opposite.

There is no jack on the power supply that would accept the standard barrel connector so I cut the plug off the wires.

If you do the same, you will need to figure out the polarity of the two wires. Usually there will be something that distinguishes the connectors. You might be lucky enough to find red and black wires running inside the same outside insulation. The one I chose has two black wires molded together. One side is marked by a broken white line. I stripped some insulation off one of the wires that was still attached to the plug. Then I used my multimeter to see if it was connected to the inside connector, or the outside barrel.

I found out that the wire with the white lines was connected to the inside connector which means it is the positive lead. I separated a couple of inches of the two wires on leads still connected to the transformer and marked this one with red tape so that there would be no confusion later.

I stripped about half an inch of insulation off each wire, twisted the braids and tinned them with solder to make them stiff and tight. These fit neatly into the + and - screw terminals on the board and were nice and secure after I tightened them down.

Before hooking Sparky up to the board, I tested the voltages to make sure that everything was safe and the polarity was correct. The wall wart was rated at 12V, but measuring the actual voltage across the two screw terminals, I found that it was really pumping 16V into the board. That is still well within the limits of the power supply. I measured the output on my power rails and found that to be 4.95V. That's close enough to our 5V needs.

I am real happy with the result, and think it is well worth the $12. The Pros on this item are the price, the way it fits tightly on the breadboard and available power. I might even use it on my Raspberry Pi. You can even hook it up to a 9V battery! I would say there are two Cons. A barrel connector jack would have been more convenient and there is no On/Off switch. I would call this gadget: 'A Buy.'
 

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
}
  




Thursday, February 14, 2013

LCD Game Part 6 - Game Over!

Below you will find the completed project. The sketch is fully documented and ready for you to copy and paste into your own IDE.

Here is a quick review of the hardware needed, to get yours going. Of course you will want an LCD panel. Digistump offers a complete LCD Shield Kit. I don't know if they are still available, but if you already have one you are nearly ready to go.

I just checked eBay and see that you can purchase an I2C 16x2 Serial LCD just like the one you see in the video for less than $10.

If you are going with your own components you will need two pull up resistors to get your digispark and LCD to talk to each other. Check Part 1 of this series for info on how to hook it up.

Finally you will need a push button and pull down resistor for the user input. I used a 1K resistor for my project, but even a 10K should work just fine. The hook up is described in the sketch comments.

The sketch below is fully documented, I have highlighted the code that has been added since Part 5. 

If you are paying attention you will notice that every time you power your digispark down and back up again, the high score goes back to zero. Look at the video again. That wasn't the case it already had a high score. In our next post we will show you how to teach Sparky to remember values when the power is removed and recall them when he powers up again later.  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
#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.

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
int highScore = 0;                      // so the first score will always beat it
int 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
  
  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);                 // show starting score
  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
    //lcd.setCursor(0,1);
    //lcd.print(char(tick+48));
    //lcd.setCursor(2,0);
    //lcd.print(' ');
    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
  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);               // 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);  
}

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
   
  }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
  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
}
  




Tuesday, February 12, 2013

LCD Game Part 5 - Back in Order

Hey, Digisparklers, we're back. And as you can see in the photo, the digits are all in order.

In Part 4 we made a few changes to our code so that we could check the state of a push button while the numbers were marching left across the display.

In this post, we are going to add a function that will reverse the order of the selected digits. To do that we get to use our swap() function again.

We will start by adding only one line to our Loop () function. I show it highlighted below so you can get it in the right place. Remember, this only happens when the button is pressed.


    lcd.setCursor(0,1);
    lcd.print(char(tick+48));
    lcd.setCursor(2,0);
    lcd.print(' ');
    flip();
    delay(1000);

Now we need that new function, flip(). I'm not passing a value to it, which doesn't sit real well with me, but we have been using a global variable, tick, which tells us all we need to know.


void flip(){
  int i=0;
  tick--;
  do {  
    swap(i,tick-i);
    i++;
  } while (i<=(tick/2));

  displayWorkingString();
  tick= 0;
  myTime = millis();
}

The last thing we do in slideLeft() is bump up the value of tick++. Here we undo that with the instruction: tick-- . In our do loop, i starts at the beginning of our character array and get's one larger with each pass. tick-i is the end of the section we want to swap and because we are subtracting a value that is getting larger, it get's smaller. The two values are approaching each other. Notice that we run this loop  while (i<=(tick/2)). So if you want to flip 8 digits, we only do it 4 times. What? Think about that. If we flipped every one of the digits, when we got done, they would be back in order.


What about odd numbers? Not a problem. If we only want to flip 3 digits, we only need to flip the first one with the third one. The middle digit doesn't move.


You will need to reboot Sparky after you solve the puzzle. And it doesn't stop running just because you get the digits in order. These are still on our to-do list. In the next post we will add those and well as some scoring. We will  also make the game more challenging by increasing the speed as you solve more and more puzzles. After that, we will add some scoring routines.

As always, here is the full sketch. Copy it and try it yourself. C U next time.


/* 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 



//#define DEBUG
#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

#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.


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

String winner ="0123456789";            // a test string to easily see game is over
String workingString = "0123456789";    // all the digits that we will scramble at the beginning of the game
int rnd;     // will hold a random number for us.
int myButton = 5; // our button hooks up here
int btnPress;     // is it down or not
byte tick = 0;
unsigned long myTime;
boolean stillRolling = true;

void setup(){
  TinyWireM.begin();                    // initialize I2C lib - comment this out to use with standard arduinos
  lcd.init();                           // initialize the lcd 
  lcd.clear();
  lcd.backlight();
  pinMode (myButton, INPUT);
  pinMode(1, OUTPUT); //LED on Model A
  
  scramblePrompt();
  scrambleString();
  displayWorkingString();
  delay(1000);
  myTime = millis();
}

void loop()  {
  btnPress = digitalRead(myButton);
  if (btnPress==1){
    digitalWrite(1,1);
    lcd.setCursor(0,1);
    lcd.print(char(tick+48));
    lcd.setCursor(2,0);
    lcd.print(' ');
    flip();
    delay(1000);
  }else{
    digitalWrite(1,0);
  }
  if(millis() - myTime > 1000){
    shiftLeft();
    myTime = millis();
  }
}

void flip(){
  int i=0;
  tick--;
  do {  
    swap(i,tick-i);
    i++;
  } while (i<=(tick/2));
  //lcd.clear();
  displayWorkingString();
  tick= 0;
  myTime = millis();
}


void shiftLeft(){
if (tick < 10){
    //delay(1000);
    lcd.setCursor(tick + 2,0);   
    lcd.print(workingString[tick]);
    lcd.print(' ');
    tick++;

  }
}

void scramblePrompt(){
  lcd.noCursor();
  lcd.print("NUMBER FLIP-FLOP");
  lcd.setCursor(0,1);
  lcd.print("Press Button Now");
  
  do {
    rnd=random(9);
  } while (!digitalRead(myButton));
 }



void scrambleString() {
  lcd.clear();
  lcd.print("NUMBER FLIP-FLOP");
  
  for (int i=0; i<10; i++){
    rnd=random(9);
    swap(i,rnd);
  }
  delay(1000);
  lcd.clear();
   
}

void swap(int x, int y){
  byte hold=workingString[x];
  workingString[x]=workingString[y];
  workingString[y]=hold;
}
  

void displayWorkingString(){
  lcd.setCursor(3,0);
  lcd.print(workingString);
  lcd.noCursor();
}

 

Monday, February 11, 2013

LCD Game Part 4 - Catching the Button Press

Back three ahead four. In Part 3 we demonstrated how we can provide a visual clue to the user that let's him decided which digits to flip. If you haven't seen it, there is a video that shows it on YouTube.

Now that we need to catch a button press while the digits are marching left, some problems turn up that must be dealt with. 

We used a one second delay to slow Sparky down and make the interface useful. If we leave that in, the user might not see an instant response to the key press and has to hold it down until the delay is over before it gets read.

If we want to go into the realm of 'interupts' we could probably come up with a solution. Instead, I have chosen to abandon the delay(1000); function inside of shiftLeft().  We can't check a pin while this delay is active. So instead of using a delay, let's build a timer and only call the function once a second. We will need to declare a variable:
unsigned long myTime;
Then in setup() we will load it up with a value from the millis() function.
  
  scramblePrompt();
  scrambleString();
  displayWorkingString();
  delay(1000);
  myTime = millis();

This writes the number of milliseconds that have passed since Sparky was powered up into our variable, myTime. Instead of calling our shiftLeft() function every time we pass through loop() we only call it when 1000 milliseconds have elapsed. That's one second. And then we reset myTime so it won't happen again in the next pass.

Finally, we package the shiftLeft() call into an if structure that tests for this condition. Here it is:


  if(millis() - myTime > 1000){
    shiftLeft();
    myTime = millis();
  }

If we take the original delay(1000) out of shiftLeft() and run this code, we will see the same result we had in Part 3. The good news is that now we can also check P5 to see if our button is pressed without being shut out of that option during the delay. 

Just so we can study what is going on as we interact with the LCD and the button, we will toss a few lines (highlighted below) into loop() that will put a new number in the lower left corner of the display that represents how many digits were shifted left when the button was pressed. I'm simply adding our counter, tick, to the ASCII code for the number zero and printing it on the LCD. That works well until tick = 10. Then you will see a colon instead of a number. 
  
  btnPress = digitalRead(myButton);
  if (btnPress==1){
    digitalWrite(1,1);
    lcd.setCursor(0,1);
    lcd.print(char(tick+48));
    delay(1000);
  }

So it's time to give it try. Copy and paste the sketch below and experiment with it. In the next part, we will flip the order of the selected digits and have the bare minimum of the functions written to actually complete the puzzle. 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
 */

//#define DEBUG
#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

#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.


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

String winner ="0123456789";            // a test string to easily see game is over
String workingString = "0123456789";    // all the digits that we will scramble at the beginning of the game
int rnd;     // will hold a random number for us.
int myButton = 5; // our button hooks up here
int btnPress;     // is it down or not
byte tick = 0;
unsigned long myTime;

void setup(){
  TinyWireM.begin();                    // initialize I2C lib - comment this out to use with standard arduinos
  lcd.init();                           // initialize the lcd 
  lcd.clear();
  lcd.backlight();
  pinMode (myButton, INPUT);
  pinMode(1, OUTPUT); //LED on Model A
  
  scramblePrompt();
  scrambleString();
  displayWorkingString();
  delay(1000);
  myTime = millis();
}

void loop()  {
  btnPress = digitalRead(myButton);
  if (btnPress==1){
    digitalWrite(1,1);
    lcd.setCursor(0,1);
    lcd.print(char(tick+48));
    delay(1000);
  }else{
    digitalWrite(1,0);
  }
  if(millis() - myTime > 1000){
    shiftLeft();
    myTime = millis();
  }
}

void shiftLeft(){
if (tick < 10){
    //delay(1000);
    lcd.setCursor(tick + 2,0);   
    lcd.print(workingString[tick]);
    lcd.print(' ');
    tick++;

  }else{
    delay(1500);
    lcd.clear();
    delay(1500);
    scrambleString();
    displayWorkingString();
    tick = 0;
    
  }
}

void scramblePrompt(){
  lcd.noCursor();
  lcd.print("NUMBER FLIP-FLOP");
  lcd.setCursor(0,1);
  lcd.print("Press Button Now");
  
  do {
    rnd=random(9);
  } while (!digitalRead(myButton));
 }



void scrambleString() {
  lcd.clear();
  lcd.print("NUMBER FLIP-FLOP");
  
  for (int i=0; i<10; i++){
    rnd=random(9);
    swap(i,rnd);
  }
  delay(1000);
  lcd.clear();
   
}

void swap(int x, int y){
  byte hold=workingString[x];
  workingString[x]=workingString[y];
  workingString[y]=hold;
}
  

void displayWorkingString(){
  lcd.setCursor(3,0);
  lcd.print(workingString);
  //cursorLocation=4;
  lcd.noCursor();
  //if(workingString==winner) YouWin();
}