Handheld Game

Ok. This is gonna be a long post. Here we go…

I am trying to duplicate a project I did with my BS2e (BASIC Stamp II model E), except this time, I am making it more complex, I’m using more LED’s, a screen, a CdS light sensor, and a 74HC595 shift register. I have never used shift registers before. I needed to use one this time because of all the LED’s I was using, and all the extra knobs and whistles. This game has two options: 1) “Light-Stopper”; this game is like those games you find at an arcade where there is a huge plastic dome with lights in a circle, and you have to get the lights to stop on the one bulb closest to you (I think it’s called jackpot). 2) “Simon”; just like the memory-testing game, Simon. I am currently developing the “Light-Stopper” part, But I am having some problems. First off, here’s how this game works:

There are 7 LED’s, arranged in a horizontal line. In the middle is a RGB LED (so I guess you could say the total amount of LEDs is 9). One LED lights up at a time from left to right, and right to left (only one LED is on at any given time. To make i clearer, the pattern just looks like the light is getting ‘bounced’ back and forth, from right to left, and vice-versa). The shift register only controls the 3 LEDs on the left, and the 3 on the right. The middle RGB is controlled directly via 3 of the ATmega168’s analog pins. Don’t worry about the four LEDs for the simon game now. There is a IR detector for input. When the user presses any button on a remote (just any IR remote from a TV, VCR, etc), it ‘stops’ the movement of the pattern (one LED is now lit, and stays lit). If they happen to get the timing right, and they stop the pattern when the middle RGB LED is lit, then they win. Pretty simple, but the programming is actually much more complicated with the shift register involved.

Lucky for me, there was sample code on the Arduino website on the 595 shift register. I implemented it into this project.

Here’s the original code I used for my BASIC Stamp II:

' {$STAMP BS2e}
' {$PBASIC 2.5}

LED1    PIN   4
LED2    PIN   1
LED3    PIN   8
LED4    PIN   14
LED5    PIN   5
LED6    PIN   10
LED7    PIN   12
IR      PIN   13
BTN1    PIN   3
BTN2    PIN   7
S0      PIN   2
S1      PIN   0
IN      PIN   15
BUZZ    PIN   9

ENDLEFT   VAR   Bit
ENDRIGHT  VAR   Bit
Slot1     VAR   Bit
Slot2     VAR   Bit
Slot3     VAR   Bit
Slot5     VAR   Bit
Slot6     VAR   Bit
Slot7     VAR   Bit
CNTR      VAR   Nib
PREV      VAR   Nib
i         VAR   Byte
TIME      VAR   Byte
TIMER     VAR   Word

FREQOUT BUZZ, 400, 1000
PAUSE 400
FREQOUT BUZZ, 400, 1000
PAUSE 400
FREQOUT BUZZ, 400, 1000
PAUSE 400
FREQOUT BUZZ, 500, 3000

Main:

TIME = 20

Start:
GOSUB Blink_Left


DO

IF BTN2 = 0 THEN
  GOTO Main
ENDIF

IF ENDLEFT = 1 THEN
  GOSUB Blink_Right
  ENDLEFT = 0
ELSEIF ENDRIGHT = 1 THEN
  GOSUB Blink_Left
  ENDRIGHT = 0
ENDIF

LOOP

Blink_Left:

GOSUB Check_Slot7
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Slot6
GOSUB Check_Slot5
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Center
GOSUB Check_Slot3
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Slot2
GOSUB Check_Slot1
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
ENDLEFT = 1
RETURN

Blink_Right:

GOSUB Check_Slot1
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Slot2
GOSUB Check_Slot3
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Center
GOSUB Check_Slot5
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
GOSUB Check_Slot6
GOSUB Check_Slot7
IF BTN1 = 0 THEN
  CNTR = 0
  TIMER = 0
  GOSUB Level_Up
ENDIF
ENDRIGHT = 1
RETURN

Check_Slot1:

HIGH LED1
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot1 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED1
RETURN

Check_Slot2:

HIGH LED2
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot2 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED2
RETURN

Check_Slot3:

HIGH LED3
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot3 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED3
RETURN

Check_Center:

HIGH LED4
FOR i = 1 TO TIME
  IF IR = 0 THEN
    GOSUB Winner
  ENDIF
NEXT
LOW LED4
RETURN

Check_Slot5:

HIGH LED5
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot5 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED5
RETURN

Check_Slot6:

HIGH LED6
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot6 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED6
RETURN

Check_Slot7:

HIGH LED7
FOR i = 1 TO TIME
  IF IR = 0 THEN
    Slot7 = 1
    GOSUB Loser
  ENDIF
NEXT
LOW LED7
RETURN

Winner:

