Emulating a 74LS165 shift register (Amiga CD32 gamepad)

Hi! I’m trying to emulate an Amiga CD32 gamepad with an Arduino Uno. The gamepad has a 74LS165 shift register that shifts out the seven buttons of the gamepad + two “signature” bits (HIGH, LOW). The buttons are active LOW.

I have a scetch set up now that reads the latch and clock signals from the Amiga and shifts out values. Basically it’s waiting for the latch pin to go low and then it detects pulses on the clock pin and sends out values accordingly.

I’m trying with “dummy” values at the moment. Problem is as soon as I set a button value as LOW everything goes haywire. Shifting all buttons as HIGH and at the end shifting the values HIGH and LOW for the signature, the Amiga detects the Arduino as a gamepad and it all seems stable when inspecting with an oscilloscope.

What am I doing wrong here? I would be very grateful for any advice, I have been fighting with this for a few days and can’t figure out what’s wrong. :frowning:

My current scetch:

// ---------------------------------------------------------------
// Defines
// ---------------------------------------------------------------
#define PIN_CLOCK 2
#define BIT_CLOCK 4 // Pin 2 is bit 2 of port D (4 = B00000100)
#define PIN_LATCH 3 
#define BIT_LATCH 8 // Pin 3 is bit 3 of port D (8 = B00001000)
#define PIN_DATA  8
#define BIT_DATA  0 // Pin 8 is bit 0 of port B

// ---------------------------------------------------------------
// Declare variables
// ---------------------------------------------------------------
byte regB; // Used for reading port B
byte regC; // Used for reading port C


// ---------------------------------------------------------------
// Setup routine
// ---------------------------------------------------------------
void setup() 
{
  // Set A0-A5 as inputs (with internal pull-ups enabled)
  for(int i=14; i<=19; i++)
    pinMode(i, INPUT_PULLUP);

  // Set unused pins at inputs (with internal pull-ups)
  for(int i=4; i<=7; i++)
    pinMode(i, INPUT_PULLUP);  
  for(int i=9; i<=13; i++)
    pinMode(i, INPUT_PULLUP); 

  // Set clock pin as input
  pinMode(PIN_CLOCK, INPUT);
  
  // Set latch pin as input
  pinMode(PIN_LATCH, INPUT);
  
  // Set data pin as output
  pinMode(PIN_DATA, OUTPUT);
  digitalWrite(PIN_DATA, HIGH);

  // Disable timers and interrupts
  disableTimers();
  noInterrupts();
}


// ---------------------------------------------------------------
// Main loop
// ---------------------------------------------------------------
void loop() 
{
  // Wait for low latch pin (to start shifting out data)
  WaitLowLatch();

  // Blue button
  // Blue already set, just wait a clock cycle
  WaitLowHighClock();

  // Red button
  PORTB |= 1; // Set data pin HIGH
  WaitLowHighClock();

  // Yellow button
  PORTB |= 1;
  WaitLowHighClock();

  // Green button
  //PORTB &= ~1; // Trying this to set the data pin LOW will break the system????
  PORTB |= 1;
  WaitLowHighClock();

  // Right trigger
  PORTB |= 1;
  WaitLowHighClock();

  // Left trigger
  PORTB |= 1;
  WaitLowHighClock();

  // Pause button
  PORTB |= 1;
  WaitLowHighClock();

  // Next bit is always high (CD32 gamepad signature)
  PORTB |= 1;
  WaitLowHighClock();

  // Last bit is always low (CD32 gamepad signature)
  PORTB &= ~1;
  WaitLowHighClock();

  // "Reset" data pin to HIGH
  PORTB |= 1;

  // Wait for latch to rise to high state
  WaitHighLatch();

  // Read inputs etc. here
  // ----------------------------------
  regC = PINC;
  regB = PINB;
}

// ---------------------------------------------------------------
// Wait for latch pin to go low
// ---------------------------------------------------------------
void WaitLowLatch()
{
  while(PIND & BIT_LATCH) {}
}

// ---------------------------------------------------------------
// Wait for latch pin to go high
// ---------------------------------------------------------------
void WaitHighLatch()
{
  while(!(PIND & BIT_LATCH)) {}
}

// ---------------------------------------------------------------
// Wait for clock pin to go low
// ---------------------------------------------------------------
void WaitLowClock()
{
  while(PIND & BIT_CLOCK) {}
}

// ---------------------------------------------------------------
// Wait for clock pin to go high
// ---------------------------------------------------------------
void WaitHighClock()
{
  while(!(PIND & BIT_CLOCK)) {}
}

// ---------------------------------------------------------------
// Wait for clock pin to go low and then high
// ---------------------------------------------------------------
void WaitLowHighClock()
{
  WaitLowClock();
  WaitHighClock();
}

// ---------------------------------------------------------------
// Disable timers
// ---------------------------------------------------------------
void disableTimers() {
  TCCR0A = 0;
  TCCR0B = 0;
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR2A = 0;
  TCCR2B = 0;
}

I have a scetch set up now to read the latch and clock from :o :o :o :o :o

เสื้อวง
เสื้อวงร็อค

I haven't studied your code much. In theory, such a thing should work fine. But emulating a shift register seems like an awfully busy thing to do. Why not just hang a real shift register off your Arduino and let your sketch code be asynchronous of what the CD32 is doing? Or if you need to keep track of exactly how many gamepad reads are happening--perhaps you're doing TAS stuff--then you probably only need to count the load strobes. In either case, an external shift register seems more flexible.

Thanks for your input! My initial thought was to have a shift register asynchronous to the atmega but it would be nice to be able to emulate the whole circuit (and add features) with only an atmega processor. There are two logic chips in a CD32 gamepad (74LS165 and 74LS125), I'm not sure how much current those three chips would use but some Amigas can only supply up to 50mA per joystick port, should probably be enough. I have successfully replicated the functionality with logic chips but when I tried the same with smd components it didn't work, I have no idea why...

I did something similar a few years back with a PlayStation 3 controller. Although I didn't bother replicating the controller hardware. Instead, I just gutted a controller and soldered wires to the button contacts. I'm lazy. Worked great, though.

Have you tried other patterns of dummy data to see if the behavior changes? I'm wondering if there might be some 'stuck button' logic kicking in when you tell the thing that all the buttons are high or low. Or what exactly does "haywire" mean? It also couldn't hurt to test your virtual shift register separate from the CD32. Got another Arduino? :slight_smile:

My like of the 'real shift register' approach is that your MCU won't be so tied up fielding shift clocks and can make a new data byte available lazily. All of that extra time might be used to support scripting the controls, receiving commands over USB, etc.

I managed to get further by using external interrupts for the clock and latch pins. I use a counter for the clock pulses (volatile variable) and set the output to the button status of the current button (CD32 signature at the end) on clock falling edge (I also tried to do it on rising edge but this caused problems).

On latch rising edge I reset the counter.

This seems to be stable and now I should be able to have as much logic I want running in the main loop since the interrupts will trigger when needed.

jaholmes:
Have you tried other patterns of dummy data to see if the behavior changes? I'm wondering if there might be some 'stuck button' logic kicking in when you tell the thing that all the buttons are high or low. Or what exactly does "haywire" mean? It also couldn't hurt to test your virtual shift register separate from the CD32. Got another Arduino? :slight_smile:

When using haywire I was referring to the oscilloscope output, it was just flickering. If the signature bits are not detected, the Amiga "falls back" to normal joystick mode. I guess the code I used caused some timing issues.