DMA not working between Arduino DUE and ethernet shiled (W5500)

Hi !

I have a little project which consists of taking every impulsions from an encoder and sending them through UDP. The encoder delivers 16384 signals (ACP) for 1 turn and 1 signal (ARP) every turn (to mark the north). The encoder can go from 4.5 RPM (ACP period is ~814 µs) to 15 RPM (ACP period is ~244 µs). The goal of this project is to measure the length of each ACP (the high time and period) and see if there is an ARP and send these informations through UDP (I am using an ethernet shield with W5500).
For this project, I use an Arduino DUE. To measure the length of the ACP, I use the SysTick value (for maximum accuracy) and I make the difference to have the number of ticks.

For 1 information, I need 4 bytes (I store directly the number of ticks, I transform the number of ticks in the software when I receive my packets). These 4 bytes are stored in a buffer (usually, a buffer of 400x4 bytes) and it is done in an interruption. The ACP high time lasts everytime 80 µs (no matter which the speed rotation). So, the worst case is to have an ACP at 15 RPM (80 µs at high and 164 µs at low). When a buffer is full, I set a flag in my interruption to send the buffer in my main loop and I switch of buffer to fill the other one and so on.

For this project, I use the Ethernet library for DMA purpose (which normally is a good solution to not "disturb" the main program). I picked it from this thread.

Here is my issue:
When I send my buffers, I have, every 400 bytes (indeed, total size of the buffer divided by the number of bytes I use. So, if I take a buffer of 500x4 bytes, something will happen every 500 bytes), something happens which, apparently, causes a drop in my ticks (here is the high time of the ACP for a little recording) (see picture).
I spent over 2 months trying to get rid of this issue but I don't manage to understand what happens.

Finally, here is what I already tried:

  • I checked everything with an oscilloscope and there are not such differences every 400 ACP
  • I checked the duration in interrupts and it takes less than 40 µs for the high and 100 µs for the low
  • I tried to add a dummy byte (so 1 information needs 5 bytes) so the buffer would be 400x5 bytes. The error occurs also every 400 bytes.
  • I tried to mix the library with the TurboSPI library (here) but nothing relevant
  • I tried to use the memcpy 32 bits (here) which uses DMA and sending it word per word. So my information would still need 4 bytes but the buffer would store 400 words.
  • I used the regular ethernet library (with regular SPI) and I have the same behaviour. It shows that the library do not put the buffer in DMA so ...

So, what I am trying to ask is whether to have more information of what causes those drops or how to use the DMA to send the buffer from the MCU to the Ethernet shield ?

Thank you all!

Here is my code :

/**************************************************************************************/
/* ACP_ARP_recorder.                                                                  */
/*                                                                                    */
/* Verson      : 8.0                                                                  */
/* Date        : 26/01/2021                                                           */
/**************************************************************************************/

#include <Ethernet.h>

// Functions which calculates the difference between 2 values of ticks and return the difference
// The SysTick counts until 84000 (which corresponds to 1 ms) and then is comes back to 0
uint32_t ticks_diff(uint32_t t0, uint32_t t1) {
  return ((t0 < t1) ? 84000 + t0 : t0) - t1;
}

//#define DEBUG

//Constants
#define ACP_PIN_HIGH            19
#define ACP_PIN_LOW             20
#define ARP_PIN                 21

#define NB_of_BYTES             4
#define SIZE                    400
#define TOTAL_SIZE              NB_of_BYTES*SIZE
#define BAUDSPEED               57600



byte mac[] = {
  0xA8, 0x61, 0x0A, 0xAE, 0x81, 0xFA
};

IPAddress ip(192, 168, 1, 20);
unsigned int localPort = 60006;

EthernetUDP Udp;

//Variables
// Both buffers that store values
volatile char buff1[TOTAL_SIZE];
volatile char buff2[TOTAL_SIZE];


//Variables for interruption routines
volatile uint32_t acpRISE     = 0;
volatile uint32_t acpRISE_pre = 0;
volatile uint32_t acpFALL     = 0;
volatile uint32_t acpHIGH     = 0;
volatile uint32_t acpPERIOD   = 0;

volatile boolean writing          = 0;
volatile boolean arpNbr           = 0;
volatile boolean change_buffer    = false;
volatile unsigned int increment   = 0;


/**************************************************************************************/
/* Initialization                                                                     */
/**************************************************************************************/
void setup()
{
  
#ifdef DEBUG
  //Setup serial output
  Serial.begin(BAUDSPEED);
#endif

  //Setup ACP and ARP inputs
  pinMode(ACP_PIN_HIGH, INPUT_PULLUP);
  pinMode(ACP_PIN_LOW, INPUT_PULLUP);
  pinMode(ARP_PIN, INPUT_PULLUP);

  //Attach interruption at the end of the initialisation (recording starts...)
  attachInterrupt(digitalPinToInterrupt(ACP_PIN_HIGH), acpRising, RISING);
  attachInterrupt(digitalPinToInterrupt(ACP_PIN_LOW), acpFalling, FALLING);
  attachInterrupt(digitalPinToInterrupt(ARP_PIN), arpRising, RISING);

#ifdef DEBUG
  Serial.println("Initialisation ISRs done");
#endif

  //Ethernet setup
  Ethernet.begin( mac, ip); //Inialize the Ethernet
#ifdef DEBUG
  Serial.println("Initialisation Ethernet begin done");
#endif
  Udp.begin(localPort); //Initialize UDP
#ifdef DEBUG
  Serial.println("Initialisation UDP done");
#endif

  while (!Udp.parsePacket()); // Wait for host to send a packet

  delay(1500);
}

