SPI Slave Mode on Teensy 4?

Status
Not open for further replies.
Hi!
I'm trying to establish a spi connection between a teensy 3.6 as master and a teensy 4 as a slave. I only would like to receive 16Bit of Data on the Teensy 4 side.
Below is my Code on the Teensy 3.6 side and the code on the Teensy 4 side.
The pin-connections are: Pin10->Pin10, Pin11->Pin11, Pin12->Pin12 and Pin13->Pin13.

It doesn't seem like the DMA-Interrupt even gets triggered.
Perhabs anyone of you can spot a mistake :)
Thank you in advance!


Code of Master:

Code:
#include <SPI.h> 
#define CS 10
// set up the speed, mode and endianness of each device
SPISettings SPI_Settings(1000000, MSBFIRST, SPI_MODE0);


void setup() {
  // set the Slave Select Pins as outputs:
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  
  // initialize SPI:
  SPI.begin();
}

uint16_t val = 1235; //send anything

void loop() {
  SPI.beginTransaction(SPI_Settings);
  digitalWrite(CS, LOW);
  SPI.transfer16(val);
  digitalWrite (CS, HIGH);
  SPI.endTransaction();
  delay(500);
}

Code of Slave:
Code:
//Inspired by Paul Stoffregen's SPI Lib and manitou48's spidma2.ino
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,BIN)
#define SAMPLES 10

DMAMEM static uint8_t rx_buffer[SAMPLES];
DMAChannel rx(false);
//volatile int ticks = 0;


void DMAChanneltransferCount() {
  if (!(rx.TCD->BITER & DMA_TCD_BITER_ELINK)) {
    rx.TCD->BITER = SAMPLES & 0x7fff;
  } else {
    rx.TCD->BITER = (rx.TCD->BITER & 0xFE00) | (SAMPLES & 0x1ff);
  }
  rx.TCD->CITER = rx.TCD->BITER;
}

void rxISR() {
  Serial.println("RX Interrupt");
  rx.clearInterrupt();
  rx.clearComplete();
  DMAChanneltransferCount();
  //ticks++;
  rx.enable();
  asm volatile ("dsb");
}

bool initSPISlaveDMA() {
  rx.disable();
  rx.source((uint8_t &) LPSPI4_RDR);
  rx.disableOnCompletion();
  rx.triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_RX);
  //rx.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
  rx.attachInterrupt(rxISR); //doesnt get called?
  rx.interruptAtCompletion(); //TCD->CSR |= DMA_TCD_CSR_INTMAJOR;
  //rx.TCD->ATTR_SRC = 0; //8Bit Modus
  rx.destinationBuffer(rx_buffer, SAMPLES + 1);
  //rx.TCD->DLASTSGA = -SAMPLES;
  //LPSPI4_TCR = (LPSPI4_TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(15);
  //LPSPI4_TCR = LPSPI_TCR_FRAMESZ(7);
  LPSPI4_TCR = (LPSPI4_TCR & 0xfffff000) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT; //16Bit Modus
  LPSPI4_FCR = 0;
  //LPSPI4_IER = LPSPI_IER_RDIE; //Receive Data Inerruopt enable
  //LPSPI4_DER = LPSPI_IER_RDIE;
  LPSPI4_DER = LPSPI_DER_RDDE; //RX DMA Request Enable
  LPSPI4_SR = 0x3f00;  // clear out all of the other status...
  LPSPI4_CR |= LPSPI_CR_MEN; //SPI-Modul einschalten!
  rx.enable();
  return 1;
}



