ItsyBitsy M4 Express (ATSAMD51 ) PWM and SPI / Ethernet (W5500) problems

Hi,

I’m trying control a LED with a ItsyBitsy M4 Express by sending it Art-Net data.
For the Ethernet module I’m using a Wiznet W5500.

With the great examples posted by @MartinL I have managed to control the brightness of the LED.
Link to thread

But when I’m receiving data on the UDP port the Arduino runs for a couple of seconds and then stops/crashes. When not receiving data on the UDP port the Arduino runs fine. If you comment out all the PWM code the Ethernet code runs without problems.

There is no difference in the outcome when using other peripheral channels: TCC0, TCC1, TCC2 and TC0, with their corresponding pins.
It will run for a few seconds and then the Arduino crashes. When printing Udp.parsePacket(); you can see that the packet sizes are erratic just before it crashes.

I suspect that the problem is that the SPI connection to the W5500 chip is using the same Generic timer as I’m using for the PWM. (GCLK7, as in all of the posted examples).

Does anybody know you to change the Generic timer on the ATSAMD51, or is this even possible?
All the examples use the GCLK7. Or is the problem to be found elsewhere?

The complete code:

#define Serial SERIAL_PORT_USBVIRTUAL

#include <Ethernet.h>
#include <EthernetUdp.h>
EthernetUDP Udp;

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 1, 12, 200);

unsigned int localPort = 6454;      // local port to listen on
unsigned int counter;

void setup() {
  
  //Generic clock

  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
  //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
  //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization


  //D7

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) G(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);

  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV1 |        // Set prescaler to 8, 48MHz/1 = 48MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)

  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

    TCC0->PER.reg = 7999;                            // Set-up the PER (period) register 6kHz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization

  TCC0->CC[0].reg = 3999;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0

  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization


  // start the Ethernet
  Ethernet.begin(mac, ip);

  // start UDP
  Udp.begin(localPort);

}

void loop() {
   //This loop will crash after a few seconds when receiving data on the UDP port.
   
  /*   
   //Not needed for this example, but can be enabled to proof working of PWM
    TCC0->CC[0].reg = 7999;                // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
    while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

    delay(500);

    TCC0->CC[0].reg = 0;                // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
    while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

    delay(500);
  */


  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print(counter);
    Serial.print(F(" - Received packet of size "));
    Serial.println(packetSize); 
    counter++; 
  }
}

The Ethernet library is the standard Ethernet 2.0 library without modifications.

Thank you

Hi qniens,

But when I'm receiving data on the UDP port the Arduino runs for a couple of seconds and then stops/crashes. When not receiving data on the UDP port the Arduino runs fine. If you comment out all the PWM code the Ethernet code runs without problems.

The SAMD51's TCx and TCCx timer peripherals run completely autonomously and independently from the CPU.

I suspect the issue has more to do with the delay(500) functions. What happens if you comment out the delay(500) functions, but retain the writes to the TCC0's CC0 register, does the Ethernet code still function correctly?

Thank you for your reply.
It makes no difference as the delay() functions are comment out.

For clarity here is the some code without the delay functions etc.
It has a simple counter that will fade the LED up.

#define Serial SERIAL_PORT_USBVIRTUAL

#include <Ethernet.h>
#include <EthernetUdp.h>
EthernetUDP Udp;

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 1, 12, 200);

unsigned int localPort = 6454;      // local port to listen on
unsigned int i;
unsigned int c;

void setup() {

  //Generic clock

  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
  //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
  //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization


  //D7

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) G(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);

  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV1 |        // Set prescaler to 8, 48MHz/1 = 48MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)

  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

    TCC0->PER.reg = 7999;                            // Set-up the PER (period) register 6kHz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization

  TCC0->CC[0].reg = 3999;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0

  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization


  // start the Ethernet
  Ethernet.begin(mac, ip);

  // start UDP
  Udp.begin(localPort);

}

void loop() {
  //This loop will crash after a few seconds when receiving data on the UDP port.

  c++;
  if (c > 7999){
    c=0;
  }

  TCC0->CCBUF[0].reg = c; 
  while (TCC0->SYNCBUSY.bit.CC0);

  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print(i);
    Serial.print(F(" - Received packet of size "));
    Serial.println(packetSize);
    i++;
  }

}

The Serial Monitor output is:

(starting from 0...)
818 - Received packet of size 528
819 - Received packet of size 528
820 - Received packet of size 528
821 - Received packet of size 528
822 - Received packet of size 528
823 - Received packet of size 528
824 - Received packet of size 528
825 - Received packet of size 528
826 - Received packet of size 528
827 - Received packet of size 528
828 - Received packet of size 528
829 - Received packet of size 1056
830 - Received packet of size 65453

The default packet size of a Art-Net packet is 528. As you can see the last two packets (829, 830) are different. 829 = double size, 530 = is close to the 16 bit max.

The exact moment it stops differences every time. For example, re-running the same code gives the following serial output:

(starting from 0...)
4645 - Received packet of size 528
4646 - Received packet of size 528
4647 - Received packet of size 528
4648 - Received packet of size 1056
4649 - Received packet of size 12799
4650 - Received packet of size 1535
4651 - Received packet of size 65306

And a third time:

(starting from 0...)
165 - Received packet of size 528
166 - Received packet of size 528
167 - Received packet of size 1056
168 - Received packet of size 65280

When you comment out in the setup:
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE);

and in the loop:
TCC0->CCBUF[0].reg = c;
while (TCC0->SYNCBUSY.bit.CC0);

The code runs fine for hours+. Of course with no LED. That’s way I thought it maybe has to do with the timer. Do you have a suggestion what I could check? Thank you

If you're using the TCC0's buffered CCBUF0 register, then it's not necessary to synchonize CC0. Just delete the line:

while (TCC0->SYNCBUSY.bit.CC0);

On the SAMD51 the buffered CCBUFx registers have no corresponding SYNCBUSY synchronization bit, like they do on the SAMD21.

Writing to the buffered CCBUFx registers will cause the value to be loaded into the CCx registers at the beginning of the new timer cycle. This prevents changes to the duty-cycle from causing glitches on you PWM output. Writes to the CCx registers on the other hand take effect at the output immediately.

Dear MartinL,

Thank you for the feedback. I don’t know that. I deleted the line.
Unfortunately the code still crashes after a few seconds.

The code runs stable (without the LED of course) if you comment out the following code:

 TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
 while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

When enabling TCC0 the code becomes unstable. I tried it also with timers TCC1, TCC2, TC0, same result.

Do you have another suggestion where the problem may be?

The complete code:

#define Serial SERIAL_PORT_USBVIRTUAL

#include <Ethernet.h>
#include <EthernetUdp.h>
EthernetUDP Udp;

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(10, 1, 12, 200);

unsigned int localPort = 6454;      // local port to listen on
unsigned int i;
unsigned int c;

void setup() {
  //Generic clock

  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
  //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
  //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization


  //D7

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) G(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);

  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV1 |        // Set prescaler to 8, 48MHz/1 = 48MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)

  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

    TCC0->PER.reg = 7999;                            // Set-up the PER (period) register 6kHz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization

  TCC0->CC[0].reg = 3999;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

  // start the Ethernet
  Ethernet.begin(mac, ip);

  // start UDP
  Udp.begin(localPort);
}

void loop() {
  //This loop will crash after a few seconds when receiving data on the UDP port.

  c++;
  if (c > 7999){
    c=0;
  }

  TCC0->CCBUF[0].reg = c; 

  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print(i);
    Serial.print(F(" - Received packet of size "));
    Serial.println(packetSize);
    i++;
  }

}

Hi qniens,

I was thinking if could possibly have something to do with the LED? Are you using the microcontroller’s pin to drive the LED directly, or through a buffer/transistor?

It’s just that by default the SAMD51’s pins are only capable sourcing or sinking 2mA. It’s possible however to increase the pin’s driver strength so that it can source/sink 8mA by simply changing the line:

PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

to:

PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].reg = PORT_PINCFG_DRVSTR | PORT_PINCFG_PMUXEN;

The Itsy Bitsy M4 also has a +5V buffered output on digital pin D5 capable of supplying up to a maximum of 35mA. It’s designed for driving NeoPixels, but is equally suitable for standard LEDs as well. In this case you just need to increase the LED’s resistor, in order to account for the increase in voltage.

Dear MartinL,
Thank you so much for your help.
Indeed the problem was in the test set-up with a common cathode RGB LED.
With the resistors I was using (100 ohm) the circuit was drawing (much) more then 2mA from the pins.

When using 1k ohm resistors the test code runs without problems.

Once again, thank you very much for your help!