Interfacing with SNES controller - data error

Hello, I currently am working with a SNES controller that I have tied into my Arduino; and for those unfamiliar with the SNES architecture, all of the buttons are inputs to a 16-bit PISO shift register.

I am putting the data obtained from pulsing the shift register into a 16bit integer and returning it in the function below:

uint16_t readNesController() 
{  
  uint16_t tempData = 0xFFFF;

  //B Button
  digitalWrite(NES_LATCH, HIGH);
  digitalWrite(NES_LATCH, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, B_BUTTON);
    
  // Y Button  
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, Y_BUTTON);
  
  // Select button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, SELECT_BUTTON);

  // Start button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, START_BUTTON);

  // Up button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, UP_BUTTON);
    
  // Down button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, DOWN_BUTTON);

  // Left button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, LEFT_BUTTON);  
    
  // Right button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, RIGHT_BUTTON);

  // A button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, A_BUTTON);

  // X button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, X_BUTTON);

  // L button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, L_BUTTON);

  // R button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, R_BUTTON)

  int count = 0;

  for (int i = 0; i <= 16; i++) {
    if(bitRead(tempData, i) == 0) {
      count++;
    }
  }

  Serial.println(tempData, BIN);
  if (count < 4) {
    return tempData;
  } else {
    return 0xFFFF;
  }

}

Note that each button is given an index value within each call to bitClear that corresponds to the order they are read in. Each bit in the tempData output represents the state of each button, 0 being closed and 1 being open.

This functions, however the tempData variable every other call to this function contains a large number of 0's, alternating like so:

1111000000000011
1111111111111111
1111000000000011
1111111111111111
...

And if a button is pressed, say B:

1111000000000011
1111111111111110
1111000000000011
1111111111111110
...

So in other words, every second call to readNesController produces incorrect data in the tempData variable. I was (rather inelegantly) filtering these out outputting 0xFFFF (no buttons pressed) when there are more then 3 zeroes in the tempData variable using a counting loop, which has been working for now but now I am wanting to have rising edge detection in other parts of the project which becomes impossible when every second call to this function will always see all buttons as not being pressed. In other words holding down a button will act as if it is being very rapidly spammed by sending out 0xFFFF every second call.

What are my options here or is there something about my code that is causing this that can be fixed?

Before it is suggested I did try pulsing the clock pin 4 more times as a part of read NesController to get the data from the pins that arent used by the controller - this still occurs - pulsing latch resets the shift register to read data line 0.

---------UPDATE---------
I very quickly realized after posting I could call the function recursively like so, so I am not outputting 0xFFFF for a data error, like so:

int count = 0;
for (int i = 0; i <= 16; i++) {
   if(bitRead(tempData, i) == 0) {
      count++;
    }
  }

  if (count < 4) {
    Serial.println(tempData, BIN);
    return tempData;
  } else {
    readNesController();
  }

but I am still wondering if there is something else I am doing incorrectly that is causing the button states to be read like this.

Not enough information. Please post all the code.

It's very rough and incomplete because it's a mashup of a few different codes and stuff over the last few years. Anyway, hope this helps.

/* work in progress july 2024 by Hallowed31 

                     0     --0v (ground)
     +5V  --  0      0     --CLOCK          Clock to Pin 3
nothing  ---  0      0     --LATCH          Latch goes to Pin 2 
nothing  ---  0      0     --SERIAL OUT     Serial Out to Pin 4

NOTE: also works on 3.3V

NES Controller wire colouring guide:
    +5V: white
    Ground: brown
    Pulse: red(3)
    Latch: orange(2)
    Serial Data Out: yellow(4) 

*/

