Handheld LED-Based Game

A while back, I posted a topic in this forum on a simple 'handheld game' that used a BASIC Stamp, a shift register, and some LEDs. I found that that original design was pretty lame, and it didn't have much functionality. I dropped the project for a couple months, and recently, I started it up again. I completely re-did the design, and added extra features. It now uses not only the BS2 microcontroller, but an Arduino Nano. They are in a master-save setup; I haven't figured out yet which will be which, but the BS2 is responsible for controlling the LCD screen, making sounds, and reading the button inputs, and the Arduino runs the bulk of the processing load. I am completely done with the hardware portion (except for one wire now that I think about it lol). This is still a work-in-progress; now I just have to program it...

Here's the (almost) finished product:
http://www.flickr.com/photos/7507189@N04/3234759799/

...And the rest you can view via my photostream from links there. I'm open to comments and suggestions!

Very nice job! Can you post the schematic? It is difficult to figure out what are you doing without it.

Yes...That will take me a while, though! It will be a very large schematic lol. When I find time, I will do so.

I'm having a very very annoying problem with logic levels, though. Here's the skinny:

Logic HIGHs and LOWs FROM the BS2 register on the Arduino, but the voltage levels of the Arduino do NOT register on the BS2 :(. There is ONE exception: serial data. The BS2 is able to receive serial data from the Arduino, so this makes programming exponentially harder, but I don't want to have to remove the BS2 and use a different micro, so I'm going to work with what I've got

Logic HIGHs and LOWs FROM the BS2 register on the Arduino, but the voltage levels of the Arduino do NOT register on the BS2

That's very peculiar. Can you measure a (static) Arduino logic level with a voltmeter, as a check? And post the result?

Sure...but what do you mean by 'static'??

I mean not changing, because a changing logic level will mess up the voltage reading. Just set one Arduino output to HIGH and another to LOW, then read the voltages.

Okay -

HIGH is around ~4.42V when hooked up to a red LED, ~4.72V with no load

LOW is like 9mV (hooked up to nothing), practically 0V.

I can post the code for both microcontrollers thus far (but they are a MESS!) if it would help...? Note that they are in 'debug' condition; I have code snippets in places they normally wouldn't be, for debugging.

Here's 3/4 of the Arduino code:

/* 
NOTES: Shift Register 1 (MAIN): 
       Q2: Bottom Left MEM LED
       Q3: Top Left MEM LED
        
       Shift Register 2:
       Q0:
       Q1:
       Q2:
       Q3:
       Q4:
       Q5:
       Q6: Top Right MEM LED
       Q7: Bottom Right MEM LED 
*/
int LButton = 6;
int RButton = 7;
int SButton = 5;
int OKAY = 8;
int READY = 1;  // (Analog)

boolean Lval; //l
boolean Rval; //r
boolean Sval;  //s
boolean OKAYval;

int clockPin = 2; // Digital 9 ; SH_CP on Shift Register      
int latchPin = 4; // Digital 10 ; ST_CP on Shift Register  
int dataPin = 3;    // Digital 11 ; DATA on Shift register  
int gnPin = 9;    // Digital (PWM) 9 ; Blue RGB LED
int blPin = 10;    // Digital (PWM) 10 ; Red RGB LED
int rdPin = 11;    // Digital (PWM) 11 ; Green RGB LED

int pin;

int D;     // Delay Time

byte j;    // Reflex/Memory LED # (0 - 7)
byte M;    // Memory LED # (2 - 3)

int i;     // Generic Counter

boolean runthru;
boolean middle;

boolean OK;

byte dataMem;                // 'Memory' LED bank
byte dataRef;                // 'Reflex' LED bank
byte dataArrayMem[2];
byte dataArrayRef[8];

char ID;
byte CMD;

char BUTTON = 'B';
char SOUND = 'S';
char CONFIRM = 'K';

void setup() {
  Serial.begin(9600);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  digitalWrite(11, HIGH);
  digitalWrite(12, LOW);
  End();
  
  // The following arrays are for lighting ONE LED at a time
  dataArrayMem[0] = B00000010; 
  dataArrayMem[1] = B00000011; 
 
  dataArrayRef[0] = B00000000;
  dataArrayRef[1] = B00000001;
  dataArrayRef[2] = B00000010;
  dataArrayRef[3] = B00000011;
  dataArrayRef[4] = B00000100;
  dataArrayRef[5] = B00000101;
  dataArrayRef[6] = B00000110;
  dataArrayRef[7] = B00000111;

  pinMode(latchPin, OUTPUT);
  pinMode(blPin, OUTPUT);
  pinMode(rdPin, OUTPUT);
  pinMode(gnPin, OUTPUT);
  pinMode(LButton, INPUT);
  pinMode(RButton, INPUT);
  pinMode(SButton, INPUT);
  
  Clear();
  delay(200);
  
  Sval = digitalRead(SButton);
  if(Sval == 1) {                // User can enter diagnostics mode by
    Serial.begin(38400);
    OK = analogRead(READY);
    while(OK == HIGH) {
      OK = analogRead(READY);
      delayMicroseconds(1);
    }
    while(OK == LOW) {
      OK = analogRead(READY);
      Serial.println(ID);
    }
  } 
}
void loop() {
  dataRef = B111111;    // Just the top row ('Refex' LED's)
  dataMem = 0;
  WriteArray();
  Menu();
}
void Menu() {
  ID = BUTTON;
  CMD = 1;
  SendData();
  Sens();
  if(Lval || Rval) {
    Lval = 0;
    Rval = 0;
    if(dataRef == B111111) {
      dataRef = B11000000;
      dataMem = B00001100;
      WriteArray();
      delay(500);
    }
    else {
      dataRef = B111111;
      dataMem = 0;
      WriteArray();
      delay(500);
    }
  }
  if(Sval) {
    Sval = 0;
    if(dataRef == B111111) {
      for(i = 0; i < 5; i++) {
        dataRef = 0;
        WriteArray();
        delay(50);
        dataRef = B111111;
        WriteArray();
        delay(50);
      }
      RefSplash();
      LightningReflex();
    }
    else {
      for(i = 0; i < 5; i++) {
        dataRef = 0;
        dataMem = 0;
        WriteArray();
        delay(50);
        dataRef = B11000000;
        dataMem = B00001100;
        WriteArray();
        delay(50);
      }
    }
  }
  Menu();  
}
void LightningReflex() {
  D = 50;
  // light each pin one by one using a function A
  for (int j = 5; j > -1; j--) {
    //ground latchPin and hold low for as long as you are transmitting
    digitalWrite(latchPin, 0);
    // Reflex Game LEDs
    lightShiftPinA(j);
    // Memory Game LEDs
    lightShiftPinA(0);
    //return the latch pin high to signal chip that it 
    //no longer needs to listen for information
    digitalWrite(latchPin, 1);
    delay(D);
    Check();
    if(j == 3) {
      Middle();
    }
  }

  // light each pin one by one using a function A
  for (int j = 0; j < 6; j++) {
    //ground latchPin and hold low for as long as you are transmitting
    digitalWrite(latchPin, 0);
    //red LEDs
    lightShiftPinB(j);
    //green LEDs
    lightShiftPinB(0);
    //return the latch pin high to signal chip that it 
    //no longer needs to listen for information
    digitalWrite(latchPin, 1);
    delay(D);
    Check();
    if(j == 2) {
      Middle();
    }
  }
  LightningReflex();
}
void Check() {
  Sens();
  if(Sval == 1) {
    if(middle == true) {
      middle = false;
      Win();
    }
    if(middle == false) {
      Sval = 0;
      switch(pin) {
        case 0:
          dataRef = B10000000;
        case 1:
          dataRef = B01000000;
        case 2:
          dataRef = B00100000;
        case 3:
          dataRef = B00010000;
        case 4:
          dataRef = B00001000;
        case 5:
          dataRef = B00000100;
      }
      for(i = 0; i < 5; i++) {
        WriteArray();
        delay(50);
        dataRef = 0;
        WriteArray();
        delay(50);
      }  
    }
  }
}
void Middle() {
  middle = true;
  runthru = !runthru;
  switch(runthru) {
    case 0:
      analogWrite(rdPin, 50);
      break;
    case 1:
      analogWrite(gnPin, 50);
      break;
  }
  delay(D);
  digitalWrite(gnPin, LOW);
  digitalWrite(rdPin, LOW);
  Check();
}
void End() {
  delay(500);
  End();
}
void Sens() {                   // Read all sensors (button states from BS2)
  digitalWrite(13, HIGH);
  //pinMode(OKAY, OUTPUT);
  //digitalWrite(OKAY, LOW);
  Lval = digitalRead(LButton);
  Rval = digitalRead(RButton);
  Sval = digitalRead(SButton);
  if(Lval == 1) {
    digitalWrite(rdPin, HIGH);
    delay(5000);
    analogWrite(READY, HIGH);
  } 
  if(Rval == 1) {
    digitalWrite(gnPin, HIGH);
    delay(5000);
    analogWrite(READY, HIGH);
  } 
  if(Sval == 1) {
    digitalWrite(blPin, HIGH);
    delay(5000);
    analogWrite(READY, HIGH);
  } 
  delay(100);
  analogWrite(READY, LOW);
  analogWrite(13, LOW);
}
void SendData() {
  //digitalWrite(OKAY, LOW);
  Serial.begin(38400);
  OK = analogRead(READY);
  while(OK == HIGH) {
    OK = analogRead(READY);
    delayMicroseconds(1);
  }
  while(OK == LOW) {
    OK = analogRead(READY);
    Serial.println(ID);
  }
  while(OK == HIGH) {
    OK = analogRead(READY);
    delayMicroseconds(1);
  }
  while(OK == LOW) {
    OK = analogRead(READY);
    Serial.println(CMD, BYTE);
  }
}
void LightLEDS() {
  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);
  //Reflex Row/Memory Block
  lightShiftPinA(j); // 'Travel' Left: 5-j, 'Travel' Right: j
  //Memory Block
  lightShiftPinA(M);  
  //return the latch pin high to signal chip that it 
  //no longer needs to listen for information
  digitalWrite(latchPin, 1);
  delay(D);
}
void WriteArray() {
  //load the light sequence you want from array
  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);
  //move 'em out
  shiftOut(dataPin, clockPin, dataRef);   
  shiftOut(dataPin, clockPin, dataMem);
  //return the latch pin high to signal chip that it 
  //no longer needs to listen for information
  digitalWrite(latchPin, 1);
  //delay(300);
}
void MemSplash() {
  D = 100;
  for(i = 0; i < 10; i++) {
    for(j = 6; j < 8; j++) {
      M = 0;
      LightLEDS();
    }
    Clear();
    for(M = 2; M < 4; M++) {
      LightLEDS();
    }
    D = D - 10;
  }
  Clear();
}
void RefSplash() {
  dataMem = 0;
  dataRef = B100001;
  WriteArray();
  delay(250);
  dataRef = B010010;
  WriteArray();
  delay(250);
  dataRef = B001100;
  WriteArray();
  delay(250);
  dataRef = 0;
  WriteArray();
  for(i = 0; i < 125; i++) {
    analogWrite(blPin, i);
    analogWrite(gnPin, i);
    analogWrite(rdPin, i);
    delayMicroseconds(2000);
  }
  delay(500);
  digitalWrite(blPin, 0);
  digitalWrite(rdPin, 0);
  digitalWrite(gnPin, 0);
}
//This function uses bitwise math to move the pins up
void lightShiftPinA(int p) {

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

  //move 'em out
  shiftOut(dataPin, clockPin, pin);   

}

