adafruit / ArduinoCore-samd

116 stars 119 forks source link

SERCOM::initSPI() Prevents Hardware Control of SPI SS Signal by Clearing SERCOM_SPI_CTRLB_MSSEN #283

Open Rymar-Eng opened 3 years ago

Rymar-Eng commented 3 years ago

In cores/SERCOM.cpp, the SPI init function SERCOM::initSPI completely overwrites the value of the CTRLB register:

...
  //Setting the CTRLB register
  sercom->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE(charSize) |
                          SERCOM_SPI_CTRLB_RXEN; //Active the SPI receiver.
...

In order to support hardware control of the SPI SS output, the MSSEN bit must be set in the CTRLB register. For example, on the Grand Central M4 board (SAMD51), pin 53 ('SS' signal) will be driven as a hardware controlled SS pin if the MSSEN bit is set in the corresponding SERCOM controller, in this case SERCOM7.

SERCOM7->SPI.CTRLB.reg|= SERCOM_SPI_CTRLB_MSSEN`

However, each call to SERCOM::InitSPI() as a result of the call to SPI.beginTransaction() clears the MSSEN bit, and leaves pin 53 disconnected.

One method (perhaps not the best one), is to preserve the existing state in the register. This must be done before the call to resetSPI(), which clears all the internal register, including CTRLB.

The completed function looks like this

void SERCOM::initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize charSize, SercomDataOrder dataOrder)
{
  uint32_t ctrlb_image = sercom->SPI.CTRLB.reg;
  resetSPI();
  initClockNVIC();

#if defined(__SAMD51__)
  sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE(0x3)  |  // master mode
                          SERCOM_SPI_CTRLA_DOPO(mosi) |
                          SERCOM_SPI_CTRLA_DIPO(miso) |
                          dataOrder << SERCOM_SPI_CTRLA_DORD_Pos;
#else
  //Setting the CTRLA register
  sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE_SPI_MASTER |
                          SERCOM_SPI_CTRLA_DOPO(mosi) |
                          SERCOM_SPI_CTRLA_DIPO(miso) |
                          dataOrder << SERCOM_SPI_CTRLA_DORD_Pos;
#endif

  //Setting the CTRLB register
  sercom->SPI.CTRLB.reg = (ctrlb_image & SERCOM_SPI_CTRLB_MSSEN) |
                          SERCOM_SPI_CTRLB_CHSIZE(charSize) |
                          SERCOM_SPI_CTRLB_RXEN; //Active the SPI receiver.

  while( sercom->SPI.SYNCBUSY.bit.CTRLB == 1 );
}

An explicit parameter could also be used to pass in the value in, but would affect higher layers of the SPI interface.

ladyada commented 3 years ago

well, the arduino API in general does not use the hardware SS pin control, it does SS with software!

Rymar-Eng commented 3 years ago

Yes, after looking at this in even more detail, there are a number of reasons why the hardware SS control in the SAMD architecture is, ummm..., sadly lacking: 1) SS cannot be controlled to stay low between bytes. 2) In the SAMD51P20, there is only one hardware SS control available for each SERCOM-based SPI

What's surprising is that in the SAM architecture (Arduino Due), multiple hardware SS controls were available, and the Arduino SPI API was "enhanced" to provide SPI_CONTINUE and SPI_LAST controls, so that SS could remain low between bytes. Then the SAMD architecture came out, and seemed to take a step backwards.

So, how does SPI DMA work? I can't find info on SS control when DMA is used. If using software SS control, then it must just stay low (active) for the entire DMA transfer?

ladyada commented 3 years ago

correct, you can use a callback