Compared to SPI, UART is even simpler - it's only 2 pins, RX & TX. UARTs are a real pain to emulate or bitbang due to their very precise timing and their asynchronous RX lines are very difficult to do if you dont have hardware interrupt pins available.

For that reason, being able to create new Serial's is awesome

How Serial is Created Now

Luckily, Atmel & Arduino did a really great job with structuring SERCOMs so you can create and assign new ones. You can find the actual code that is used to create the "Serial" SERCOM in variants.cpp

      Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
Uart Serial( &sercom5, PIN_SERIAL_RX, PIN_SERIAL_TX, PAD_SERIAL_RX, PAD_SERIAL_TX ) ;
void SERCOM0_Handler()
{
  Serial1.IrqHandler();
}

void SERCOM5_Handler()
{
  Serial.IrqHandler();
}
    

If you have a Feather, you wont see the SERCOM5_Hander or Serial(&sercom5...) lines

The first hardware Serial object (on D0 and D1) is created with:

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

The actual definitions of those macros is available in variants.h

// Serial1
#define PIN_SERIAL1_RX       (0ul)
#define PIN_SERIAL1_TX       (1ul)
#define PAD_SERIAL1_TX       (UART_TX_PAD_2)
#define PAD_SERIAL1_RX       (SERCOM_RX_PAD_3)

You can see here that Serial1 is of type Uart and if you expand the macros:

Uart Serial1( &sercom0, 0, 1, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;

We can match this up with the pins we know are used for Serial1

PA11	D0		SERCOM0.3	SERCOM2.3
PA10	D1		SERCOM0.2	SERCOM2.2

You can see that the first argument (&sercom0) does in fact match our observation that Serial1 is on SERCOM0. The second and third arguments match with the RX & TX pins.

The last two args are what actually define the pin muxing. We can see these definitions in SERCOM.h

typedef enum
{
	SERCOM_RX_PAD_0 = 0,
	SERCOM_RX_PAD_1,
	SERCOM_RX_PAD_2,
	SERCOM_RX_PAD_3
} SercomRXPad;

typedef enum
{
	UART_TX_PAD_0 = 0x0ul,	// Only for UART
	UART_TX_PAD_2 = 0x1ul,  // Only for UART
	UART_TX_RTS_CTS_PAD_0_2_3 = 0x2ul,  // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3
} SercomUartTXPad;

The two together let us define the muxing. For example we can define which pin is for RX

  • SERCOM_RX_PAD_0 means RX on SERCOMn.0
  • SERCOM_RX_PAD_1 means RX on SERCOMn.1
  • SERCOM_RX_PAD_2 means RX on SERCOMn.2
  • SERCOM_RX_PAD_3 means RX on SERCOMn.3

and then for TX:

  • UART_TX_PAD_0 means TX on SERCOMn.0
  • UART_TX_PAD_2 means TX on SERCOMn.2

As you can tell, you can't quite have every possibility, for example you cannot have TX on pad 1 or pad 3. But you do have a lot of options!

SAMD51 Serial SERCOM Pads

The SAMD51 is a little more restrictive, you can only have TX on PAD 0 - PAD 2 is no longer permitted as it was on the SAMD21

OK so let's make a new Serial SERCOM already

I understand, you want to make Serials! Lets try creating a new SERCOM and testing it out

Let's make a Serial device on SERCOM #1, lets look at what pin muxing options we've got:

Pin	Arduino 'Pin'	SERCOM		SERCOM alt
-----------------------------------------
PA18	D10		SERCOM1.2	SERCOM3.2
PA16	D11		SERCOM1.0	SERCOM3.0
PA19	D12		SERCOM1.3	SERCOM3.3
PA17	D13		SERCOM1.1	SERCOM3.1

We can put TX on D11 (SERCOM1.0) or D10 (SERCOM1.2), RX can be on any pin.

How about we have D10 be TX and D11 be RX?

That would mean something like:

Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);

Lets test it out!

Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);

void SERCOM1_Handler()
{
  Serial2.IrqHandler();
}

void setup() {
  Serial.begin(115200);

  Serial2.begin(115200);
}

uint8_t i=0;
void loop() {
  Serial.println(i);
  Serial2.write(i++);
  delay(10);
}

