Trouble operating the Due in SPI slave mode (using low level API)

So I'm attempting to use my Arduino Due to act as a PS2 controller, and to start with I broke out an extension cable and have the Arduino listening in to the SPI host commands (a PS2 to USB adaptor for initial testing). Chip select, clock and data lines are all connected per the reference materials I found (e.g. Interfacing a PS2 (PlayStation 2) Controller - Curious Inventor), and all registers appear configured properly per the SAM3X/A specification document via the libsam functions/globals. However, the data I'm getting is kinda garbage and I'm unsure what else to try next.

Here's my code:

uint32_t databuf[10][64];
int recordlen[10];
int count = 0;
int record = 0;

int modefault = 0;
int overrun = 0;

void setup() {
    // Configure the pins for SPI in peripheral mode
    PIO_Configure(
        g_APinDescription[PIN_SPI_SS0].pPort,
        g_APinDescription[PIN_SPI_SS0].ulPinType,
        g_APinDescription[PIN_SPI_SS0].ulPin,
        g_APinDescription[PIN_SPI_SS0].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PIN_SPI_MOSI].pPort,
        g_APinDescription[PIN_SPI_MOSI].ulPinType,
        g_APinDescription[PIN_SPI_MOSI].ulPin,
        g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PIN_SPI_MISO].pPort,
        g_APinDescription[PIN_SPI_MISO].ulPinType,
        g_APinDescription[PIN_SPI_MISO].ulPin,
        g_APinDescription[PIN_SPI_MISO].ulPinConfiguration);
    PIO_Configure(
        g_APinDescription[PIN_SPI_SCK].pPort,
        g_APinDescription[PIN_SPI_SCK].ulPinType,
        g_APinDescription[PIN_SPI_SCK].ulPin,
        g_APinDescription[PIN_SPI_SCK].ulPinConfiguration);
    
    SPI_Configure(SPI0, ID_SPI0, SPI_MR_PS | SPI_MR_MODFDIS);
    SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_NCPHA);
    //SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_CPOL);
    //SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_CPOL | SPI_CSR_NCPHA);
    SPI_Enable(SPI0);
    
    Serial.begin(57600);
    printf("Start\r\n");
}

void loop() {
    uint32_t StatusReg = SPI0->SPI_SR;
    
    // Count mode faults and overruns
    if (StatusReg & SPI_SR_MODF)
        modefault++;
    
    if (StatusReg & SPI_SR_OVRES)
        overrun++;
    
    // Rising edge on slave select (e.g. new message!)
    if (StatusReg & SPI_SR_NSSR)
    {
        recordlen[record] = count;

        count = 0;
        record++;
    }

    // If we have recorded 10 messages, print them.
    if (record >= 10)
    {
        for (int j=0; j<10; j++)
        {
            printf("[%d]:", recordlen[j]);
            for (int i=0; i<recordlen[j]; i++)
            {
                printf("%02X ", databuf[j][i]);
            }
            printf("\r\n");
        }
        printf("Mode Fault: %d; Overrun: %d\r\n", modefault, overrun);
        record = 0;
        count = 0;
        modefault = 0;
        overrun = 0;
    }
    
    if (count < 64)
    {
        // Receive data is available. Log it.
        if (StatusReg & SPI_SR_RDRF)
        {
            uint32_t readval = SPI0->SPI_RDR & 0xFFFF;
        
            databuf[record][count] = readval;
            count++;
        }
    }
}

Sample output:

[0]:
[21]:00 0C 00 00 0C 06 00 00 60 00 03 00 00 00 00 00 00 F0 63 1E 01 
[19]:8F 00 01 9E 0C 00 00 00 00 60 00 07 FF FF FF FF FF FF FF 
[19]:FE 00 07 F8 30 00 01 FF FF FF FF F0 00 00 00 00 00 00 00 
[19]:01 80 00 3C 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[43]:01 80 00 60 06 00 00 00 00 00 00 7F FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:FF 00 00 C0 18 00 00 00 00 00 01 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:E0 00 0C 00 C0 00 00 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:FC 00 03 00 60 00 00 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:FC 00 01 80 30 00 00 00 00 00 01 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
Mode Fault: 0; Overrun: 1
[0]:
[45]:FF FE 00 00 60 0C 00 00 00 00 00 00 07 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:C0 00 06 00 C0 00 00 00 00 00 00 1F FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[44]:FE 00 00 30 06 00 00 00 00 00 00 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:C0 00 0C 01 80 00 00 00 00 00 01 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:FC 00 00 C0 0C 00 00 00 00 00 00 07 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[43]:FF 80 00 30 0C 00 00 00 00 00 00 1F FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[44]:FC 00 00 C0 18 00 00 00 00 00 00 07 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[42]:C0 00 18 06 00 00 00 00 00 00 1F FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
[44]:FE 00 00 60 18 00 00 00 00 00 00 07 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
Mode Fault: 0; Overrun: 1