...and the remaining 1/4:

//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) {

  //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; 
  }
  //move 'em out
  shiftOut(dataPin, clockPin, pin);   
}

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);
}
//blinks both registers based on the number of times you want to 
//blink "n" and the pause between them "d"
//starts with a moment of darkness to make sure the first blink
//has its full visual effect.
void blinkAll_2Bytes(int n, int d) {
  digitalWrite(latchPin, 0);
  shiftOut(dataPin, clockPin, 0);
  shiftOut(dataPin, clockPin, 0);
  digitalWrite(latchPin, 1);
  delay(200);
  for (int x = 0; x < n; x++) {
    digitalWrite(latchPin, 0);
    shiftOut(dataPin, clockPin, 255);
    shiftOut(dataPin, clockPin, 255);
    digitalWrite(latchPin, 1);
    delay(d);
    digitalWrite(latchPin, 0);
    shiftOut(dataPin, clockPin, 0);
    shiftOut(dataPin, clockPin, 0);
    digitalWrite(latchPin, 1);
    delay(d);
  }
} 
void Clear() {
  digitalWrite(latchPin, 0);
  shiftOut(dataPin, clockPin, 0);
  shiftOut(dataPin, clockPin, 0);
  digitalWrite(latchPin, 1);
}
void Win() {

}
void DIAGNOSTICS() {
  for(i = 0; i < 15; i++) {
    digitalWrite(13, HIGH);
    delay(50);
    digitalWrite(13, LOW);
    delay(50);
  }
  delay(100);
  blinkAll_2Bytes(2,500);
  delay(500);
  digitalWrite(rdPin, HIGH);
  delay(1000);
  digitalWrite(rdPin, LOW);
  delay(1000);
  digitalWrite(gnPin, HIGH);
  delay(1000);
  digitalWrite(gnPin, LOW);
  delay(100);
  digitalWrite(blPin, HIGH);
  delay(100);
  digitalWrite(blPin, LOW);
  delay(100);
  End();
}

