March 30, 2020, 05:06:24 am

News:

The Wiki is fixed!  Again!Go, log in, add stuff!


SNES controller signaling question

Started by rhys79, September 24, 2012, 06:46:33 am

Previous topic - Next topic

rhys79

September 24, 2012, 06:46:33 am Last Edit: September 24, 2012, 07:55:43 am by rhys79
Ok, I've searched the forums, I've searched the web, and I can't find the answer to this question.

I had no trouble finding the signaling specs for a single nes/snes controller.  Easy to deal with in a microcontroller.  My question is, on the console end, are both controllers polled for their button states simultaneously, or does the console poll one controller at a time?

I found one post in a forum that stated that the clock and data lines are shared between the two ports, with each port having it's own latch line.  I couldn't find any other confirmation of this though, so I'm hoping somebody here can give me a difinitive answer.  If that is the case, the controllers would have to be polled in series, which would make my life much easier :)

Thanks!

-----Edit-----

Never mind, dug around and found schematics, and it looks like the the latch line is shared and the clock and data lines are seperate.  That makes my life a little more difficult, but oh well, I'll figure it out.....

NFG

The polling sequence sounds like something that would be software controlled, but I don't know a lot about SNES coding. 

Glad you found the answer, thanks for sharing it.  (Got a link to those schems?  =D)

rhys79

I have no idea which of the dozens of sites I grabbed them from, their sitting in my downloads folder right now....  I attached them to this post if anybody wants them.  Their not great, but it was enough for me to figure out what I needed.  Knowing that both controller ports have their own independent clock and data line tells me I don't have to worry about the timings of the controller button state polling in relation to each other, just watch for a latch signal, and start dumping the button states of both controllers at the same time.  Should be interesting to get the code to manage that kind of timing on a single AVR for two controllers.  I'm hoping to deduplicate the hardware from a reciever for each wireless controller down to a single reciever for two wireless controllers.  I've been mucking around with Atmel AVR's a bit, but this will be my first serious project with them.  Should be interesting....

rhys79

Here's the other part of the schematic archive.

Hojo_Norem

September 25, 2012, 04:00:00 am #4 Last Edit: September 25, 2012, 04:02:38 am by Hojo_Norem
I've had some experience with (S)NES pad to AVR interfacing.  The pads are able to share the clock line just fine.  That means you only need two IOs + one per joypad on your AVR.  All it means is that in your code you just need to read the state of all the pads in the same joypad clockcycle.  Speaking of clockcycles, the device reading the pads (you AVR in this case) is responsible for generating the latch and clock signals and as the logic inside the pads is simple standard logic I dare say that whatever timing you code manages it should work.

What I discovered while developing my Super Supergun is that you need to pull the pad's clock high before toggling the latch line because for some reason if I latched the pads with the clock low the second pad's readings would be all screwy.
Formerly 'butter_pat_head'

rhys79

I appreciate the insight.  Interfacing to the joypads isn't an issue, there is a stock arduino library for doing so, not a big deal there.  It's the reciever end that interfaces to the console that I'm still trying to figure out.  I don't have easy access to an o-scope or logic analyzer to probe the controller timing on the console to find out for certain how it handles the two controller ports in relation to each other.  I'll have to figure it out by trial and error.  Based on the schematic, the latch line is shared by both ports with seperate clock and data lines for each port, so my best guess is that the console pushes the latch signal high, the reads out both controllers button states at the same time.  I'll write my first go around with the code under that assumption, and if that doesn't work, I'll go from there....

rhys79

September 29, 2012, 06:30:39 pm #6 Last Edit: September 29, 2012, 06:32:17 pm by rhys79
Just to update, I have the code and hardware designs completed, but untill all the bits and bobs come in to put everything together, I can't really test and debug.  I have the NRF24 wireless boards now, and two atMega328p chips, but I'm broke till the end of next week.  I'll post the code here if anyone wants to check it out and let me know what you think....

Here's the transmitter code
/*
Copyright (C) 2012 John Byers <jbyers2@wgu.edu>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation
*/

/**
* Dual Wireless Retro Controller Adapter Transmitter
*
* This is the transmitter side code for the adapter set.
*/

#include <SNESpadDual.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>

//
// Hardware Configuration
//

RF24 radio(9,10);
SNESpad nintendo = SNESpad(4,3,5,6);

//
// Variable Inits
//

unsigned long state = 0;
const uint64_t pipes = 0xF0F0F0F0E1LL;

void setup()
{
  radio.begin();
  radio.setRetries(15,15);
  Serial.begin(57600);
}

void loop() {
 
  // Get controller button states and write out to serial monitor for debugging 
  state = nintendo.buttons();
  Serial.println(~state, BIN);
 
  // Send button states to reciever and output transmission status for debugging
  bool ok = radio.write( &state, sizeof(unsigned long) );
  if (ok)
    Serial.println("ok...");
  else
    Serial.println("failed!!");
}


