Seeeduino XIAO Serial Communication Interfaces (SERCOM)
The XAIO as a USB-Serial Converter-> <-I²C Light Sensor using a Seeeduino XIAO
Three Nay, Four Hardware Serial Ports on a SAM D21 XIAO-> <-Overview of the SAMD21 Arm Cortex-M0+ Based Seeeduino XIAO

There are some quite good explanations on how to add a serial communication interface on a SAM D21 based board. I will link three from "big players" in the IoT world: Arduino: Adding more Serial Interfaces to SAMD microcontrollers, Sparkfun: Adding More SERCOM Ports for SAMD Boards and Adafruit: Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports. They are certainly worth reading, but they are not germane to the Seeeduino XIAO. I will explain why and how to add a supplementary SPI, I²C or USART port on the XIAO if it is possible to forego one of the other predefined ports.

Thanks to Elaine Wu who included a link to this page in the Tutorials section of her blog entitled Seeeduino XIAO Resources Roundup. What will be your next project idea made by Seeeduino XIAO?.

Table of contents

  1. SAM D21 Serial Communication Interfaces
  2. Seeeduino XIAO SERCOM
  3. Two I²C Ports on the Seeeduino XIAO
  4. Modifying the XIAO variant Files
  5. Using the Modified XIAO variant in PlatformIO
  6. Using the Modified XIAO variant in the Arduino IDE
  7. Everything but the Kitchen Sink
  8. A Word or Two to the Wise

SAM D21 Serial Communication Interfaces toc

Here is a very clear overview of the architecture of serial peripherals on SAM D21 microcontrollers by Atmel found in a 2015 application note AT11628: SAM D21 SERCOM I2C Configuration.

3 SERCOM Implementation in SAM D21 Microcontrollers

Generally microcontrollers will have separate serial communication modules with different pinouts for each module. Separate dedicated peripherals and user registers will be available for each module. For example USART will be a separate peripheral with dedicated pins for its function and I²C will be a separate peripheral with its own dedicated pins.

In SAM D microcontrollers, all the serial peripherals are designed into a single module as serial communication interface (SERCOM). A SERCOM module can be either configured as USART or I²C or SPI selectable by user. Each SERCOM will be assigned four pads from PAD0 to PAD3. The functionality of each pad is configurable depending on the SERCOM mode used. Unused pads can be used for other purpose and the SERCOM module will not control them unless it is configured to be used by the SERCOM module.

For example, SERCOM0 can be configured as USART mode with PAD0 as transmit pad and PAD1 as receive pad. Other unused pads (PAD2 and PAD3) can be either used as GPIO pins or can be assigned to some other peripherals. The assignment of SERCOM functionality for different pads is highly flexible making the SERCOM module more advantageous compared to the typical serial communication peripheral implementation.

There are six serial communication interfaces (SERCOMx, x=0,...,5) on the SAM D21 (the SAM D51 has more interfaces). Each SERCOM can handle any one of four protocols: classic serial communication (full-duplex USART or half-duplex single wire), I²C (2 or 4 wire, master or slave), SPI (with optional harware slave select) or Lin (slave). I had to look up LIN (Local Interconnect Network) which was not mentioned in the 2015 note. It turned out to be a serial communication protocol used in vehicles. Furthermore, not only can the protocol of a serial communication interface be changed, but the four "pads" of each SERCOM can be associated with a primary set of four pins and with a second, alternate, set of pins. Actually, SERCOM 4 has two and SERCOM 5 has three complete sets of alternate pad assignments while SERCOM3 has a partial second alternate assignment. This is often referred to as multiplexing. The two tables below present the pertinent information about the serial interfaces in Table 7-1. PORT Function Multiplexing for SAM D21 A/B.CD Variant Devices and SAM DA1 A/B Variant Devices.

PinSerial Communication Interface

Be careful when interpreting the data in the tables. SERCOM0 and ALT-SERCOM0 refer to the same serial interface; the ALT is just an indication that the interface pads are multiplexed onto an alternate set of pins.