And some sample output of what it SHOULD be giving me per some SPI captures I downloaded from Procrastineering - Project blog for Johnny Chung Lee: Simulated PS2 Controller for Autonomously playing Guitar Hero

01 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Or at least something with the same structure (01 followed by 4-something).

Here's a photo of my setup:

I also had the thought that I could be running into EMI issues, but the controller itself is still fully functional through the breakout interface, so this seems unlikely.

Try this (as a test):

uint32_t databuf[10][64];
int recordlen[10];
int count = 0;
int record = 0;

int modefault = 0;
int overrun = 0;

#include <SPI.h>

void setup() {
  // Configure the pins for SPI in peripheral mode

  /*    PIO_Configure(
          g_APinDescription[PIN_SPI_SS0].pPort,
          g_APinDescription[PIN_SPI_SS0].ulPinType,
          g_APinDescription[PIN_SPI_SS0].ulPin,
          g_APinDescription[PIN_SPI_SS0].ulPinConfiguration);
      PIO_Configure(
          g_APinDescription[PIN_SPI_MOSI].pPort,
          g_APinDescription[PIN_SPI_MOSI].ulPinType,
          g_APinDescription[PIN_SPI_MOSI].ulPin,
          g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration);
      PIO_Configure(
          g_APinDescription[PIN_SPI_MISO].pPort,
          g_APinDescription[PIN_SPI_MISO].ulPinType,
          g_APinDescription[PIN_SPI_MISO].ulPin,
          g_APinDescription[PIN_SPI_MISO].ulPinConfiguration);
      PIO_Configure(
          g_APinDescription[PIN_SPI_SCK].pPort,
          g_APinDescription[PIN_SPI_SCK].ulPinType,
          g_APinDescription[PIN_SPI_SCK].ulPin,
          g_APinDescription[PIN_SPI_SCK].ulPinConfiguration);

      SPI_Configure(SPI0, ID_SPI0, SPI_MR_PS | SPI_MR_MODFDIS);
      SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_NCPHA);
      //SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_CPOL);
      //SPI_ConfigureNPCS(SPI0, 0, SPI_CSR_CPOL | SPI_CSR_NCPHA);
      SPI_Enable(SPI0);  */

 SPI.begin(10);
 SPI.setDataMode(10, SPI_MODE0);
 REG_SPI0_CR |= 0x1;                   // SPI enable (write only)
 REG_SPI0_WPMR = 0x53504900;           // Write Protection disable
 REG_SPI0_MR = 0x2;                    // DLYBCS=0, PCS=0, PS=1, MSTR=0
 REG_SPI0_CSR = REG_SPI0_CSR & 0x3;    // DLYBCT=0, DLYBS=0, SCBR=0, 8 bit transfer
 
  Serial.begin(57600);
  printf("Start\r\n");
}

void loop() {
  uint32_t StatusReg = SPI0->SPI_SR;

  // Count mode faults and overruns
  if (StatusReg & SPI_SR_MODF)
    modefault++;

  if (StatusReg & SPI_SR_OVRES)
    overrun++;

  // Rising edge on slave select (e.g. new message!)
  if (StatusReg & SPI_SR_NSSR)
  {
    recordlen[record] = count;

    count = 0;
    record++;
  }

  // If we have recorded 10 messages, print them.
  if (record >= 10)
  {
    for (int j = 0; j < 10; j++)
    {
      printf("[%d]:", recordlen[j]);
      for (int i = 0; i < recordlen[j]; i++)
      {
        printf("%02X ", databuf[j][i]);
      }
      printf("\r\n");
    }
    printf("Mode Fault: %d; Overrun: %d\r\n", modefault, overrun);
    record = 0;
    count = 0;
    modefault = 0;
    overrun = 0;
  }

  if (count < 64)
  {
    // Receive data is available. Log it.
    if (StatusReg & SPI_SR_RDRF)
    {
      uint32_t readval = SPI0->SPI_RDR & 0xFFFF;

      databuf[record][count] = readval;
      count++;
    }
  }
}

