Usando un Arduino para emular un registro de desplazamiento CD4021 para controlar un NES [duplicado]

Esto es probablemente una tontería cuando puedo esperar a que lleguen mis CD4021 (74HC165N) y ahorrarme la molestia, pero siento que esto se puede hacer y simplemente me falta algo. He estudiado detenidamente toda la documentación que puedo encontrar en la web sobre el protocolo del controlador de NES y cómo emular un registro de desplazamiento de entrada en serie y salida en paralelo de 8 bits, pero parece que no puedo hacer que funcione correctamente. . Estoy usando interrupciones en el pestillo y las señales de reloj de la NES y bombeando los bits usando la manipulación del puerto, así que es un poco feo. Esta es también la primera vez que escribo algo similar. Puede que me esté equivocando en el momento.

¿Alguien puede decirme qué puedo estar haciendo mal? Aquí hay alguna documentación sobre el protocolo NES: http://www.mit.edu/~tarvizo/nes-controller.html , y el protocolo SNES: http://www.repairfaq.org/REPAIR/F_SNES.html (más detallado pero ligeramente diferente, no estoy seguro en cuál confiar).

Aquí está mi código tal como está ahora:

#include <Arduino.h>

#define plPin PIND
#define latchPin 2
#define latchShift 2
#define dataPort PORTB
#define dataPin 8
#define dataShift 0
#define dataMask (B00000001 << dataShift)

byte origButtons = B11111111; // Set each bit low (0) to push button.
volatile byte buttons;
volatile byte bit_num = 0;

void setup() {
  pinMode(dataPin, OUTPUT);
  pinMode(latchPin, INPUT);
  dataPort = (dataPort & ~dataMask) | (B00000000 << dataShift);
  attachInterrupt(0, latch, CHANGE);
  attachInterrupt(1, pulse, RISING);
  delay(1);
}

void loop() {

}

void latch() {
  // Check the state of the latch pin to see if we're rising (1) or falling (0).
  if ((plPin & (1 << latchShift)) != 0) {
    // Start pulled high.
    dataPort = (dataPort & ~dataMask) | (B00000001 << dataShift);
  } else {
    // Pull low if A button is pressed at fall of latch pulse.
    dataPort = (dataPort & ~dataMask) | ((buttons >> bit_num) & B00000001) << dataShift;
    bit_num++;
  }
}

