Teensy 4 as SPI Slave

@KurtE

I took you sketch and broke it up into a Master and Slave sketch so I could test a few things. Right now its set up to go from SPI to SPI and of course it has our pin changes to it. What I was testing was sending more than 1 sample from the Master to the Slave.

On the Master I have:
Code:
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x, BIN) //Serial.println(x,BIN)
#define SAMPLES 1

#define SPIM SPI
#define CS_MASTER 10

volatile bool received_data = false;
DMAMEM static uint16_t rx_buffer[SAMPLES];
uint16_t loop_count = 0;

void setup() {
  while (!Serial);
  Serial.begin(115200);
  // I am going to use SPI1 as the master.
  SPIM.begin(); // initialize it... 
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SDI (MISO) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_02 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SDO (MOSI) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_03 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SCK (CLK) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 PCS0 (CS) */
  
  pinMode(CS_MASTER, OUTPUT);
  digitalWrite(CS_MASTER, HIGH);
  
}

void loop() {
  elapsedMicros emloop = 0;
  received_data = false;
  digitalWrite(CS_MASTER, LOW);
[COLOR="#FF0000"]  SPIM.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
  //uint16_t spi_returned_data  = SPIM.transfer16(loop_count);
  SPIM.transfer16(0x9254);
  SPIM.transfer16(0x06);
  SPIM.transfer16(0x01);
  SPIM.transfer16(0x02);
  SPIM.transfer16(0x03);
  SPIM.transfer16(0xD0D0);
  SPIM.endTransaction();[/COLOR]
  //while (!received_data && (emloop < 250000)) ;
  //Serial.printf("SPI Transfer: %x Return: %x delay: %d\n", loop_count, spi_returned_data,(uint32_t)emloop);
  digitalWrite(CS_MASTER, HIGH);
  delay(250);
  loop_count++;
}
and the slave sketch I have:
Code:
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x, BIN) //Serial.println(x,BIN)
#define SAMPLES 6

#define SPIS SPI
#define ChipSelectSlave 10
IMXRT_LPSPI_t *spis_regs = &IMXRT_LPSPI4_S;
#define SPIS_DMAMUX_SOURCE_RX DMAMUX_SOURCE_LPSPI4_RX

volatile bool received_data = false;
DMAMEM static uint16_t rx_buffer[SAMPLES];
uint16_t loop_count = 0;

DMAChannel rx(false);

void rxISR() {
  rx.clearInterrupt();
  received_data = true;
  arm_dcache_delete(rx_buffer, SAMPLES); //delete Cache!
  spis_regs->TDR = rx_buffer[0];  //Transmit data back to the Master
  asm volatile ("dsb");
}

bool initSPISlaveDMA() {
  rx.begin(true);
  rx.source((uint16_t &) spis_regs->RDR);  //receiving data from the master
  rx.triggerAtHardwareEvent(SPIS_DMAMUX_SOURCE_RX); 
  rx.attachInterrupt(rxISR);
  rx.interruptAtCompletion(); //TCD->CSR |= DMA_TCD_CSR_INTMAJOR;
  rx.destinationBuffer(rx_buffer, SAMPLES + 1);
  rx.enable();
  return 1;
}

bool initSPISlave() {
  spis_regs->CR &= ~LPSPI_CR_MEN; //Modul ausschalten
  spis_regs->CR = LPSPI_CR_RST; //Master Logic reset! (Control Register => Software Reset)
  spis_regs->CR &=  ~LPSPI_CR_RST; //Master Logic reset! (Control Register => Software Reset)
  spis_regs->TCR = LPSPI_TCR_FRAMESZ(15); //16Bit Mode
  spis_regs->DER = LPSPI_DER_RDDE; //RX DMA Request Enable
  spis_regs->CR |= LPSPI_CR_MEN; //Enable SPI Module!
  return 1;
}

