Overview of the SAMD21 Arm Cortex-M0+ Based Seeeduino XIAO
I²C Light Sensor using a Seeeduino XIAO-> <-Blink with a Wio Lite RISC-V with ESP8266
"Hello XIAO" in PlatformIO-> <-Rethinking the Raspberry Pi Hardware Watchdog

Despite perturbations in the transportation network in Canada and the Covid-19 pandemic, a long-awaited package from Seeed Studio arrived on March 18. Surprisingly, it contained a Seeeduino XIAO which was not expected with the other items because it was on pre-order. So, contrary to my expectations, this will be the second device, after the Raspberry Pi, which I will test as a new hardware watchdog for the home automation system.

This post contains my first impressions of the XIAO. Because I don't really have much experience with micro-controllers except for a bit of work with the ESP8266 and the very occasional experiment with Arduino boards, this overview will be at a rather superficial level. Hopefully, it may be of interest to other neophytes. I welcome advice and corrections from those more knowledgeable: click on the link below.

Table of contents

  1. Meet the Seeeduino XIAO
  2. Precursors, Competitors and References
  3. XIAO Input Output Pins
  4. Arduino IDE
  5. PlatformIO
  6. A "Hello World" Sketch or Two
    1. Uploading/Bootloader/COM Port/Reset Problem
  7. Digital Input and Output
    1. Obligatory Blink Sketch
    2. Timer Based Blink Sketch
    3. Pulse Width Modulation
    4. Digital Input
  8. Analogue Input and Output
    1. Analogue Input Calibration
    2. Analogue Measure of Light with an LDR
    3. Analogue Output
  9. I²C Bus
    1. Displaying Light Levels on an I²C OLED Display
  10. SPI Bus
  11. Disclaimer and First Impression

Meet the Seeeduino XIAO toc

According to Wikipedia, a xiao is a Chinese flute typically made of bamboo not unlike a recorder except that there is no mouthpiece and the player blows air across a hole in the top of the tube. The Seeeduino XIAO, on the other hand, is an inexpensive Arduino type board based on the SAM D21G an ARM-Based micro-controller by Atmel (now Microchip). The board is tiny; the image of the XIAO below greatly exaggerates its 18 by 20 mm size (18 by 22 mm with the USB C socket).

The board is well made, looks robust and has a very clean appearance. Most components are under a metal cover (hidden by a sticker with pin and function labels) with only four exposed LEDs below the USB-C connector. These are labelled P, L, R and T. There are no components on the underside of the board, but a couple of pads are of importance.

The 32-bit SAM D21 was introduced in 2012 as a low power and high performance alternative to the mostly 8 bit devices used for IoT applications at the time. The high performance is relative, the core cannot perform floating point arithmetic in hardware. The same applies to 32 bit integer division and 32 bit integer multiplication with 64 bit results. Nevertheless, the M0+ core is a significant step up from AVR type micro-controllers.

Core Size (bits) 8 32
Speed (MHz) 20 48
Flash Memory (K bytes) 8 32 256
RAM (K bytes) 0.5 2 32
I/O 6 23 38
ADC (number/bits) 4/10 8/10 14/12
DAC (number/bits) 0 1/10
Unit price ($US) 0.95 2.08 3.00

Prices were obtained from Digikey on March 20, 2020. At just under $5.00 from Seeed Studio, the XIAO looks like a bargain. I very much doubt one could purchase the CPU, an external crystal, a voltage regulator, four LEDs, various capacitors and resistors, metal can and a USB-C connector to make a similar board any cheaper. You could try, the schematic is available.

Precursors, Competitors and References toc

The Seeeduino XIAO is not the first board based on the SAMD21 microprocessor. The major players in the hobbyist world have numerous boards in production:

See the Zero, MKR Zero and MKR 1000.
Adafruit, and
See the METRO M0, Feather M0 Basic Proto and the many other Feather devices based on the same processor.
See the Redboard Turbo, SAMD21 Dev Breakout and SAMD21 Mini Breakout.

There are others. Freescale (an offshoot of Motorola but now owned by NXP which is itself an offshoot of Phillips) may have been one of the first to offer boards based on the SAM D21. PjRC and avdweb produce small boards based on the same processor. Even Seeed Studio has at least four other SAM D21 based boards on offer (such as the Seeeduino Lotus Cortex-M0+ and Seeeduino LoRaWAN).

Given the number of boards available, there is good information on the Web about the SAM D21. Adafruit has a tutorial Adafruit Feather M0 Basic Proto, Wanna play with the ARM Cortex M0 chipset? that is useful for XIAO users, particularly the section entitled Adapting Sketches to M0 & M4. The same is true at Sparkfun: RedBoard Turbo Hookup Guide and SAMD21 Mini/Dev Breakout Hookup Guide. The avdweb site also has useful information although it may be a little difficult to find. I use the search facility to find something I remember having seen previously.

The data sheet for the processor can be obtained from Microchip itself. Of course, The XIAO Wiki at Seeed is the main source of information about the XIAO. It is the source of the images and pin diagram found in this post.

The original Arduino core for the SAMD21 CPU is available on GitHub, Seeed Studio has a fork with variants for its own boards.

XIAO Input Output Pins toc

Seeed Studio did have to make some choices in creating such a small board that is nevertheless breadboard friendly. There are 7 pins holes along each of the two long edges which can accommodate standard 0.1" headers. Three pins are used for power (3.3V, 5V and ground), leaving only 11 pins for I/0. However, there are an additional 2 pads on the underside for the serial debug interface, a reset pad to the side of the USB-C socket and 3 of the four LEDs on the other side of the socket are connected to additional I/O lines.

The pin names Ax, where x = 0 to 10, are those used in the Arduino IDE, the Dx names shown on the diagram are not used to identify the I/O pins in the IDE. The addition of those Dx names is probably to underline the flexibility of the chip. Each I/O pin can be a digital input or output or an analogue input. The table below is another view of the board connections based on the pin identification found in the schematic.

Seeedino XIAO
Function SAMD21 Pin A/D SAMD21 Pin Function
PA10/EIC/AIN18/SCOM2PAD2+/TC1 2 3.3V