bool initSPISlave()
{
  LPSPI4_CR &= ~LPSPI_CR_MEN; //Module disable

  CCM_CBCMR &= ~CCM_CCGR1_LPSPI4(CCM_CCGR_ON); //Clock ausschalten
  //return 1;
  //FAST-IO
  
    uint32_t fastio = IOMUXC_PAD_DSE(6) | IOMUXC_PAD_SPEED(1);
    IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = fastio;
    IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_02 = fastio;
    IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_03 = fastio;
    CCM_CBCMR |= CCM_CCGR1_LPSPI4(CCM_CCGR_ON); //Clock reaktivieren
  

  //LPSPI Muxing:
  IOMUXC_LPSPI4_SCK_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_SDI_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_SDO_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_PCS0_SELECT_INPUT = 0;

  //Pad Muxing:
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_03 = 3; //SCK
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01 = 3; //SDI (MISO)
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02 = 3; //SDO (MOSI)
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 3; //LPSPI4 PCS0 (CS)

  //Master-Logic Reset:
  LPSPI4_CR = LPSPI_CR_RST; //Master Logic reset! (Control Register => Software Reset)

  /*
    LPSPI4_CFGR1 &= ~LPSPI_CFGR1_MASTER; //Master Modus disable
    LPSPI4_CFGR1 |= LPSPI_CFGR1_NOSTALL; //prevent stall from RX
  */

  //FIFO Watermark for 16Bit?
  LPSPI4_FCR = LPSPI_FCR_RXWATER(15);

  LPSPI4_CR |= LPSPI_CR_RTF | LPSPI_CR_RRF | LPSPI_CR_MEN | LPSPI_CR_DBGEN | LPSPI_CR_DOZEN; //enable!

  return 1;
}

void setup()
{
  Serial.begin(115200);
  SPI.begin();

  /*
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  pinMode(12, OUTPUT);
  pinMode(13, INPUT);
  */

  rx.begin(true);

  while (!Serial);
  Serial.println("init SPI!");

  if (initSPISlave()) {
    Serial.println("SPI SLAVE init!");
  }
  if (initSPISlaveDMA()) {
    Serial.println("DMA Channel init!");
  }
  
  Serial.println(SPI.pinIsChipSelect(10));
  Serial.println(SPI.pinIsMOSI(11));
  Serial.println(SPI.pinIsMISO(12));
  Serial.println(SPI.pinIsSCK(13));

  rx_buffer[0] = 0;
  
  //Print Registers:
  /*
    PRREG(LPSPI4_CR);
    PRREG(LPSPI4_SR);
    PRREG(LPSPI4_DER);
    PRREG(LPSPI4_CFGR0);
    PRREG(LPSPI4_CFGR1);
    PRREG(LPSPI4_CCR);
    PRREG(LPSPI4_FCR);
    PRREG(LPSPI_FSR_RXCOUNT(4));
    PRREG(LPSPI4_TCR);
    PRREG(LPSPI_RSR_RXEMPTY);
    PRREG(LPSPI4_RDR);
  */
}

void loop()
{
  Serial.println(rx_buffer[0]); //always Zero

  //PRREG(LPSPI_FSR_RXCOUNT(4));
  //PRREG(LPSPI4_SR);
  delay(1000);
}
 
Last edited by a moderator:
Sorry, I hope you don't mind I edited and added code tags. Lots easier to read when the indents are preserved (Select text and hit the # button)

I have been meaning to take a look at SPI slave code on the T4, but have not done so yet.

This morning, I may not have time to do a complete look over this. I would probably have a tendency, to look at this code along with some SPI DMA master side by side to see if anything jumps out at me. We do have some DMA SPI master code around in a few places, including SPI library itself, My display library (ILI9341_t3n) and a few other display library ILI9481_t3, ST7735_t3...

In my first quick look through, one thing, that I know is wrong is:
Code:
 //FIFO Watermark for 16Bit?
  LPSPI4_FCR = LPSPI_FCR_RXWATER(15);
The Rx Water mark in this register is how many entries max in the FIFO queue before you get interrupts. I am not sure if you want the whole FIFO (16 words) full before you get notification.

I will try to take a look soon, right now in the middle of looking at some stuff with Wire...
 
Thank you very very much for your answer! :) I am also trying to read through the datasheet section of the lpspi again. but as you can clearly see i'm no expert at all
 
Sorry, again I don't have enough time (or energy) to go completely through all of this but again will try to give some hints...

Your slave code starts off in setup, calling SPI.begin() so many of the things are already initialized, including setting MOSI, MISO, SCK into the proper SPI mode.

So you can remove most of the stuff in your initSPISlave code, including

turning off enable the clock as this was done SPI.begin.