Here is the reciever code

/*
Copyright (C) 2012 John Byers <jbyers2@wgu.edu>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation
*/

/**
* Dual Wireless Retro Controller Adapter Transmitter
*
* This is the reciever side code for the adapter set.
*/

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>

//
// Hardware Configuration
//

RF24 radio(9,10);

//
// Variable Inits
//

unsigned long state = 0;
const uint64_t pipes = 0xF0F0F0F0E1LL;
int strobe = 4;
int clock = 3;
int data1 = 5;
int data2 = 6;

void setup()
{
  radio.begin();
  radio.setRetries(15,15);
  Serial.begin(57600);
  pinMode(strobe, INPUT);
  pinMode(clock, INPUT);
  pinMode(data1, OUTPUT);
  pinMode(data2, OUTPUT);
 
  //
  //Open radio pipe for writing
  //
 
  radio.openReadingPipe(1,pipes);

  //
  //Start Listening
  //
 
  radio.startListening();

  //
  //Dump the configuration of the RF unit for debugging
  //

  radio.printDetails();
 
}

void loop()
{
  // Wait for data
  bool get_data = false;
  unsigned long state;
  bool timeout = false;
  if ( radio.available() ) {
    unsigned long started_waiting_at = millis();
    radio.read( &state, sizeof(unsigned long) );
    Serial.println(state, BIN);
 
    // Data captured, now wait for console to trigger latch
    while ( ! digitalRead(strobe) && ! timeout ) {
      if ( millis() - started_waiting_at > 200 ) {
        timeout = true;
      }
    }
    get_data = true;
  }
 
  if ( timeout ) {
    Serial.write("Console strobe timeout!!!");
  }
  else if ( ! get_data) {
    Serial.write("No data from transmitter!!!!");
  }
  else {
    // Data captured and latch triggered, output button states
    int data_bit1;
    int data_bit2;
    byte i;
    digitalWrite(data1,bitRead(state,0));
    digitalWrite(data1,bitRead(state,16));
    while ( i = 1, i < 16, i++ ) {
      while ( ! digitalRead(clock) ) {
        delayMicroseconds(1);
      }
      digitalWrite(data1,bitRead(state,i));
      digitalWrite(data2,bitRead(state,(i+16)));
      delayMicroseconds(12);
    }
    Serial.println("sucess!!!");
  }
}


And attached is the modified snespad library, and maniacbugs NRF24 library.

The hardware is a combination of the breadboard arduino design and Micro's wireless controller/reciever design.  I'm not too worried about the transmitter code working, but I'm not positive on the reciever code.  Once I get all the bits and bobs in to breadboard the designs, I'll let everyone know if it works....

rhys79

October 02, 2012, 02:15:00 am #7 Last Edit: October 02, 2012, 07:28:47 am by rhys79
Major update to the code, using interrupts and an ping/pong to tell the transmitter the reciever is ready for another packet.....  ::)  Can you tell I'm new to this.....

Reciever code:

/*
Copyright (C) 2012 John Byers <jbyers2@wgu.edu>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation
*/

/**
* Dual Wireless Retro Controller Adapter Transmitter
*
* This is the reciever side code for the adapter set.
*/

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>

//
// Hardware Configuration
//

RF24 radio(9,10);

//
// Variable Inits
//

volatile unsigned long state2 = 0xFFFFFFFF;
volatile byte i = 0;
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };
volatile int strobe = 2;
int clock = 3;
volatile int data1 = 5;
volatile int data2 = 6;
bool firstLoop = true;
volatile int status2 = 1;

void setup()
{
  //Setup radio and serial debugging
  radio.begin();
  radio.setRetries(0,15);
  radio.enableDynamicPayloads();
  Serial.begin(57600);
 
  //setup SNES pins
  pinMode(strobe, INPUT);
  pinMode(clock, INPUT);
  pinMode(data1, OUTPUT); digitalWrite(data1, LOW);
  pinMode(data2, OUTPUT); digitalWrite(data2, LOW);

  //open radio pipes
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1,pipes[0]);
 
  //Dump the configuration of the RF unit for debugging - remove in final code
  radio.printDetails();

  //Setup Interupts 
  attachInterrupt(strobe,latch,CHANGE);
  attachInterrupt(clock,data,RISING);

}