LabelColourSAMD21 Pin (Arduino Pin)Function
PGreen-Power indicator
TBluePA19/EIC/SCOM1PAD3+/TC3 (11)UART TX activity
RBluePA18/EIC/SCOM1PAD2+/TC3 (12)UART RX activity
LYellowPA17/I2C/EIC/SCOM1PAD1+/TC2 (13)Free

Serial Wire Debug Interface
SAMD21 PinFunction

AINAC Analogue Inputs
EICExternal Interrupt Controller
REFExternal Voltage Reference
SCOMSerial Communication
TCTimer Counter

Warning: The Seeeduino XIAO is a 3.3 volts device and applying 5 volts to an input will overload and damage the XIAO. There may be a 5 volt power connection on the board, but DO NOT use it to power say an I²C RTC clock because the latter's data connections would then be at the 5 volt level when in a HIGH state. Look at the absolute maximum ratings of VPIN in the following table.

Table 41-2. Absolute Maximum Ratings
VDDPower supply voltage03.8V
IVDDCurrent into a VDD pin-92 (1)mA
IGNDCurrent out of a GND pin-130 (1)mA
VPINPin voltage with respect to GND and VDDGND-0.6VVDD+0.6VV
TstorageStorage temperature-60150°C
1.Maximum source current is 46mA and maximum sink current is 65mA per cluster. A cluster is a group of GPIOs as shown in the table below. Also note that each VDD/GND pair is connected to two clusters so current consumption through the pair will be a sum of the clusters source/sink currents.

Source: Microchip, SAM D21/DA1 Family, Complete Datasheet DS40001882E, p. 1008.

Arduino IDE toc

Seeeduino XIAO programs can be written and downloaded to the device flash memory in the Arduino IDE. The initial steps to install the needed tools in the IDE are described in the Software section of the wiki. The procedure is the same as that required for non-AVR boards such as the ESP based boards. In short, it is a two-step procedure that needs to be done only once.

  1. Add https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json to the list of Additional Boards Manager URLs in the IDE Preferences (File/Preferences).
  2. Install the latest version of Seeed SAMD Boards by Seeed Studio (version 1.7.6 on June 24, 2020) core library in Board Manager (Tools/Board:xxxx/Board Manager... and enter Seeed SAMD in the search filter).

To go on to create a sketch, plug a USB-C cable from the desktop to XIAO. The green power LED will light on the device and it will show up on a Linux desktop as ttyACMxxx. If using Windows it will show up as a COM port.

michel@hp:~$ ls -l /dev/ttyACM* crw-rw---- 1 root dialout 166, 0 mar 22 20:11 /dev/ttyACM0

In the Arduino IDE, it is even easier to identify the correct port as the image shows (Tools/Port:...).

The correct board must also be used. This is done by selecting the Seeeduino XIAO board in the Tools/Board: menu, as shown above. More details can be found elsewhere on this site, in the Adafruit and Sparkfun tutorial referenced above and in the Seeeduino XIAO wiki.

PlatformIO toc

PlatformIO now supports the Seeeduino XIAO. Some, including myself, prefer PlatformIO to the Arduino IDE. New users of PlatformIO or those that are curious about that programming environment may want to consult a short post on how to program the XIAO in that environment: "Hello XIAO" in PlatformIO. The information in the rest of this post remains pertinent for users of PlatformIO.

A "Hello World" Sketch or Two toc

Here is a simple, do nothing, "hello world" sketch which nevertheless taught me a few lessons.

/*  hello_world.ino */ #define BAUD 115200 void setup() {  delay(5000);  unsigned long serialBeginTime = millis();  Serial.begin(BAUD);  while(!Serial);  unsigned long serialOpenedTime = millis();  Serial.printf("\n\nSerial opened in %d milliseconds (baud: %d).\n", serialOpenedTime - serialBeginTime, BAUD);  Serial.println("The symbolic names for the 11 I/O pins");  Serial.printf("A0 = %d\n", A0);  Serial.printf("A1 = %d\n", A1);  Serial.printf("A2 = %d\n", A2);  Serial.printf("A3 = %d\n", A3);  Serial.printf("A4 = %d\n", A4);  Serial.printf("A5 = %d\n", A5);  Serial.printf("A6 = %d\n", A6);  Serial.printf("A7 = %d\n", A7);  Serial.printf("A8 = %d\n", A8);  Serial.printf("A9 = %d\n", A9);  Serial.printf("A10 = %d\n", A10); //  Serial.printf("A11 = %d\n", A11);  // this will not compile even if there is a pin 11 (and 12 etc.) //  Serial.printf("D1 = %d\n", D1);    // this will not compile, Dx aliases/variables do not exist } void loop() {  Serial.println("Hello world");  delay(5000); }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

This is the output displayed in the IDE serial monitor.

Serial opened in 7363 milliseconds (baud: 115200). The symbolic names for the 11 I/O pins A0 = 0 A1 = 1 ... A10 = 10 Hello world Hello world ...

The first surprise was the time it took for the serial port to come up. When I first tried a simple 10 or 100 millisecond delay after the Serial.begin(), which I was systematically using in ESP8266 or Arduino Uno sketches, the serial output in the setup() section was not displayed in the IDE serial monitor. To be fair, the almost 8 seconds delay in the above case was unusually long. Delays of about 0.7 to 0.8 seconds are typical. Given the variability, it is not a bad idea to suspend the setup code until the serial port is available as shown in the sketch.

D. J. Park had some interesting comments about that little bit of code. First Serial.begin() is not needed. I tried commenting it out and the serial monitor did display the output of the sketch at 115200 baud. I assume that is a default rate because it worked even after powering off the Xiao and then powering it back on. Secondly the time measure is "meaningless [because it] includes the time taken by the user to open the terminal application and open the port." D. J. goes on to say that a delay of 1700 ms seemed sufficient on his Windows 10 system.

This is a good spot to add a comment of my own about this delay. While it made sense to wait until a serial connection to the console was established before continuing with the sketches in this post, in practice that is a very bad idea. It was puzzling that the I2C sketches in the following post would not work whenever the XIAO was powered independantly. It finally dawned on me that it was being hung up by the !Serial() in the setup(). Consequently this is how I currently do the test.