Also setting of the pins in the right configurations (minus the CS)
Code:
  uint32_t fastio = IOMUXC_PAD_DSE(6) | IOMUXC_PAD_SPEED(1);
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 = fastio;
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_02 = fastio;
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_03 = fastio;
  CCM_CBCMR |= CCM_CCGR1_LPSPI4(CCM_CCGR_ON); //Clock reaktivieren


  //LPSPI Muxing:
  IOMUXC_LPSPI4_SCK_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_SDI_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_SDO_SELECT_INPUT = 0;
  IOMUXC_LPSPI4_PCS0_SELECT_INPUT = 0;

  //Pad Muxing:
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_03 = 3; //SCK
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01 = 3; //SDI (MISO)
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02 = 3; //SDO (MOSI)
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 3; //LPSPI4 PCS0 (CS)
But if you are going to do your own. then you probably need to change lines like:
Code:
IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_03 = 3; //SCK
to
Code:
IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_03 = 3 | 0x10 ; //SCK

As I said this should take care of MOSI, MISO, SCK

Now for SCK you can do it your way (need the 0x10) ... Or let SPI library do it for you, by adding a call to setCS like:
Code:
SPI.setCS(10);

Setting up DMA...

Note: I see your buffer is setup: DMAMEM static uint8_t rx_buffer[SAMPLES];
Simple warning about DMAMEM (or memory allocated using malloc). Uses the upper memory that has caching and unless you handle it, you may find that what your program sees is not what was received by DMA. As DMA writes directly to physical memory and your code will read from cache if it is cached...
Some details up on main product page: https://www.pjrc.com/store/teensy40.html
More up in the thread: https://forum.pjrc.com/threads/57326-T4-0-Memory-trying-to-make-sense-of-the-different-regions
You may want to look around for threads that talk about arm_dcache_delete and the like.
Example in SPI library it does: arm_dcache_delete(retbuf, count);

Also I noticed that you have not set it up to be slave mode. That is you have the line:
Code:
    LPSPI4_CFGR1 &= ~LPSPI_CFGR1_MASTER; //Master Modus disable
commented out
Likewise you have the line to turn on NOSTALL disabled, but I think that one is a MASTER flag so probably wont do anything.

That is all for now.

Hope that helps
Kurt
 
Thank you for your help I'm getting Interrupts now! :) But I'm reading always Zero at the moment?!




Code:
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x,BIN)
#define SAMPLES 10


DMAMEM static uint16_t rx_buffer[SAMPLES];
DMAChannel rx(false);
volatile int ticks = 0;


void DMAChanneltransferCount() {
  if (!(rx.TCD->BITER & DMA_TCD_BITER_ELINK)) {
    rx.TCD->BITER = SAMPLES & 0x7fff;
  } else {
    rx.TCD->BITER = (rx.TCD->BITER & 0xFE00) | (SAMPLES & 0x1ff);
  }
  rx.TCD->CITER = rx.TCD->BITER;
}

void rxISR() {
  Serial.println("RX Interrupt");
  PRREG(LPSPI4_RDR);

  rx.clearInterrupt();
  rx.clearComplete();

  DMAChanneltransferCount();
  //ticks++;

  rx.enable();
  asm volatile ("dsb");
}

bool initSPISlaveDMA() {
  rx.disable();
  rx.source((uint8_t &) LPSPI4_RDR);
  //rx.disableOnCompletion();

  rx.triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_RX);
  //rx.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
  rx.attachInterrupt(rxISR); //doesnt get called?
  rx.interruptAtCompletion(); //TCD->CSR |= DMA_TCD_CSR_INTMAJOR;

  
  rx.TCD->ATTR_SRC = 0; //8Bit Modus
  rx.destinationBuffer(rx_buffer, SAMPLES + 1);
  rx.TCD->DLASTSGA = -SAMPLES;

  
  //LPSPI4_TCR = (LPSPI4_TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(15);
  //LPSPI4_TCR = LPSPI_TCR_FRAMESZ(7);
  //LPSPI4_TCR = (LPSPI4_TCR & 0xfffff000) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT; //16Bit Modus


  LPSPI4_TCR = LPSPI_TCR_FRAMESZ(15); // | LPSPI_TCR_CONT; //16Bit Modus

  LPSPI4_FCR = 0;


  LPSPI4_DER = LPSPI_DER_RDDE; //RX DMA Request Enable

  LPSPI4_SR = 0x3f00;  // clear out all of the other status...
  LPSPI4_CR |= LPSPI_CR_MEN; //SPI-Modul einschalten!
  rx.enable();
  return 1;
}