This is SPI slave mode initialization.
Try to add this code in SPI.cpp and SPI.h

void SPIClass::init() {
if (initialized)
return;
initCb();
SPI_Configure(spi, id, SPI_MR_MSTR | SPI_MR_PS | SPI_MR_MODFDIS| SPI_MR_WDRBT);
SPI_Enable(spi);
spi->SPI_IER =  (SPI_IER_NSSR | SPI_IER_TDRE | SPI_IER_RDRF);
initialized = true;
}


//////addtionnal slave mode for initialization
void SPIClass::initSlave() {
if (initialized)
return;
initCb();
SPI_Configure(spi, id, 0);
SPI_Enable(spi);
////////////////////////////////////////////////////////////////////
//Write protection
spi->SPI_WPMR = 0;
//Control Register
spi->SPI_CR = (SPI_CR_LASTXFER | SPI_CR_SPIEN);

//Mode Register
int val SPI_PCS(0);//0(10) 1(4) 2(52)
spi->SPI_MR = (SPI_MR_PCS(val) | SPI_MR_MODFDIS | SPI_MR_PS | SPI_MR_WDRBT);

//Trasmit Data Register
spi->SPI_TDR = (SPI_TDR_PCS(val) | SPI_CR_LASTXFER);

//Interrupt Enable Register
spi->SPI_IER = ( SPI_IER_RDRF);

//Chip Select Register
spi->SPI_CSR[0]= (SPI_CSR_DLYBCT(DLYBCT) | SPI_CSR_SCBR(SCBR)|
 SPI_CSR_CSAAT | SPI_CSR_NCPHA|SPI_CSR_BITS_16_BIT);
///////////////////////////////////////////////////////////////////
spi->SPI_WPMR = 0;
spi->SPI_CR = (SPI_CR_LASTXFER | SPI_CR_SPIEN);
spi->SPI_CSR[0]= (SPI_CSR_DLYBCT(DLYBCT) | SPI_CSR_SCBR(SCBR)| SPI_CSR_CSAAT | SPI_CSR_NCPHA|SPI_CSR_BITS_16_BIT);
initialized = true;
}

SPI_Slave_init.cpp (1.22 KB)

Thanks for the help guys, but I get the same behaviour with both your alternate initializations as my original. At least I'm not doing anything weird code-wise :).

Hirofumi, note that your code also requires additional #defines for SCBR and DLYBCT, the way it is currently written. It also programs the chip-select twice, and forces it into 16-bit mode. I'll consider whether that works better overall, but right now I'm operating in 8-bit mode :slight_smile:

I also tried adding some pull-up resistors (~10K) to the clock, data and chip select lines without any benefit.

Took a closer look at the picture.

CS-----green wire
SCK----red wire
MOSI---blue wire

Where's the GND connection to your interface on the breadboard?

Right now, ground isn't connected, though in retrospect that does appear to be an obvious problem. Does the Arduino have a pin that can be used for 'ground input?' for SPI purposes? I was under the impression that most GND pins were reference outputs, except perhaps if powering the board from Vin (which I am not presently). If I was to link up GND lines, which pin on the Arduino should I be using?

If I was to link up GND lines, which pin on the Arduino should I be using?

It doesn't really matter, however there is a convenient GND terminal on the six pin SPI connector. It's covered by the blue wire ... check the Due Pinout Diagram for reference.

Also, note that the Due is limited to 3.3V max on its I/O.

I'm assuming there's some slight tolerance on that, as the PS2 controller is roughly 3.6 V. Or should I be setting up a voltage divider to scale it down exactly?

3.6v is unlikely to be a problem. If the 3.6v device has some crazy low impedance, a series resistor of 100 ohms or so will ensure it can't overload the Due input.