#define BAUD 115200 #define WAIT_FOR_CONSOLE 20000  // Set to 0 to forego waiting for a serial connection to the console void setup() { #if WAIT_FOR_CONSOLE > 0 Serial.begin(BAUD);                                         // needed for !Serial test while (!Serial && (millis() < WAIT_FOR_CONSOLE)) delay(10); // Waiting at most WAIT_FOR_CONSOLE ms for a connection to the console #endif ...

Note that in this case, it is necessary to initialize the Serial object otherwise the !Serial() test may fail.

The second surprise was how clean the serial output is. There is no garbage at boot time which seems inevitable with the ESP8266. Consequently, there is no need for the initial line feeds which I often used in ESP sketches to separate the desired serial output from what precedes.

The third surprise was that the code worked. In the documentation found on the Web I often saw that SerialUSB would have to be used to send text to the IDE serial monitor. The explanation is found in the last two lines of the variant.h header file in the .../packages/Seeeduino/hardware/samd/1.7.0/variants/XIAO_m0/ directory:

// Alias Serial to SerialUSB #define Serial SerialUSB

Had I used SerialUSB, the sketch would have compiled and worked just as well. By the way, just where the packages subdirectory is found will depend on the desktop operating system and the type of installation chosen for the Arduino software. I have a portable installation in a custom directory on a Linux system, so the full path to variant.h is:


If I insist on that file, it's because it warrants examination. In that file, 17 I/O signals are defined, among which are those 11 that are brought out to pins identified with the A0 to A10 names. If you want to use Dx pin names, which is the usual nomenclature with ESP8266 based boards, then you could add the following in each sketch or, once, in the variant.h file.

#define D1 PIN_A1 #define D2 PIN_A2 #define D3 PIN_A3 #define D4 PIN_A4 #define D5 PIN_A5 #define D6 PIN_A6 #define D7 PIN_A7 #define D8 PIN_A8 #define D9 PIN_A9 #define D10 PIN_A10

Or perhaps you might want to mimic the definition of the analogue pins.

/* * Digital pins */ #define PIN_D1 (1ul) #define PIN_D2 (PIN_D1 + 1) ... #define PIN_D10 (PIN_D1 + 9) static const uint8_t D1 = PIN_D1; ... static const uint8_t D10 = PIN_D10;

This will use up a further 10 bytes. However, the hello_world.ino sketch "uses 12584 bytes (4%) of program storage space. Maximum is 262144 bytes" so 10 bytes does not seem like much.

Lastly, I observed that it could be difficult to download the sketch a second time. This problem merits a subsection of its own which follows next. The section title promised more than one sketch. Here is the second one.

/*  pin_attr.ino */ #define BAUD 115200 #define SINGLE_TABS "%d\t%s\t%s\t%s\t%s\n"        // use this format string to copy to a spreadsheet #define ALIGN_TABS "%d\t%s\t\t%s\t\t%s\t\t%s\n"   // use this format for pretty output in IDE monitor #define TABS ALIGN_TABS void setup() {  Serial.begin(BAUD);  while(!Serial);  Serial.println("Seeeduino XIAO I/O Pin Attributes\n");  Serial.println("Pin\tAnalog In\tAnalog Out\tDigital In/Out\tPWM");  for (int i=0; i<PINS_COUNT; i++) {     Serial.printf(TABS,     i,     (i < NUM_ANALOG_INPUTS) ? "yes" : "no",     (i < NUM_ANALOG_OUTPUTS) ? "yes": "no",     (i < PINS_COUNT) ? "yes": "no",     (digitalPinHasPWM(i)) ? "yes" : "no");  } } void loop() { }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

The following table was made with output from the sketch and the content of variant.h.

Seeeduino XIAO I/O Pin Names and Attributes
IndexStatic intMacroInOut

Like characters in a Russian novel, some pins have five names!

You may want to install the arduino ZeroRegs library and run its example sketch which "... prints the [configuration] registers for the Arduino Zero (and similar boards)" including the XIAO. I can't say that I can make much sense of the output, but it's something that could be useful later when I learn more about the SAM D21.

Uploading/Bootloader/COM Port/Reset Problem toc

There is an entry, Reset, in the Seeeduino XIAO wiki on the subject with a short video showing how to reset the board by shorting the two pads labelled RST beside the USB connector. I must admit that I do not quite understand the accompanying explanation. This inability to upload a sketch or to reset the board seems to be a common problem which shows up in the forum more than once ( XIAO Boot issue, Serial Monitor with XIAO not working, and An error occurred when uploading sketch to seeeduino lorawan board via arduino ide). Most often, the same answer is provided: the board must be reset twice when it hangs in this fashion. Lady Ada says as much in Bootloader Launching. The problem is further discussed in a question on the Feather HELP! page:

I don't understand why the COM port disappears, this does not happen on my Arduino UNO!

UNO-type Arduinos have a separate serial port chip (aka "FTDI chip" or "Prolific PL2303" etc etc) which handles all serial port capability separately than the main chip. This way if the main chip fails, you can always use the COM port.

M0 and 32u4-based Arduinos do not have a seperate chip, instead the main processor performs this task for you. It allows for a lower cost, higher power setup...but requires a little more effort since you will need to 'kick' into the bootloader manually once in a while.

The avdweb site also contains a discussion of the problem, Arduino Zero com port detection and upload problems which a similar explanation of its source. Unfortunately none of the proposed solution seems to work for me. With experience I have learned to wait long enough after a failed attempt at uploading a sketch before trying again. If I am patient enough, it usually works on the second try. The yellow LED seems to be a good indicator. If it is quickly blinking, then the device is busy ant the upload will not work. The LED should be either fully on or if it is slowly pulsing up and down, wait for a few cycles before attempting to upload. Now I can repeatedly upload a sketch, being patient between retries and only occasionally resorting to hard resets. I think that initially I often did not manage to cleanly "ground" the RST pad twice in quick succession. With experience it seems that I have mastered that.