There are additional constraints. Only 8 pins (PA08, PA09, PA12, PA13, PA16, PA17, PA22 and PA23) support I²C. Note that these connect to pads 0 and 1 of SERCOM 0, 1, 2 and 3 respectively because the data (SDA) signal must be mapped to pad 0 of a SERCOM, while the clock signal (SCL) must be mapped to pad 1 (reference: Table 7-5. SERCOM Pins Supporting I²C. When it comes to USARTs, the TX line must be mapped to either pad 0 or pad 2 of serial interface on the SAM D21 but on the SAM D51 family TX can only be on pad 0 (Reference: Creating a new Serial in Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports By lady ada).

Seeeduino XIAO SERCOM toc

Of course the flexibility is much reduced with the Seeeduino XIAO which brings out only 11 pins. Here is the relevant information from the previous table, using the Arduino pin labels.

Seeedino XIAO
InterfaceSERCOM PadsDefault

As can be seen the XIAO firmware creates three serial communication interfaces, USART, I²C and SPI, with three of the SERCOMs. Had Seeed Studio decided to use SERCOM0 with its primary pad assignment for the I²C bus, then it would not have been possible to have a SPI bus since SERCOM0 and ALT-SERCOM0 are, as stated before, the same serial communication interface. Here is representation of the serial interfaces based on the physical layout of the XIA0.

Seeedino XIAO

There are two other serial connections:

Let’s try to add these to our table.

Serial InterfacePadProtocol
0 1 2 3

Thanks to Dieter for pointing out that there was an error in the pad assignement of ALT-SERCOM4 in the previous (May 2022) version of this post. The assignement was correct in the previous four tables including the one just four lines above! Sorry to have mislead others with this mistake.
(March 20, 2023)

A blank cell means that the corresponding pad is connected to a pin that is not brought out on the XIAO, while a dash '-' means that the pin is available but the pad is not used and therefore not connected to the pin. Those pins can thus be used for other purposes.

The SWD needs to be used in conjunction with a debug probe which costs an order of magnitude more than the XIAO at a minimum. The SWD pins are shown as multiplexed to SERCOM1, but I cannot find any mention of that in the variant.cpp file for the XIAO which is part of the SeeedStudio SAMD Arduino core. However it is in the arduino_zero variant.cpp file in the same repository. Elsewhere, I show that the pins used for the SWD pin multiplexed to SERCOM1 can be used for as a two wire (TX and RX) serial interface.

The USB connection uses pins PA24 and PA25. These could be connected as pads 2 and 3 respectively of either SERCOM3 or ALT-SERCOM5. Even though the USB port is mapped to a serial device, it is independent of the SERCOM interfaces. I seem to recall reading in an application note that if the USB stack is loaded, then the two pins (or rather three pins because there's a third enable pin) are exclusively used for that purpose and cannot be reassigned. That is why removing the declarations of SERCOM3 and SERCOM5 changes absolutely nothing. A blink sketch that also writes "ON" and "OFF" to Serial while toggling the state of a LED works just as well as when the two SERCOMs were declared. Presumably, if one did not need to use the USB stack, it could be disabled and the USB connector could be used as a TTL level serial port. Because I currently do not have an ICE probe which would be needed to reload the bootloader, I have no intention of investigating this topic any further.

Forgetting about the USB and SWD pins, the XIAO exposes three, or more precisely two and a half, serial interfaces: SERCOM0, SERCOM2 and (half of) SERCOM4. So if more than one SPI or USART channel is needed, all that can be done is to modify use configuration of a preexisting interface. Given that the only "legal" (more on that in the next section) I²C pin assignment available on the XIAO is with SERCOM2 and that a full-duplex SPI connection cannot be set up on SERCOM4, there are 6 ways to assign the mode of the three available SERCOM, assuming that all three are used.


There's not much to be gained from interchanging the SPI and USART assignments, so in effect there are potentially four other ways of defining the use of SERCOM0 and SERCOM2: 2 USART, 2 SPI, SPI+USART USART+I²C. I must admit here that I have only tested the two USART + I²C configuration. Just to complicate things, I will mention that it might be possible to set up a half SPI interface on SERCOM4 with just a SCLK and MISO or MOSI signal. That's another possibility that I have not investigated.

Two I²C Ports on the Seeeduino XIAO toc

Before diving into the details of the serial communication interfaces on the SAM D21 chip, I forged ahead and connected two I²C devices to the XIAO without realizing that this was not recommended. The "standard" I²C pins A4 and A5 (SDA and SCL respectively) are used to drive an I²C OLED display. The second I²C port is used to connect the XIA to a Raspberry Pi as a slave I²C device. Since the default I²C interface is on SERCOM2, the second port can be on SERCOM0 (SDA on A1, SCL on A9) sacrificing the SPI interface or on SERCOM4 (SDA on A6, SCL on A7) sacrificing the USART interface. The two circuits are shown below. Note how the XIAO and display are powered from the 5 volt output of the Pi.

The funny thing is that this worked (albeit with a slight problem to be discussed). So for those willing to break the rules, the set of possibilities is much larger.


It must be stressed that most of these combinations have not been thoroughly tested. The example discussed here was done on a breadboard with short connections between the Pi, the XIAO and the OLED display. The I²C channel between the Pi and the XIAO required very little bandwidth; there was an exchange of only two bytes every five seconds. So setting up a second I²C interface on the XIAO in any sort of "production" setting needs to be thoroughly investigated. Nevertheless, the example is interesting from the programming point of view.

Here is a sketch that replaces the SPI protocol on SERCOM0 or the USART channel on SERCOM4 with a second I²C instance.

/* * two_hdw_i2c   */ #include <Arduino.h>            // Needed for PlatformIO #include <Wire.h>               // I2C library #include "wiring_private.h"     // for pinPeripheral() function     #include <U8g2lib.h>            // I2C OLED display #define BAUD          115200    // Baud for serial port (/dev/ttyACM0 on Linux) #define DATA_DELAY       667    // Delay (ms) between output value updates #define REFRESH_DELAY   2000    // Delay (ms) between display updates #define I2C_SLAVE_ADDRESS  4    // arbitrary choice in range (0x03 to 0x77) different from other devices on bus #define USE_SERCOM0             // Comment out to set up I2C on SERCOM4 which, by default, *** WILL NOT WORK *** #ifdef USE_SERCOM0 #define ALT_SERCOM         sercom0 #define ALT_SERCOM_SDA     1        // SERCOM0, PAD0 #define ALT_SERCOM_SCL     9        // SERCOM0, PAD1 #else #define ALT_SERCOM         sercom4 #define ALT_SERCOM_SDA     6       // SERCOM4, PAD0 #define ALT_SERCOM_SCL     7       // SERCOM4, PAD1 #endif // Using hardware I2C bus with noname 128x64 0.96" OLED display, this will use Wire object and sercom2 U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); volatile int serviceCount = 0; TwoWire myWire(&ALT_SERCOM, ALT_SERCOM_SDA, ALT_SERCOM_SCL); extern "C" {  #ifdef USE_SERCOM0  void SERCOM0_Handler(void) {  #else  void SERCOM4_Handler(void) {  #endif      serviceCount++;          // to check this handler is used    myWire.onService();  } } int dataValue; // request data block handler void requestEvent(void) {  int level = dataValue;              myWire.write((level >> 8) & 0xFF);      // send higher 8 bits of data  myWire.write(level & 0xFF);             // send lower 8 bits of data   } void setup() {  // Start serial port, waiting up to 20 seconds  Serial.begin(BAUD);  unsigned long startserial = millis();  while (!Serial && (millis() - startserial < 20000)) delay(10);  // start second Wire object to respond to the Raspberry Pi  myWire.begin(I2C_SLAVE_ADDRESS);  pinPeripheral(ALT_SERCOM_SDA, PIO_SERCOM_ALT);  pinPeripheral(ALT_SERCOM_SCL, PIO_SERCOM_ALT);  myWire.onRequest(requestEvent);    // setup the OLED display and first Wire object  u8x8.begin();  u8x8.setPowerSave(0);  u8x8.setFont(u8x8_font_chroma48medium8_r);    // End of setup  Serial.println("Test of two hardware I2C buses");  Serial.printf("\n1st hardware I2C bus: SDA: A%d, SCL: A%d\n", SDA, SCL);  Serial.println("using sercom2 in master mode <--> LCD Display\n");    Serial.printf("\n2nd hardware I2C bus: SDA: A%d, SCL: A%d\n", ALT_SERCOM_SDA, ALT_SERCOM_SCL);  Serial.print("using ");  #ifdef USESERCOM0  Serial.print("sercom0");  #else  Serial.print("sercom4");  #endif  Serial.println(" in slave mode <--> Raspberry Pi\n");  Serial.println("\nWaiting for data requests"); } int runcount = 0; char buf[200]; unsigned long dataReadTime = 0;  // LDR read timer unsigned long refreshTime = 0;  // LCD refreh timer void loop(){  if (millis() - dataReadTime >= DATA_DELAY) {    // generate random data as if reading a sensor of some type    if (runcount == 0)      dataValue = random(4096);    runcount++;    if (runcount > 12) runcount = 0;  // give myself time to compare the data on the display and on the Raspberry Pi    // restart the timer    dataReadTime = millis();  }  if (millis() - refreshTime >= REFRESH_DELAY) {       // update the display    sprintf(buf, "data value: %d", dataValue);    u8x8.clearLine(0);    u8x8.drawString(0,0, buf);    Serial.printf("dataValue: %d, service count: %d\n", dataValue, serviceCount);    // restart the timer    refreshTime = millis();  } }

This sketch can be downloaded by clicking on this link: two_hdw_i2c.ino. A Python script such as light_sensor.py described in I²C Light Sensor using a Seeeduino XIAO On the Raspberry Pi will display the data generated by the sketch.

Note how easy it was to set up the second I²C instance in lieu of the SPI instance on SERCOM0:

  1. Create a TwoWire instance specifying the serial communication interface and the pad 0 and pad 1 connections: TwoWire myWire(sercom0, A1, A9).
  2. Set the TwoWire onService method as the SERCOM0 interrupt handler.
  3. Start the TwoWire object in slave mode specifying the I²C address: myWire.begin(4);.
  4. Assign the physical pins to the serial interface: pinPeripheral(A1, PIO_SERCOM_ALT); pinPeripheral(A2, PIO_SERCOM_ALT);.

Unfortunately, when the very same thing is attempted, except for adding the extra I²C instance on SERCOM4, there is a linking problem.

Linking everything together... ... /tmp/arduino_build_733537/core/variant.cpp.o: In function `SERCOM4_Handler': variant.cpp:(.text.SERCOM4_Handler+0x0): multiple definition of `SERCOM4_Handler' /tmp/arduino_build_733537/sketch/two_hdw_i2c.ino.cpp.o:two_hdw_i2c.ino.cpp:(.text.SERCOM4_Handler+0x0): first defined here...

Indeed we do find the following assignment in .../packages/Seeeduino/hardware/samd/1.7.0/variants/XIAO_m0/variant.cpp.

Uart Serial1( &sercom4, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ; void SERCOM4_Handler() {  Serial1.IrqHandler(); }

If those few lines are removed from the variant.cpp file, then the sketch will compile and run correctly as long as the OLED SDA and SCL pins are now connected to pins A6 and A7 of the XIAO. The lesson learned from this experiment is that it was necessary to do some "surgery" to the variant.cpp file to prevent the creation of the Serial1 object and assignment of its interrupt handler.

Modifying the XIAO variant Files toc

The changes described here and in the following section are included in version 1.8.3 of the SAMD Arduino Core by SeeedStudio, released 13 hours ago at the time of writting this addendum. So there is no need to modify the files variant.h, variant.cpp and boards.txt as explained in this section. Consequently, when version 1.8.3 of core is used, then Serial1 can be disabled or enabled with the additional SERCOM4 option for the "Seeeduino XIAO" in the Tools menu.

Many thanks to Geoff Evans (geocom) who took on the task of submitting a pull request for these changes, and thanks to the Seeed Studio SAMDx1 Arduino Core team for committing that change to the core.

May 24, 2022

Wholesale removal of the Serial1 Uart from the variant files is not the best way to go. What if the USART is needed in another project? Setting up 9 different variant.h and variant.cpp files to take care of all the possible permutations of serial protocols on the XIAO is not practical. Then I thought of using macros to select one of the nine possibilities outlined above, but that's just as awkward. Instead, I decided to add a single macro NO_USART_INTERFACE to excise the Serial1 object when another type of serial interface is needed on SERCOM4. As shown in the above sketch, it is always possible to modify the serial instance of SERCOM0 and SERCOM2 if needed.

Here is the last part of the variant.h file with the preprocessor macro appearing twice to avoid moving the couple of lines defining the SERIAL_PORT_HARDWARE and SERIAL_PORT_HARDWARE_OPEN macros to the first appearance of NO_USART_INTERFACE.

// Serial ports // ------------ #ifdef __cplusplus #include "SERCOM.h" // Instances of SERCOM extern SERCOM sercom0; extern SERCOM sercom1; extern SERCOM sercom2; extern SERCOM sercom3; extern SERCOM sercom4; extern SERCOM sercom5; #ifndef NO_USART_INTERFACE #include "Uart.h" // Serial1 extern Uart Serial1; #define PIN_SERIAL1_TX (6ul) #define PIN_SERIAL1_RX (7ul) #define PAD_SERIAL1_TX (UART_TX_PAD_0) #define PAD_SERIAL1_RX (SERCOM_RX_PAD_1) #endif // not NO_USART_INTERFACE #endif // __cplusplus // These serial port names are intended to allow libraries and architecture-neutral // sketches to automatically default to the correct port name for a particular type // of use. For example, a GPS module would normally connect to SERIAL_PORT_HARDWARE_OPEN, // the first hardware serial port whose RX/TX pins are not dedicated to another use. // // SERIAL_PORT_MONITOR Port which normally prints to the Arduino Serial Monitor // // SERIAL_PORT_USBVIRTUAL Port which is USB virtual serial // // SERIAL_PORT_LINUXBRIDGE Port which connects to a Linux system via Bridge library // // SERIAL_PORT_HARDWARE Hardware serial port, physical RX & TX pins. // // SERIAL_PORT_HARDWARE_OPEN Hardware serial ports which are open for use. Their RX & TX // pins are NOT connected to anything by default. #define SERIAL_PORT_USBVIRTUAL SerialUSB #define SERIAL_PORT_MONITOR SerialUSB #ifndef NO_USART_INTERFACE #define SERIAL_PORT_HARDWARE Serial1 #define SERIAL_PORT_HARDWARE_OPEN Serial1 #endif // Alias Serial to SerialUSB #define Serial SerialUSB

The changes to variant.cpp is straightforward.

#ifndef NO_USART_INTERFACE Uart Serial1( &sercom4, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ; void SERCOM4_Handler() { Serial1.IrqHandler(); } #endif

Of course, the NO_USART_INTERFACE macro is not defined in these files because it needs to be defined only when needed.

Using the Modified XIAO variant Files in PlatformIO toc

The changes described here and in the following section are included in version 1.8.3 of the SAMD Arduino Core by SeeedStudio. Currently, PlatformIO does not yet use version 1.8.3 of the SAMD Arduino Core by Seeed. Until that is the case, the changes to the two variant files in the <.platformio>/packages/framework-arduino-samd/variants/XIAO_m0/ directory will have to be done manually in order to use the NO_USART_INTERFACE build flag to disable Serial1 as described below.
May 24, 2022

In PlatformIO (see "Hello XIAO" in PlatformIO), those changes to the two variant files in the <.platformio>/packages/framework-arduino-samd/variants/XIAO_m0/ directory are just about the only thing needed. The two_hdw_i2c sketch will compile and run in PlatformIO using SERCOM4 (remove or comment out the USE_SERCOM0 macro in the sketch) with the following platformio.ini configuration file.

[env:seeeduino_xiao] platform = atmelsam board = seeeduino_xiao framework = arduino monitor_speed = 115200 build_flags = -DNO_USART_INTERFACE

Using the Modified XIAO variant Files in the Arduino IDE toc

If version 1.8.3 of the SAMD Arduino Core by SeeedStudio is used, then there is no need to modify the boards.txt file as described in this section. With this new version of the core, Serial1 can be disabled or enabled with the additional SERCOM4 option for the "Seeeduino XIAO" in the Tools menu. The screen capture below shows how the serial port is disabled.
May 24, 2022

It is a bit more complicated to use the modified variant file in the Arduino IDE. The simplest is to add a platform.local.txt file in the .../arduino-1.8.10/portable/packages/Seeeduino/hardware/samd/1.7.2/ directory alongside the platform.txt file to override a number of directives in the later file.

# platform.local.txt # # Override compiler.cpp.extra_flags in boards.txt. # # For more info: # https://arduino.github.io/arduino-cli/platform-specification/ compiler.cpp.extra_flags=-DNO_USART_INTERFACE

This works, but it is not very convenient because this configuration file is in force at the board level. Something equivalent to the platformio.ini file which is in effect at the sketch level only would be preferable. The best I could come up with was adding an option in the Tools menu for the XIAO board.

Setting SERCOM4 to "None" instead of "USART" means that the NO_USART_INTERFACE macro will be defined. Adding this menu option requires changes to the boards.txt file.

  1. The new menu entry was added at the start of the boards.txt. The part to the right of the equal sign is the option text displayed in the Tools IDE menu.

  2. The possible option values were added at the end of the Seeed XIAO M0 (SAMD21) definition.

    seeed_XIAO_m0.menu.sercom4.include=USART seeed_XIAO_m0.menu.sercom4.include.sercom4_flag= seeed_XIAO_m0.menu.sercom4.exclude=None seeed_XIAO_m0.menu.sercom4.exclude.sercom4_flag=-DNO_USART_INTERFACE

    The first and third lines define the two values, "USART" and "None", which appear in the SERCOM4 submenu. The second and fourth line take care of assigning a value to sercom4_flag depending on which choice is made. That value will either be an empty string or the compiler directive -DNO_USART_INTERFACE.

  3. The sercom4_flag variable is added to the seeed_XIAO_m0.build.extra_flags= line which is found about 15 lines above the added menu choices.
    seeed_XIAO_m0.build.extra_flags= -DARDUINO_SAMD_ZERO -D__SAMD21__ -D__SAMD21G18A__ -DARM_MATH_CM0PLUS -DSEEED_XIAO_M0 {build.usb_flags} {sercom4_flag}

These changes are better than manually editing the platform.local.txt file but definitely not the best solution. And that's because the IDE does not save the menu choices for each sketch, but only for the last sketch edited in the IDE.

Everything but the Kitchen Sink toc

For those not aware of that idiom or cliché , it roughly means "nearly everything one can reasonably image"

If you like the idea of modifying the variant.h file to accommodate all the possible modes that can be assigned to the 3 SERCOM interfaces, including not assigning any serial interface, then take a look at Use the Seeeduino XIAO with Multiple UART Ports by gears-computer-workshop.

The site is in Japanese, but Google Translate does a good job of translating into English, or French for that matter. Besides, the code is easily understood.

A Word or Two to the Wise toc

I'll end with two warnings.

The XAIO as a USB-Serial Converter-> <-I²C Light Sensor using a Seeeduino XIAO
Three Nay, Four Hardware Serial Ports on a SAM D21 XIAO-> <-Overview of the SAMD21 Arm Cortex-M0+ Based Seeeduino XIAO