HIGH LED4
PAUSE 50
LOW LED4
PAUSE 50
HIGH LED4
PAUSE 50
LOW LED4
PAUSE 50
HIGH LED4
PAUSE 50
LOW LED4

HIGH LED1
HIGH LED7
PAUSE 500
HIGH LED2
HIGH LED6
PAUSE 500
HIGH LED3
HIGH LED5
PAUSE 500
HIGH LED4
PAUSE 1000

FOR i = 1 TO 40
  HIGH LED4
  PAUSE 20
  LOW LED4
NEXT

LOW LED1
LOW LED2
LOW LED3
LOW LED5
LOW LED6
LOW LED7

FREQOUT BUZZ, 100, 1500
PAUSE 5
FREQOUT BUZZ, 100, 1300
PAUSE 5
FREQOUT BUZZ, 100, 1500
PAUSE 5
FREQOUT BUZZ, 200, 3000
PAUSE 100

FOR i = 1 TO 20
  IF i = 5 THEN
    HIGH LED2
  ELSEIF i = 10 THEN
    LOW LED2
    HIGH LED6
  ELSEIF i = 15 THEN
    LOW LED6
    HIGH LED2
  ELSEIF i = 20 THEN
    LOW LED2
    HIGH LED6
  ELSEIF i = 25 THEN
    LOW LED6
    HIGH LED2
  ELSEIF i = 30 THEN
    LOW LED2
    HIGH LED6
  ELSEIF i = 35 THEN
    LOW LED6
    HIGH LED2
  ELSEIF i = 40 THEN
    LOW LED2
    HIGH LED6
  ELSEIF i = 45 THEN
    LOW LED6
    HIGH LED2
  ELSEIF i = 50 THEN
    LOW LED2
    HIGH LED6
  ENDIF

  HIGH LED1
  HIGH LED7
  PAUSE 25
  LOW LED1
  LOW LED7
  HIGH LED3
  HIGH LED5
  PAUSE 25
  LOW LED3
  LOW LED5
  PAUSE 50

NEXT
RETURN

Loser:

IF Slot1 = 1 THEN
  LOW LED1
  PAUSE 200
  HIGH LED1
  PAUSE 200
  LOW LED1
  Slot1 = 0
ELSEIF Slot2 = 1 THEN
  LOW LED2
  PAUSE 200
  HIGH LED2
  PAUSE 200
  LOW LED2
  Slot2 = 0
ELSEIF Slot3 = 1 THEN
  LOW LED3
  PAUSE 200
  HIGH LED3
  PAUSE 200
  LOW LED3
  Slot3 = 0
ELSEIF Slot5 = 1 THEN
  LOW LED5
  PAUSE 200
  HIGH LED5
  PAUSE 200
  LOW LED5
  Slot5 = 0
ELSEIF Slot6 = 1 THEN
  LOW LED6
  PAUSE 200
  HIGH LED6
  PAUSE 200
  LOW LED6
  Slot6 = 0
ELSEIF Slot7 = 1 THEN
  LOW LED7
  PAUSE 200
  HIGH LED7
  PAUSE 200
  LOW LED7
  Slot7 = 0
ENDIF

FREQOUT BUZZ, 100, 1500
PAUSE 100
FREQOUT BUZZ, 200, 1000
PAUSE 100

GOTO Start

Level_Up:

PAUSE 100

FREQOUT BUZZ, 100, 2500
PAUSE 20
FREQOUT BUZZ, 100, 2500
PAUSE 20
FREQOUT BUZZ, 100, 2500
PAUSE 500

LOW LED1
LOW LED2
LOW LED3
LOW LED4
LOW LED5
LOW LED6
LOW LED7
PAUSE 1000

DO

PREV = CNTR
IF BTN1 = 0 THEN
  PAUSE 300
  CNTR = CNTR + 1
ENDIF

IF CNTR > PREV THEN
  TIMER = 0
ENDIF

IF CNTR = 1 THEN
  HIGH LED1
  TIMER = TIMER + 1
  TIME = 50
ELSEIF CNTR = 2 THEN
  HIGH LED1
  HIGH LED2
  TIMER = TIMER + 1
  TIME = 40
ELSEIF CNTR = 3 THEN
  HIGH LED1
  HIGH LED2
  HIGH LED3
  TIMER = TIMER + 1
  TIME = 30
ELSEIF CNTR = 4 THEN
  HIGH LED1
  HIGH LED2
  HIGH LED3
  HIGH LED4
  TIMER = TIMER + 1
  TIME = 20
ELSEIF CNTR = 5 THEN
  HIGH LED1
  HIGH LED2
  HIGH LED3
  HIGH LED4
  HIGH LED5
  TIMER = TIMER + 1
  TIME = 10
ELSEIF CNTR = 6 THEN
  HIGH LED1
  HIGH LED2
  HIGH LED3
  HIGH LED4
  HIGH LED5
  HIGH LED6
  TIMER = TIMER + 1
  TIME = 8
ELSEIF CNTR = 7 THEN
  HIGH LED1
  HIGH LED2
  HIGH LED3
  HIGH LED4
  HIGH LED5
  HIGH LED6
  HIGH LED7
  TIMER = TIMER + 1
  TIME = 6
ENDIF

LOOP UNTIL TIMER > 650
TIMER = 0
CNTR = 0
GOTO Start

yea, that was too many characters, so I had to post it in a second post.

I want the Arduino (ATmega168) to mimic that code. But that’s really hard with this shift register. Basically, I need it to check the IR detector input every time it lights up an LED. How do I do that? And it’s really finicky with this IR detector, too. You have to put the “pinMode(irPin, INPUT)” before every reading is taken, or it does not work. AND you have to pause about 100ms after declaring the pin as an input before taking a reading. That’s going to slow this down too much!

So there’s a few problems I need to solve:

  1. The finicky IR detector readings
  2. How to take readings from the IR detector at each lighting of an LED
  3. (also; I’m adding this) Get the middle RGB LED to light up right after the ones next to it to complete the pattern (remember, this LED is controlled directly by the ATmega168, not the shift register.
  4. (I’m adding this too) How to light up just one LED with the shift register??

Thanks for the support.

Here’s half of the Arduino code:

int btnPin = 2;    // Digital 2 ; 'Toggle' Button
int irPin = 3;     // Digital 4 ; IR Detector
int bryPin = 5;   // Digital 5 ; Bottom Right Yellow LED
int trgPin = 6;   // Digital 6 ; Top Right Green LED
int blgPin = 7;   // Digital 7 ; Bottom Left Green LED
int tlyPin = 8;   // Digital 8 ; Top Left Yellow LED
int clockPin = 9; // Digital 9 ; SH_CP on Shift Register
int latchPin = 10; // Digital 10 ; ST_CP on Shift Register
int dataPin = 11;    // Digital 11 ; DATA on Shift register
int blPin = 0;    // Analog 0 ; Blue RGB LED
int cdsPin = 2;   // Analog 2 ; CdS Cell
int rdPin = 3;    // Analog 3 ; Red RGB LED
int gnPin = 4;    // Analog 4 ; Green RGB LED

int irval = 1;
int btnval = 1;
int cdsval = 0;
int pos = 0;
int i = 0;;

void setup()
{
  Serial.begin(9600);
  pinMode(latchPin, OUTPUT);
  pinMode(btnPin, INPUT);
  //pinMode(irPin, INPUT);
  pinMode(bryPin, OUTPUT);
  pinMode(trgPin, OUTPUT);
  pinMode(tlyPin, OUTPUT);
  pinMode(blPin, OUTPUT);
  pinMode(rdPin, OUTPUT);
  pinMode(gnPin, OUTPUT);
  clearLCD();
  Serial.print(0xFE, BYTE);
  Serial.print(0x0C, BYTE);
  determineBacklight();
  delay(100);
  selectLineOne();
  delay(100);
  Serial.print("Main Menu");
  delay(500);
  for(i = 0; i < 10; i++) {
    scrollLeft();
    delay(100);
  }
  delay(1000);
}

void loop()
{  
  clearLCD();
  pos = 130;
  selectLineCustom();
  Serial.print("Select Menu");
  pos = 197;
  selectLineCustom();
  Serial.print("Option");
  delay(700);
  for(i = 0; i < 15; i++) {
    scrollLeft();
    delay(100);
  }
  clearLCD();
  mainMenu();
}

void selectLineOne(){  //puts the cursor at line 0 char 0.
   Serial.print(0xFE, BYTE);   //command flag
   Serial.print(128, BYTE);    //position
}
void selectLineTwo(){  //puts the cursor at line 0 char 0.
   Serial.print(0xFE, BYTE);   //command flag
   Serial.print(192, BYTE);    //position
}
void selectLineCustom() {
   Serial.print(0xFE, BYTE);
   Serial.print(pos, BYTE);
   pos = 0;
}
void scrollRight() {
  Serial.print(0xFE, BYTE);
  Serial.print(0x1C, BYTE);
}
void scrollLeft() {
  Serial.print(0xFE, BYTE);
  Serial.print(0x18, BYTE);
}
void clearLCD(){
   Serial.print(0xFE, BYTE);   //command flag
   Serial.print(0x01, BYTE);   //clear command.
}
void determineBacklight() {
  cdsval = analogRead(cdsPin);
  if(cdsval < 500) {
    backlightOn();
  }
  else if(cdsval > 500) {
    backlightOff();
  }
}

void backlightOn(){  //turns on the backlight
    Serial.print(0x7C, BYTE);   //command flag for backlight stuff
    Serial.print(157, BYTE);    //light level.
}
void backlightOff(){  //turns off the backlight
    Serial.print(0x7C, BYTE);   //command flag for backlight stuff
    Serial.print(128, BYTE);     //light level for off.
}
void serCommand(){   //a general function to call the command flag for issuing all other commands   
  Serial.print(0xFE, BYTE);
}
void End() {
  delay(500);
  End();
}

…And the other half:

void mainMenu() {
  Serial.print("Press toggle to switch options");
  delay(2000);
  clearLCD();
  Serial.print("Press select to select an option");
  delay(2000);
  clearLCD();
  Serial.print("Light-Stopper");
  option1();
}
void option1() {
    pinMode(irPin, INPUT);
    delay(100);
    irval = digitalRead(irPin);
    btnval = digitalRead(btnPin);
    if(irval == 0 && btnval == 0) {
      clearLCD();
      Serial.print("??");
    }
    else if(irval == 0 && btnval == 1) {
      clearLCD();
      Serial.print("Light-Stopper");
      lightStopper();
    }
    else if(irval == 1 && btnval == 0) {
      clearLCD();
      Serial.print("Simon");
      option2();
    }
    else if(irval == 1 && btnval == 1) { 
      option1();
    }
}
void option2() {
    pinMode(irPin, INPUT);
    delay(100);
    irval = digitalRead(irPin);
    btnval = digitalRead(btnPin);
    if(irval == 0 && btnval == 0) {
      Serial.print("??");
    }
    else if(irval == 0 && btnval == 1) {
      clearLCD();
      Serial.print("Simon");
      Simon();
    }
    else if(irval == 1 && btnval == 0) {
      clearLCD();
      Serial.print("Light-Stopper");
      option1();
    }
    else if(irval == 1 && btnval == 1) {
      option2();
    }
}
void Simon() {
  Serial.print("Under Construction!");
  delay(1000);
  Simon();
}
void lightStopper() {
  // light each pin one by one using a function A
  for (int j = 8; j > 0; j--) {
    lightShiftPinA(j);
    delay(20);
  } 

 // light each pin one by one using a function A
  for (int j = 0; j < 8; j++) {
    lightShiftPinB(j);
    delay(20);
  } 
  lightStopper();
}
void lightShiftPinA(int p) {
  //defines a local variable
  int pin;

  //this is line uses a bitwise operator
  //shifting a bit left using << is the same
  //as multiplying the decimal number by two. 
  pin = 1<< p;

  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);
  //move 'em out
  shiftOut(dataPin, clockPin, pin);   
  //return the latch pin high to signal chip that it 
  //no longer needs to listen for information
  digitalWrite(latchPin, 1);

}