When the XIAO is in this "bootloader" mode, it shows up as a storage device named Arduino on my system as can be seen below.

Uploading will work. Hardy explorers can consult the definitive source about the USB Flashing Format (UF2).

Digital Input Output toc

All I/O pins on the XIAO can be used as digital pins, which means pins that should be in either a LOW state or HIGH state when used as inputs or outputs.

We will begin this discussion with a simple LED flashing sketch with a slight twist as it that blinks all three LEDs on the XIAO that are connected to I/O lines. Note that these I/O connections are active LOW: it is necessary to write a 0 to the pin to turn the corresponding LED on which is contrary to the typical Arduino convention.

/*  blink.ino */ #define BAUD 115200 #define FLASHES 4 #define SHORT_DELAY 75 #define LONG_DELAY  1825 #define BLUE_LED_TX   11   //= PIN_LED_TXL = PIN_LED3 #define BLUE_LED_RX   12   //= PIN_LED_TXL = PIN_LED3 #define YELLOW_LED    13   //= PIN_LED = LED_BUILTIN int LEDS[3] = {YELLOW_LED, BLUE_LED_TX, BLUE_LED_RX}; int ledIndex; void setup() {  Serial.begin(BAUD);  while(!Serial);  // initialize I/O signals connected to the LED  for (ledIndex=0; ledIndex<3; ledIndex++) {    pinMode(LEDS[ledIndex], OUTPUT);    }  // reset LED index  ledIndex = 0;  Serial.printf("blink.ino\n", BAUD);  Serial.printf("Arduino pin number of blue TX (T) LED: %d (= PIN_LED_TXL: %d, PIN_LED3: %d)\n", BLUE_LED_TX, PIN_LED_TXL, PIN_LED3);  Serial.printf("Arduino pin number of blue RX (R) LED: %d (= PIN_LED_RXL: %d, PIN_LED2: %d)\n", BLUE_LED_RX, PIN_LED_RXL, PIN_LED2);  Serial.printf("Arduino pin number of yellow  (L) LED: %d (= LED_BUILTIN: %d, PIN_LED: %d)\n", YELLOW_LED, LED_BUILTIN, PIN_LED); } void loop() {  for (int i=0; i<FLASHES; i++) {    digitalWrite(LEDS[ledIndex], LOW);   // turn a LED on    delay(SHORT_DELAY);                  // wait    digitalWrite(LEDS[ledIndex], HIGH);  // turn the LED off    delay(SHORT_DELAY);                  // wait  }  delay(LONG_DELAY);    // next LED  ledIndex = (++ledIndex)%3; }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

It just is a bit difficult to show on a static page but, trust me, the three LEDs do flash four times quickly in sequence.

D. J. Park wrote "the led pins are controlled as low side switches like open drain and ... the digital pin connected to the led [turns the latter ON when set LOW and OFF when set HIGH]. I think it is worth mentioning in your post since most people will expect HIGH is ON." Fair enough, but users of ESP8266 devices such as the Lolin/Wemos D1 mini and the nodeMCU boards are used to the reverse logic also in place on the XIAO. At least the comments in the script were explicit about that.

There are two timer based blink sketches in the Arduino IDE example sketches for the Seeeduino XAIO. Try them (File/Examples/ scroll down to Examples for Seeeduino XAIO and select TimerTC3/ISRBlink and TimerTCC0/ISRBlink), they both work. The sketches are almost identical, the only difference is in the timer used. Here they are combined, with the choice of the timer done with a macro directive.

/*  isr_blink.ino */ #define USE_TIMER_TC3  // use TimerTc3, comment out to use TimerTcc0 #define BAUD 115200 #define TIMER_TIME 1000000   // micro seconds, careful too big a value will not work #ifdef USE_TIMER_TC3  #include <TimerTC3.h>  #define TIMER TimerTc3 #else    #define TIMER TimerTcc0  #include <TimerTCC0.h> #endif volatile bool isLEDOn = false; void setup() {    Serial.begin(BAUD);    while (!Serial) { delay(10); }      pinMode(LED_BUILTIN, OUTPUT);        TIMER.initialize(TIMER_TIME);    TIMER.attachInterrupt(timerIsr);    Serial.printf("%d microseconds timer based blink using ", TIMER_TIME);    #ifdef USE_TIMER_TC3    Serial.println("TimerTc3");    #else    Serial.println("TimerTcc0");    #endif      pinMode(PIN_LED_TXL, INPUT); // turn off serial tx LED signalling } bool oldState = ~isLEDOn; void loop() {    int state;    noInterrupts();    state = isLEDOn;    interrupts();      if (state != oldState) {      Serial.print((state) ? "ON " : "OFF\n");      oldState = state;    } } void timerIsr() {        digitalWrite(LED_BUILTIN, isLEDOn);    isLEDOn = !isLEDOn; }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

See the TimerOne & TimerThree Libraries example program from PJRC for an excellent explanation of the use of the volatile type and of the need to disable interrupts in the main loop. Note how the mode of the PIN_LED_TXL pin is set to INPUT. Otherwise the blue TX LED will flash at each character written to the serial monitor which distracts from the flashing yellow LED. The output in the IDE serial monitor,

1000000 microseconds timer based blink usingTimerTc3 OFF ON OFF ON OFF ON OFF ON OFF ON OFF ON OFF

is not very interesting but it does follow the yellow LED on/off pattern. I cannot find information about the TimerTC3 and TimerTCC0 libraries, so I will have to look at the source code and the SAM D21 datasheet if there's a need to use timers in the future.

Pulse Width Modulation toc

Simple Arduino style pulse width modulation is available on pins A1 to A10 and on the three I/O lines connected to the on-board LEDs. The example Fade.ino sketch (File/Examples/Basic/Fade) works, here is a modified version the makes three of the XIAO LEDS fade in and out at different rates.

