1
\$\begingroup\$

I'm trying to use an ATmega328 to interface with a NES controller, but I'm running into some issues.

The ATmega328 is programmed to pulse the latch pin, wired to the 4021's P/S pin, high for ~10 μs, then alternating between reading and storing the data pin (Q7 on 4021) and pulsing the clock pin. However, the ATmega328 only reads 0 for every shift, no matter what buttons are pressed on the controller.

Testing the controller using a breadboard (No ATmega328), some things I found were:

  • When latch is high, data (Q7) reads the same as P7 (A button, high when released, low when pressed), and data sustains its value when latch is low. This is as expected, however, when latch is low and data is high, pulling P7 low pulls data low, but pulling P7 high does not pull data high, it instead stays low.
  • When the clock has a rising edge, instead of shifting data to the next bit, data instead matches P7 like before, and the same applies where pulling P7 low pulls data low, but not back up.
  • For any test I did, only P7 seemed to have any effect, and P0-6 did not seem to do anything at anytime.

The controller I am using is tested with and fully work with original hardware with all buttons.

Program that the ATmega328 is using:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

char unsigned buff[64];
uint8_t buffAvailable;
uint8_t buffIndex;
uint8_t buttonData;
uint8_t shiftRegister;

int main(void){

  DDRD = 0b00001100;

  sei();
  UBRR0L = (uint8_t)(1 & 0xFF);
  UBRR0H = (uint8_t)(1 >> 8);
  UCSR0B |= (1 << RXEN0) | (1 << TXEN0) | (1<<RXCIE0);
  
  sendByte(0x55);
  sendByte(0xAA);
  sendByte(0x88);
  while (1) {

    buttonData = 0x00;
    shiftRegister = 0x00;

    PORTD |= 0b00001000;
    _delay_us(10);
    PORTD &= 0b11110111;

    buttonData |= (PIND & 0x10) >> 1;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) >> 2;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) >> 4;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) >> 3;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10);
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) << 1;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) << 2;
    shiftRegister |= (PIND & 0x10) >> 4;
    shiftRegister <<= 1;
    PORTD |= 0b00000100;
    PORTD &= 0b11111011;

    buttonData |= (PIND & 0x10) << 3;
    shiftRegister |= (PIND & 0x10) >> 4;
    
    buttonData = ~buttonData;

    //buttonData = PIND;

    //sendByte(0xab);
    while (buffAvailable > 0) {
      char unsigned data = readByte();
      switch(data) {
        case 0x00:
          sendByte(buttonData & 0xFC);
          break;
        case 0x01:
          sendByte(0x00);
          break;
        case 0x02:
          sendByte((buttonData & 0x03) << 3);
          break;
        case 0x03:
          sendByte(buttonData & 0x80 ? (buttonData & 0x40 ? 0x80 : 0xFF) : (buttonData & 0x40 ? 0x00 : 0x80));
          break;
        case 0x04:
          sendByte(buttonData & 0x20 ? (buttonData & 0x10 ? 0x80 : 0x00) : (buttonData & 0x10 ? 0xFF : 0x80));
          break;
        case 0x05:
          sendByte(0x80);
          break;
        case 0x06:
          sendByte(0x80);
          break;
        case 0x07:
          sendByte(0x00);
          break;
        case 0x08:
          sendByte(0x00);
          break;
        case 0x40:
          sendByte(0x06);
          break;
        case 0xDE:
          sendByte(buttonData);
          sendByte(shiftRegister);
          break;
        case 0xFF:
          sendByte(0x88);
          break;
        default:
          sendByte(0x00);
          break;
      }
    }
  }
  return(0);
}

ISR(USART_RX_vect) {
  uint8_t b = UDR0;
  uint8_t ind = (char*)(buffIndex+buffAvailable);
  while (ind >= 0x40) ind -= 0x40;
  buff[ind] = b;
  buffAvailable++;
}

char readByte() {
  char unsigned b = buff[buffIndex];
  if (buffIndex == 0x3F) {
    buffIndex = 0x00;
  } else {
    buffIndex++;
  }
  buffAvailable--;
  return b;
}

