Serial1 transmissions landing me in Dummy Handler loop

I'm working on a custom SAMD51 board similar to Adafruit's Metro M4 or Feather M4 and I need to move a few pins and serial ports around. Though when I try to change the pins used for Serial1 my program gets locked up in a cortex_handler.c empty function called Dummy_Handler. From what I can gather this may have something to do with the interrupt routines that Arduino's Serial implements, but I'm not sure how or why exactly this wouldn't be working. Looking up the call stack the program is interrupted during a delay inside the Arduino Serial library. Even stranger when modify the variant file similarly and compile for Sparkfun's SAMD51 Thing Plus I'm able to print to Serial1 with no issues.

I'm able to initialize Serial1 just fine with something like Serial1.begin(9600); though when I attempt to print or write I'm pushed to the Dummy_Handler.

Currently I'm trying to use SERCOM0 on pins PA08/PA09 and I have also tried SERCOM4 on PA12/PA13 with the same result. Does this sound familiar to anyone and is this Dummy Handler loop a generic fault?

I can share my variant files and anything else that could help if this is potentially helpful - thanks.

Hi fostac,

Serial1 has already been defined by Adafruit’s SAMD51 core code and is available on digital pins D0 and D1.

However additional ports can be created using spare SERCOM modules. Here’s an example for the Adafruit Feather M4 that creates an additional serial port called Serial2 using SERCOM4, on pins A2 (Tx) and A3 (Rx):

#include "wiring_private.h"             // pinPeripheral() function

// Create Serial2 object
Uart Serial2 (&sercom4, A3, A2, SERCOM_RX_PAD_1, UART_TX_PAD_0);

void SERCOM4_0_Handler()                // Interrupt handler functions
{
  Serial2.IrqHandler();
}
void SERCOM4_1_Handler()
{
  Serial2.IrqHandler();
}
void SERCOM4_2_Handler()
{
  Serial2.IrqHandler();
}
void SERCOM4_3_Handler()
{
  Serial2.IrqHandler();
}
 
void setup() {
  Serial2.begin(115200);              // Open the Serial2 port at 115200 baud
 
  pinPeripheral(A2, PIO_SERCOM_ALT);  // Assign SERCOM functionality to A2
  pinPeripheral(A3, PIO_SERCOM_ALT);  // Assign SERCOM functionality to A3
}

void loop() {
  if (Serial2.available())            // Check if incoming data is available on A3
  {
    byte byteRead = Serial2.read();   // Read the most recent byte on A3
    Serial2.write(byteRead);          // Echo the byte back out on A2
  }
}

The code simply echos back text entered at the Arduino IDE console.

The key to selecting the correct SERCOM module is the multiplexing table in the SAMD51 datasheet. If the SERCOM module is in column C, use the PIO_SERCOM definition as an argument for the pinPeripheral() function:

pinPeripheral(12, PIO_SERCOM);  // Assign SERCOM functionality to D12

…where as if it’s in column D, use PIO_SERCOM_ALT:

pinPeripheral(A2, PIO_SERCOM_ALT);  // Assign SERCOM functionality to A2

The other point to note, is that the SAMD51’s serial port output assignments aren’t quite as flexible as the SAMD21’s, as the transmit pin must be on SERCOM/PAD[0].

MartinL: Serial1 has already been defined by Adafruit's SAMD51 core code and is available on digital pins D0 and D1.

]

Maybe I don't understand the variant model then - are you saying Serial1 is defined somewhere above the board variant files? When I'm trying to modify the Serial1 pins I'm changing the pin definitions in g_APinDescription within variant.cpp to reflect the pins and corresponding SERCOMs I'd like to us. Typically I'll copy off a similar enough board like the metro_m4 variant and use that as a starting point.

So the D0/D1 definitions of the Metro M4:

// 0/1 - SERCOM/UART (Serial1)
{ PORTA, 23, PIO_SERCOM, PIN_ATTR_PWM_G, No_ADC_Channel, TCC0_CH3, TC4_CH1, EXTERNAL_INT_7 }, // RX: SERCOM3/PAD[1]
{ PORTA, 22, PIO_SERCOM, PIN_ATTR_PWM_G, No_ADC_Channel, TCC0_CH2, TC4_CH0, EXTERNAL_INT_6 }, // TX: SERCOM3/PAD[0]

would end up looking like this:

// 0/1 - SERCOM/UART (Serial1)
{ PORTA, 8, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NMI },
    { PORTA, 9, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_9 },

or something like this if I'm trying to use the alternative SERCOM functionality

// 0/1 - SERCOM/UART (Serial1)
{ PORTA, 8, PIO_SERCOM_ALT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NMI },
    { PORTA, 9, PIO_SERCOM_ALT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_9 },

Is Serial1 immutable for some reason?

Hi fostac,

Is Serial1 immutable for some reason?

Well...yes and no.

If you go down to the bottom of the Metro M4's "variant.cpp" file, you'll see the line:

Uart Serial1( &sercom3, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;

Adafruit has already created the Serial1 object. Obviously, you're free to delete this and create your own, but I'd question whether it's worth it? It's easier to simply create new additional serial ports: Serial2, Serial3 and Serial4.

The question is whether your custom board has its own copy of the core code, or if it's using the core code of an existing board, such as the Metro M4. If it's the latter, any subsequent updates from Adafruit will overwrite any changes you make to the core code, including the "variant" files.

I see, so to maybe shine a little more light on what the build process looks like here - I've copied off a full version of Adafruit's forked Arduinocore-SAMD and placed it in the hardware folder of my project. I'm using arduino-cli to reference the core files, including the variants and other arduino core files, to build a binary of my sketch. I then copy off a variant from the variants directory, add it to boards.txt and modify the copied variant files. Simple pin swaps like LED_BUILTIN's D13 and moving other SERCOMS seem to work well enough though when I attempt to swap Serial1 in particular I get sent to the Dummy Handler on print.

The modifications to the variant.cpp looks like this:

// 0/1 - SERCOM/UART (Serial1)
  //{ PORTB, 17, PIO_SERCOM, PIN_ATTR_PWM_G, No_ADC_Channel, TCC0_CH4, NOT_ON_TIMER, EXTERNAL_INT_1 }, // RX: SERCOM5/PAD[1]
  //{ PORTB, 16, PIO_SERCOM, PIN_ATTR_PWM_G, No_ADC_Channel, TCC0_CH5, NOT_ON_TIMER, EXTERNAL_INT_0 }, // TX: SERCOM5/PAD[0]
  { PORTA, 8, PIO_SERCOM_ALT, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NMI },
  { PORTA, 9, PIO_SERCOM_ALT, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_9 },

...

Uart Serial1( &sercom2, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;

I've also tried to use other pins with the primary and alternate SERCOM combinations with no luck.

The intent with this whole build process is to sort of containerize all build files required to work on the sketch and generate a binary. Once it's all working I'll fork the adafruit core and maintain this board's variant in that fork.

Hi fostac,

Your changes to the "variant.cpp" file look good.

Looking in the "variant.h" file, what SERCOM modules are SPI and I2C using? Just to verify that they're not using SERCOM2.

Here’s a hack that should make things easier. I did SOME testing on a Grand Central, but thorough testing of multiple serial ports is a bit beyond my hardware and patience…
Let us know if there are problems (or if it works.)

/*
 * sercomlib.h
 * July 2020, by Bill Westfield
 * Released to the public domain.
 * 
 * This defines macros to pair up "un-used" sercom interfaces with Arduino driver-level
 *  definitions like UART/HardwareSerial, on SAMD51-based boards.
 *  
 *  Use it like:
 *     sercom_UseSerial(sercomNumber, SerialNumber)
 *  It's all relatively ugly pre-processor magic, and it
 *  assumes that symbols like sercomN, (from variant.cpp)
 *  and PIN_SERIALn_TX, PIN_SERIALn_TX (from variant.h)
 *  are already defined (True for Adafruit's Grand Central Station board,
 *  but not for some others.
 *  SerialN and the SERCOMn_x_Handler() end up being defined.
 *     
 *  At some point, this might get expanded to allow I2C and SPI as well,
 *  and support other chip families.
 */
#include <variant.h>
#define sercom_UseSerial(_sercom, _serialNum) \
  Uart Serial##_serialNum( &sercom##_sercom, PIN_SERIAL##_serialNum##_RX, PIN_SERIAL##_serialNum##_TX, SERCOM_RX_PAD_1, UART_TX_PAD_0 ) ; \
  void SERCOM##_sercom##_0_Handler(void) {  Serial##_serialNum.IrqHandler(); } \
  void SERCOM##_sercom##_1_Handler(void) {  Serial##_serialNum.IrqHandler(); } \
  void SERCOM##_sercom##_2_Handler(void) {  Serial##_serialNum.IrqHandler(); } \
  void SERCOM##_sercom##_3_Handler(void) {  Serial##_serialNum.IrqHandler(); }

And a sample sketch:

#include "sercomlib.h"
/*
 *  July 2020 by Bill Westfield.  Released to the public domain.
 */
/*
 *  Attach the extra SERCOM peripherals to Serial Ports (UART mode.)
 *  Note that for Grand Central, the required PIN_SERIALn_RX/etc defines are already in variant.h
 *  For other boards, it may be necessary to define them.
 */
//              SERCOM#, Serial#
sercom_UseSerial(  4,      2)   // Serial2 - pins 18 and 19
sercom_UseSerial(  1,      3)   // Serial3 - pins 16 and 17
sercom_UseSerial(  5,      4)   // Serial4 - pins 14 and 15


void setup() {
  // Start ports at different speeds to demo their independence.
  Serial.begin(9600);
  Serial1.begin(19200);     // Serial1 (pins 0 and 1) alread defined.
  Serial2.begin(38400);
  Serial3.begin(115200);
  Serial4.begin(9600);
  while (!Serial)           // Wait for USB Serial to finish initialized
    ;
}

void loop() {
  Serial.println("This is Serial");
  
  Serial1.println("This a Serial 1");    // Check Transmit
  while (Serial1.available()) {          // Check receive
    Serial1.print("Serial 1 read ");
    Serial1.println(Serial1.read());
  }
  Serial2.println("This a Serial 2");
  while (Serial2.available()) {
    Serial2.print("Serial 2 read ");
    Serial2.println(Serial2.read());
  }
  Serial3.println("This a Serial 3");
  while (Serial3.available()) {
    Serial3.print("Serial 3 read ");
    Serial3.println(Serial3.read());
  }
  Serial4.println("This a Serial 4");
  while (Serial4.available()) {
    Serial4.print("Serial 4 read ");
    Serial4.println(Serial4.read());
  }
  delay(1000);
}