Thought I would mention, that every once in awhile I play around with some of the DMA SPI slave code to see if I can make it work reasonably. Current test versions is to use two T3.6s connected to each other. Right now I have them sending 16 byte packets of data to each other... Note: the data from one does not currently influence the data being sent by the other. That is currently I don't have it setup where the Master makes a request, which then the slave returns data based on the stuff from master. I may do that next... Or may try that from an RPI (or ODroid or UP) to a Teensy.
Current Master test program:
Code:
//==============================================================
// SPI Master quick test - DMA version
#include <SPI.h>
EventResponder event;
uint8_t xxx[16];
uint8_t yyy[16];
void asyncEventResponder(EventResponderRef event_responder) {
digitalWriteFast(2, HIGH);
SPI.endTransaction();
Serial.print("YYY: ");
for (uint8_t i = 0; i < sizeof(yyy); i++) Serial.printf("%02x ", yyy[i]);
Serial.println();
}
void setup() {
while (!Serial && (millis() < 2000)) ;
Serial.println("Test SPI DMA master");
//SPI.setMISO(8);
//SPI.setMOSI(7);
SPI.setSCK(14);
pinMode(2, OUTPUT);
digitalWriteFast(2, HIGH);
SPI.begin();
event.attachImmediate(&asyncEventResponder);
for (uint8_t i = 0; i < sizeof(xxx); i++) {
xxx[i] = i;
}
}
void loop() {
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWriteFast(2, LOW);
SPI.transfer(xxx, yyy, sizeof(xxx), event);
delay(1000);
}
This is using the asynch version of the SPI transfer (i.e. DMA). Actually it is not needed here, but did to show how it can work...
The Slave code is a little more complex here. I was curious if I could control what got sent back, and decide when the master signals that the transfer was about to start, so I have the Slave Chip select pin. I am using pin2... It must be a CS that can be used as a Slave CS (Could have used 10 here as well). There is still a lot of diagnostic code that is commented out in here, to help debug some of the DMA issues. Also I have an interrupt on the TX side as well as the RX side. The TX one is not needed, It will be triggered as soon as the last byte of the transfer is put into the TX FIFO. It is the RX one that lets you know that the whole transfer was completed. I left the TX one in as it could be used to say, I am now free to update the TX buffer.
Code:
//==============================================================
// SPI Slave quick test - DMA... See if I can use SPI system to intialize.
#include <SPI.h>
#include <DMAChannel.h>
DMAChannel *_dmaTX = nullptr;
DMAChannel *_dmaRX = nullptr;
uint8_t loop_count = 0;
uint8_t xxx[16];
uint8_t yyy[16];
volatile uint8_t spi_client_state = 0;
uint8_t previous_spi_client_state = 0;
void setup() {
while (!Serial && (millis() < 2000)) ;
pinMode(13, OUTPUT);
pinMode(3, OUTPUT);
for (int i = 0; i < 5; i++) {
digitalWriteFast(13, HIGH);
delay(250);
digitalWriteFast(13, LOW);
delay(250);
}
Serial.println("Test SPI DMA Slave");
// SPI.setMISO(8);
// SPI.setMOSI(7);
SPI.setSCK(14);
SPI.begin(); // This sets the MISO/MOSI/SCK pins and gets us access to memory.
// Now lets convert this over to slave.
KINETISK_SPI0.MCR = SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); // stop transfer all states high
SPI0_CTAR0_SLAVE = SPI_CTAR_FMSZ(7); // We are doing 8 bit transfers.
// Now setup pin 2 as Slave select
SPI.setCS(2); // sets the pin as a hardware chip select pin.
attachInterrupt(2, &CSPinFalling, FALLING);
_dmaRX = new DMAChannel();
_dmaRX->disable();
_dmaRX->attachInterrupt(&dma_rxisr);
_dmaRX->interruptAtCompletion();
_dmaRX->source((volatile uint8_t&)KINETISK_SPI0.POPR);
_dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode...
_dmaRX->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX);
_dmaRX->disableOnCompletion();
_dmaTX = new DMAChannel();
_dmaTX->destination((volatile uint8_t&)KINETISK_SPI0.PUSHR);
_dmaTX->TCD->ATTR_DST = 0; // Make sure set for 8 bit mode
_dmaTX->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
_dmaTX->attachInterrupt(&dma_txisr);
_dmaTX->interruptAtCompletion();
_dmaTX->disableOnCompletion();
_dmaTX->disable();
KINETISK_SPI0.MCR = SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); // stop transfer all states high
KINETISK_SPI0.MCR |= SPI_MCR_CLR_TXF;
KINETISK_SPI0.MCR |= SPI_MCR_CLR_RXF;
for (uint8_t i = 0; i < sizeof(xxx); i++) {
xxx[i] = 's';
}
//setupSlaveDMA(); // Setup once at startup.
}
void setupSlaveDMA() {
// Now lets setup DMA
_dmaTX->sourceBuffer(xxx, sizeof(xxx));
_dmaRX->destinationBuffer((uint8_t*)yyy, sizeof(yyy));
//dumpDMA_TCD("TX:", _dmaTX);
//dumpDMA_TCD("RX:", _dmaRX);
SPI0_MCR = 0;
SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
//SPI0_SR = 0xFF0F0000;
_dmaTX->enable();
_dmaRX->enable();
//Serial.printf("SPI MCR=%x RSER=%x SR=%x\n", SPI0_MCR, SPI0_RSER, SPI0_SR);
//Serial.printf("DMA CR: %x ES: %x ERQ: %x EEI: %x ERR: %x\n", DMA_CR, DMA_ES, DMA_ERQ, DMA_EEI, DMA_ERR);
}
void loop() {
uint8_t new_client_state = spi_client_state; // get it in one shot
if (new_client_state != previous_spi_client_state) {
switch (new_client_state) {
case 0: /*Serial.println();*/ break;// break to new line
case 1:
break;
case 2:
digitalWriteFast(13, LOW);
Serial.print("XXX: ");
for (uint8_t i = 0; i < sizeof(xxx); i++) Serial.printf("%02x ", xxx[i]);
Serial.print("\nYYY: ");
for (uint8_t i = 0; i < sizeof(yyy); i++) Serial.printf("%02x ", yyy[i]);
Serial.println("");
spi_client_state = 0;
new_client_state = 0;
loop_count++;
uint8_t val = loop_count;
for (uint8_t i = 0; i < sizeof(xxx); i++) xxx[i] = val++;
//setupSlaveDMA(); // Setup again after the previous one completed.
}
previous_spi_client_state = new_client_state;
}
delay(1);
}
typedef struct __attribute__((packed, aligned(4))) {
uint32_t SADDR;
int16_t SOFF;
uint16_t ATTR;
uint32_t NBYTES;
int32_t SLAST;
uint32_t DADDR;
int16_t DOFF;
uint16_t CITER;
int32_t DLASTSGA;
uint16_t CSR;
uint16_t BITER;
} TCD_DEBUG;
void dumpDMA_TCD(const char *psz, DMABaseClass *dmabc)
{
Serial.printf("%s %08x %08x:", psz, (uint32_t)dmabc, (uint32_t)dmabc->TCD);
TCD_DEBUG *tcd = (TCD_DEBUG*)dmabc->TCD;
Serial.printf("%08x %04x %04x %08x %08x ", tcd->SADDR, tcd->SOFF, tcd->ATTR, tcd->NBYTES, tcd->SLAST);
Serial.printf("%08x %04x %04x %08x %04x %04x\n", tcd->DADDR, tcd->DOFF, tcd->CITER, tcd->DLASTSGA,
tcd->CSR, tcd->BITER);
}
void CSPinFalling() {
// setup DMA slave transfer
digitalWriteFast(3, HIGH);
spi_client_state = 1; // we are now set to be in receive mode
setupSlaveDMA(); // Setup once at startup.
digitalWriteFast(3, LOW);
digitalWriteFast(13, HIGH);
}
//-------------------------------------------------------------------------
// DMA RX ISR
//-------------------------------------------------------------------------
void dma_rxisr(void) {
// Serial.println(">");
_dmaRX->clearInterrupt();
_dmaRX->clearComplete();
SPI0_RSER = 0;
//port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); // clear out the queue
SPI0_SR = 0xFF0F0000;
// port().CTAR0 &= ~(SPI_CTAR_FMSZ(8)); // Hack restore back to 8 bits
spi_client_state = 2; // Say that we completed here.
}
//-------------------------------------------------------------------------
// DMA TX ISR
//-------------------------------------------------------------------------
void dma_txisr(void) {
// Serial.println("!TX!");
/* dumpDMA_TCD("TX:", _dmaTX);
dumpDMA_TCD("RX:", _dmaRX);
Serial.printf("SPI MCR=%x RSER=%x SR=%x\n", SPI0_MCR, SPI0_RSER, SPI0_SR);
Serial.printf("DMA CR: %x ES: %x ERQ: %x EEI: %x ERR: %x\n", DMA_CR, DMA_ES, DMA_ERQ, DMA_EEI, DMA_ERR);
*/
_dmaTX->clearInterrupt();
_dmaTX->clearComplete();
}
If I decide to play around where maybe the Teensy has a set of logical Registers that the host can set or can query. I may set it up such that it may do it something like:
Send a fixed size command header, with Command, start reg, count reg - How big this message is may depend on how many registers I support
You would set and clear the chip select with this header. With a very slight delay, I would then reselect the CS pin and for logical writes, output the data to the slave, or buffer characters. At this point the slave would now how many characters the master is sending and again can set up their DMA transfers...