//This function uses that fact that each bit in a byte
//is 2 times greater than the one before it to
//shift the bits higher
void lightShiftPinB(int p) {
  //defines a local variable
  int pin;

  //start with the pin = 1 so that if 0 is passed to this
  //function pin 0 will light. 
  pin = 1;

  for (int x = 0; x < p; x++) {
    pin = pin * 2; 
  }

  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);
  //move 'em out
  shiftOut(dataPin, clockPin, pin);   
  //return the latch pin high to signal chip that it 
  //no longer needs to listen for information
  digitalWrite(latchPin, 1);

}
void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
  // This shifts 8 bits out MSB first, 
  //on the rising edge of the clock,
  //clock idles low

  //internal function setup
  int i=0;
  int pinState;
  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, OUTPUT);

  //clear everything out just in case to
  //prepare shift register for bit shifting
  digitalWrite(myDataPin, 0);
  digitalWrite(myClockPin, 0);

  //for each bit in the byte myDataOut?
  //NOTICE THAT WE ARE COUNTING DOWN in our for loop
  //This means that %00000001 or "1" will go through such
  //that it will be pin Q0 that lights. 
  for (i=7; i>=0; i--)  {
    digitalWrite(myClockPin, 0);

    //if the value passed to myDataOut and a bitmask result 
    // true then... so if we are at i=6 and our value is
    // %11010100 it would the code compares it to %01000000 
    // and proceeds to set pinState to 1.
    if ( myDataOut & (1<<i) ) {
      pinState= 1;
    }
    else {      
      pinState= 0;
    }

    //Sets the pin to HIGH or LOW depending on pinState
    digitalWrite(myDataPin, pinState);
    //register shifts bits on upstroke of clock pin  
    digitalWrite(myClockPin, 1);
    //zero the data pin after shift to prevent bleed through
    digitalWrite(myDataPin, 0);
  }

  //stop shifting
  digitalWrite(myClockPin, 0);
}