bool initSPISlave()
{
  LPSPI4_CR &= ~LPSPI_CR_MEN; //Modul ausschalten
  /*
    //Fast-IO for CS:
    IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = IOMUXC_PAD_DSE(6) | IOMUXC_PAD_SPEED(1);
    //LPSPI Muxing:
    IOMUXC_LPSPI4_PCS0_SELECT_INPUT = 0; //CS
    //Pad Muxing:
    IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 3 | 0x10; //CS
  */

  //Master-Logic Reset:
  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_CFGR1 |= LPSPI_CFGR1_NOSTALL;
  
  /*
    LPSPI4_CFGR1 &= ~LPSPI_CFGR1_MASTER; //Master Modus deaktivieren
    LPSPI4_CFGR1 |= LPSPI_CFGR1_NOSTALL; //prevent stall from RX
  */

  //FIFO Watermark einstellen für (FIFOSize-1) --> hier: ab 16Bit empfangen triggern!
  LPSPI4_FCR = LPSPI_FCR_RXWATER(0);
  //LPSPI4_CR |= LPSPI_CR_RTF | LPSPI_CR_RRF | LPSPI_CR_MEN | LPSPI_CR_DBGEN | LPSPI_CR_DOZEN; //SPI-Modul einschalten!
  LPSPI4_CR |= LPSPI_CR_MEN | LPSPI_CR_DBGEN | LPSPI_CR_DOZEN;

  return 1;
  //initSPISlaveDMA();
}

void setup()
{
  Serial.begin(2000000);
  SPI.begin();
  SPI.setCS(10);
  rx.begin(true);

  while (!Serial);
  Serial.println("initialisiere SPI!");

  if (initSPISlave()) {
    Serial.println("SPI SLAVE initialisiert!");
  }
  if (initSPISlaveDMA()) {
    Serial.println("DMA Channel initialisiert!");
  }
  rx_buffer[0] = 0;

  //NVIC_ENABLE_IRQ(IRQ_LPSPI4);

  PRREG(LPSPI4_CR);
  PRREG(LPSPI4_SR);
  PRREG(LPSPI4_DER);
  PRREG(LPSPI4_CFGR0);
  PRREG(LPSPI4_CFGR1);
  PRREG(LPSPI4_CCR);
  PRREG(LPSPI4_FCR);
  //PRREG(LPSPI_FSR_RXCOUNT(4));
  PRREG(LPSPI4_TCR);
  //PRREG(LPSPI_RSR_RXEMPTY);
  //PRREG(LPSPI4_RDR);

}

void loop()
{
  arm_dcache_delete(rx_buffer, SAMPLES); //delete Cache!
  Serial.println(rx_buffer[0]);
  //PRREG(LPSPI_FSR_RXCOUNT(4));
  PRREG(LPSPI4_SR); //Statusregister
  delay(1000);
}
 
Actually that's not right I'm getting values, but much lower ones than the send values... For example I sent the uint16_t val = 200 and received 34. Strange!
 
This is the current Slave Code:

Code:
#include <DMAChannel.h>
#include <SPI.h>
#define PRREG(x) Serial.print(#x" 0x"); Serial.println(x, BIN) //Serial.println(x,BIN)
#define SAMPLES 2


DMAMEM static uint16_t rx_buffer[SAMPLES];
DMAChannel rx(false);
volatile int ticks = 0;


void DMAChanneltransferCount() {
  if (!(rx.TCD->BITER & DMA_TCD_BITER_ELINK)) {
    rx.TCD->BITER = SAMPLES & 0x7fff;
  } else {
    rx.TCD->BITER = (rx.TCD->BITER & 0xFE00) | (SAMPLES & 0x1ff);
  }
  rx.TCD->CITER = rx.TCD->BITER;
}