And here's the BASIC Stamp II code:

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

OK      PIN  0
Pzo     PIN  7
LCD     PIN  8
READY   PIN  13
ATX     PIN  15

' -----[LCD Commands]----------------------
' CMD followed by:
' -128 - 143 (Line 1), 144 - 159 (Line 2) = Set Cursor Position
' -ASCII letters 'K'-'P' = Set baud to 2.4K, 4.8K, 9.6K, 14.4K, 19.2K, and 38.4K bps respectively
CMD     CON  $FE   ' COMMAND Instruction
CLLCD   CON  $01   ' Clear LCD
CURR    CON  $14   ' Cursor Right
CURL    CON  $10   ' Cursor Left
SCR     CON  $1C   ' Scroll Right
SCL     CON  $18   ' Scroll Left
DON     CON  $0C   ' Display ON
DOFF    CON  $08   ' Display OFF
BBRT    CON  $7C   ' Backlight Brightness (PWM)

BTURBO  CON  6
TMOUT   CON  10000 ' Serial timeout
B       CON  $54   ' 9600 Bps

Yes     CON  1
No      CON  0

i       VAR  Word

id      VAR  Word
datta   VAR  Word

p       VAR  Word

LOW 9
LOW 10
LOW 11

'OUTPUT READY
INPUT READY
LOW READY

