DMA SPI and SPI transactions

Status
Not open for further replies.

christoph

Well-known member
DMA SPI and SPI transactions (rewrite for 1.20 RC2)

How would I combine the DMA SPI library and SPI transactions?

I'm planning to write a teensyduino 1.20 release of the DMA SPI driver, which could make use of a few new features introduced in teensyduino 1.20. These are:
  • SPI transactions
  • Dynamic DMA channel allocation
  • Interrupt vector table in RAM
I'm not sure how I should use the SPI transaction features. The SPI must be configured for DMA before starting transfers (refers to my DMA SPI transaction feature), and needs to start a transaction (Paul's SPI transaction feature) before using the SPI. When it has finished, it has to release the SPI again, so would something like this be the correct way to use it:
Code:
SPI.beginTransaction(SPISettings());

// configure SPI for DMA usage here

// register transfers with the DMA SPI driver
// the transfer objects handle SPI settings (speed, mode, ...) and chip select

// wait for transfer to finish, or pause the driver

// configure SPI for non-DMA usage

SPI.endTransaction();

The DMA SPI driver for 1.20 could of course also make use of the SpiSettings class, to facilitate transition between DMA and non-DMA usage of the SPI in case users want to try one or the other.

Does the above sound reasonable? To those who have used the DMA SPI driver before (or want to use it), please make suggestions if you feel that the interface needs some adjustments here and there, i.e. naming of methods, argument type and order, default values, and so on.

Regards

Christoph
 
Last edited:
moved from Teensy rc section to here...
------------
OK, thanks.

I have RadioHead within this two-way wireless bulk data transfer via low cost wireless, that I'm testing. Currently, there's no time-overlapped radio SPI and SPI Flash chip I/O.
I could code something (using Transactions) to force it to happen (say, an SPI flash chip read while I know the radio is sending or receiving), and see if errors occur as they should, and if Transactions cure it.

But first I need to try to cause/reproduce the RFM69 clocking with radio interrupt pending issue you reported. I spoke with Semtech (chip source) and Erik, their very knowledgeable engineer says there's been no reports of this but he doesn't know what timing might exist in fielded systems; just no reports of issues. He said he'd search for errata on this. They have shipped in quantities for 7 years, he says.

So I'll try to reproduce this with the software environment I have - which is:
Python GUI program on Windows/Linux/Mac computer. RadioHead protocol stack on that computer (my Python versions of RH). Addressed datagrams from PC to wireless gateway connected by serial link to computer.
Gateway bridges packets to/from wireless network and the serial port to the computer. No change in protocol; computer has network address.
End-node receives packets, ACKs each and when enough small packets have come in, it writes the cumulative data to SPI flash. The radio is made dormant during this SPI flash write (hundreds of bytes sent). But I could cause radio activity, e.g., have it transmit something while the SPI flash chip transfers are happening.
After writing flash, end node transmits "ACCEPTED" as it does for every packet. Gateway ACKs that and bridges the packet to the comptuer who then sends the next packet of data.
This goes on for 4500 bytes in my test case. (Binary version of a 12KB .hex file; can store any kind of file- 16MB of flash).
At the end, a CRC16 compute/compare is done by reading the flash. So there's CRC per packet and CRC for the entire file, and lost packet detection too (packets' sequence numbers are checked).

I can also address the file data to the gateway's SPI flash chip rather than the end-node. In this case, there's no radio I/O. This is a proof-check for if there's data corruption across the wireless link.

will advise.
steve
 
The SPI must be configured for DMA before starting transfers (refers to my DMA SPI transaction feature), and needs to start a transaction (Paul's SPI transaction feature) before using the SPI. When it has finished, it has to release the SPI again, so would something like this be the correct way to use it:

Yes, that looks reasonable.

I must confess, I didn't really consider DMA in this scheme. As your code develops, it may be necessary to add something to Teensy's version of the SPI library. Perhaps SPI.usingDMA(), so beginTransaction and endTransaction() can guard against a DMA event interrupting other transactions.

I don't have all the answers. These are certainly good questions!
 
So I'll try to reproduce this with the software environment I have - which is:

Please try running my test case. It requires only 2 boards with RFM69 radios. No other hardware is needed.

I'll attach the same rf69_client_extra.ino code to this message. Of course, you'll also need my patched copies of SPI and RadioHead. Or just install Teensyduino 1.20-rc2, which has both of them.

To run the test, program one board with File > Examples > RadioHead > rf69 > rf69_server, and leave it running. Best to position it in a way where you can see the LED, which blinks during communication because it's on the same pin as the SCK signal. If you don't get communication, that LED on the server board can give you a good indication whether the server heard the request and sent the reply back to the client.

Then program the other board with rf69_client_extra.ino, which tries to do the same as RadioHead's rf69_client example, but with extra unused communication on the SPI bus. While it's running, view with the Arduino Serial Monitor.

You can comment out DO_OTHER_SPI_ACTIVITY, or just reprogram with the original rf69_client example from RadioHead, to see the difference.

This test requires no special setup. Only two boards with RFM69 radios are needed. Just run those 2 programs on the 2 boards, and watch with the serial monitor. Easy!
 

Attachments

  • rf69_client_extra.ino
    2.3 KB · Views: 168
Please Steve, I beg of you, just run my test code as-is. Can you do just this 1 simple test for me? No Python, no integration into your project's software, just run RadioHead's rf69 server on 1 board and my rf69_client_extra.ino on the other (using RadioHead and SPI from 1.20-rc2) and watch in the Arduino Serial Monitor. Can you please do just that and report the results? Please?!
 
Yes, that looks reasonable.

I must confess, I didn't really consider DMA in this scheme. As your code develops, it may be necessary to add something to Teensy's version of the SPI library. Perhaps SPI.usingDMA(), so beginTransaction and endTransaction() can guard against a DMA event interrupting other transactions.

I don't have all the answers. These are certainly good questions!

I didn't take a close look at the current teesyduino SPI driver's internals, so I might be wrong about the following. As I see it, I have control over the SPI between a pair of beginTransaction() and endTransaction() calls, so I can use it in DMA mode. Right? If that is the case, the DMA driver would finish all pending DMA SPI transfers and then call endTransaction() when it has finished. It can also be paused, in which case it would also call endTransaction().

So, Paul, what happens when driver A called beginTransaction() and is transmitting and receiving data, and another driver B calls beginTransaction() (e.g. in a pin ISR) during driver A's transaction, before driver A called endTransaction()? As in:
Code:
//driver A:
Spi.beginTransaction();
selectPeripheralA();

transferImportantStuff(); <-- driver B calls beginTransaction() to handle an external event

deselectPeripheralA();
Spi.endTransaction();
Would driver B's call block until driver A called endTransaction()? I wouldn't expect that, because that would lock up the application.

I apparently have not yet understood what transactions actually do, and how they interact with each other (if they interact at all).

Is it possible to tell if a transaction is in progress, i.e. beginTransaction() was called without a terminating endTransaction()?

stevech, Paul, I don't know why you are now discussing the RadioHead library here. If you really feel that it is related to this thread, go ahead.

Regards

Christoph
 
what happens when driver A called beginTransaction() and is transmitting and receiving data, and another driver B calls beginTransaction() (e.g. in a pin ISR) during driver A's transaction, before driver A called endTransaction()?

The scenario isn't supposed to ever happen, because SPI.beginTransaction() masks all the interrupts that could call SPI.beginTransaction().

It's a voluntary system that depends upon any library implementing SPI within interrupts to call SPI.usingInterrupt().

Is it possible to tell if a transaction is in progress, i.e. beginTransaction() was called without a terminating endTransaction()?

Normally, no. But there is some test code you can enable that detect this condition and turns on a LED.
 
Ah, I found this needle in the haystack (as below). Server example unmodified. Transactions on the client side only.
I'll give it a try now.
steve


Please try running my test case. It requires only 2 boards with RFM69 radios. No other hardware is needed.

I'll attach the same rf69_client_extra.ino code to this message. Of course, you'll also need my patched copies of SPI and RadioHead. Or just install Teensyduino 1.20-rc2, which has both of them.

To run the test, program one board with File > Examples > RadioHead > rf69 > rf69_server, and leave it running. Best to position it in a way where you can see the LED, which blinks during communication because it's on the same pin as the SCK signal. If you don't get communication, that LED on the server board can give you a good indication whether the server heard the request and sent the reply back to the client.

Then program the other board with rf69_client_extra.ino, which tries to do the same as RadioHead's rf69_client example, but with extra unused communication on the SPI bus. While it's running, view with the Arduino Serial Monitor.

You can comment out DO_OTHER_SPI_ACTIVITY, or just reprogram with the original rf69_client example from RadioHead, to see the difference.

This test requires no special setup. Only two boards with RFM69 radios are needed. Just run those 2 programs on the 2 boards, and watch with the serial monitor. Easy!
 
Moving on - after the DMA memcpy worked, I tried to set up an SPI transfer using the new DMA functions. The overall plan was to use two channels (just like the current DmaSpi driver does), one for Tx and one for Rx. Here's the sketch:

Code:
#include <WProgram.h>

#include "DMAChannel.h"
#include "SPI.h"

/** Hardware setup:
 plain Teensy 3.1 with DOUT connected to DIN
 nothing else
**/

#define DMASIZE 10
uint8_t src[DMASIZE];
volatile uint8_t dest[DMASIZE];
volatile uint8_t devNull;

void dmaSpiDone()
{
  Serial.println("SPI Rx ISR: DMA SPI transfer done");
}

void setup()
{
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
  Serial.println("Hi!");

  /** Prepare source and destination **/
  for (uint8_t i = 0; i < DMASIZE; i++)
  {
    src[i] = i;
  }
  memset((void*)dest, 0x00, DMASIZE);
  Serial.println("Buffers are prepared");Serial.flush();

  /** set up SPI **/
  SPISettings spiSettings;
  SPI.begin();
  SPI.beginTransaction(spiSettings);
  SPI0_SR = 0xFFFFFFFF;
  SPI0_RSER = 0;
  SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS;
  SPI0_RSER |= SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; 
  
  Serial.println("SPI is prepared"); Serial.flush();

//  /** set up a DMA channel for transmitting src to the SPI **/
  DMAChannel txChannel;
  txChannel.sourceBuffer(src, DMASIZE);
  txChannel.destination((volatile uint8_t&)SPI0_PUSHR);
  txChannel.disableOnCompletion();
  txChannel.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
  Serial.println("Tx channel is prepared"); Serial.flush();

  /** set up a DMA channel for receiving from the SPI to dest **/
  DMAChannel rxChannel;
  rxChannel.source((volatile uint8_t&)SPI0_POPR);
  rxChannel.destinationBuffer(dest, DMASIZE);
  rxChannel.disableOnCompletion();
  rxChannel.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX);
  rxChannel.attachInterrupt(dmaSpiDone);
  rxChannel.interruptAtCompletion();
  
  Serial.println("Rx channel is prepared"); Serial.flush();

  Serial.println("Starting DMA transfer");
  rxChannel.enable();
  txChannel.enable();
  while(!rxChannel.complete())
  {
  }
  Serial.println("Finished DMA transfer");

  int n = memcmp((const void*)src, (const void*)dest, DMASIZE);
  Serial.printf("bytes correct: %d\n", n);

  Serial.println("Setup done."); Serial.flush();
  SPI.endTransaction();
  SPI.end();
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWriteFast(LED_BUILTIN, true);
  delay(500);
  digitalWriteFast(LED_BUILTIN, false);
  delay(500);
}

The problem is - of course - that it doesn't work. The output is:
Code:
Hi!
Buffers are prepared
SPI is prepared
Tx channel is prepared
Rx channel is prepared
Starting DMA transfer

So the rx channel doesn't get enough triggers from the SPI. That can mean that either the Rx channel is not configured correctly, or that the Tx channel isn't. I'm not sure how to debug this because I'm not familiar with the DmaChannel methods. I tried comparing my DmaSpi code with their implementation, and my overall impression is that nothing essential is missing.

Are there any obvious errors in the channel setup?
 
OK, second try. I managed to get at least an interrupt when the Tx channel has finished, but still no success for the Rx channel. Sketch:
Code:
#include <WProgram.h>

#include "DMAChannel.h"
#include "SPI.h"

/** Hardware setup:
 plain Teensy 3.1 with DOUT connected to DIN
 nothing else
**/

/** buffers to send from and to receive to **/
#define DMASIZE 10
uint8_t src[DMASIZE];
volatile uint8_t dest[DMASIZE];

/** The DMA channel objects **/
DMAChannel txChannel;
DMAChannel rxChannel;

/** for timing checks **/
elapsedMicros txTime;

/** supposed to be called when the Tx DMA is done **/
void txComplete()
{
  txChannel.clearInterrupt(); // we need this to not get any more ISR calls
  uint32_t t = txTime;
  Serial.println("Tx complete"); Serial.flush();
  Serial.println(t);
  txTime = elapsedMicros();
}

/** supposed to be called when the Rx DMA is done **/
void rxComplete()
{
  Serial.println("Rx complete"); Serial.flush();
}

