Close

Working with SideWinder Protocol

A project log for Control Freak

Collection of Arduino Micro firmware and schematics for connecting your retro Joysticks and Gamepads to USB port.

danjovicdanjovic 04/22/2020 at 01:580 Comments

I have a SideWinder controller that did not worked with an implementation of this protocol to V-USB (3DP-Vert) probably because my controller is Gamapad and not a force feedback joystick. I have tried to analyze the code but coudn't get too far with that. Too complex for my current skills.

My sidewinder Gamepad

I have read a more recent article about interfacing SideWinder with AVR and it was great because it pointed me to the right path, yet the code uses interrupts and it is also for a controller different from mine.

Well, having in mind the possibility to make it work also with the Digispark sidekick I have started to write my own code to read the bits coming from the joystick.

It starts to releasing the capacitors to charge and waitin for the first falling edge of the Button 1 line.

After that it starts to shift in the bits using a rudimentary state machine, and only finishes when no more data have been received after a given period.

Doing that I can receive a variable number of bits, filling up an array of bytes with the bits received.

At the end the code return the number of bits  (parity bit inclusive). The code also perform the padding of the last bits just in case the count is not a multiple of 8.

Worth to mention that the code must run with interrupts disabled as the Arduino housekeeping of timers can mess with the reception of bits, but it takes mere 500us.

Well, talk is cheap, here's the code.

/*
  SideWinder Controller sampler function
  Danjovic - 21/apr/2020
*/

// 125 counts to overflow @16MHz/64 = 500us
#define initStopWatch() do {   TIFR2 |= (1<<TOV2); TCNT2=131;  } while (0)

// check timer 2 overflow flag
#define timeExceeded() (TIFR2 & (1<<TOV2))

// check clock line
#define inputHigh() (PIND & (1<<2))
#define inputLow()  !(PIND & (1<<2))

// check data line
#define dataIn()  !(PIND & (1<<3))

// Release capacitors to charge
#define releaseCapacitors() DDRB &= ~( (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) )

#define resetCapacitors()  do {DDRB|=((1<<0)|(1<<1)|(1<<2)|(1<<3)); PORTB&=~((1<<0)|(1<<1)|(1<<2)|(1<<3));} while (0)

enum sideWinderStateMachine {
  initMachine = 0,
  waitStart,
  waitFall,
  waitRise,
  timedOut
};

uint8_t sample[4]; // 64 bits can be less, can be more

//
// SETUP
//
void setup() {
  // Initialize Timer2
  TCCR2A = (0 << WGM20) // WGM2 2..0 = 0 normal mode
           | (0 << WGM21)
           | (0 << COM2B0)
           | (0 << COM2B1)
           | (0 << COM2A0)
           | (0 << COM2A1);

  TCCR2B = (0 << CS20) // CS2 2..0 = 100 prescaler/64
           | (0 << CS21)
           | (1 << CS22)
           | (0 << WGM22);

  TCNT2 = 0;
  TIFR2 |= (1 << TOV2); // clear timer flag

  // Pins D2..D5 as Buttons 1, 2, 3, 4
  DDRD &= ~( (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) ) ;
  PORTD |= (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5); 

  Serial.begin(9600);
  Serial.println("Start");

}
//
// LOOP
//
void loop() {
  uint8_t i;

  i = sampleSideWinder();
  
  if (i > 0 & i<128) {
    Serial.print("Received:");
    Serial.print(i);
    Serial.print(" buttons");
    for (i=0;i < sizeof(sample); i++) {
      Serial.print (" ");
      Serial.print (sample[i],BIN);
      } 
    Serial.println();

  } else
    Serial.println("fail");

  delay(5);

}


//
//  Sample SideWinder controller. Can receive multiple bytes 
//
uint16_t sampleSideWinder() {
  uint8_t shiftRegister;
  uint8_t index;
  uint8_t bitCount;
  uint8_t parityCount;
  uint8_t state=initMachine;
  uint8_t bufferFull;
  uint8_t i;

 // Interrupts must be turned off so Arduino timer housekeeping
 // do not interfere with data stream timing
  cli(); 

  releaseCapacitors();

  // State machine to receive data stream in bytes 
  do {
    switch (state) {
      case initMachine:
        shiftRegister = 0;
        index = 0;
        bitCount = 0;
        parityCount = 0;
        state= 0;
        bufferFull = 0;
        initStopWatch();   // timeout at 600us
        state = waitStart;
        break;

      case waitStart:  // wait for the first falling edge
        if (inputLow()) {
          state = waitRise;
        }
        break;

      case waitFall:  
        if (inputLow()) {
          state = waitRise;
          sample[index] = shiftRegister;
          if ((bitCount&0x07) == 0) {  // 3 LSBit goes zero every 8th bit received
            shiftRegister = 0;
            index++;
            if (index>=sizeof(sample))
               bufferFull=1;  
          }
        }
        break;

      case waitRise:
        if (inputHigh()) {
          shiftRegister <<= 1;
          if (dataIn()) {
            shiftRegister |= 1;
            parityCount++;
          }      
          bitCount++;
          state = waitFall;
        }
        break;
    } //switch
  } while (!timeExceeded() & !bufferFull);

  resetCapacitors();

  sei();

  // Justify last bits received to left (padding)
  i = (bitCount & 0x07); 
  while (i<8) {
    sample[index]<<=1;
    i++;
    }
  // parityCount should be an Odd number
  
  // return bit 7 of bitCount set if parity check fails.
  return ( (bitCount & 0x7f) | (~parityCount<<7) );
}

Discussions