void pulse() {
  // On the rising edge of the clock pulse, check for the state of the next button.
  // If the button is pressed, pull (or stay) low. Otherwise, pull high.
  //
  // Bit Math
  // --------------
  // Example values:
  // dataPort: 11111111
  // buttons: 11001100 (1 = HIGH/not pressed - 0 = LOW/pressed)
  // bit_num: 3
  // Reverse the dataMask (result: 11111011).
  // AND that result to the current value of dataPort (result: 11111011).
  // Shift buttons to the right by bit_num (result: 00011001).
  // AND 00000001 to that result so the least significant digit is the value of the current button (result: 00000001).
  // Shift that result by the data pin's position (result: 00000100).
  // OR that with the result from ANDing the reverse of the data mask and the current value of dataPort - see 4 lines up (result: 11111111).
  // Final result: button 3 not pressed.
  if (bit_num == 7) {
    // On the last pulse in a sequence, spit out the last button, wait until it has been sampled by the CPU, and reset the pin to the default low.
    dataPort = (dataPort & ~dataMask) | ((buttons >> bit_num) & B00000001) << dataShift;
    delayMicroseconds(12);
    dataPort = (dataPort & ~dataMask) | (B00000000 << dataShift);
    bit_num = 0;
  } else {
    // Otherwise, just spit out the current button and increment the offset.
    dataPort = (dataPort & ~dataMask) | ((buttons >> bit_num) & B00000001) << dataShift;
    bit_num++;
  }
}
¿Por qué no usar el modo esclavo SPI, para que el hardware haga el trabajo pesado?
Lo había considerado, simplemente no podía entenderlo dada la documentación de Arduino. Tampoco pude encontrar un buen tutorial sobre cómo usarlo como esclavo que respondiera a los pulsos.
Sí... olvídate de las cosas de Arduino. Ir a la fuente .
aquí hay un artículo con un ejemplo de Arduino SPI-Slave. Donde usa Interrupciones. Desde el ISR de Latch, puede precargar el SPDR que haría que el esclavo se desplace y luego el ISR del SPI después del cambio podría precargar el siguiente SPDR.
mpflaga: Gracias por el consejo y el enlace al artículo. Sin embargo, mi problema es que Arduino estaría creando su propia señal de reloj SPI a medida que lee los datos. El problema es que necesito que lea los datos, bit a bit, utilizando la señal de reloj que le envía la NES. Así es como funciona el registro de desplazamiento en el controlador.
Nada en su código sugiere que esté usando SCK como salida.
Sobre el código anterior y qué tiene de malo. Ponga algunas huellas en él y camine lentamente a través de la secuencia. Honestamente, eso es mucha matemática para seguir en puertos discretos. La mayoría de los usuarios de Arduino simplifican las cosas con digitalRead y digitalWrite, que abstraen todo ese puerto y quitan las cosas de la vista. También puede simularlo y medirlo en 123circuits u otro simulador Arduino.
¿Por qué desplazas el 0 a la izquierda en un par de lugares?
en su código, veo que pinMode (dataPin, salida) pero nunca veo dataPin realmente emitido. Me pregunto si tiene dataPort y botones al revés. Donde parece que dataPort (portb) es la entrada paralela de 8 bits y los botones son el pestillo de la misma. pero parece que está escribiendo en dataPort, no desplazándolo hacia dataPin.
En la respuesta #1 de Gammons a la que se hace referencia anteriormente, hay dos ejemplos de Maestro y Esclavo correspondiente. SS y SCK del código posterior son entradas junto con MISO y MOSI, siendo SO y SI. Donde SCK y miSO imitarán a 74HC165N y 4021, como se desee. Tenga en cuenta que el estado predeterminado de todos los pines es ENTRADA. Y el ejemplo de Gammon discretamente (sin biblioteca) es el SPCR con solo habilitarlo para el ISR. Por lo tanto, se ingresan SS, SCK y MOSI. de modo que cuando SS es bajo, el valor de SPDR se desplazará cuando se cambie SCK.
mpflaga: Tiene que usar matemáticas de bits para ser lo suficientemente rápido como para reaccionar a las señales de la NES. Sin embargo, gracias por los sitios para simular cosas. dataPin es parte de PORTB. Simplemente no quería usar DDRB para configurar todos los pines cuando solo necesitaba configurar uno. Gracias por señalar el ejemplo que necesito. Es posible que también haya encontrado una manera de hacerlo usando este código basado en interrupciones, pero probaré ambos y descubriré cuál funciona.
Excepto que esto es para un registro de desplazamiento de 8 bits que usa una NES en lugar de uno de 16 bits que usa una SNES.

Respuestas (2)

Lo siguiente se compilará. Y debería hacer lo que el Slave SPI como se discutió anteriormente: Vale la pena señalar que en un UNO con 328, gran parte de los puertos de ancho de byte no tienen los 8 pines disponibles.

// Note that Port B has the SPI and Port D has the UART so start testing with C[0..5]
// where C[6,7] will likely be zero. Once working then later move to Port D which has
// all 8 bits, but without any debug on the uart.
void setup (void)
{
  pinMode(MISO, OUTPUT); // have to send on master in, *slave out*
  SPCR |= _BV(SPE);      // turn on SPI in slave mode
  SPCR |= _BV(SPIE);     // turn on interrupts
  attachInterrupt(0, latch, FALLING); //pin 2 on UNO, could be other modes...
  // may need to tweak the SPI mode to match chips. Something for later
}  // end of setup

void latch() {
  SPDR = PINC; // grab Port C input and place it for upcoming shift.
}

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  // ISR will occur after 8 bit shift out of prior contents of SPDR 
  // by Master SCLK with SS low
  SPDR = SPDR; // read moSI and write it out on next miSO for cascading.
}  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{
  // don't really need to do anything.
}  // end of loop

Lo hice aquí, pero para SNES (16 bits). Exactamente el mismo código funcionará para NES, ya que NES solo leerá los primeros 8 bits. ¿Alguien tiene un código para emular un registro de desplazamiento de entrada de 16 bits con un ATtiny2313?