/*  pwm.ino */ int brightness[3] = {32, 127, 255}; int fadeAmount[3] = {5, 12, -7}; void setup() {  for (int i = 11; i<14; i++) {    pinMode(i, OUTPUT);  }   } void setPwm(int pin, int duty) {  if (duty >= 255) {    digitalWrite(pin, 255);  } else {    if (duty < 0) duty = 0;    analogWrite(pin, duty);  } } void loop() {  for (int i = 0; i < 3; i++) {    // set the brightness of pin 11, 12 and 13    //analogWrite(i+11, brightness[i]);    setPwm(i+11, brightness[i]);    // change the brightness for next time through the loop:       brightness[i] = brightness[i] + fadeAmount[i];    // reverse the direction of the fading at the ends of the fade:        if (brightness[i] <= 0) {      fadeAmount[i] = -fadeAmount[i];        brightness[i] = 0;    } else if (brightness[i] >= 255) {      fadeAmount[i] = -fadeAmount[i];      brightness[i] = 255;    }  }     // wait for 30 milliseconds to see the dimming effect  delay(30); }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

See the warning by Lady Ada about the difference between AVR and ARM cortex devices when writing the value 255: analogWrite() PWM range. I have not investigated that topic. Nevertheless the setPwm() routine implements the recommendation to use digitalWrite() to obtain a 100% duty cycle.

Digital Input toc

A normally open push button is used to experiment with digital inputs on the XIAO. The connection could not be simpler: one side of the switch is connected to ground, the other to an I/O pin. In the following sketches digital pin 7 of the XIAO is used, but any of the 11 pins could be used. Here are too very short sketches which ensure that the yellow LED is on when the button is pressed (when digital pin 7 is grounded) and off when the button is released (and when pin 7 is brought to Vcc by the internal resistor).

/*  button.ino */ #define BUTTON 7  // Can be any I/O pin from 0 to 10 void setup() {  // initialize the LED pin as an output:  pinMode(LED_BUILTIN, OUTPUT);  // initialize the pushbutton pin as an input and enable the internal pull up resistor:  pinMode(BUTTON, INPUT_PULLUP); } void loop() {  digitalWrite(LED_BUILTIN, digitalRead(BUTTON));    // Exploiting the fact that the XIAO LEDS are active LOW. }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

I will make three comments about this sketch.

  1. Note how the button pin mode is set to INPUT_PULLUP which activates the internal pull up resistor as well as setting the I/O pin connected to the button as an input.
  2. The only statement in the loop() procedure, sets the pin controlling the yellow LED to the value read from the button input. This works because of the reversed logic of the LED. All the XIAO LEDS are active LOW which means the pin connected to them must be set to 0 to turn on the LED. The logic of the statement digitalWrite(LED_BUILTIN, digitalRead(BUTTON)); is
    if (digitalRead(BUTTON) == HIGH) {  // If the buttonState is HIGH, the button is release so turn the LED off:  digitalWrite(LED_BUILTIN, HIGH); } else {  // If the buttonState is LOW, the button is pressed so turn LED on:  digitalWrite(LED_BUILTIN, LOW);
  3. The state of the button is continuously polled in this sketch, and the LED pin is continuously updated.

It is not difficult to write to the LED pin only when necessary.

/*  button2.ino */ #define BUTTON 7      // Can be any I/O pin from 0 to 10 int state = 1;        // BUTTON is initially HIGH void setup() {  // initialize the LED pin as an output:  pinMode(LED_BUILTIN, OUTPUT);  // initialize the pushbutton pin as an input and enable the internal pull up resistor:  pinMode(BUTTON, INPUT_PULLUP); } void loop() {  if (digitalRead(BUTTON) != state) {    state = 1 - state;    digitalWrite(LED_BUILTIN, state);  } }

It is possible to forego all this polling busy work by using an interrupt.

/*  button_int.ino */ #define BUTTON  7        // Can be any I/O pin from 0 to 10 #define DEBOUNCE_TIME 10 // millisecond button debouncing time void buttonISR() {  noInterrupts();  delay(DEBOUNCE_TIME);  digitalWrite(LED_BUILTIN, digitalRead(BUTTON));  interrupts(); } void setup() {  // initialize the LED pin as an output:  pinMode(LED_BUILTIN, OUTPUT);  // initialize the pushbutton pin as an input and enable the internal pull up resistor:  pinMode(BUTTON, INPUT_PULLUP);  attachInterrupt(digitalPinToInterrupt(BUTTON), buttonISR, CHANGE);   } void loop() { // empty right now, add code as needed. }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

As D.J. Park reminded me, the digitalPinToInterrupt() macro is defined twice.

D. J. added that he "commented [out] that line in variant.h file so that there will be no warning." He motivates that choice as follows: "Arduino.h is included first and it includes variant.h. The macro in the variant.h is defined first, and the same macro in Arduino.h is defined again. The macro defined later takes effect, so #define digitalPinToInterrupt(P) ( P ) is used."

Being the paranoid type, I decided to investigate before changing variant.h. I looked at all the variant.h files of the SAM D21 devices noting two things: the compliance value and the presence or not of a digitalPinToInterrupt(p) macro in the file.

femto_m010610#define digitalPinToInterrupt(P) (g_APinDescription[P].ulExtInt)
XIAO_mo10610#define digitalPinToInterrupt(P) (g_APinDescription[P].ulExtInt)

Then I looked at the macro in the Arduino.h file and the attachInterrupt() code in WInterrupts.c located in the same directory.

#if (ARDUINO_SAMD_VARIANT_COMPLIANCE >= 10606) // Interrupts #define digitalPinToInterrupt(P)   ( P ) #endif

void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) {  static int enabled = 0;  uint32_t config;  uint32_t pos;  #if ARDUINO_SAMD_VARIANT_COMPLIANCE >= 10606  EExt_Interrupts in = g_APinDescription[pin].ulExtInt;  #else  EExt_Interrupts in = digitalPinToInterrupt(pin);    #endif ...    

Clearly D. J. was correct to remove the macro from the variant file. Had that macro ever been invoked, the attached interrupt would have been g_APinDescription[g_APinDescription[BUTTON].ulExtInt].ulExtInt; which would not have been good.

The XIAO core is a moving target. The version number is now 1.7.5 and the current version of variant.h no longer contains the offending digitalPinToInterrupt(P) macro definition. That's confirmation the advice from D. J. was, once again, correct.
One could be tempted to attach the interrupt with the simpler attachInterrupt(BUTTON, buttonISR, CHANGE); which would obviously work. The one reason to leave the digitalPinToInterrupt() macro in place is that other Arduino compatible boards may require such a function because pin and interrupt numbers do not map identically.

All three versions of these sketches work, choose whichever is better for a specific application. My button libraries mdButton and mdButton2 will have to be updated to handle the XIAO. This should be done soon.

Analogue Input and Output toc

Up to now, I have had basically no experience with analogue input and output because I have mostly worked with ESP8266 based devices, many of which did not even use its single analogue input pin. The SAM D21 is quite different, as almost every I/O pin can be used for digital or analogue input and output. This was a good opportunity to learn some of the details about this aspect of micro-controllers.

Analogue Input Calibration toc

Pins A1 and A2 to the XIAO are connected to the ground and 3.3 volts pins and the following simple sketch performs continuous analogue reads of both pins.

/* adc_in.ino */ #define ADC_GND_PIN A1 #define ADC_VCC_PIN A2 #define ADC_BITS 12 // The SAM D1 on Seeeduino XIAO has 12 bit ADC inputs void setup() { Serial.begin(115200); while(!Serial); Serial.println("\nReading Ground and VCC values with Analog Input"); analogReadResolution(ADC_BITS); pinMode(ADC_GND_PIN, INPUT); pinMode(ADC_VCC_PIN, INPUT); } int gndValue; int vccValue; void loop() { gndValue = analogRead(ADC_GND_PIN); delay(10); vccValue = analogRead(ADC_VCC_PIN); Serial.printf("ADC(GND) = %d, ADC(VCC) = %d\n", gndValue, vccValue); delay(1500); }

The analogue inputs of the SAM D21 microprocessor on the Seeeduino XIAO have 12 bit digital analogue converters. That means that the input voltage measured at the pin is translated into one of 2^12 = 4096 possible values. When the input voltage is 0 volts, the value read should be 0, when voltage is equal to Vcc (approximately 3.3 volts) the input value should be 4095. That is not quite what was returned by the sketch.

Reading Ground and VCC values with Analog Input ADC(GND) = 0, ADC(VCC) = 4052 ADC(GND) = 0, ADC(VCC) = 4052 ADC(GND) = 0, ADC(VCC) = 4054 ADC(GND) = 0, ADC(VCC) = 4050 ADC(GND) = 0, ADC(VCC) = 4052 ADC(GND) = 0, ADC(VCC) = 4052 ADC(GND) = 0, ADC(VCC) = 4057 ...

While the returned value when measuring 0 volts was fine, there was quite a bit of variation in the values returned when measuring Vcc as can be seen with the following histogram established after 741 measurements.

Note how the highest returned value was 4067 which is short of 4095, the theoritical maximum value. Among the example sketches installed in the Arduino IDE along with the SAM D1 board manager there is one named CorrectADCResponse. I ran it and obtained this output.

Calibrating ADC with factory values Reading GND and 3.3V ADC levels ADC(GND) = 0 ADC(3.3V) = 4064 Offset correction (@gain = 2048 (unity gain)) Offset = 0, ADC(GND) = 0 Gain correction Gain = 2048, ADC(3.3V) = 4064 ... Gain = 2067, ADC(3.3V) = 4094 Gain = 2068, ADC(3.3V) = 4094 Gain = 2069, ADC(3.3V) = 4095 Gain = 2070, ADC(3.3V) = 4095 Gain = 2071, ADC(3.3V) = 4095 Gain = 2072, ADC(3.3V) = 4095 ... Gain = 2078, ADC(3.3V) = 4095 Readings after corrections ADC(GND) = 0 ADC(3.3V) = 4095 ================== Correction values: Offset = 0 Gain = 2069 Add the next line to your sketch: analogReadCorrection(0, 2069); ==================

I added two lines to the adc_in.ino sketch. The first, inserted at the beginning of the sketch, added a library.

#include "SAMD_AnalogCorrection.h"

In the setup() routine, the following line

analogReadCorrection(0, 2069);

was added as instructed. Reading the analogue input lines yielded the expected results with these corrections.

Reading Ground and VCC values with Analog Input ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ADC(GND) = 0, ADC(VCC) = 4095 ...

Analogue Measure of Light with an LDR toc

Here is the simple circuit used to measure the quantity of light built around a light dependant resistor (LDR) and a fixed resistor. The 5K value of the fixed resistor is just an indication, it is not critical. Besides, I do not know the characteristics of the LDR. Any of the 11 I/O pins of the XIAO could be used, I chose A3.

/*  ldr_in.ino */ // Baud for serial port (/dev/ttyACM0 on Linux) #define BAUD 115200 // Analog input pin #define LDR 3 void setup(){  Serial.begin(BAUD);    while (!Serial) { delay(10); }  pinMode(LDR, INPUT);  analogReadResolution(ADC_RESOLUTION);      // #define ADC_RESOLUTION 12 in header file    // .../packages/Seeeduino/hardware/samd/1.7.0/variants/XIAO_m0/variant.h } int level; void loop(){  level = analogRead(LDR);  //Serial.printf("LDR level: %d\n", level); // ok for serial monitor, but  Serial.println(level);                     // no text is better for serial plotter  delay(2000); }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

I took these measures in the late afternoon when the light level in the room was decreasing as the sun was getting closer to the horizon.

Values over 4000 can be obtained by bringing a COB LED quite near the LDR. The measured value can drop quickly to 0, or very near that level, by cupping my hand tightly around the LDR and thus shutting out most of the light.

Analogue Output toc

There is a single "true analogue" output signal on the XIAO which is available on pin A0. The term true analogue was in quotation marks because the signal is obtained through a 10 bit digital to analogue converter (DAC) so that the output voltage can be set to one of 2^10 = 1024 values. The actual voltage is given by the following formula:

  DAC output voltage  Vout = (DATA/1023)*Vref

where DATA is any value from 0 to 1023. The reference voltage, Vref can be one of many values, but by default it is the XIAO input voltage which is nominally 3.3V. Accordingly only discrete voltages can be obtained but the resolution, which by default is 0.003226 volts or 3.226 millivolts, is probably better than I will ever need. Using a Vref of 1 volt, the step will fall to slightly less than 1 millivolt: 0.9775.

To test this analogue signal, I simply connected A0 to A8 but any other analogue input from A1 to A10 could be used although it is best to avoid A1. Here is a simple sketch that simply ramps the output analogue signal up and down in a linear fashion and reads the pin output. In essence it produces a triangular wave with code rather similar to the sketch using PWM to pulse the intensity of LEDs.

/* dac2adc.ino */ #include "SAMD_AnalogCorrection.h"     // Baud for serial port (/dev/ttyACM0 on Linux) #define BAUD 115200 // If the SCALE is defined, then DAC output will be scaled by 4 (2^12 = 4 * 2^10) // so that the DAC output and the ADC input plots match //#define SCALE   // Resolutions // #define DAC_RESOLUTION  10     // 2^10 = 1024  (hence possible output value 0 to 1023)     //#define ADC_RESOLUTION  12 in header file .../packages/Seeeduino/hardware/samd/1.7.0/variants/XIAO_m0/variant.h   // 2^12 = 4096  (hence possible input values 0 to 4095) #define DAC_PIN 0  // DAC output pin #define ADC_PIN 8  // ADC input pin, could be any pin from 1 to 10 int step = 16; void setup() {  Serial.begin(BAUD);    while(!Serial);  pinMode(ADC_PIN, INPUT);  analogReadResolution(ADC_RESOLUTION);  analogReadCorrection(0, 2069);  // This correction will be different for each XIAO    //pinMode(DAC_PIN, OUTPUT);     // ***** DO NOT set A0 mode to OUTPUT when using the DAC *****  analogWriteResolution(DAC_RESOLUTION);  pinMode(1, INPUT); } int invalue; int outvalue = 0; void loop() {    analogWrite(DAC_PIN, outvalue);    invalue = analogRead(ADC_PIN);    #ifdef SCALE      Serial.printf("OUT(DAC):%d\tIN(ADC):%d\tMax:%d\n", 4*outvalue, invalue, 4095);    #else      Serial.printf("OUT(DAC):%d\tIN(ADC):%d\tMaxDAC:%d\tMaxADC:%d\n", outvalue, invalue, 1023, 4095);    #endif    outvalue += step;    if ((outvalue < 0) || (outvalue > 1023)) {      step = -step;      if (outvalue < 0)        outvalue = 0;      else        outvalue = 1023;    }    delay(10);       }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

The loop() function pumps out lines of output and input measures along with two reference values to the serial port.

OUT(DAC):63 IN(ADC):2783 MaxDAC:1023 MaxADC:4095 OUT(DAC):47 IN(ADC):2787 MaxDAC:1023 MaxADC:4095 OUT(DAC):31 IN(ADC):2784 MaxDAC:1023 MaxADC:4095 OUT(DAC):15 IN(ADC):2784 MaxDAC:1023 MaxADC:4095 OUT(DAC):0 IN(ADC):2785 MaxDAC:1023 MaxADC:4095 OUT(DAC):16 IN(ADC):2788 MaxDAC:1023 MaxADC:4095 OUT(DAC):32 IN(ADC):2784 MaxDAC:1023 MaxADC:4095 OUT(DAC):48 IN(ADC):2785 MaxDAC:1023 MaxADC:4095 OUT(DAC):64 IN(ADC):2783 MaxDAC:1023 MaxADC:4095 ...

The serial output of the sketch can be viewed in the IDE serial plotter (Tools/Serial Plotter).

The graph could be surprising on first look, but remember this plot is not a direct measure of the output and input voltages. The btriangular blue wave shows the numeric data written to the DAC to specify the output voltage on pin 0. The red wave (or higher amplitude wave, for colour-blind readers) shows the numeric data read from the ADC on pin 8 as it converts the output voltage read on pin A1. The ADC has a 12 bit resolution, so it divides the 0 to 3.3 voltage range into 4096 steps. Writing 64 to the output DAC will result in an output voltage of 0.2064 (=3.3*64/1023). The ADC reads that voltage as 4095*0.2064/3.3 = 256.12, which is 4 times 64 (when ignoring rounding and measurement errors). The input voltage is quite close to the output voltage notwithstanding the scale factor. This can be verified by scaling either the DAC or ADC measure. When SCALE is defined the output value is scaled and the result is the following graph.

It is now obvious that what is read is precisely what is output.

Important Note: when using pin 0 to output the DAC signal, DO NOT set the pin mode to OUTPUT. Here is what happens if pinMode(DAC_PIN, OUTPUT); statement is included the setup() function. (See analogWrite() DAC on A0.)

Remember the red truncated wave is the actual voltage on the output pin, the blue wave is the set output voltage which is manifestely not obtained at times. Note that the DAC and ADC measures has been converted to microvolts. The sketch, dac2dac_v.ino, that produced this plot can be found in the archive that accompanies this post.

I²C Bus toc

To test the XIAO I²C bus, I connected it to a couple of I²C devices: an 0.96" OLED display and a module with RTC clock (DS3231) and EEPROM ().

      XIAO      OLED     RTC & EEPROM
    -------     ----     ------------

This preliminary is just a scan of the I²C bus to ensure that the connected devices can be found. I did this with the i2c_scanner.ino sketch that can be easily be found on the Web such as in Thomas Feldmann gist. I changed just a couple of lines in the setup() function.

... // This sketch tests the standard 7-bit addresses // Devices with higher bit address might not be seen properly. // #include <Wire.h> #define BAUD 115200 void setup() { Serial.begin(BAUD); Wire.begin(); while (!Serial); // Waiting for Serial Monitor Serial.println("\nI²C Scanner"); } void loop() ...

This is the output in the IDE serial monitor.

Scanning... I2C device found at address 0x3C ! I2C device found at address 0x57 ! I2C device found at address 0x68 ! done

Those are the correct addresses of the three I²C devices. Let's go on to test I²C communications.

Displaying Light Levels on an I²C OLED Display toc

For a second test a 0,96" OLED display is connected the hardware I²C bus on the XIAO: SDA is pin PA8 (A4) and SCL is pin PA9 (A5). The display is a no name 128 by 64 pixels SSD1306 purchased from China. The rest of the setup has already been seen. It is a voltage divider made with a fixed 5 K ohms resistor and a light dependant resistor (LDR) tied together. That junction is connected to pin PA10 (A3) which is used as an analogue input.

/*  ldr_oled.ino */ #include <Arduino.h> #include <U8g2lib.h> // Using hardware IIC bus (SDA: 4, SCL: 5) with noname 128x64 0.96" OLED display U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); // Baud for serial port (/dev/ttyACM0 on Linux) #define BAUD 115200 // Analog input pin #define LDR 3 // Analog to digital resolution: 12 bits #define ADC_BITS 12 // Delay (ms) between readings of LDR value #define REFRESH_DELAY 2000   void setup(){  Serial.begin(BAUD);    while(!Serial);  Serial.println("Test of hardware I2C with noname 128x64 OLED");  Serial.printf("SDA: %d, SCL: %d\n", SDA, SCL);  u8x8.begin();  u8x8.setPowerSave(0);  u8x8.setFont(u8x8_font_chroma48medium8_r);    pinMode(LDR, INPUT);  analogReadResolution(ADC_BITS);  Serial.printf("Connected LDR to pin A%d/nsetup() completed.", LDR); } int level; char buf[200]; void loop(){  level = analogRead(LDR);  sprintf(buf, "LDR level: %d", level);  u8x8.clearLine(0);  u8x8.drawString(0,0, buf);  Serial.println(buf);  delay(REFRESH_DELAY); }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

The sketch uses the U8g2 library by oliver version 2.27.6. That library was installed using the IDE library manager. Note that the default hardware I²C bus and that the SDA and SCL signals are not specified.

// Using hardware IIC bus (SDA: 4, SCL: 5) with noname 128x64 0.96" OLED display U8X8_SSD1306_128X64_NONAME_HW_I²C u8x8(/* reset=*/ U8X8_PIN_NONE);

The variant.h file contains the pin numbers of the I²C SDA and SCL signals. This is a rather large library, but when compilation is complete, the IDE reports that program storage is not dented in an appreciable way.

Sketch uses 20660 bytes (7%) of program storage space. Maximum is 262144 bytes.

SPI Bus toc

Unfortunately, I do not have sensors that use the Serial Peripheral Interface (SPI) communication protocol. I do have a SPI display, but it is currently being used. So the only test I could do quickly was to connect a cheap USB logic analyzer and to look at the output while running the SPI example sketch found in the XIAO Wiki. There was a problem with using the name SS for the slave select pin so I changed that to CS. Setting the mode of the chip select pin was also missing.

/* spi_test.ino */ #include <SPI.h> // Baud for serial port (/dev/ttyACM0 on Linux) #define BAUD 115200 const int CS = 7; void setup (void) {  Serial.begin(BAUD);    while(!Serial) delay(50);  pinMode(CS, OUTPUT);  digitalWrite(CS, HIGH); // disable Slave Select  SPI.begin ();  SPI.setClockDivider(SPI_CLOCK_DIV8);//divide the clock by 8 } void loop (void) {   char c;   digitalWrite(CS, LOW); // enable Slave Select   // send test string   for (const char * p = "Hello, world!\r" ; c = *p; p++) {      SPI.transfer (c);      Serial.print(c, HEX);      Serial.print(" ");   }   Serial.println();   digitalWrite(CS, HIGH); // disable Slave Select   delay(1000); }

The sketch is available in the xiao_sketches.zip archive along with all the other sketches presented on this page.

I added serial print statements to see the "Hello world" message as hexadecimal values to make it easier to verify that the message is indeed printed on the SPI bus.

48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 D 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 D 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 D

Here is the capture of three pins of the XIAO involved in the transmission of the message on the SPI bus.

At that level it can be seen that 14 characters were transmitted while the CS signal was active (LOW), which is a good omen because that is exactly the number of characters in the message. The first two characters are shown below, but it was necessary to cut out the time lapsed between them because otherwise the image would have been much too wide.

That shows the SPI bus does work as expected.

Disclaimer and First Impressions toc

As already stated, Seeed Studio did provide the Seeeduino XIAO at a discount for which I am grateful. The company did not impose any restrictions, just asking that I write up a review in return. Furthermore, the amount involved was not sufficient to buy even a small fraction of my soul. Nevertheless, it is only fair that I warn readers before emitting an opinion about this device.

I do not have much experience with micro-controllers of the size of the XIAO. The only comparison I can make is with a Digispark compatible ATTiny85 mini board. The build quality of the XIAO is much better, but then the ATTiny device was obviously a knock off. While both boards can be programmed in the Arduino environment, the experience is quite different. Uploading a sketch to the ATTiny is awkward, while it is rather simpler with the XIAO once used to the procedure. The real and meaningful difference is in the capabilities of the two devices. One feels at ease with a 32-bit CPU with 256K of RAM, while one has to always be cognizant of the limit imposed by 512 bytes of RAM in the case of the ATTiny. I think it will be possible to use the ATTiny85 as a hardware watchdog of my home automation system, but I also believe that it will be possible to do as much and then much more with the XIAO. I am looking forward to completing both projects and presenting the results here.

There are some things that could be improved. Unfortunately, the Seeeduino XIAO is not yet officially supported in the PlatformIO environment. Hopefully that will be changed in the future. Currently, there is a dearth of information specific to the XIAO. It is a new product, so it will take a bit of time before others start using the XIAO and before the more knowledgeable write reports showing us how to do more than blink LEDs as shown above. I agree with Seeed, the XIAO is both tiny and cute, and hopefully it will be a success.

I²C Light Sensor using a Seeeduino XIAO-> <-Blink with a Wio Lite RISC-V with ESP8266
"Hello XIAO" in PlatformIO-> <-Rethinking the Raspberry Pi Hardware Watchdog