void loop()
{
  //check if this is the first execution of loop()
  if (firstLoop) {
    int status1 = 1;
   
    //send ping packet to let transmitter know we are ready for a packet
    bool ok = radio.write( &status1, sizeof(int));
   
    //begin listenting for button state packet
    radio.startListening();
   
    //debug check to make sure ping packet was sent - remove in final code
    if (!ok) {
      Serial.println("sync packet transmission failed");
    }
    else {
      Serial.println("sync packet transmission successful");
           
      //let the program know we have successfully executed the first loop
      firstLoop = false;
    }
  }
 
  //check for data packet from transmitter
  if ( radio.available() )
  {
    //read data packet from transmitter
    unsigned long state = 0;
    radio.read( &state, sizeof(unsigned long) );
   
    //debug output recieved packet contents - remove in final code
    Serial.println(state, BIN);
   
    //copy button state data to volatile variable for use in interrupts
    state2 = state;
  }
 
  //debuging output if no data packet present yet  - remove in final code
  else
  {
    Serial.println("No data recieved yet");
  }
}

//Latch interrupt routine

void latch()
{
  //check to see if latch signal is high or low
  if (strobe) {
   
    //set both data lines high
    digitalWrite(data1,HIGH);
    digitalWrite(data2,HIGH);
  }
  else {
   
    //set first bit on data lines
    digitalWrite(data1,bitRead(state2,i));
    digitalWrite(data2,bitRead(state2,(i+16)));
   
    //initialize clock signal counter to 0
    i = 0;
   
    //debug status output
    Serial.println("Bit0 out");
  }
}

//Data interrupt routine

void data()
{
  //increment clock signal counter
  i++;
 
  //output next bit on data lines
  digitalWrite(data1,bitRead(state2,i));
  digitalWrite(data2,bitRead(state2,(i+16)));
 
  //debug status output
  Serial.print("Bit");
  Serial.print(i);
  Serial.println(" out");
 
  //check to see if this is the final clock cycle in a read cycle
  if(i=15)
  {
    //drive data lines low
    digitalWrite(data1,LOW);
    digitalWrite(data2,LOW);
   
    //take radio out of listening mode
    radio.stopListening();
   
    //read ping bit into ISR local variable from volatile local
    int status1 = status2;
   
    //send ping packet to transmitter
    bool ok = radio.write( &status1, sizeof(int));
   
    //debug check to see if packet sent - remove in final code
    if (!ok) {
      Serial.println("sync packet transmission failed");
    }
    else {
      Serial.println("sync packet transmission successful");
    }
   
    //put radio back in listening mode
    radio.startListening();
  }
}


Transmitter code:

/*
Copyright (C) 2012 John Byers <jbyers2@wgu.edu>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation
*/

/**
* Dual Wireless Retro Controller Adapter Transmitter
*
* This is the transmitter side code for the adapter set.
*/

#include <SNESpadDual.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>

// Hardware Configuration
RF24 radio(9,10);
SNESpad nintendo = SNESpad(4,3,5,6);

// Variable Inits
unsigned long state = 0;
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

void setup()
{
 
  //setup radio and serial debugging lines
  radio.begin();
  radio.setRetries(0,15);
  radio.enableDynamicPayloads();
  Serial.begin(57600);

  //setup radio pipes and put radio in listening mode
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1,pipes[1]);
  radio.startListening();
 
  //Dump the configuration of the RF unit for debugging
  radio.printDetails();
}

void loop() {
 
  //check if radio has recieved ping from reciever and read data in
  int ready = 0;
  if (radio.available() ) {
    radio.read (&ready, sizeof(char));
  }
 
  //if ping has been recieved, read in button states and transmit
  if (ready) {
   
    //take radio out of listening mode
    radio.stopListening();
   
    // Get controller button states
    state = nintendo.buttons();
   
    //debugging output - remove in final code
    Serial.println(~state, BIN);
 
    // Send button states to reciever
    bool ok = radio.write( &state, sizeof(unsigned long) );
   
    //debugging output - remove in final code
    if (ok) {
      Serial.println("transmission successful");
    }
    else {
      Serial.println("transmission failed!!");
    }
   
    //put radio back in listening mode to await next ping
    radio.startListening();
  }
}

rhys79

Long time since the last update I know, but if anyone is still interested....

Hardware is done and works great.  I had to go back to the drawing board on the firmware though.  The overhead of using Arduino training wheels was totally mucking with the delicate timings needed for this to work.  I've completely rewritten the entire code base and libraries in native C (that was a learning curve from hell....) and I'll be testing the code again this evening.  Made a couple of goofs the first go around, have them fixed, and hopefully all will go well.  I'll update again once I've tested the code.

If everything works as intended, I will have nine units available in the coming weeks if anyone is interested.  Price to be determined.  The test boards used mostly spare parts, scrap, and samples, so I'm not sure how much it will cost me to populate the boards, put them in cases, and such to create a finished product yet.

sazyario

I'm wondering if you're willing to publish your newest work.
My personal use only. I was looking at micro's wireless stuff but I need to adapt to drag genesis. I can't read his hex. In hoping I can somehow use your code base and modify it somehow to work with the 6 button genesis controller