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
- SAMD 21G Serial Communication Interfaces
- Seeeduino XIAO SERCOM
- Two I²C Ports on the Seeeduino XIAO
- Modifying the XIAO
variant
Files - Using the Modified XIAO
variant
in PlatformIO - Using the Modified XIAO
variant
in the Arduino IDE - A Word to the Wise
SAMD 21G Serial Communication Interfaces
There are six serial communication interfaces (SERCOMx, x=0,...,5) on the SAM D21G, each of which can handle any one of four protocols: classic serial communication (full-duplex USART or half-duplex single wire), I²C (master or slave), SPI or Lin (slave). I had to look up LIN (Local Interconnect Network) which turned out to be a serial communication protocol used in vehicles. The XIAO firmware creates three serial ports: USART, I²C and SPI with three of the interfaces. The fourth is used for the USB connection, the fifth for the two-pin serial wire debug (SWD) interface is on the back side of the board. As far as I can tell SERCOM5 is not physically connected.
Not only can the protocol of a serial communication interface be changed, but the connections of each interface can be multiplexed onto different sets of pins. Each interface has four "pads" or connections associated with a primary set of four pins and with a second, alternate, set of pins. Actually, SERCOM 4 and 5 have three complete sets of alternate pad assignments and SERCOM3 has a partial second alternate assignment. The table below presents part of the data in Table 7-1. PORT Function Multiplexing for SAM D21 A/B.CD Variant Devices and SAM DA1 A/B Variant Devices.
Interface | Pads | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 0' | 1' | 2' | 3' | 0" | 1" | 2" | 3" | |
SERCOM0 | PA08 | PA09 | PA10 | PA11 | ||||||||
ALT-SERCOM0 | PA04 | PA05 | PA06 | PA07 | ||||||||
SERCOM1 | PA16 | PA17 | PA18 | PA19 | ||||||||
ALT-SERCOM1 | PA00 | PA01 | PA30 | PA31 | ||||||||
SERCOM2 | PA12 | PA13 | PA14 | PA15 | ||||||||
ALT-SERCOM2 | PA08 | PA09 | PA10 | PA11 | ||||||||
SERCOM3 | PA22 | PA23 | PA24 | PA25 | ||||||||
ALT-SERCOM3 | PA16 | PA17 | PA18 | PA19 | PA20 | PA21 | ||||||
SERCOM4 | PB12 | PB13 | PB14 | PB15 | ||||||||
ALT-SERCOM4 | PA12 | PA13 | PA14 | PA15 | PA12 | PA13 | PA14 | PA15 | PB08 | PB09 | PB10 | PB11 |
SERCOM5 | PB16 | PB17 | PA20 | PA21 | ||||||||
ALT-SERCOM5 | PA22 | PA23 | PA24 | PA25 | PB02 | PB04 | PB22 | PB13 | PB30 | PB31 | PB00 | PB01 |
Seeeduino XIAO SERCOM
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.
Interface | Pads | Default Protocol | |||
---|---|---|---|---|---|
0 | 1 | 2 | 3 | ||
SERCOM0 | A4 | A5 | A2 | A3 | |
ALT-SERCOM0 | A1 | A9 | A10 | A8 | SPI |
ALT-SERCOM2 | A4 | A5 | A2 | A3 | I²C |
ALT-SERCOM4 | A6 | A7 | USART |
Be careful when interpreting the data in the table. SERCOM0 and ALT-SERCOM0 refer to the same serial interface; the ALT is just an indication that an alternate set of pad connections is used. 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. There is no possibility of adding a serial interface on the XIAO. There are three, or more precisely two and a half, available serial interfaces on the pins of the device, SERCOM0, SERCOM2 and SERCOM4 so that all one can do is to modify the communication protocol of the interfaces if more than one SPI, I²C or USART channel is needed. While that means that there are 3×3×2 = 18 possibilities (SERCOM4 cannot be used for as a full-duplex SPI), there is not much to be gained by inverting say the SPI and I²C bus. Indeed whenever possible, the pin assignment for the I²C bus should not be changed from the default assignment because of the electrical characteristics of the pins. The table below summarizes the 9 remaining possibilities.
Protocols | SERCOM0 | SERCOM2 | SERCOM4 |
---|---|---|---|
3×USART | USART | USART | USART |
3×I²C | I²C | I²C | I²C |
2×SPI + (1×USART or 1×I²C) | SPI | SPI | USART |
SPI | SPI | I²C | |
2×I²C + (1×USART or 1×SPI) | I²C | I²C | USART |
SPI | I²C | I²C | |
2×USART + (1×SPI or 1×I²C) | USART | I²C | USART |
SPI | USART | USART | |
1×SPI + 1×I²C + 1×USART | SPI | I²C | USART |
It might be possible to set up a half SPI interface on SERCOM4 with just a SCLK and MISO or MOSI signal; I have not investigated that possibility.
Two I²C Ports on the Seeeduino XIAO
The following example shows how to create two hardware I²C channels on a XIAO. 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 will be used to connect to a Raspberry Pi as a slave 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 Pi 5 volt output.
Here is a sketch that replaces the SPI protocol on SERCOM0 or the USART channel on SERCOM4 with a second I²C instance.
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:
- Create a
TwoWire
instance specifying the serial communication interface and the pad 0 and pad 1 connections:TwoWire myWire(sercom0, A1, A9)
. - Set the
TwoWire onService
method as the SERCOM0 interrupt handler. - Start the
TwoWire
object in slave mode specifying the I²C address:myWire.begin(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.
Indeed we do find the following assignment in .../packages/Seeeduino/hardware/samd/1.7.0/variants/XIAO_m0/variant.cpp
.
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
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
.
The changes to variant.cpp
is straightforward.
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
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.
Using the Modified XIAO variant
Files in the Arduino IDE
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.
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.
- 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 theTools
IDE menu.menu.sercom4=SERCOM4 - 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_INTERFACEThe 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 tosercom4_flag
depending on which choice is made. That value will either be an empty string or the compiler directive-DNO_USART_INTERFACE
. - The
sercom4_flag
variable is added to theseeed_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. It would be a good policy to add a comment at the start of each XIAO sketch about the correct setting for the new menu choice in XIAO sketches.
A Word to the Wise
I'll end with a warning. In the fisrt version if this post I included modified versions of variant.h
, variant.cpp
and boards.txt
. But I decided against doing that.
All modified files will be overwritten when the board definition is updated. How do I know? Since starting this post, the Seeed SAMD Boards by Seeed Studio has been updated from version 1.7.0 to 1.7.1 to 1.7.2. As of November 2020 the version is up to 1.7.9. So if you modified these files according to the above suggestions, be careful when updating the board definition in the Arduino IDE or the platform definition in PlatformIO.