void setup() {
  while (!Serial);
  Serial.begin(115200);
  // I am going to use SPI1 as the master.
  //SPIM.begin(); // initialize it... 
  //pinMode(CS_MASTER, OUTPUT);
  //digitalWrite(CS_MASTER, HIGH);
  
  SPIS.begin();
  pinMode(ChipSelectSlave, INPUT);
  uint8_t cs_mask =  SPIS.setCS(ChipSelectSlave);
  Serial.printf("Slave CS: %d set: %x\n", ChipSelectSlave, cs_mask);

  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SDI (MISO) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_02 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SDO (MOSI) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_03 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 SCK (CLK) */
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00 = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) | IOMUXC_PAD_PKE; /* LPSPI4 PCS0 (CS) */
  
  Serial.println("Init SPI!");
  if (initSPISlave()) {
    Serial.println("SPI SLAVE init!");
  }
  if (initSPISlaveDMA()) {
    Serial.println("DMA Channel init!");
  }
}

void loop() {
 if(received_data) {
   for(uint16_t i = 0; i < SAMPLES; i++){
      Serial.printf("SPI Transfer: %d %x\n", i, rx_buffer[i]);
   }
   Serial.println();
   //received_data = false;
 }

}
what I am seeing as being received on the slave is:
Code:
SPI Transfer: 0 9254
SPI Transfer: 1 6
SPI Transfer: 2 1
SPI Transfer: 3 54ba
SPI Transfer: 4 4091
SPI Transfer: 5 3a4c
It gets the first 3 correct but why its not getting the rest correct?

EDIT: I did notice this in DMAChannel.h
// An interrupt routine can be run when the DMA channel completes
// the entire transfer, and also optionally when half of the
// transfer is completed.
But not sure how to say don't do half?
 
Last edited:
maybe the watermark? if it is not cleared (i dont know the default), but the interrupt may fire when that limit in fifo is reached. In SPIMST we can't use watermarks because we need to access the words immediately (not have fifo fill up to watermark before firing). try clearing the LPSPI4_FCR register to turn off watermark
 
maybe the watermark? if it is not cleared (i dont know the default), but the interrupt may fire when that limit in fifo is reached. In SPIMST we can't use watermarks because we need to access the words immediately (not have fifo fill up to watermark before firing). try clearing the LPSPI4_FCR register to turn off watermark

Thanks @tonton81 will check it out. Right now just wanted to play with something simpler for SPI Slave than MSTransfer. Started out just to test out our pin configuration changes now experimenting - been looking at both. Interesting enough if I tell it to use 12 samples instead of 6 it works, i.e., the rx_buffer is set to 12 but only the first 6 are contain the valid data.

Guess I will be reading the manual again
 
@tonton81 - @KurtE

Got it working after I posted the explanation of what was going to you but confused on definitions I guess.

If I change this line :
Code:
  rx.interruptAtCompletion(); //TCD->CSR |= DMA_TCD_CSR_INTMAJOR;
to
Code:
  rx.interruptAtHalf(); //TCD->CSR |= DMA_TCD_CSR_INTMINOR;
it works as I would expect and will printout what I am sending:
Code:
SPI Transfer: 0 9254
SPI Transfer: 1 6
SPI Transfer: 2 1
SPI Transfer: 3 2
SPI Transfer: 4 3
SPI Transfer: 5 d0d0
without adjusting watermark. So question what is MAJOR versus MINOR loop mean?
 
Hey Guys

I am having same issue, works perfectly with one sample but when I increase the SAMPLES the second half of the data is getting corrupted.
I tried the rx.interruptAtHalf(); function and the results were the same.
Can anyone help me?

Code:
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x)          \
  Serial.print(#x " 0x"); \
  Serial.println(x, BIN) // Serial.println(x,BIN)
#define SAMPLES 20
#define ChipSelectSlave 10
#define CS_MASTER 0
volatile bool received_data = false;

DMAMEM static uint16_t rx_buffer[SAMPLES];
uint16_t loop_count = 0;

DMAChannel rx(false);

void rxISR()
{
  rx.clearInterrupt();
  received_data = true;
  arm_dcache_delete(rx_buffer, SAMPLES); // delete Cache!
  LPSPI4_TDR = rx_buffer[0];
  asm volatile("dsb");
}

bool initSPISlaveDMA()
{
  rx.begin(true);
  rx.source((uint16_t &)LPSPI4_RDR);
  rx.triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_RX);
  rx.attachInterrupt(rxISR);
  rx.interruptAtCompletion(); // TCD->CSR |= DMA_TCD_CSR_INTMAJOR;
  rx.destinationBuffer(rx_buffer, SAMPLES + 1);
  rx.enable();
  return 1;
}

