Teensy LC SPI::transfer16

christoph

Well-known member
As far as I can tell, the SPI::transfer16 method needs to be fixed. It crashes because (it seems) SPI_S has to be read before attempting to transfer data. Also reported here: https://forum.pjrc.com/threads/27689-Teensy-LC-Beta-Testing?p=64209&viewfull=1#post64209

The method's code was:
Code:
inline static uint8_t transfer16(uint16_t data) {
  SPI0_C2 = SPI_C2_SPIMODE;
  SPI0_DH = data >> 8;
  SPI0_DL = data;
  while (!(SPI0_S & SPI_S_SPRF)) ; // wait
  uint16_t r = (SPI0_DH << 8) | SPI0_DL;
  SPI0_C2 = 0;
  return r;
}

This fixed it:
Code:
inline static uint16_t transfer16(uint16_t data) {
  SPI0_C2 = SPI_C2_SPIMODE;
  while (!(SPI0_S & SPI_S_SPTEF)) ; // wait for Tx empty
  *((uint16_t*)&KINETISL_SPI0.DL) = data;
  while (!(SPI0_S & SPI_S_SPRF)) ; // wait for Rx not empty
  uint16_t r = *((uint16_t*)&KINETISL_SPI0.DL);
  SPI0_C2 = 0;
  return r;
}
Remarks:
  • the code posted by Frank B (simply reading SPI0_S) didn't work for me, I had to keep the first loop checking for SPTEF;
  • writes to both DL:DH at once (this is not mentioned in the datasheet, so I just tried it because I need that for 16-bit DmaSpi);
  • my version returns a uint16_t.
 
Makes sense: From the PDF:
The SPTEF bit in the S register indicates when the transmit data buffer is ready to accept
new data. When the transmit DMA request is disabled (TXDMAE is 0): The S register
must be read when S[SPTEF] is set before writing to the SPI data registers; otherwise, the
write is ignored.

Does it crash or does it just hang? If the write failed, then there won't be anything to read...
 
I'm not sure if it was a crash or a hang, but is that really important? Either way a solution is needed and Frank has pointed out one.

Is the proposed solution good enough for now or does it leave something to be desired? If it is sufficient, I'll create a pull request.
 
It is probably something for Paul to say. It would work, not sure if the smallest or fastest.

For example, it may be sufficient just to read the register.
Code:
inline static uint16_t transfer16(uint16_t data) {
  SPI0_C2 = SPI_C2_SPIMODE;
  uint8_t tmp __attribute__((unused)) = SPI0_S;
  SPI0_DH = data >> 8;
  SPI0_DL = data;
  while (!(SPI0_S & SPI_S_SPRF)) ; // wait
  uint16_t r = (SPI0_DH << 8) | SPI0_DL;
  SPI0_C2 = 0;
  return r;
}
Note: I have not tried this, but the only change from current code should be the dummy read of ths SPIO_S register.
Changing the data size in the previous statement clears any active transfer and clears state, so should not have to worry about empty...

As for casting the castings the words to fit into DH and DL, that is something that Paul needs to say Yeah or Nah

Kurt
 
For me the dummy read didn't work as expected and caused (at least) a hang. The combined 16-bit read or write should be faster than individual 8-bit accesses, which is what Paul aims for. It might be bad style because this way of manipulating the DL::DH regs is not documented.
 
Last edited:
Back
Top