Tuesday, July 26, 2022

Itsy bitsy Teensy trouble - with Slave SPI

This is another one of those posts mostly meant for myself.

I've been hitting my head against the wall for four days, trying to get Slave SPI working on a Teensy 4.0, with a Teensy 4.1 as the master.

I started out with the https://github.com/tonton81/SPISlave_T4 library (and https://forum.pjrc.com/threads/66389-SPISlave_T4) , but could not make it work, so I stripped it down to the minimum. After a while (after removing a while-loop from the ISR) I got some response. However, it was not stable and it seemed like it would only receive every second byte.

After a lot more troubleshooting I got it working on every transfer, but only if writing 0 to the TX buffer. Any other values would give different troubles.

In the end, it turned out to most likely be a combination of things - mixing up MOSI and MISO pins as well as wrong impedance settings on the pins. Removing and attaching probes to the pins also made stuff fail.

Anyway, next time I would check the following:

- Make sure CFG1 is set to full duplex (00 or 11) 

- Check crossed vs straight connections between MISO and MOSI, matching CFG1

- Connect/disconnect logic probes on MISO/MOSI (works when bytes go missing)

- Make sure master deasserts PCS between frames, I wrote 16 bits without rising the PCS (or rather, rising for a too short time) between blocks of 8 bits when frame size was 8 bit.

- Make sure master speed is > 1MHz

I have yet to find a completely stable solution and I am worried that connecting multiple slaves to the same master may not work as intended. 

Learned

Anyway, here are some of the things I learned:

- The minimum SPI speed on the Teensy is 1MHz (without doing special stuff)

- To set SPI in slave mode: set bit 1 in CFG1 to 0 and update Clock gating register for the appropriate LPSPI, for example CCM_CCGR1 |= (3UL << 6); (lpspi4_clk_enable)

- When CFG1 bits 25-24 are 00, MOSI on the master connects to MISO on the slave and vice versa.

- When CFG1 bits 25-24 are 11, MOSI on the master connects to MOSI on the slave (and MISO to MISO).

- When communicating between two Teensy 4.x, the pin impedance must be changed on both slave and master. For example: 

IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE;

- Even when changing the impedance, I needed to experiment with connecting/disconnecting the logic probes.

- TX buffer empty flag is set immediately after the last TXFIFO byte is shifted out, at the same time an RX interrupt is triggered (if RXWATER is 0).

- The SPI documentation talks about different size descriptions:

    - Transfer - when sending data - Transfer complete IR is triggered when all data in TXFIFO has been sent (FIFO is empty)

    - Word - Up to 32bits, 0-padded if less than 32 bits received. FIFOs are 16 words long.

    - Frame - Frame complete IR is triggered when PCS (chip select) goes high (deasserts). A Frame may be from 8bits long, length must be divisible by 2 and may be longer than 32bits (span multiple words). If word length is reached, the frame is split across multiple words in the RXFIFO.

- TDF and RDF - Transmit and Receive Data flags are set by the system and reset by reading from/writing to the TX and RXFIFOs, not by clearing interrupt flags.

- The SPI slave example sets IER bit 1 which is Transmit Data Interrupt. It will be triggered when the number of bytes in the TXFIFO is less than TXWATER. With TXWATER 0 this will be triggered at the same time a byte is received. I don't understand why this is chosen instead of for example FCF.

- The SPI slave library, and also the more elaborate example at https://github.com/tonton81/SPI_MSTransfer_T4, uses a lot of looping and waiting inside the ISR, which cannot be a good way of doing things...

- With an 8bit frame and a single byte in the TX buffer, the following are set on RX: Frame completed, Word completed, RX data ready, TX data ready. RXFIFO in FSR is incremented. If another byte is received before a new byte is written to TX, we get a TX underrun.

Symptoms

Here are some of the symptoms I experienced:

- Missing or erroneous slave sends when anything but 0 was written to TDR - i suspect that we tried writing and reading from the same pins and that messed up stuff, though not entirely sure WHAT was going on.

- TX buffer underruns even if I wrote to the output buffer every time a byte was received

- Elements in the TX buffer disappeared (even if length was correct) as verified by logic probes. 

- When writing the same bytes over and over to TX, the same bytes disappeared every time.

- Swapping master and slave made DIFFERENT bytes disappear.

- Missing RX, some of the received bytes never ended up in the RXFIFO

- All receives worked when I only wrote 0 to TX buffer.

- It looked like I needed to receive 16bits and/or only every second byte was received.

- When unplugging either the MISO or MOSI cable, sends started working perfectly

- Removing probes sometimes made stuff work - but only after powercycling the Teensys.



Test code

My slave test code can be found at

https://github.com/xonik/teensy-spislave-test

Master code is

#include <SPI.h>

SPISettings settings(10000000, MSBFIRST, SPI_MODE0);


void setup() {

  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; 

  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_02 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; 

  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_03 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; 

  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; 


  Serial.begin(1000000);

  pinMode (10, OUTPUT);

  digitalWriteFast(10,HIGH);

  Serial.println("SPI Starting");


  SPI.begin();   

  SPI.beginTransaction(settings);  

}


void loop() {

  digitalWriteFast(1,HIGH);

  digitalWriteFast(1,LOW); 

  delay(500);


  digitalWriteFast(10,LOW);

  Serial.println(SPI.transfer(0xAA));

  digitalWriteFast(10,HIGH);  

}


No comments:

Post a Comment