void sendByte(char b) {
  while(!(UCSR0A & (1 << UDRE0)));
  UDR0 = b;
}

Using a serial monitor to send 0xDE to get shift register data

Schematic used:

schematic

simulate this circuit – Schematic created using CircuitLab

What could I be doing wrong?

\$\endgroup\$
7
  • \$\begingroup\$ Are you sure the communication between the arduino and computer is working? Can you get it to send messages on demand? \$\endgroup\$
    – Hearth
    Commented Oct 30, 2022 at 19:27
  • \$\begingroup\$ How do you make sure the clock pulse is not too fast? The AVR runs at 16 MHz and toggling an IO pin does not take many clock cycles. The CD4021 can only handle a 3 MHz clock signal. \$\endgroup\$
    – Justme
    Commented Oct 30, 2022 at 19:43
  • \$\begingroup\$ The communication between the ATMega and computer is confirmed to be working. The test commands that don't use the controller send just fine \$\endgroup\$
    – SomeGuy
    Commented Oct 30, 2022 at 19:48
  • 1
    \$\begingroup\$ I'll increase the pulse duration to see if that does anything \$\endgroup\$
    – SomeGuy
    Commented Oct 30, 2022 at 19:49
  • \$\begingroup\$ I added 3us delays after every write to clock, but I still just 0s for every bit. \$\endgroup\$
    – SomeGuy
    Commented Oct 30, 2022 at 20:14

2 Answers 2

2
\$\begingroup\$

CD4021 is a synchronous shift register. It requires a clock for both serial shifts and parallel loads. You're not clocking the load.

To latch parallel data:

  1. Set PAR/#SER to 1 (par).
  2. Toggle the clock (0, then 1) to latch.

To shift serial data:

  1. Set PAR/#SER to 0 (ser).
  2. Read Q7 and shift into the variable.
  3. Toggle the clock (0, then 1).
  4. Repeat 2-3 a total of 8 times.

Readable C code for that might look as follows:

#include <avr/cpufunc.h>

static __attribute__((noinline)) void SR_DELAY(void) { _NOP(); _NOP(); _NOP(); _NOP(); }
#define SR_PAR() do { PORTD |= 0x80; SR_DELAY(); } while (0)
#define SR_SER() do { PORTD &= ~0x80; SR_DELAY(); } while (0)
#define SR_CLK_PULSE() do { PORTD &= ~0x40; SR_DELAY(); PORTD |= 0x40; SR_DELAY(); } while (0)
#define SR_BIT() (PIND & 0x10)

uint8_t read_controller() {
  SR_PAR();
  SR_CLK_PULSE();
  SR_SER();
  uint8_t result = 0;
  for (uint8_t i = 0; i < 8; ++i) {
    if (SR_BIT()) result |= 1;
    result <<= 1;
    SR_CLK_PULSE();
  }
  return result;
}
```
\$\endgroup\$
-1
\$\begingroup\$

I believe you have a non functional 4021 chip.

My theory is internally the CLK is not working properly. This is why P7 (aka 'A' button) is the only thing that works. When Latch is brought low that put it in serial out mode. And P7 is essentially put on the output pin.

Internally the CLK pulses aren't shifting the D flip flops and such and hence you continue to see what the output of P7 is.

(I stumbled upon this because i'm having the same exact problem with my NES controller and I spent a couple hours probing around and it's the conclusion i came to. Cause i've ruled out shorts and it makes sense.)

\$\endgroup\$
2
  • \$\begingroup\$ A fully functional 4021 does nothing if you just toggle the state of the P/S pin. That's exactly what the OP has observed. So, we don't know whether the 4021 is functional or not: toggling P/S is not enough. \$\endgroup\$ Commented Dec 29, 2022 at 22:29
  • \$\begingroup\$ @Kubahasn'tforgottenMonica - his code appears to toggling the CLK Pin as well. Whether it helps the Op or not. I have observed this with a DLA on an NES Controller having a 4021. Seems inappropriate to be given a -1. \$\endgroup\$
    – blak3r
    Commented Dec 31, 2022 at 3:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.