aaaand...nothing! Why? Good question! Check out those pin definitions in the variants.cpp file:

const PinDescription g_APinDescription[]=
...
 { PORTA, 18, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM3_CH0, TC3_CH0, EXTERNAL_INT_2 }, // TC3/WO[0]
 { PORTA, 16, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH0, TCC2_CH0, EXTERNAL_INT_0 }, // TCC2/WO[0]

The first argument is the port (e.g. PORTA or PORTB), the second is the port's pin number, e.g. PORTA, 18 -> PA18. The third argument 'sets' the type of pinmux we're going to use.

In this case, these are set to be used as PIO_TIMER!

We have to tell the Arduino core to change the MUX type before we reassign the SERCOM to this pin

You can do that by calling pinPeripheral(pinnumber, function) which is an internal function but hey we're being hardk0re

#include <Arduino.h>   // required before wiring_private.h
#include "wiring_private.h" // pinPeripheral() function

Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
void SERCOM1_Handler()
{
  Serial2.IrqHandler();
}

void setup() {
  Serial.begin(115200);

  Serial2.begin(115200);
  
  // Assign pins 10 & 11 SERCOM functionality
  pinPeripheral(10, PIO_SERCOM);
  pinPeripheral(11, PIO_SERCOM);
}

uint8_t i=0;
void loop() {
  Serial.print(i);
  Serial2.write(i++);
  if (Serial2.available()) {
    Serial.print(" -> 0x"); Serial.print(Serial2.read(), HEX);
  }
  Serial.println();
  
  delay(10);
}

You can use your oscilloscope to see the data traces

Say you only want to transmit Serial data, you can leave out the pinPeripheral(10, PIO_SERCOM) line, and you can keep using #10 however you like

We can also try SERCOM2:

Pin	Arduino 'Pin'	SERCOM		SERCOM alt
-----------------------------------------
PA14	D2		SERCOM2.2	SERCOM4.2
PA09	D3		SERCOM0.1	SERCOM2.1
PA08	D4		SERCOM0.0	SERCOM2.0
PA15	D5		SERCOM2.3	SERCOM4.3

Lets put TX on D4 (SERCOM2.0) and RX on D3 (SERCOM2.1)

#include <Arduino.h>   // required before wiring_private.h
#include "wiring_private.h" // pinPeripheral() function

Uart Serial2 (&sercom2, 3, 4, SERCOM_RX_PAD_1, UART_TX_PAD_0);
void SERCOM2_Handler()
{
  Serial2.IrqHandler();
}

void setup() {
  Serial.begin(115200);

  Serial2.begin(115200);
  
  // Assign pins 3 & 4 SERCOM functionality
  pinPeripheral(3, PIO_SERCOM_ALT);
  pinPeripheral(4, PIO_SERCOM_ALT);
}

uint8_t i=0;
void loop() {
  Serial.print(i);
  Serial2.write(i++);
  if (Serial2.available()) {
    Serial.print(" -> 0x"); Serial.print(Serial2.read(), HEX);
  }
  Serial.println();
  
  delay(10);

}

Note that since pin 3 and 4 use a SERCOM alt mux, we have to pass in PIO_SERCOM_ALT rather than PIO_SERCOM when we call pinPeripheral()

SAMD51 Interrupts

The SAMD51 has 4 total interrupt handlers, you'll need to define all of them. Here's an example for the SAMD51

#define PIN_SERIAL2_RX 30  // PA05
#define PAD_SERIAL2_RX (SERCOM_RX_PAD_1)
#define PIN_SERIAL2_TX 18  // PA04
#define PAD_SERIAL2_TX (UART_TX_PAD_0)

Uart Serial2( &sercom0, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX );

Stream *SERIALOUT = &Serial2;

void SERCOM0_0_Handler()
{
  Serial2.IrqHandler();
}
void SERCOM0_1_Handler()
{
  Serial2.IrqHandler();
}
void SERCOM0_2_Handler()
{
  Serial2.IrqHandler();
}
void SERCOM0_3_Handler()
{
  Serial2.IrqHandler();
}

This guide was first published on Jan 24, 2016. It was last updated on Jan 24, 2016.

This page (Creating a new Serial) was last updated on Jan 24, 2016.

Text editor powered by tinymce.