/** Wait for and consume a keypress over USB **/
void waitForKeyPress()
{
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
}

/** Compare the buffers and print the destination contents if there's a mismatch **/
void compareBuffers()
{
  int n = memcmp((const void*)src, (const void*)dest, DMASIZE);
  if (n == 0)
  {
    Serial.println("Tx->Rx correct");
  }
  else
  {
    Serial.print("Tx->Rx incorrect; Rx: ");
    for (uint8_t i = 0; i < DMASIZE; i++)
    {
      Serial.printf("0x%02x ", dest[i]);
    }
    Serial.print('\n');
  }
}  

void setup()
{
  waitForKeyPress();
  Serial.println("Hi!");

  /** Prepare source and destination **/
  for (uint8_t i = 0; i < DMASIZE; i++)
  {
    src[i] = i;
  }
  memset((void*)dest, 0x00, DMASIZE);
  Serial.println("Buffers are prepared");Serial.flush();

  /** set up SPI **/
  SPISettings spiSettings;
  SPI.begin();
  
  // transmit 10 bytes and measure time to get a feel of how long that takes
  SPI.beginTransaction(spiSettings);
  elapsedMicros us;
  for (uint8_t i = 0; i < DMASIZE; i++)
  {
    dest[i] = SPI.transfer(src[i]);
  }
  uint32_t t = us;
  Serial.print("Time for transfer: ");Serial.print(t);Serial.println("us");
  SPI.endTransaction();
  compareBuffers();
  
  Serial.println("Press a key to continue");
  waitForKeyPress();
  
  /** Prepare the SPI for DMA operation **/
  SPI.beginTransaction(spiSettings);
  
  SPI0_SR = 0xFF0F0000;
  SPI0_RSER = 0;
  SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; 
  
  Serial.println("SPI is prepared"); Serial.flush();

//  /** set up a DMA channel for transmitting src to the SPI **/
  txChannel.sourceBuffer(src, DMASIZE);
  txChannel.destination((volatile uint8_t&)SPI0_PUSHR);
  txChannel.disableOnCompletion();
  txChannel.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
  txChannel.attachInterrupt(txComplete);
  txChannel.interruptAtCompletion();
  Serial.println("Tx channel is prepared"); Serial.flush();

  /** set up a DMA channel for receiving from the SPI to dest **/
  rxChannel.source((volatile uint8_t&)SPI0_POPR);
  rxChannel.destinationBuffer((volatile uint8_t*)dest, DMASIZE);
  rxChannel.disableOnCompletion();
  rxChannel.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX);
  rxChannel.attachInterrupt(rxComplete);
  rxChannel.interruptAtCompletion();
  
  Serial.println("Rx channel is prepared"); Serial.flush();

  Serial.println("Press a key to continue");
  waitForKeyPress();

  Serial.println("Starting DMA transfer");
  memset((void*)dest, 0x00, DMASIZE);
  txTime = elapsedMicros();
  rxChannel.enable();
  txChannel.enable();
  delay(100);
  compareBuffers();
  
  while(!rxChannel.complete())
  {
  }
  Serial.println("Finished DMA transfer");

  SPI.endTransaction();
  SPI.end();
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWriteFast(LED_BUILTIN, true);
  delay(500);
  digitalWriteFast(LED_BUILTIN, false);
  delay(500);
}

Output:
Code:
Hi!
Buffers are prepared
Time for transfer: 24us
Tx->Rx correct
Press a key to continue
SPI is prepared
Tx channel is prepared
Rx channel is prepared
Press a key to continue
Starting DMA transfer
Tx complete
12
Tx->Rx incorrect; Rx: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
So the Rx channel never completes what it's configured for.

I don't understand why the Rx channel doesn't complete, and I'm not sure if it even sees any data. The destination buffer dump indicates that no bytes are written, yet the SPI is configured to generate DMA requests, and there's a DMA channel waiting for them.

Any hints are welcome.
 