bool initSPISlave()
{
  LPSPI4_CR &= ~LPSPI_CR_MEN;         // Modul ausschalten
  LPSPI4_CR = LPSPI_CR_RST;           // Master Logic reset! (Control Register => Software Reset)
  LPSPI4_CR &= ~LPSPI_CR_RST;         // Master Logic reset! (Control Register => Software Reset)
  LPSPI4_TCR = LPSPI_TCR_FRAMESZ(15); // 16Bit Mode
  LPSPI4_DER = LPSPI_DER_RDDE;        // RX DMA Request Enable
  LPSPI4_CR |= LPSPI_CR_MEN;          // Enable SPI Module!
  return 1;
}

void setup()
{
  // I am going to use SPI1 as the master.
  SPI1.begin(); // initialize it...
  pinMode(CS_MASTER, OUTPUT);
  digitalWrite(CS_MASTER, HIGH);

  Serial.begin(115200);
  SPI.begin();
  SPI.setCS(ChipSelectSlave);

  while (!Serial)
    ;

  Serial.println("Init SPI!");
  if (initSPISlave())
  {
    Serial.println("SPI SLAVE init!");
  }
  if (initSPISlaveDMA())
  {
    Serial.println("DMA Channel init!");
  }
}

void loop()
{
  elapsedMicros emloop = 0;
  received_data = false;
  digitalWrite(CS_MASTER, LOW);
  SPI1.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
  for (uint8_t i = 0; i < SAMPLES; i++)
    SPI1.transfer16(i);
  SPI1.endTransaction();
  while (!received_data && (emloop < 250000))
    ;
  for (uint8_t i = 0; i < SAMPLES; i++)
    Serial.printf("SPI Transfer: %x SPI DMA: %x delay: %d\n", i, rx_buffer[i], (uint32_t)emloop);
  delay(1000);
  // loop_count++;
}

Code:
SPI Transfer: 0 SPI DMA: 0 delay: 250000
SPI Transfer: 1 SPI DMA: 1 delay: 250004
SPI Transfer: 2 SPI DMA: 2 delay: 250009
SPI Transfer: 3 SPI DMA: 3 delay: 250013
SPI Transfer: 4 SPI DMA: 4 delay: 250017
SPI Transfer: 5 SPI DMA: 5 delay: 250022
SPI Transfer: 6 SPI DMA: 6 delay: 250026
SPI Transfer: 7 SPI DMA: 7 delay: 250031
SPI Transfer: 8 SPI DMA: 8 delay: 250035
SPI Transfer: 9 SPI DMA: 9 delay: 250040
SPI Transfer: a SPI DMA: a delay: 250044
SPI Transfer: b SPI DMA: b delay: 250049
SPI Transfer: c SPI DMA: c delay: 250053
SPI Transfer: d SPI DMA: d delay: 250058
SPI Transfer: e SPI DMA: e delay: 250062
SPI Transfer: f SPI DMA: f delay: 250066
SPI Transfer: 10 SPI DMA: 316 delay: 250071
SPI Transfer: 11 SPI DMA: 55 delay: 250075
SPI Transfer: 12 SPI DMA: 53 delay: 250080
SPI Transfer: 13 SPI DMA: 42 delay: 250084

interestingly if I set the SAMPLES to 100 then set it back to 50 then I will start getting all the data back correctly, but as soon as I change it again half of it will start getting corrupted.

Thanks
 
It also seems like the ISR is getting triggered differently depending on how many samples are required, sometimes it will be once at the start or once or twice between each transfer or not at all.
 
Back
Top