void rxISR() {
  rx.clearInterrupt();
  rx.clearComplete();
  //DMAChanneltransferCount();
  rx.enable();
  asm volatile ("dsb");
  Serial.println("RX Interrupt");
}

bool initSPISlaveDMA() {
  rx.disable();
  rx.source((uint8_t &) LPSPI4_RDR);
  
  rx.disableOnCompletion();
  rx.triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_RX);
  rx.attachInterrupt(rxISR);
  rx.interruptAtCompletion(); //TCD->CSR |= DMA_TCD_CSR_INTMAJOR;

  rx.TCD->ATTR_SRC = 1; //16Bit Mode
  rx.destinationBuffer(rx_buffer, SAMPLES + 1);
  rx.TCD->DLASTSGA = -(SAMPLES);

  LPSPI4_TCR = LPSPI_TCR_FRAMESZ(15); // | LPSPI_TCR_CONT; //16Bit Modus
  LPSPI4_FCR = 0;
  LPSPI4_DER = LPSPI_DER_RDDE; //RX DMA Request Enable
  LPSPI4_SR = 0x3f00;  // clear out all of the other status...
  LPSPI4_CR |= LPSPI_CR_MEN; //SPI-Modul einschalten!
  rx.enable();
  return 1;
}



bool initSPISlave()
{
  LPSPI4_CR &= ~LPSPI_CR_MEN; //Modul ausschalten
  //Master-Logic Reset:
  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_CFGR1 |= LPSPI_CFGR1_NOSTALL;
  LPSPI4_FCR = LPSPI_FCR_RXWATER(0);
  LPSPI4_CR |= LPSPI_CR_MEN | LPSPI_CR_DBGEN | LPSPI_CR_DOZEN; //enable
  return 1;
}

void setup()
{
  Serial.begin(2000000);
  SPI.begin();
  SPI.setCS(10);
  rx.begin(true);

  while (!Serial);
  Serial.println("initialisiere SPI!");

  if (initSPISlave()) {
    Serial.println("SPI SLAVE initialisiert!");
  }
  if (initSPISlaveDMA()) {
    Serial.println("DMA Channel initialisiert!");
  }
  rx_buffer[0] = 0;

  //NVIC_ENABLE_IRQ(IRQ_LPSPI4);

  PRREG(LPSPI4_CR);
  PRREG(LPSPI4_SR);
  PRREG(LPSPI4_DER);
  PRREG(LPSPI4_CFGR0);
  PRREG(LPSPI4_CFGR1);
  PRREG(LPSPI4_CCR);
  PRREG(LPSPI4_FCR);
  //PRREG(LPSPI_FSR_RXCOUNT(4));
  PRREG(LPSPI4_TCR);
  //PRREG(LPSPI_RSR_RXEMPTY);
  //PRREG(LPSPI4_RDR);
}

void loop()
{
  arm_dcache_delete(rx_buffer, SAMPLES); //delete Cache!
  PRREG(rx_buffer[0]);
  PRREG(rx_buffer[1]);
  Serial.println(rx_buffer[0]);

  //PRREG(LPSPI_FSR_RXCOUNT(4));
  //PRREG(LPSPI4_SR); //Statusregister
  delay(1000);
}
 
For anyone who's interested: this is my polished version of the code.
(Sets up T4 in SPI Slave Mode and receives 16Bit Values with DMA)

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 ChipSelectSlave 10

DMAMEM static uint16_t rx_buffer[SAMPLES];
DMAChannel rx(false);

void rxISR() {
  rx.clearInterrupt();
  asm volatile ("dsb");
  Serial.print("RX Interrupt --> Val = "); Serial.println(rx_buffer[0]);
}

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() {
  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() {
  arm_dcache_delete(rx_buffer, SAMPLES); //delete Cache!
}
 
Last edited:
You are welcome, glad you got it working.. I may have to play with it, when I am done with some other distractions
 
A implementation in the SPI-Lib would be pretty nice i think.. For other users too :) I'm pretty sure my code can be improved too
 
I think there is a mistake on the line:
Code:
rx.destinationBuffer(rx_buffer, SAMPLES + 1);

I think it should be:
Code:
rx.destinationBuffer(rx_buffer, SAMPLES * 2);
.