Paul, in DMABaseClass::source(const unsigned char &p), what is the reason for this if-statement:
Code:
if ((uint32_t)p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 1;
p is a reference, so using its value in the if-statement doesn't make sense to me. Also, there is no else to the if, so reconfiguring a channel can silently go wrong if NBYTES was nonzero before and the channel is reconfigured to use a peripheral address.

What was your idea behind this?
 
I've checked the SPI's status register after the transfer (100 ms after tx channel was enabled) and found that both the rx drain and the rx overflow flag are set. When I don't enable the tx channel, they are not set. To me that means that the rx channel doesn't see the drain flag and therefore doesn't copy from POPR to memory.
 
Paul, in DMABaseClass::source(const unsigned char &p), what is the reason for this if-statement:
Code:
if ((uint32_t)p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 1;
p is a reference, so using its value in the if-statement doesn't make sense to me. Also, there is no else to the if, so reconfiguring a channel can silently go wrong if NBYTES was nonzero before and the channel is reconfigured to use a peripheral address.

What was your idea behind this?

It's meant to be a convenience feature, so you don't need to use transferSize(1). It only applies when the source address is regular memory (below address 0x40000000 is regular memory, above is I/O and system registers), and when the transfer size hasn't already been explicitly or implicitly set (the value in NBYTES is still zero).

Many of the peripherals organize their 32 bit registers so all the stuff you might need for a certain use is in 8 or 16 of the bits. Often bytes or 16 bit words need to be written to 1/4 or 1/2 of a 32 bit register to accomplish some goal, the transfer size is never implied by setting a register address. Usually you'd configure either the source or destination as a regular memory address, so whichever one isn't a hardware register automatically sets NBYTES for you. The most common case is fixed hardware register to memory-based buffers, where all the buffer source & destination functions set the transfer size based on your data type.

Of course, you can always call transferSize() to explicitly configure 1, 2 or 4 byte transfers. But unless you're doing DMA directly from hardware registers directly to other hardware registers, it gets configured automatically based on the data types you used.
 
These will NOT return a reference to the underlying code:
Code:
txChannel.destination((volatile uint8_t&)SPI0_PUSHR);
rxChannel.source((volatile uint8_t&)SPI0_POPR);

SPIO_PUSHR and SPIO_POPR are not objects.

You will have to do something like this:
Code:
txChannel.transferSize(2); // or however many bytes are read per transfer, 2 is for a short (2 bytes)
dmaChan0.TCD->SADDR = &SPI0_PUSHR;

Or do what I finally did in this thread (http://forum.pjrc.com/threads/26418-DMA-Modes) and write a new function call to accept memory locations.

Those are the only 2 ways I found to set the correct register into the SADDR.
 
It's meant to be a convenience feature, so you don't need to use transferSize(1). It only applies when the source address is regular memory (below address 0x40000000 is regular memory, above is I/O and system registers), and when the transfer size hasn't already been explicitly or implicitly set (the value in NBYTES is still zero).

So shouldn't it be
Code:
if ((uint32_t)&p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 1;
(notice the '&' in front of the 'p') or something similar then? Your code uses the value stored at p, turning it into an address is missing. All other code in that method does it, just the if doesn't.
 
These will NOT return a reference to the underlying code:
Code:
txChannel.destination((volatile uint8_t&)SPI0_PUSHR);
rxChannel.source((volatile uint8_t&)SPI0_POPR);

SPIO_PUSHR and SPIO_POPR are not objects.
If I want to create a reference, I don't need an object. Plain old ints aren't objects, and I can create a reference to them.

The compiler doesn't complain, so if you're right and I cannot do the above, what is passed around here in this case? When I check the Tx channel's destinationAddress() after doing the above call, it is 0x4002C034 and that is the correct address of SPI0_PUSHR. However, the Rx channel's srouceAddress() doesn't seem to be correct. How can that happen?

You will have to do something like this:
Code:
txChannel.transferSize(2); // or however many bytes are read per transfer, 2 is for a short (2 bytes)
dmaChan0.TCD->SADDR = &SPI0_PUSHR;
That seemed to do the trick, I now have
Code:
rxChannel.source((volatile uint8_t&)SPI0_POPR);
rxChannel.TCD->SADDR = &SPI0_POPR;
rxChannel.transferSize(1);
Serial.println((uint32_t)rxChannel.sourceAddress(), 16);
So let's expand this using the code in source():
Code:
rxChannel.TCD->SADDR = &SPI0_POPR; // however as demolishun pointed out this doesn't work as intended
rxChannel.TCD->SOFF = 0;
rxChannel.TCD->ATTR_SRC = 0;
if ((uint32_t)SPI0_POPR < 0x40000000 || rxChannel.TCD->NBYTES == 0) rxChannel.TCD->NBYTES = 1; // this is weird as it ends up reading POPR
rxChannel.TCD->SLAST = 0;
rxChannel.TCD->SADDR = &SPI0_POPR;
rxChannel.TCD->NBYTES = 1;

Anyway now that the transfer seem to work, I can continue work on the library. Thanks!
 
@christoph,
Hey, I had to create a work account since I don't have my password from home here. I think I was way off in my conclusion that the refs are not working with memory locations. After reading your experience with one function versus another I made a test program to see if the refs were working or not.

Code:
/* 
  References and registers.
  A test program to understand what the heck is going on.
*/

// function that check mem address with pointer
void pointerPrint(void * ptr){
  Serial.println((unsigned long)ptr, HEX);
}

// functions that take refs
void test1(volatile const unsigned short & p){
  volatile const unsigned short* mem = &p;
  
  Serial.println((unsigned long)mem, HEX);
}

const int ledPin = 13;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  
  Serial.begin(9600);
}

void loop() {  
  
  delay(2000);
  
  pointerPrint((void *)&SPI0_POPR);
  test1((volatile const unsigned short &)SPI0_POPR);
  
  pointerPrint((void *)&SPI0_PUSHR);
  test1((volatile const unsigned short &)SPI0_PUSHR);
  
  pointerPrint((void *)&ADC0_RA);
  test1((volatile const unsigned short &)ADC0_RA);
}

The result:
Code:
4002C038
4002C038
4002C034
4002C034
4003B010
4003B010

So the refs are working fine. So I guess there is something going on with the source function. I am not sure what exactly is wrong. I will look at my code tonight, but I suspect it is what you pointed out:
Code:
if ((uint32_t)p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 1;

The compiler doesn't complain, so if you're right and I cannot do the above, what is passed around here in this case? When I check the Tx channel's destinationAddress() after doing the above call, it is 0x4002C034 and that is the correct address of SPI0_PUSHR. However, the Rx channel's srouceAddress() doesn't seem to be correct. How can that happen?
I am not sure how one can pass and one fail. That is really confusing. In my test everything passed. How can that be? Very strange. Does using a ref to assign to the SADDR cause some wonkyness? I don't know. I will create a test tonight that checks to see what happens and read the contents of SADDR.

I felt like I needed to chime since I was wrong about the references. Sorry for any confusion.
 
Everything worked fine when I added the & to the if-statement, resulting in
Code:
if ((uint32_t)&p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 1;
In my case, the original code without the & read from SPI0_POPR and used some invalid value from that register. If that invalid value was greater than 0x40000000, NBYTES was not set to 1 and so my DMA operation failed. I've issued a pull request for that: https://github.com/PaulStoffregen/cores/pull/33
 
Let's get back into the nasty world of getting along with other libraries.

I have to decide on which level the DmaSpi library handles SPI transactions.

What to look out for:
  1. A DmaSpi transfer doesn't start when it is configured and registered with the library, but later when the library is allowed to use the SPI and and previous transfers are finished
  2. DmaSpi operation is started (it begins handling pending transfers) and stopped (it finishes a current transfer and postpones pending transfers) by the application
  3. SPI devices have very different interfaces, and it might be necessary to use more than one transfer between selecting and deselecting the chip
  4. This means that the ChipSelect classes don't necessarily select and deselect a chip
  5. Transfers begin outside or inside the rx DMA channel's ISR
  6. Transfers always end inside the rx DMA channel's ISR

Considering 1) and 2), handling SPI transactions between start() and stop() could be a waste of SPI time if no transfers are pending - other libraries could make use of it instead. Nonetheless, this is what the library is currently doing. This way, no other library (such as the CC3000 library that uses SPI transactions) can interrupt DmaSpi.

Handling SPI transactions on a ChipSelect level could be dangerous because people writing code for devices in 3) (4 as well) would be responsible for calling beginTransaction() and endTransaction(). As the transition between two transfers might occur in the rx channel's ISR, see 5) and 6), I'm not sure how this would interact with other libraries kicking in at exactly this point, especially because the SPI might be left configured for use with DMA when other libraries start using it.

My gut feeling is that I should make the chip select classes responsible for calling beginTransaction and endTransaction, because the ActiveLowChipSelect class is already included in the library and could be used for most purposes (btw it doesn't depend on DmaSpi so it can also be used in other libraries. We could turn it into a separate library). That way the SPI is not blocked for longer than necessary, but again I'm not sure what happens when another library tries to use the SPI while DmaSpi is between to transfers in the ISR.
 
Status
Not open for further replies.
Back
Top