Unfortunately, my first attempts at modifying the mode of a serial communication (SERCOM) interface of the Seeeduino XIAO worked. Unfortunately because I became a victim of that little success. Thinking that I knew everything important about SAM D21/D51 serial communication interfaces, I boldly wrote a post about it in May of 2020. In the last few days I had to update that post to add statements which boil down to "I should not have created a second I²C port" and "it's really more complicated than explained here". Let's be clear, there's nothing that should never have been published in that first post. Indeed, if you know nothing about the SERCOM interfaces of the SAM D21/D51 families of microcontrollers, then I humbly suggest that you read it before pressing on. However if all you want is a recipe for getting 3 or even 4 hardware serial ports on a XIAO and don't care about the technical details then, by all means, jump to the last two sections.
Without wanting to nitpick, let's be a bit more careful about serial hardware nomenclature. The documentation says that the SAM D21/D51 SERCOM modules can be configured in universal synchronous and asynchronous receiver-transmitter (USART) mode. However it is unlikely that a serial port will ever be used in synchronous mode; I have never knowingly done that and I wouldn't even know where to begin using a USART. In practice, USART will refer to universal asynchronous receiver-transmitter (UART) from now on. This post shows how to set up SERCOM modules on the XIAO as UARTs which are accessed with instances of the
Uart class called
Serialx where x = 1, 2, 3 or 4.
Table of contents
- Three UARTs on a XIAO
- Take Two: Different Pin Assignment
- Order Matters
- Libraries for the Extra Serial Ports
- Four UARTs on a XIAO
- The Full Enchilada: Four Pin UART
- The Take Away
- The Give Away
The good news is that it is perfectly "legal" to set any SERCOM interface to USART mode as well as all supported SERCOM pad to pin assignment (as long as the TX on pad 0 or pad 2 restriction is respected). There are no restrictions à la I²C that we saw in the previous topic. Since
Serial1 is created by default only two hardware UARTs need to added to get to the proposed count of hardware serial ports on the XIAO. Here is the wiring for the example sketch.
As can be seen, instead of connecting the TX and RX pins of each serial port together as done during loop tests, each serial port TX pin is feed to the next serial port's RX pin in round-robin fashion. Not shown is the USB connection to the desktop. Accordingly, in addition to the three hardware
Serialx (x=1, 2, 3) ports shown, there is the fourth
USBSerial interface which is used to download the firmware and power the XIAO.
The only other reference about this type of thing that I could find was by kio denshi: XIAO Serial Extension 2 from March 2021. The site is in Japanese, but with the help of one of the many translation tools on the Web it was easy to follow what the author did. As it happens the UART names and the wiring are different. While that does not really matter, it does serve as a warning that there is no apparent universal convention on this matter or, at least, there's none known to me.
loop function begins by echoing any character received by any one of the three UARTs to the
Serial) interface so that it will show up in the IDE serial monitor. Those received characters come from the UARTs themselves because at regular, but different intervals, each UART transmits a simple message, its name and a number that always increases. For
Serial1 that number is the number of seconds that the loop has been running, but for
Serial3 it is the number of interrupts it has handled. Here is an example of the output in the PlatformIO serial monitor, but the same will be seen in the serial monitor of the Arduino IDE.
The code for the sketch is not complicated but it is long for a demonstration sketch because there's much repetition.
Warning: do not copy and paste this code, it contains an error as explained in the next section.
The implementation in the
loop function is a mess. There's a lot of
flushing of the
Serial transmit buffer because I am trying to avoid having the USART's interrupt handlers interfering with each other. I could have done better, but I don't care. The important bit here is setting up the USARTs and most of that is done at the very beginning. There is no need to create
Serial1, that's done in the Arduino core. Look at the end of
variant.cpp (in the
../framework-arduino-samd-seeed/variants/XIAO_m0 directory) to see how
Serial1 is created. Basically, the same thing is done here for
Serial3. Each of those objects is an instance of the
Uart and it is necessray to set up the interrupt handler of the interface. The difference is of course the choice of the SERCOM interface and the assignment of its pads and choice of GPIO pins. That was covered in the previous post but here is the pertinent table for this purpose.
|SERCOM Pads||Serial Interface|
There is another part of the configuration of the two additional serial interfaces. The mode of the GPIO pins used by the serial interfaces has to be specified. That's the role of the four
pinPeripheral(Axx, PIO_SERCOM_ALT) function calls in the
setup function of the sketch. The
pinPeripheral() function is really an extended
pinMode function. Indeed one could make A10 an input GPIO pin with a pullup resistor with the call
That's it. Everything seems to works as desired. Of course, in actual use, one would not have the
serviceCountx variables in the SERCOM interrupt handlers.
So what's the problem? Why did I write [t]hings are not as simple as portrayed above. There is another "gotcha" when an attempt is made to assign an "unusual" pin to a SERCOM pad in the previous post? Because when I replaced
and adjusted the wiring accordingly, the program no longer worked.
Serial3 simply did not function. I tried other combinations such as
Uart Serial3(&sercom2, A3, A4, SERCOM_RX_PAD_3, UART_TX_PAD_0); but without success again. My powers of deduction led me to believe that the source of the problem laid in the implementation of the SAM D21 Arduino core, no less! There is a table in
variant.cpp that maps GPIO pins of the device to the Arduino pin number. That table also contains a description of the "default" peripheral function of the pins. In the XIAO version, slots 2, 3 and 4 of the
const PinDescription g_APinDescription table (which correspond pins A2, A3 and A4) contain the following information.
According to the table, the default peripheral function of pin PA11 (A3) is PIO_ANALOG while the default peripheral function of PA08 (A4) is PIO_SERCOM_ALT. The documentation says something about this.
Each pin is by default controlled by the PORT as a general purpose I/O and alternatively it can be assigned to one of the peripheral functions A, B, C, D, E, F, G or H. To enable a peripheral function on a pin, the Peripheral Multiplexer Enable bit in the Pin Configuration register corresponding to that pin (PINCFGn.PMUXEN, n = 0-31) in the PORT must be written to one. The selection of peripheral function A to H is done by writing to the Peripheral Multiplexing Odd and Even bits in the Peripheral Multiplexing register (PMUXn.PMUXE/O) in the PORT.
That wonderful prose immediately precedes Table 7-1. PORT Function Multiplexing for SAM D21 A/B.CD Variant Devices and SAM DA1 A/B Variant Devices. in the microcontroller datasheet. The columns in the table correspond to the peripheral functions. I concluded that it was not possible to assign SERCOM2 PAD3 to pin PA11 (A3) because its PIO type is
PIO_ANALOG (peripheral function B in SAMD jargon) not
PIO_SERCOM_ALT (peripheral function D). Clearly, Table 7.1 shows that any single GPIO pin can be assigned one of a variable number peripheral functions. I concluded that the authors of the SAMD D21 Arduino Core simplified things by limiting a GPIO pin be only one type of peripheral function or else being used as a general purpose input/output pin.
With that explanation for the inability to use a different SERCOM pad assignment, I ran out of ideas about fixing the problem. Just to prove that I had stumbled on a plausible explanation, I modified the
g_APinDescription table, setting PIO type of PA10 and PA11 to PIO_SERCOM_ALT. The exact definition for the two pins was simply copied from the Arduino Zero
variant.cpp file, so that was easy. Once that change was made, the
pinPeripheral(A2, PIO_SERCOM_ALT) and
pinPeripheral(A2, PIO_SERCOM_ALT) performed the desired multiplexing and the program worked.
Brilliant, I'm a genious I thought. But I was barking up the wrong tree. As the next section shows things are simpler after all.
On the theory that if the PIO_SERCOM_ALT peripheral function could not be assigned to a pin an error code should be returned, I decided to check on the result returned by the
pinPeripheral() function. I was surprised to see that
pinPeripheral(A2, PIO_SERCOM_ALT) returned 0. A closer look at the
pinPeripheral() function revealed that it never references the PIO type of the pin defined in the
g_APinDescription when setting the pin's peripheral function; it just passes on the PIO type parameter when the latter is not an input or output type of mode. That was perplexing because clearly the peripheral function value in the description table played a role, otherwise, the surgery on the table would not have worked. I wish I could report that deep thought on my part revealed what was happening. Instead a search for
g_APinDescription in all the files in the
../framework-arduino-samd-seeed/variants/XIAO_m0/ directory was what finally got me going in the right direction. This showed up.
Now I could see what was happening. In
setup() the correct pin peripheral function was defined with
Unfortunately in the subsequent call to
Serial3.begin() the pin's peripheral function was reset to
PIO_ANALOG. The solution was simple, change the order:
The same thing has to be done with
Serial2. Then the sketch works just as well as with the default pin assignment. Here is the sketch.
ORDER_MATTERS macro at the start of the file and confirm that the order in which
pinPeripheral() are called is important if non default pin assignment is used.
I like how Kio-Denshi handled the extra serial ports by creating a couple of libraries. On the other hand, inserting the pin and pad assignments in
variant.h is not, as I have argued before, the best idea. Since I see no overriding reason to do that the pin definitions were placed in each individual
Serialx.h header file. Here is the
Serial2.h header file.
And here is the accompanying
Serial2.cpp source code.
A library can be created for the third USART with the
Serial3.cpp files that are the same mutatis mutandis.
With this approach, if one wants to use an alternate multiplexing for the second or third UART, then one would have to create yet another library. This gets cumbersome very quickly as there are 6 possible pin assignments for each of the two extra UARTS. It's true that in any one sketch there can be only one definition of a UART per SERCOM. Consequently I am not sure that one would want to define all 12 libraries (especially as I have yet to figure out a viable way to do this in the Arduino IDE). Nevertheless, I have provided an example of how this could be done.
Now that I have finally cought up with everyone and know how to take care of pin peripheral function assignments on the SAM D21/D51 microcontrollers, it's time to move on to serious business. Let's up the ante and get a fourth hardware USART running on the XIAO. So how is this done? The Serial Wire Debug interface, exposed as two pads on the bottom side of the XIAO, is run on two pins that can be multiplexed to the (ALT-)SERCOM1 interface. Since I did not want to solder wires onto the SWD pads, it was a bit tricky to connect the fourth serial port.
The connections cobbled together with a bit of painter's tape, a small spring clamp and some Dupont wires remained in place long enough to run a test program which is just an obvious extension of the previous program. Here is its output.
Here is the
Serial4.h header file.
And here is the accompanying
Serial4.cpp source code.
Unfortunately the PA30 and PA31 pins (respectively the SWCLK and SWDIO signals) are not in the pin description table in the
variant.cpp file found in the
../framework-arduino-samd-seeed/variants/XIAO_m0/ directory. So they had to be added at the end of the table in slots 17 and 18.
I wonder if anyone could have a use for this fourth serial port? First, it would disable the debug interface which could make developement of complex programs more difficult. Secondly, the change to the
variant.cpp file is never a good idea since the file "belongs" to the SAMD Arduino Core developpers. In other words, these changes will be overwritten each time the board definition is updated. Finally, connecting to the SWD pads is not as straightforward as connecting to the other pins of the device. This latter problem is neatly solved with the Seeeduino XIAO expansion board whch brings out the SWD interface and adds many peripherals such as an 0.96" OLED and an real time clock. It does cost 3 times as much as the XIAO itself.
Four hardware serial interfaces; can anyone do better? The 3 GPIO pins connected to the blue and yellow LEDs on the board correspond to pads 1, 2 and 3 of SERCOM1. But even if one could modify the USB code to stop flashing these LEDs in time with USB exchanges and somehow connect to the GPIO signals, a serial hardware interface would not be gained because the
Serial4 also uses SERCOM1. As for pins A0 to A3 which remain free, they cannot be used by any SERCOM module. I suppose that some bit-banged software UART could be run on these pins, but that would be cheating. Accordingly, I feel pretty confident in asserting that 4 hardware serial ports is the limit on the XIAO.
Serial 2 and
Serial 3 could have been set up to include the RTS and CTS hardware flow control signals. In that case there is no choice about pin assignment, the TX pin must be multiplexed on pad 0, RX on pad 1, RTX on pad 2 and CTS on pad 3 of the SERCOM.
I have not pursued this subject any further at this point. However those interested could look at section 184.108.40.206 Hardware Handshaking in the SAM D21/DA1 Family Complete Datasheet for more information.
Two additional hardware serial ports can easily be added to the XIAO. For each additional serial port, one of two pins can be chosen for the TX signal and one of three pins can be chosen for the RX signal.
|Extra UART with TX and RX Only|
|SERCOM||TX pin||RX pin (one of)||Name|
The SERCOM pad number to assign to the I/O pin is in square brackets. The asterisk indicated that the I/O pin's function is not set to
PIO_SERCOM_ALT by default and that it will be necessary to explicitely change it with the
The interfaces could also be set up as four pin UARTs. There is no choice in pin assignments in the case of a USART with flow control.
|Extra UART with TX, RX and Flow Control|
|SERCOM||TX pin||RX pin||RTS pin||CTS pin|
A third extra serial port can be enabled but it will be necessary to modify the
variant.cpp file as explained above.
|Third Extra USART with TX and RX Only|
|SERCOM||TX pin||RX pin||Name|
If a pin with an asterisk is chosen for the TX, the RX or CTS signal then it is necessary to manually set its mode with a
pinPeripheral(Axx, PIO_SERCOM_ALT) statement. That statement must appear after the
The source code for the examples discussed in this post is available on GitHub at sigmdel/xiao_usarts.