The + 1 only works when SAMPLES is 1. One sample at 16 bits per sample is 2 bytes (SAMPLES + 1). But when SAMPLES is bigger, this falls apart.

I also wonder if there is an easy way to allow the Teensy slave to send data back to the master with this implementation. Any help would be greatly appreciated.
 
For anyone who's interested: this is my polished version of the code.
(Sets up T4 in SPI Slave Mode and receives 16Bit Values with DMA)

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 ChipSelectSlave 10

DMAMEM static uint16_t rx_buffer[SAMPLES];
DMAChannel rx(false);

void rxISR() {
  rx.clearInterrupt();
  asm volatile ("dsb");
  Serial.print("RX Interrupt --> Val = "); Serial.println(rx_buffer[0]);
}

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() {
  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() {
  arm_dcache_delete(rx_buffer, SAMPLES); //delete Cache!
}

This code works great as a test for me, but how can we measure between CS low and high, then get an entire buffer of all of the bytes?
 
Anyone playing with a non-DMA version, interrupt driven version of SPI Slave? I been working on one and my setup is as simple as this:

Code:
#include "SPISlave_T4.h"
SPISlave_T4<&SPI> mySPI;

void setup() {
  Serial.begin(115200);
  while (!Serial);
  mySPI.begin();
}

void loop() {
}

Currently it does work and right now I'm just sending back the received data as a response, and can handle unlimited byte transfers in the same assertion.
Now, there is one issue, and I been searching google about it. In SPIMODE0, the slave never runs, despite changing the polarity of the clock edges. The only reliable configuration is turning on the clock phase, and using another teensy as master, sending in SPIMODE2

Will that be an issue for people who want to use a slave if it doesn't use SPIMODE0?
Reference of similar issue with LPSPI slave mode but PCS related:
Code:
https://community.nxp.com/t5/S32K/SPI-Slave/m-p/666620
 
Another interesting issue I see with SPI slave mode, I added 8, 16, 32bit data transfer support. Here is whats weird:

LC is sending (SPI.transfer()) 8 bit data over to 8 bit slave mode T4. This works.
LC then sends 16 bit transfers to T4 in 8 bit mode, this also works.
LC then sends 8 bit transfers * 2 to T4 in 16 bit slave mode.... slave T4 jams up.
LC then sends 16 bit transfer (transfer16) to T4 in 16bit slave mode, that works.
Unfortunately I can't test 32 bit mode (no transfer32) but it seems the higher the slave bits the less forgiving it is to 8 bit transfers, probably a timing of some sort.
LC 2x transfer16's will not work when slave is in 32 bit mode either.

I guess the LPSPI slave mode prefers bits back-to-back as opposed to 2x 8 bit transfers within the same CS assertion
Kinda explains why theres a lack of support for SPI slave mode.

Whats working so far:
* 8 and 16 bit transfers
* SPIMODE2
* unlimited data transfers

Code:
#include "SPISlave_T4.h"
SPISlave_T4<&SPI, [COLOR="#0000FF"]SPI_16_BITS[/COLOR]> mySPI;

void setup() {
  Serial.begin(115200);
  while (!Serial);
  mySPI.begin();
}

void loop() {
  Serial.println(millis());
  delay(100);
}

EDIT, I fixed it by moving the TCR out of begin and resetting it from the ISR right before it exits scope
I can now send 8bit*2 LC -> T4 16bit slave mode and 2x16bit LC -> T4 32bit slave mode
 
Last edited:
Oh! Isn't this interesting. Another limited feature of SPI slave. Even if the SION bit is set, it will NOT return 1 if the CS pin is HIGH. None of the SPI slave pins register as high or low on GPIO7_DR, however...... as a normal GPIO, the SION bit works!
Right now I have pin 9 as input on T4 jumped to pin 10 (CS input) and reading pin 9 for the state for the slave.

Just like back when I did SPI_MST, the GPIO had to be monitored for streaming circular bytes in the SPI transfers, sure, I could work off the "Busy" flag of the SR, however, if you transfer (faster than it's capable of receiving) or too slow (slave ends up waiting for data) the "Busy" flag goes idle EVEN if the CS remains active low. With the added GPIO 9, we no longer have to rely on the "Busy" flag and know we are still in the streaming process to prevent early exits.

So in SPI slave mode, SION does NOT work at all :(
Workaround is to use an additional GPIO tied to the slave's CS for input.

another less likely workaround is to add a few microseconds before exiting while loop if the "Busy" flag went idle. That might be an option I guess, to give the flag a tiny bit of time before a timeout exit.......

That works, just tested it.

Code:
SPISlave_T4_FUNC void SPISlave_T4_OPT::SLAVE_ISR() {
  SLAVE_PORT_ADDR;
  [COLOR="#FF0000"]uint32_t timeout = micros();[/COLOR]
  while ( SLAVE_SR & 0x1000100 ||[COLOR="#FF0000"] (micros() - timeout < 50 )[/COLOR] ) { /* MBF (Busy) / WCF (Word Complete) */
    if ( (SLAVE_SR & (1UL << 8)) ) { /* WCF set */
      uint32_t val = SLAVE_RDR;
      Serial.print(val); Serial.print(" ");
      SLAVE_TDR = val + 2;
      SLAVE_SR = (1UL << 8); /* Clear WCF */
      [COLOR="#FF0000"]timeout = micros();[/COLOR]
    }
  }
  Serial.println();
  SLAVE_SR = 0x3F00; /* Clear remaining flags on exit */
  SLAVE_TCR_REFRESH;
  asm volatile ("dsb");
}

and the gpio version: (preferred method)

Code:
SPISlave_T4_FUNC void SPISlave_T4_OPT::SLAVE_ISR() {
  SLAVE_PORT_ADDR;
  while ( [COLOR="#FF0000"]!digitalReadFast(9)[/COLOR] ) { /* MBF (Busy) / WCF (Word Complete) */
    if ( (SLAVE_SR & (1UL << 8)) ) { /* WCF set */
      uint32_t val = SLAVE_RDR;
      Serial.print(val); Serial.print(" ");
      SLAVE_TDR = val + 2;
      SLAVE_SR = (1UL << 8); /* Clear WCF */
    }
  }
  Serial.println();
  SLAVE_SR = 0x3F00; /* Clear remaining flags on exit */
  SLAVE_TCR_REFRESH;
  asm volatile ("dsb");
}

The GPIO method is more reliable, especially if you have delays on another Master sending *between* frames during same assertion.

EDIT:
Reference:
Code:
https://community.nxp.com/t5/i-MX-RT/Does-I-MXRT1062-LPSPI-TCR-Comand-Register-require-a-write-to-TDR/m-p/1170494
Quote from NXP Techsupport:
About the GPIO SION
I have checked with our internal expert, the SION just can be used when the MUX is the GPIO, then customer can use the PSR to check the pin status.
When configure the MUX as SPI_CS, then even SION is set, we can't use the GPIO PSR to detect the pin situation, the expert already confirm it, and you and my test result also confirm it. So, the SION function with SPI_CS may not useful to you.
 
Last edited:
Yes! I got SPIMODE0 working! :D

It turns out that aside from setting the TDR in the ISR with the RDR read, the initial TDR must ALSO be populated initially in setup! So while the ISR keeps setting a new TDR while reading the RDR, setup only has to set TDR once and that's enough to make the SPIMODE's work properly. :)

I am going with GPIO method for the SPI slave library.

Code:
#include "SPISlave_T4.h"
SPISlave_T4<&SPI, SPI_8_BITS> mySPI;

void setup() {
  Serial.begin(115200);
  while (!Serial);
  mySPI.[COLOR="#FF0000"]activePin[/COLOR](9); /* pin 9 and pin 10 are jumped due to the SPI registers SION bit is non functional, pin 9 is used as INPUT by library to maintain continuous SPI transfers */
  mySPI.begin();
}

void loop() {
}

I didn't know what to call the extra GPIO pin required to be monitored (while jumped to CS (9+10 jumped)), so i used activePin(pin) unless someone suggests otherwise
On the plus side we can set pin 9 also as INPUT_PULLUP, to keep slave deasserted if no master is there :)
 
Last edited:
Status
Not open for further replies.
Back
Top