class Flasher
{
    int ledPin;
    long OnTime;
    long OffTime;
    int ledState;
    unsigned long previousMillis;
  public:
    Flasher(int pin, long on, long off)
    {
      ledPin = pin;
      pinMode(ledPin, OUTPUT);
      OnTime = on;
      OffTime = off;
      ledState = LOW;
      previousMillis = 0;
    }
    void Update()
    {
      unsigned long currentMillis = millis();
      if ((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;
        previousMillis = currentMillis;
        digitalWrite(ledPin, ledState);
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // turn it on
        previousMillis = currentMillis;
        digitalWrite(ledPin, ledState);
      }
    }
};
#include <NESpad.h>
const int latch = 2, clock = 3, datin = 4, a = 5, b = 6, select = 7, start = 8, up = 9, down = 10, left = 11, right = 12;
NESpad nintendo = NESpad(latch, clock, datin);
int nesEntry;
byte nes = 0;
byte nesInput = 0;
int thisMode = 0; // we will pass this variable as argument to void secondFunction(int thisMode)
int numModes = 8;
bool locked = true;

enum states
{
  restart,
  getSet,
  gameOn,
};

// start with the homing
states gameMode = restart;

void controllerRead() {
  nes = 0;
  digitalWrite(latch, LOW);
  digitalWrite(clock, LOW);
  digitalWrite(latch, HIGH);
  delayMicroseconds(2);
  digitalWrite(latch, LOW);
  nes = digitalRead(datin);
  for (int i = 1; i <= 7; i ++) {
    digitalWrite(clock, HIGH);
    delayMicroseconds(2);
    nes = nes << 1;
    nes = nes + digitalRead(datin) ;
    delayMicroseconds(4);
    digitalWrite(clock, LOW);
  }
}

/* these are all the same pin, different blink rates, pin 13. Change to suit) */
Flasher led0(13, 100, 100);
Flasher led1(13, 200, 200);
Flasher led2(13, 300, 300);
Flasher led3(13, 400, 400);
Flasher led4(13, 500, 500);
Flasher led5(13, 600, 600);
Flasher led6(13, 700, 700);
Flasher led7(13, 800, 800);
Flasher led8(13, 50, 50); // nes code waiting and mode8();
void setup() {
  Serial.begin(115200);
  pinMode(datin, INPUT);
  pinMode(latch, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(a, OUTPUT);
  pinMode(b, OUTPUT);
  pinMode(select, OUTPUT);
  pinMode(start, OUTPUT);
  pinMode(up, OUTPUT);
  pinMode(down, OUTPUT);
  pinMode(left, OUTPUT);
  pinMode(right, OUTPUT);
  digitalWrite(latch, HIGH);
  digitalWrite(clock, HIGH);
}
void loop() {
  if (Serial.available() > 0) {
    char input = Serial.read();
    switch (input) {
      case 'h':
        gameMode = restart;
        break;
      case 's':
        gameMode = getSet;
        break;
      case 'r':
        gameMode = gameOn;
        break;
      case '+':
        thisMode = (thisMode + 1) % numModes;
        break;
      case '-':
        thisMode--;
        break;
    }
    if (thisMode > 7) {
      thisMode = 0;
    }
    else if (thisMode < 0) {
      thisMode = 7;
    }
  }
  switch (gameMode) {
    case restart:
      reset();
      gameMode = getSet;
      break;
    case getSet:
      interimStage();
      while (locked == true) {
        contra();
      }
      thisMode = 0;
      gameMode = gameOn;
      Serial.println("game on!");
      delay(1500); // just so you see that it happened in program flow
      break;
    case gameOn:
      gameplay(thisMode);
      break;
  }
}

void reset() {
  Serial.println("reset");
  delay(500); // just so you see that it happened in program flow
}
void interimStage() {
  Serial.println("setting up");
  delay(1000); // just so you see that it happened in program flow
}

void gameplay(int thisMode) {
  int mode = thisMode; // grab the global variable and prep for local use
  controllerRead();
  nesInput = nintendo.buttons();
  digitalWrite(a,  nesInput & NES_A );
  digitalWrite(b,  nesInput & NES_B );
  digitalWrite(select,  nesInput & NES_SELECT );
  digitalWrite(start,  nesInput & NES_START );
  digitalWrite(up,  nesInput & NES_UP );
  digitalWrite(down, nesInput & NES_DOWN );
  digitalWrite(left, nesInput & NES_LEFT );
  digitalWrite(right, nesInput & NES_RIGHT );
  delay(1);
  switch (mode) {
    case 0:
     int ascii = 0;
    char audioClip = '6';
      mode0(ascii, audioClip);
      break;
    case 1:
      mode1();
      break;
    case 2:
      mode2();
      break;
    case 3:
      mode3();
      break;
    case 4:
      mode4();
      break;
    case 5:
      mode5();
      break;
    case 6:
      mode6();
      break;
    case 7:
      mode7();
      break;
    case 8:
      mode8();
      break;
  }
}


void mode0(int ascii, char audioClip) {
  if ((nesInput & NES_B) && (nesInput & NES_UP)) { //Episode: "Journey's End" Medley
    char soundClip = audioClip;
    int asciiText = ascii;
    // Serial1.print(soundClip); // for expansion to Processing
    //char '6' is the trigger to Processing to change graphical status button
   switch(asciiText){
    case 0:
    badWolf();
    break;
   }
  }
  led0.Update();
}
void mode1() {
  Serial.println("m1");
  led1.Update();
}
void mode2() {
  Serial.println("m2");
  led2.Update();
}
void mode3() {
  Serial.println("m3");
  led3.Update();
}
void mode4() {
  Serial.println("m4");
  led4.Update();
}
void mode5() {
  Serial.println("m5");
  led5.Update();
}
void mode6() {
  Serial.println("m6");
  led6.Update();
}
void mode7() {
  Serial.println("m7");
  led7.Update();
}
void mode8() {
  Serial.println("m8");
  led8.Update();
}

void contra() {
  led8.Update();
  controllerRead();
  switch (nesEntry) {
    case 0:
      if (nes == B11110111) { // UP
        nesEntry = 1;
        codeInfo();
        delay(350);// prevent doubling entry for up and down only
      }
      break;
    case 1:
      if  (nes == B11110111) { //UP
        nesEntry = 2;
        codeInfo();
      }
      break;
    case 2:
      if (nes == B11111011) { //DOWN
        nesEntry = 3;
        codeInfo();
         delay(350);// prevent doubling entry for up and down only
      }
      break;
    case 3:
      if (nes == B11111011) { //DOWN
        nesEntry = 4;
        codeInfo();
      }
      break;
    case 4:
      if (nes == B11111101) { //LEFT
        nesEntry = 5;
        codeInfo();
      }
      break;
    case 5:
      if (nes == B11111110) { //RIGHT
        nesEntry = 6;
        codeInfo();
      }
      break;
    case 6:
      if (nes == B11111101) { //LEFT
        nesEntry = 7;
        codeInfo();
      }
      break;
    case 7:
      if (nes == B11111110) { //RIGHT
        nesEntry = 8;
        codeInfo();
      }
      break;
    case 8:
      if (nes == B10111111) { //B
        nesEntry = 9;
        codeInfo();
      }
      break;
    case 9:
      if (nes != B11111111) { //no buttons pressed
        if (nes != B01111111) { //A
          if (nes != B10111111) { //B
            nesEntry = 0;
          }
        }
      }
      if (nes == B01111111) {    //A
        nesEntry = 10;
        codeInfo();
      }
      break;
    case 10:                         //Tripwire 2
      if (nes != B11111111) { //no buttons pressed
        if (nes != B11101111) { //start
          if (nes != B01111111) { //A
            nesEntry = 0;   //If you enter something wrong, like UP,
          }            // nesEntry will go back to 0
        }
      }
      if (nes == B11101111) {     //START
        nesEntry = 11;
        codeInfo();
        Serial.println();
      }
      break;
    case 11:   //If the code has been entered correctly nesEntry = 11
      codeInfo();
      locked = false;
      break;
  }

  if (nes == B11011111) { //SELECT, OFF
    nesEntry = 0; // reset on purpose
  }
}


void codeInfo() {
  Serial.print("Code Entry: ");
  Serial.print(nesEntry);
  Serial.println("/11 correct");
}

void badWolf(){
   Serial.print(F("Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F("   Bad   Wolf "));
    delay(250);
    Serial.print(F("Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F("   Bad   Wolf "));
    delay(250);
    Serial.print(F("Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F("   Bad   Wolf "));
    delay(250);
    Serial.print(F("     I am definitely "));
    delay(200);
    Serial.print(F(" BadWolf  "));
    delay(250);
    Serial.println(F("       BAD      WOLF"));
    delay(450);
    Serial.println(F("     I am    definitely § ☺ "));  //Alt + 21, 1
    delay(400);
    Serial.println(F("\t                          Bad       Wolf"));
    delay(350);
    Serial.println("\n\r");
    Serial.println("\n\r");
    delay(200);
    Serial.print(F("Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(250);
    Serial.print(F(" Bad Wolf "));
    delay(500);
    Serial.println(F("\t \t \t \t \t \t  Bad        Wolf"));
    delay(250);
    Serial.println(F("\t ► ► ► ► ► ► BAD   WOLF ◄ ◄ ◄ ◄ ◄ ◄ "));   //Alt + 16, 17
    delay(500);
    Serial.println(F("               Bad       Wolf   "));
    delay(350);
    Serial.println(F("\t \t \t \t \t  BAD    WOLF      "));
    delay(350);
    for (int k = 0; k < 50; k++) {
      Serial.print(F(" BAD  WOLF "));
      delay(100);
    }
    Serial.println(F("                   BAD  WOLF "));
    delay(200);
    Serial.print(F(" I am  definitely "));
    delay(100);
    Serial.print(F(" I am  definitely "));
    delay(100);
    Serial.print(F(" I am  definitely "));
    delay(100);
    Serial.print(F(" I \t am \t  definitely "));
    delay(100);
    Serial.println(F("\t I am  definitely "));
    delay(300);
    Serial.println(F("   \t    \t         \t \t \t  \t   "));  //all the tabs create cool dots in their place on CoolTerm
    Serial.println(F("   \t    \t  \t  \t    \t   \t  "));
    Serial.println(F(" \t   "));
    Serial.println(F("\t             I am  definitely "));
    delay(300);
    Serial.println(F("\t \t                              a MADMAN with  a  Box "));
    delay(5000);
    Serial.println();
    Serial.println();
    Serial.println();
    Serial.println();
}

//void gameAction() {

//}

The led Flasher class is Adafruit's, the enum bits are I think a partial solution to something else one of the gurus here fixed for someone, the contra code bit is a relic of something I found online but the work in progress general state machine of NES whatever on Arduino is mine.

As I say, hope you find at least something useful in there.

I like the idea of using the shift operand and adding the digital input for each clock pulse, but unfortunately when I implemented it, it still results in the output looking exactly like I showed it above, with the alternating 'error' bitstream showing up in the serial monitor.

const uint8_t B_BUTTON         = 0;
const uint8_t Y_BUTTON         = 1;
const uint8_t SELECT_BUTTON    = 2;
const uint8_t START_BUTTON     = 3;
const uint8_t UP_BUTTON        = 4;
const uint8_t DOWN_BUTTON      = 5;
const uint8_t LEFT_BUTTON      = 6;
const uint8_t RIGHT_BUTTON     = 7;
const uint8_t A_BUTTON         = 8;
const uint8_t X_BUTTON         = 9;
const uint8_t L_BUTTON         = 10;
const uint8_t R_BUTTON         = 11;

uint16_t nesRegister  = 0;

#define NES_DATA    12    // data pin

#define NES_CLOCK   13    // clock pin
#define NES_LATCH   10    // latch pin
void setup() 
{
  Serial.begin(9600);
  
  pinMode(NES_DATA, INPUT);
  
  pinMode(NES_CLOCK, OUTPUT);
  pinMode(NES_LATCH, OUTPUT);
  
  digitalWrite(NES_CLOCK, LOW);
  digitalWrite(NES_LATCH, LOW);
}

void loop() {
  readNesController();
}

uint16_t readNesController() {  
  uint16_t tempData = 0xFFFF;

  //B Button
  digitalWrite(NES_LATCH, HIGH);
  digitalWrite(NES_LATCH, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, B_BUTTON);
    
  // Y Button  
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, Y_BUTTON);
  
  // Select button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, SELECT_BUTTON);

  // Start button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, START_BUTTON);

  // Up button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, UP_BUTTON);
    
  // Down button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, DOWN_BUTTON);

  // Left button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, LEFT_BUTTON);  
    
  // Right button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, RIGHT_BUTTON);

  // A button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, A_BUTTON);

  // X button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, X_BUTTON);

  // L button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, L_BUTTON);

  // R button
  digitalWrite(NES_CLOCK, HIGH);
  digitalWrite(NES_CLOCK, LOW);
  if (digitalRead(NES_DATA) == LOW)
    bitClear(tempData, R_BUTTON);


  int count = 0;
  for (int i = 0; i <= 16; i++) {
    if(bitRead(tempData, i) == 0) {
      count++;
    }
  }
  Serial.println(tempData, BIN);
  if (count < 4) { 
    return tempData;
  } else {
    readNesController();
  }
}

This is the debugging code I am using as it was when I made the post (including my edit), and also makes up a part of a larger project but meets all the requirements for debugging just the SNES controller on its own. The only way I found to consistently not have this alternating behaviour is to include a stupidly large delay of 150ms within readNesController which is significantly slower then counting zeros to determine if its an error then recursively calling the readNesController function if so.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.