IF IN6 = 0 THEN
  HIGH 11
  SERIN ATX\READY,BTURBO,TMOUT,Serial_Error,[id]    ' OK is LOW when ready for data
  GOSUB DIAGNOSTICS
ENDIF

SEROUT 8, B, [CMD, CLLCD]
PAUSE 4000
SEROUT 8, B, [CMD, 131, "Welcome to"]
PAUSE 1000
SEROUT 8, B, [CMD, CLLCD]
PAUSE 100
SEROUT 8, B, [CMD, 131, "Lightning         Reflex!!"]
PAUSE 2000
FOR i = 1 TO 14
  SEROUT 8, B, [CMD, SCL]
  PAUSE 100
NEXT

Main:
DO
  DEBUG CR, "Main"

  GOSUB Get_Data
  GOSUB Determine_Operation

LOOP

Get_Data:

DEBUG CR,"Getting data..."
SERIN ATX\READY,BTURBO,TMOUT,Serial_Error,[id]    ' OK is LOW when ready for data
PAUSE 1
SERIN ATX\READY,BTURBO,TMOUT,Serial_Error,[datta] ' OK is LOW when ready for data
'DEBUG CR,id, CR, DEC datta
RETURN

Serial_Error:
  DEBUG CR,"Serial timeout."
  GOTO Main

Determine_Operation:

DEBUG CR, "Determine Operation"

SELECT id
  CASE "B":
    DEBUG CR, "Buttons"
    GOSUB Read_Buttons
ENDSELECT
RETURN

Read_Buttons:

DEBUG CR, "Reading Buttons"

  IF IN6 = 0 THEN
    HIGH 11            ' Select Button
    DO
      PAUSE 1
    LOOP WHILE READY = 0
    LOW 11
    GOTO Main
  ENDIF

  IF IN5 = 0 THEN        ' Left Button
    HIGH 9
    DO
      PAUSE 1
    LOOP WHILE READY = 0
    LOW 9
    GOTO Main
  ENDIF

  IF IN4 = 0 THEN        ' Right Button
    HIGH 10
    DO
      PAUSE 1
    LOOP WHILE READY = 0
    LOW 10
    GOTO Main
  ENDIF
GOTO Read_Buttons

DIAGNOSTICS:

PAUSE 2000
DEBUG CR, "DIAGNOSTICS MODE"
SEROUT 8, B, [CMD, CLLCD]
SEROUT 8, B, ["DIAGNOSTICS MODE"]
END

HIGH is around ~4.42V when hooked up to a red LED, ~4.72V with no load

That surprised me, being so much lower than 5.0V, so I checked my own Arduino (which still had the LCD attached). I got a reading of, at the lowest, 4.97V.

Having said that, the logic TRUE threshold on the PIC inside the BS2 should still register a 4.4V signal as TRUE. So, I'm baffled for now!