/**************************************************************************************/
/* Main loop                                                                          */
/**************************************************************************************/


void loop()
{
#ifdef DEBUG
  Serial.print(Udp.remoteIP(), Udp.remotePort());
#endif

  //Check if the writing should be done
  if (writing)
  {
    
    writing = false;

    if (change_buffer) {
      Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); //Initialize packet send
      Udp.write((char*)buff1, sizeof(buff1) / sizeof(char)); // The cast is needed because buff1 is volatile
      Udp.endPacket(); //End the packet
    }
    
    else
    {
      Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); //Initialize packet send
      Udp.write((char*)buff2, sizeof(buff2) / sizeof(char));
      Udp.endPacket(); //End the packet
    }
  }
}



/**************************************************************************************/
/* This method is called at the beginning of each ACP.                                */
/**************************************************************************************/
void acpRising()
{
  // Storing values for high time and period
  volatile uint32_t acpRISE_now = SysTick->VAL;
  acpPERIOD = ticks_diff(acpRISE, acpRISE_now);
  acpRISE_pre = acpRISE;
  acpRISE = acpRISE_now;
}

/**************************************************************************************/
/* This method is called at the end of each ACP.                                      */
/**************************************************************************************/
void acpFalling()
{
  // Storing values for high time and period
  volatile uint32_t acpFALL_now = SysTick->VAL;
  acpHIGH = ticks_diff(acpRISE_pre, acpFALL);

  // Checking which buffer needs to be filled
  if (!change_buffer)
  {
    // Filling the buffer
    buff1[increment++] = ((acpHIGH & 0x3F00) >> 8) | ((acpPERIOD & 0x10000) >> 10) | ((arpNbr << 7) & 0x80);
    buff1[increment++] = (acpHIGH & 0xFF);
    buff1[increment++] = ((acpPERIOD & 0xFF00) >> 8);
    buff1[increment++] = (acpPERIOD & 0xFF);
  }

  else {
    buff2[increment++] = ((acpHIGH & 0x3F00) >> 8) | ((acpPERIOD & 0x10000) >> 10) | ((arpNbr << 7) & 0x80);
    buff2[increment++] = (acpHIGH & 0xFF);
    buff2[increment++] = ((acpPERIOD & 0xFF00) >> 8);
    buff2[increment++] = (acpPERIOD & 0xFF);
  }
  
  arpNbr = 0;

  // If buffer is full ...
  if (increment == NB_of_BYTES * SIZE) {
    // ... retart at the beginning ...
    increment = 0;
    // ... changing buffer ...
    change_buffer = !change_buffer;
    // ... and enables the flag to send the buffer over UDP
    writing = true;
  }
  
  // Storing values for high time and period
  acpFALL = acpFALL_now;

}

/**************************************************************************************/
/* This method is called at the beginning of each ARP.                                */
/**************************************************************************************/
void arpRising()
{
  // If ARP, then flag is at 1
  arpNbr = 1;
}

Does the same problem occur if you only use a buffer of 200 x 4 bytes?

4 x 400 bytes is over the MTU for Ethernet so the data will need to be split into multiple packets and transmitted as multiple packets. If the data is under the MTU (1500) the data should go as a single packet. With 1500 bytes there will be 12000 bits, so at 10MHz that would take over 800uS (please check my arithmetic) not sure if that tallies with your experience. I'm assuming if there was a single packet it would not have to wait, but for multiple packets it might have to wait for one to go before sending the next.

If you are on 100Mbit it would obviously take less time.

Whether 10Mb/s or 100Mb/s the SPI could be the limiting factor for transmission speed.

Thank you for your reply!

I also did some tests with a (for th extreme) 10x4 bytes buffer, and my drops happen every 10 bytes too ... So the problem is the same not matter the size of the buffer ... :slight_smile:

I see nothing in your code related to DMA, unless the Ethernet library is using it which I doubt....IMO DMA in this code would be useless.

You could try out this test: Instead of sending your buffers (buff1 and buff2) thru Ethernet, send them to the Serial monitor and see if the last elements of each packet are wrong. If you get results as expected in Serial monitor, then Ethernet packet transmission screws up something. In the other case, review carefully your code with much smaller buffer sizes to understand where the bug lies.

Okay I am going to try this ! Thank you for your tips!!

Btw, I wanted to use DMA because as far as I know, when my buffer is in the DMA, I could send it through ethernet without uncomfortable internal ISRs (which could cause a stop in timers, I had this problem with Arduino MEGA, this is the main reason why I switched to DUE). So, my measurements in my external ISRs would have been really accurate :slight_smile:

ard_newbie:
You could try out this test: Instead of sending your buffers (buff1 and buff2) thru Ethernet, send them to the Serial monitor and see if the last elements of each packet are wrong. If you get results as expected in Serial monitor, then Ethernet packet transmission screws up something. In the other case, review carefully your code with much smaller buffer sizes to understand where the bug lies.

It is even worser. I got much more random drops... It has nothing to compare with before

Which library and what IDE version are you using?

I have IDE 1.8.13 and the default library that comes with but can see no evidence of DMA support in it.

[EDIT: Just reread your first post and found the library]

I am also using the version 1.8.13 :slight_smile:

A couple of suggestions for tests that may help to determine what is happening, or may not.

  1. If you stop the actual writing in the Loop function so that the code just fills the buffers and then swaps to the other buffer - does the problem still occur. This is a little of a long shot, but eliminates the UDP packet sending if stopping it does not eliminate the problem.

  2. If you change the code so that it sends the UDP packets every time round the loop what effect does that have?

Seems very strange that the effect is 1/4 of the buffer size. The Due is a 32 bit processor so it would be more understandable if it happened with the full buffer sending 4 x the SIZE but obviously there is nothing in the code for that.

countrypaul:

  1. If you stop the actual writing in the Loop function so that the code just fills the buffers and then swaps to the other buffer - does the problem still occur. This is a little of a long shot, but eliminates the UDP packet sending if stopping it does not eliminate the problem.

Then, how can I see my values in my buffers ?

countrypaul:
2) If you change the code so that it sends the UDP packets every time round the loop what effect does that have?

It is the exact same behaviour ...

Then, how can I see my values in my buffers ?

Yes, sorry, stupid suggestion - I obviously didn't think that through.

It is the exact same behaviour ...

Does that mean every 16bytes you get a drop in the ticks? Is the drop the same order of magnitude regardless of number of bytes between transmissions?

Just wondering if this is a feature of the library you are using - how does the default one behave? Could it be a consequence of the DMA transfer inhibiting the processor from carrying out an isr on time? I know this seems contradictory in that the whole point of the DMA is to free the processor.

countrypaul:
Does that mean every 16bytes you get a drop in the ticks? Is the drop the same order of magnitude regardless of number of bytes between transmissions?

Well, I also did a test with not 4 but 5 bytes of data (I just added 1 more dummy byte that simply counted form 0 to 255). I had also drops every 400 bytes (for a 400x5 bytes buffer).

countrypaul:
Just wondering if this is a feature of the library you are using - how does the default one behave? Could it be a consequence of the DMA transfer inhibiting the processor from carrying out an isr on time? I know this seems contradictory in that the whole point of the DMA is to free the processor.

As I wrote in my post, I switched to the regular SPI library and I had the same behaviour. It confirmed that the DUE did not use the DMA to send the data. :slight_smile:

I've reread this thread a couple of times, to make sure I've not misread things (though I can't be sure), and as a result I have had a look through the library you are using. What strikes me is that there is no information about what various #DEFINE symbols are expected to be, so where they are set. I had a look at the SDfat library and that seems to have a whole section setting the various SPI #DEFINES. The tests that were done on this ethernet library also included using the SDfat library and I wonder if this resulted in different settings to the default being used in the ethernet code, especially affecting spi_dma.cpp?

If when you use this library there is no DMA taking place and since you get the same results with the standard library that appears to be the case - I wonder if some #DEFINE needs to be set before the files are compiled. After the includes in spi_dma.cpp the first line is querying USE_ARDUINO_SPI_LIBRARY but I have no idea if this is set or not (and if so where).

I just wonder if you simply include SDfat.h before ethernet.h whether that changes any settings and has any effect on the SPI DMA settings. It is about the only thing I can think of at present but is probably a long shot.

countrypaul:
If when you use this library there is no DMA taking place and since you get the same results with the standard library that appears to be the case - I wonder if some #DEFINE needs to be set before the files are compiled. After the includes in spi_dma.cpp the first line is querying USE_ARDUINO_SPI_LIBRARY but I have no idea if this is set or not (and if so where).

The USE_ARDUINO_SPI_LIBRARYis defined in w5100.h file. It is used to choose wether you want to use the DMA or not. Obviously, I defined the USE_NATIVE_SAM3X_SPI constant to use DMA. :slight_smile:

countrypaul:
I just wonder if you simply include SDfat.h before ethernet.h whether that changes any settings and has any effect on the SPI DMA settings. It is about the only thing I can think of at present but is probably a long shot.

I tried to include the SdFat library (from greiman) and nothing changes ... I still have those drops...

I looked in the default Ethernet library and not the one you are using which is probably why I missed the definition of USE_ARDUINO_SPI_LIBRARY.

I did notice when looking in the correct library that the w5100.h has W5200_ETHERNET_SHIELD defined rather than W5500_ETHERNET_SHIELD . Have you changed this in your case?

Yep I paid attention to this and switched to W5500 :wink:

Is it worth putting a PrintLn statement into the spiBegin in spi-dma.c, just to ensure it is entering the routine with the settings we expect? Have you already tried that as I seem to be suggesting things you've already done :confused:

Once again I did it to be sure I used the right configuration :wink: