There's a new member of the XIAO family: the XIAO ESP32C5 which is somewhat disconcerting because the XIAO ESP32C6 appeared back in November 2024. Don't blame Seeed Studio for the awkward sequence. It was Espressif that released the ESP32-C5 at large in May 2025 almost two years after the ESP32-C6 became officially available in January 2023. How does Seeed Studio or any other development board maker, keep up with the ever-increasing number of systems on a chip (SoCs)? The ESP32C5 is the 15th board in the XIAO series and the third based on an Espressif RISC-V SoC.
This post aims to review the newest member of the XIAO family, in much the same way as older members of the XIAO boards were looked at on this site. As before, this is an idiosyncratic exploration of a new development board. Basically, I am testing the capabilities of the board by running modified example sketches that sometimes investigate matters at a slightly less superficial level than needed for an example. The conceit here is that most everything of interest to the hobbyist can be verified to work with a minimum of gear: a LED and bits of wire and a couple of alligator clips are sufficient for most of the sketches presented below. The more complex tests used a second microcontroller and a logic analyzer. The latter, which cost less than a sad meal at a local burger joint, was a no name 24MHz, 8 channel, USB device readily found in far away places.
Table of Content
- Disclosure
- Documentation
- Almost a Spitting Image of Its Siblings
- Pins and Pads
- Ultra Low Power Core
- Antenna, Buttons, and Bootloader
- Where is the Board?
- Programming the XIAO
- Source Code
- Explorations
- Presentation of a Typical Sketch
- Digital Output and Input
- Analogue Input
- Deep Sleep
- Serial Communication
- Wi-Fi Communication
- Zigbee
- Bluetooth LE
- Thread and Matter
- Conclusions
Disclosure
In late 2025, Seeed Studio informed me of the upcoming launch of the XIAO ESP32C5 and invited me to join an early adopter program to help in testing the new device. Flattered, I eagerly accepted although some of the proposed tests were clearly beyond my limited capabilities. I was not asked to write anything about the new XIAO. So as before, there was no pressure or expectations on the part of Seeed Studio. What I have written below is entirely my work with no direct input from Seeed Studio. Consequently, any and all mistakes are mine.
As for testing the new XIAO, all that can be said is that everything I have tried up to now works very well.
Documentation
The new board is being launched with a Getting Started with Seeed Studio XIAO ESP32-C5 Wiki (the Getting Started Guide for short) that is chock full of information and example code. Given the initial paucity of information with some of the XIAOs, this is a very welcome improvement. I said that Seeed had no direct impact on my review, but the documentation did. Where possible, I will not reproduce sketches that are found in the Getting Started Guide. Consequently, every user of the XIAO ESP32C5 should read the guide carefully and work through the examples. In all modesty this review can only be a small contribution given what Seeed Studio has already done. Hopefully, that did not sound pompous or obsequious. Now is a good time to state that I do not claim any expertise about the subject matter covered in this post, or any other post on this site for that matter.
Understandably, there is not much documentation on the XIAO ESP32C5 specifically. Luckily, the DroneBotWorkshop already contains a very informative article entitled Seeeduino XIAO ESP32-C5 as well as an accompanying video. The link is in the article.
There are other ESP32-C5 based boards, of course. Sites such as Github do contain some repositories related to that SoC. A search on Github returned 23 hits including the repository associated with this post. More on that later.
Almost a Spitting Image of Its Siblings
The XIAO ESP32C5 by Seeed Studio is the latest addition to the Seeed Studio XIAO Series of diminutive development boards. Diminutive is redundant, XIAO means small and these boards are tiny as can be seen on the image to the right. Seeed Studio says 21 x 17.8 mm; let's not quibble about 1 mm differences. Here's another measurement I have never given before: the printed circuit board is 1.2mm thick according to my plastic digital caliper. The thickness of some disappointing SuperMini boards was only 0.6mm. Also, the corners of those boards were not rounded. These two facts explain why a couple had damaged crenelations. The XIAOs are mechanically solid devices, except for the buttons on some models. That little problem has been corrected on the XIAO ESP32C5 as discussed later.
All boards in the XIAO family share the same form factor and the same pin layout for the I2C, SPI and UART serial peripherals. That consistency makes the whole family of boards a valuable resource for those interested in sampling different SoC with a minimum of fuss.
The SoC and other components such as a crystal oscillator and extra storage are under a metal can which is itself covered by an identifying label. This shielding limits RF interference meaning that the XIAO development boards are FCC certified. That is often not the case for development boards from other sources. Consequently, the boards are not just for hobbyists. The castellated pads along the edges of the board and the absence of components on the reverse side means that they can be easily soldered flat onto another circuit board. Of course, 0.1" headers can be soldered into the through holes of the XIAO allowing it to be mounted on a socket or soldered in matching through holes on a bigger circuit board as is more common. In other words, these boards could easily be integrated into commercial products.
The component side also has various buttons, LEDs, and in some models, sensors, antenna or antenna connector. There are solder pads on the reverse side of each model which provides connections to a debug port and in many models to a battery. Given that the debug interface can be quite different from one SoC to another, there is no consistent layout for the pads on the reverse side of the XIAO boards. With that said, a single pattern has been used for the reverse side pads on latter XIAOs and all the Risc V based boards share that common 8-pad layout for the JTAG debug interface. The two pads to connect to a battery are not compatible unfortunately.
As the name implies the new XIAO is based on the ESP32-C5 microcontroller from Espressif which makes it the third board of the series based on a RISC-V microprocessor. The table below compares the three offerings.
| XIAO ESP32C5 | XIAO ESP32C6 | XIAO ESP32C3 | |
|---|---|---|---|
| Espressif SoC | ESP32-C5HR8 | ESP32-C6FH4 | ESP32-C3FN4 |
| High Performance Core | RISC-V 32-bit core @240 MHz | RISC-V 32-bit core @160 MHz | RISC-V 32-bit core @160 MHz |
| Low Power Core | RISC-V 32-bit core @20Mhz | RISC-V 32-bit core @20Mhz | |
| WiFi | 2.4 GHz & 5 GHz dual-band Wi-Fi 6 | 2.4 GHz Wi-Fi 6 | 2.4 GHz Wi-Fi |
| Bluetooth | Bluetooth 5 (LE) | ||
| Memory | 384 KB SRAM, 8 MB Flash, 8 MB PSRAM | 512KB SRAM, 4 MB Flash | 400KB SRAM, 4MB Flash |
| Interfaces | I2C / UART / SPI and JTAG on back side pads | ||
| Digital and PWM Pins | 11 | ||
| Analog (ADC) Pins | 1 + 4 on back side pads | 7 | 3 |
| Onboard Buttons | Reset and boot | ||
| Onboard LEDs | Charge and user | Charge | |
| Battery Charge Chip | SGM40567 | ETA4054S2F | |
As stated in the above table the XIAO ESP32C5 is built around the Espressif ESP32-C5HR8. Here is a link to the SoC's datasheet.
Pins and Pads
Below is my simplified rendering for the specific edge pin layout and reverse side pad layout of the XIAO ESP32C5 insofar as the high performance core is concerned.

- The left button labelled R is the reset button.
- The right button labelled B is the boot button. It is connected to GPIO pin number 28 identified as
BOOT_PINin the Arduino Core. - The yellow user LED labelled L is connected to GPIO pin number 27 identified as
LED_BUILTINandBUILTIN_LEDin the Arduino core. - The red LED labelled C is not a power-on indicator. It is a charge indicator which is only meaningful when a battery is connected to the board.
Ultra Low Power Core
Like the XIAO ESP32C6, the low power Risc-V core has access to only some of the I/O pins found on the development board. On the XIAO ESP32C6, this means that 6 of the 7 ULP I/O pins are available, but only two are on an edge pad. The others represent alternate uses of some of the reverse side pads principally used for JTAG debugging.
| I/O pin | ULP pin | ULP peripheral | Arduino |
|---|---|---|---|
| 0 | LP_GPIO0 | LP_UART_DTRN | D1 |
| 1 | LP_GPIO1 | LP_UART_DSRN | D0 |
| 2 | LP_GPIO2 | LP_I2C_SDA | A1 |
| 3 | LP_GPIO3 | LP_I2C_SCL | A2 |
| 4 | LP_GPIO4 | LP_UART_RXD | A3 |
| 5 | LP_GPIO5 | LP_UART_TXD | A4 |
These symbolic names for the LP pins and peripherals were taken from the XIAO ESP32C6 Pin Definition diagram in the Getting Started Guide. As far as I can make out, these are not defined in any files in the ESP32 Arduino core. There is an interesting discussion on Building low power applications on Espressif chips: Ultra-Low-Power (ULP) coprocessor by Marius Vikhammer that is a few months old. The LP-core found on the ESP32-C5, ESP32-C6 and ESP32-P4, which is the third and latest iteration of low power cores, has powerful capabilities.
However, support for the Low Power core of the ESP32-C6 (and presumably ESP32-C5) remains on the Feature request todo list of the Arduino ESP32 Core Project since being placed there in May of 2024. Hopefully, this will eventually be done, but until then the ESP-IDF must be used to program the low power core.
Antenna, Buttons, and Bootloader
The XIAO comes with an elegant mat black antenna almost twice its size but much thinner. It's the 2.4 GHz FPC Antenna (2.9 dBi), the same that comes with the XIAO ESP32C3. The U.FL coaxial connector is very small, a bit fiddly and will probably not survive very many connections (the female connector on the antenna is rated for only 30 reconnections, see Hirose U.FL), so it's best to avoid disconnecting it. The FPC antenna is probably not optimal for the 5 GHz band. Seeed Studio offers a 2.4G/5G External [rod] Antenna with RP-SMA Male Connector [which] delivers much higher gain than the built-in FPC Antenna included in the package!. It is available here.
There are two small push buttons at the bottom of the board on either side of the U.FL antenna connector, opposite the USB-C connector. The button on the left resets the controller, while the boot mode button on the right can be used to put the system in bootloader mode and is available as a run time user input. That's the opposite placement of those buttons on the XIAO ESP32C3. I am very glad to see that Seeed Studio managed to return to solid 2x3 mm metal tact push buttons instead of the dome buttons with a thin plastic cover on the XIAO ESP32C6 and XIAO ESP32S3. I have managed to destroy a few of those rather delicate buttons.
It is not usually required, but it may be necessary to restart the board in bootloader mode. The sequence of button presses and releases to get into bootloader mode is pretty common.
- Press and keep depressed the reset button.
- Press and keep depressed the boot button.
- Release the reset button.
- Release the boot button.
Another way of putting the board in bootloader mode.
- Remove power to the board, usually by disconnecting the USB cable.
- Press and keep depressed the boot button.
- Apply power to the board, usually by connecting the USB cable.
- Release the boot button.
Be deliberate about these steps, there is no rush. Once the sequence is completed, the board will be in bootloader mode waiting for new firmware. Uploading should now work. Remember that after the upload is finished, the board remains in bootloader mode and the reset button must be pressed and released to return the board to normal mode where it will execute the newly uploaded firmware.
In all probability, it will be necessary to explicitly set the board in bootloader mode if the SoC is in a sleep mode that turns off the serial port. This is what happens in the deep sleep sketches presented below. While experimenting, I have at times put a XIAO in a state where it is rebooting almost immediately on booting. There again it will be necessary to reset the board into bootloader mode to upload a new firmware. However, most times when there's a problem in uploading the firmware to the XIAO, it's because I am trying to upload a sketch from an IDE while another application (another copy of the IDE, or a communication program) is connected to the board. With a bit of discipline ensuring that only one application is connected to the board up at a time, it's almost certain that an upload will go through without a problem.
Where is the Board?
Enough with the introduction, it's time to play with the board. When a XIAO is connected to a desktop, or portable, computer with a USB cable it will show up as terminal (TTYxxxx) device if the desktop is running Linux. In Windows, one should hear the usual beep-boop sound when the USB cable is connected and the board will be reachable through a COMxxx port. Here is how to find the particular device in Linux. Start printing the kernel ring buffer with the dmesg -w command where the -w (or --follow) flag means the utility will stay active and print messages as the kernel publishes them. Wait until dmesg has finished printing all the old kernel messages and then plug in the USB cable from the XIAO to a USB port on the desktop.
As usual the CtrlC keyboard combination halts execution of the process. Listing all USB devices once the XIAO is connected is another way of finding the board. That's useful for the very first time a development board is connected to the desktop. Once one know that its USB device time, then it is fairly easy to identify the board from then on.
Once the device is know, it becomes possible to connect to it with a serial communication program. I now use picocom for this purpose.
No baud is specified and it would not matter if it were 115200, 9600 or 500000 or probably anything else when connecting to the board. Just like the XIAO SAMD21, ESP32C3 and ESP32C6, the USB data lines are connected directly to the microcontroller which has a built-in USB serial controller with a CDC-ACM virtual serial port. It is USB 2.0 compliant and capable of 12 Mbits/s transfer speeds (Source ESP32C5 Series Datasheet, Version 1.0 pp 23).
Programming the XIAO
All the sketches presented below were initialy developed in the Arduino IDE using the Espressif ESP32 Arduino core version 3.3.5 (arduino-esp32 on GitHub). Using version 3.3.6 is recommended to avoid a few error messages.
This post is much too long to include instructions on how to get the free IDE and how to install the ESP32 Arduino core. If this is all new, here are two references that can help going about doing this.
- Get Started with Arduino last updated on 2023-02-14 by jianjing Huang
- Getting Started with ESP32 Arduino by Jan Procházka 2025-11-01.
There are so many more articles and numerous videos on the subject. Just make sure that anything consulted is recent enough. Avoid anything that is about the Arduino IDE previous to version 2. The current version of the IDE is 2.3.7 which is what I used.
The Arduino IDE is a simple yet effective stand-alone application which is highly recommended especially for beginners. However some prefer to add the PlaformIO IDE extension in a code editor. The most often used code editor is Visual Studio Code (VSCode). Once VSCode is installed, the extension must be installed. Thankfully, it is in the VSCode marketplace, so that will be a simple operation. Follow the instructions in the Platfom IO with Seeed Studio XIAO ESP32-C5 Wiki.
Each PIO project must contain a configuration file called platformio.ini in its root directory. Here is a simplified version.
Note: In a quick test, setting platform = Seeed Studio as suggested in the Wiki and in the Seeed repository did not work for me, the explicit URL shown above did.
The platform used, platform-seeedboards, is from Seeed Studio. It is actively maintained and supports the XIAO boards and other development boards from Seeed. The platform-espressif32 platform may be another possibility, but PlatformIO has been slow or reluctant in the past to support the latest esp32 Arduino core. I would not currently recommend using that platform, but it has not been tested in quite a few months. Currently the platform does not contain a board definition for the XIAO ESP32C5.
Unfortunately, the PlatformIO IDE extension can no longer be easily installed in VSCodium which is a fork of VSCode without the telemetry and AI additions. On the other hand, the pioarduino IDE extension is available in the VSCodium marketplace and is installed in the same way as the PlatformIO IDE extension is installed in VSCode. One would have hoped that the above configuration file would then have worked, but that was not the case. The platform-seeedboards could not be installed without manually adding some missing requirements in the Python virtual environment. Instead I recommend using the pioarduino-espressif32 platform. So the platformio.ini file must be modified.
That almosts works. There's a pattern here, but thankfully that is the last hurdle. The platform-espressif32 from pioarduino does not yet contain a board definition for the XIAO ESP32C5. To get around that problem, a local copy of the board definition from the Seeed Studio repository is used. That required some modifications to the configuration file.
As currently configured, all the projects in the companion GitHub repository can be compiled successfully with version 55.03.36 of the pioarduino-espressif32 platform released on January 21, 2026. All the messy details about the location of the board definition, the platform to use, the local library and so on are confined to the platformion.ini file available in the repository.
Source Code
The sketches presented in this post are available in a GitHub repository: XIAO ESP32C5 Sketches. The repository can be easily obtained free of charge.

The complete repository can be cloned if git is installed on the desktop machine. Click on the green Code button and then click on the copy icon to get the URL of the git repository.
The complete repository can be downloaded as a Zip archive. Click on the green Code button and then on the Download ZIP link.
Individual files can be downloaded also.

Navigate to the file, open it in the web interface and then click on the Download raw file icon.
The file structure is a bit more complicated than need be if one uses the Arduino IDE exclusively. Each project or sketch (PIO or Arduino terminology respectively) is stored in a directory structure and adheres to file and directory names conventions so that they may be compiled in either the Arduino IDE or the PlatformIDE.
Explorations
Let's see what we can find out about the XIAO ESP32C5 using the Espressif esptool utility and then from the ESP32 Arduino Core itself.
Using esptool
A Python script from Espressif called esptool does the heavy lifting when it comes to uploading firmware on the Espressif SoCs. It can be instructive to use it explicitly to get some information about the XIAO. If this is a first use of this tool, look at the help screen to get an idea of its capabilities. Of course, it will be necessary to adjust the path to the script.
If like me, the reader finds this is mostly unintelligible, do not worry because the Arduino IDE invokes the script setting all those flags as needed. But here is how to get some information about the SoC with the tool.
We already knew that the ESP32-C5 can use the 5 GHz Wi-Fi band in addition to the 2.4 GHz Wi-Fi band that almost all ESP modules can use. IEEE 802.15.4 is a technical standard that defines low-rate wireless personal area networking (LR-WPAN). It is the basis for the Zigbee and Thread wireless protocols often used in the home automation sector.
I am not too sure what to make with the definitions of the MAC, BASE_MAC and MAC_EXT. This is my first encounter with an 8 octet MAC address. Something that I may have to look into, although I'll present a sketch about the base MAC address later.
Do Nothing Sketch
We can get what seems to be a lot more information by uploading a sketch that does nothing.

Before compiling and uploading this sketch, turn debugging on by setting Core Debug Level to Verbose in the Tools menu (screen capture).
Surprisingly there's no mention of the 5 GHz Wi-Fi capability of the ESP32-C5. As can be seen in the listing, the PSRAM is not enabled. Let's enable PSRAM in the Tools menu (screen capture) and, compile and upload the empty sketch and run it again.
I could not measure power consumption when running this small sketch because it was below the threshold of my small inline USB power meter.
Information Sketches
Instead of the classic "Hello World!" program, I decided that the first non-empty sketches uploaded to the XIAO would print out some information about the SoC and about the XIAO board.
Output of sys_info.ino
Note that the MAC address (obtained using the ESP.getEfuseMac()) is the extended 8 octet MAC address encountered before.
The second sketch in the same vein prints out the pin assignment as defined in pins_arduino.h found in the Arduino Espressif32 package in the following directory: ~/.platformio/packages/framework-arduinoespressif32/variants/XIAO_ESP32C3/ or ~/.arduino15/packages/esp32/hardware/esp32/3.0.2/variants/XIAO_ESP32C3/ if using Arduino IDE in Linux.
Output of pin_names
Note that the ESP32, ARDUINO_XIAO_ESP32C3, and ARDUINO_USB_CDC_ON_BOOT are not defined in the pins_arduino.h. They are nevertheless useful when creating sketches that accommodate slight differences between different boards.
Output of macs.ino
Since almost all ESP32 boards come with a radio peripheral that can be run as a Wi-Fi station or a Wi-Fi access point or both at once. They usually have Bluetooth capabilities. All these must have a unique identifier which is called a MAC (medium access control) address in the case of Wi-Fi or a BD_ADDR in the case of Bluetooth. The macs.ino sketch will print out these addresses to the serial monitor.
Recall the output from the esptool
Long default 8-byte MACs are only found on ESP32 SoC which have Zigbee and Thread capabilities (formally the IEEE 802.15.4 standard). These default MACs are Zigbee IEEE addresses. The 8-byte default MAC is obviously the base MAC with the 2-byte MAC extension spliced into the middle.So far I have only seen ff:fe or fe:ff extensions. Make of that what you will.
Output of heap.ino
Since the XIAO has 8 Mbytes of external SPI RAM called PSRAM for pseudo-static random access memory, I wanted to know if it is significantly slower than the SRAM (static random access memory) on the SoC. The sketch provides an answer, but that turned out to be rather underwhelming compared to the complicated nature of heap memory on ESP32 microcontrollers.
If the term heap does not mean anything, here is a quick overview which corresponds to the full extent of my knowledge on the subject. The figure on the right shows the allocation of memory in a 8080 or Z80 CP/M computer running a Turbo Pascal program. The OS owns two blocks of memory, a small workspace starting at address 0x0000 and a larger chunk at the very top of the memory (ending at 0xFFFF if the computer has a full complement of 64 Kbytes of memory). The memory between, called the TPA (transient program area) was handed over to the program. A Turbo Pascal program would put its code just above the CP/M workspace and it would store its global variables just below the OS. The dynamically allocated memory was in between. At the top growing downward is the stack where local variables are created as functions and procedures were entered and then destroyed when the same functions and procedures were exited. The heap was at the bottom of the dynamic memory area growing upward towards the stack. The heap was for blocks of memory that the running program explicitly created and destroyed. These blocks of heap memory could exist for as long as the program ran or could be destroyed at any time during the execution of the program. The programmer had to ensure that an allocated heap block was properly freed (destroyed) when no longer needed so that the heap manager could reuse the memory. The programmer also hoped very much that the heap and stack never collided or there would be a very messy situation.
What's the point of this journey down memory lane? The external PSRAM of the XIAO is made available to firmware running on the SoC as an extension of the heap. I naively imagined that this meant that the PSRAM was just tucked into the memory map in an area next to where the SRAM heap was. The fact that the malloc function would automatically select the PSRAM when allocating large blocks of memory from the heap reinforced that delusion that I was working with.
In my initial investigation, small one-byte blocks were allocated with the ps_malloc function which explicitly allocates memory from the PSRAM and with the malloc function which I assumed would use the SRAM at first. I was trying to find the start of the SRAM and PSRAM heaps and also in which direction the heap was being filled. Then I would try with very large blocks of memory that could not possibly be contained in the SRAM so see if ps_malloc and malloc would allocate the block at the same address. It all seemed to work correctly until I tried consecutive 10-byte and 100-byte blocks, which ended up being allocated in what looked to be a haphazard way. We were not in Kansas anymore Toto.
From the little reading I have since done on the subject, I can't pretend to really understand how the heap works. Here is a short quote from the Espressif documentation.
PSRAM on ESP32-C5The ESP32-C5 contains multiple types of RAM:
...
- DRAM (Data RAM) is memory that is connected to CPU's data bus and is used to hold data. This is the most common kind of memory accessed as a heap.
- IRAM (Instruction RAM) is memory that is connected to the CPU's instruction bus and usually holds executable data only (i.e., instructions). If accessed as generic memory, all accesses must be aligned to 32-Bit Accessible Memory.
- D/IRAM is RAM that is connected to CPU's data bus and instruction bus, thus can be used either Instruction or Data RAM.
It is also possible to connect external SPI RAM to the ESP32-C5. The external RAM is integrated into the ESP32-C5's memory map via the cache, and accessed similarly to DRAM.
All DRAM memory is single-byte accessible, thus all DRAM heaps possess the
MALLOC_CAP_8BITcapability. Users can callheap_caps_get_free_size(MALLOC_CAP_8BIT)to get the free size of all DRAM heaps.
The gist seems to be that the heap is a mixture of different types of memory with different capabilities. Here is part of the output of the heap.ino with my interpretation of its meaning in bold and italic.
The sketch gets a 1000-byte block at 0x4081507c which is from the normal internal heap area and a 1000-byte block at 0x423050908 which is from the external PSRAM. A test is done to compare the read and write times in the two blocks:
That was a pretty constant difference, which amounts to about 17% slower access with the PSRAM. Here is the full ouput of the sketch.
Strangely, there appears to be a discrepancy between the free internal heap size as reported by ESP.getFreeHeap() and by heap_caps_print_heap_info(MALLOC_CAP_INTERNAL) when the sketch first starts. After the 1000-byte block is allocated on the internal heap and then freed, the two functions report the same free heap size.
Presentation of a Typical Sketch
Because the macs.ino sketch is relatively short and simple and it is in the form that most of the following sketches use, it will be presented here is some detail.
The actual macs.ino file only contains comments.
In this case, it has a copyright notice under my name. As can be seen I am implicitly placing all of my code in this project into the public domain. If that is not acceptable then the 0BSD license applies. Many other sketches are variations on other open source code and where possible, that will be acknowledged in the *.ino file and licensing details will be provided.
There must be a properly named .ino file in an Arduino project. PlatformIO prefers that the code be in a file named main.cpp, which Arduino will happily compile along with all the other source files in the directory. So it makes sense to leave the usual header informational comments at the start of a traditional Arduino sketch in the .ino file and to move the code into the main.cpp file. There is boilerplate at the start of the main.cpp file in all the remaining sketches in the repository.
First there's a reminder to check the .ino file for license information and attribution for the code when it is known.
Then I like to list the dependencies near the top. The basic header file Arduino.h is usually the first included file. It must be specified in a PlatformIO project. The Arduino compiler will automatically add the file in all cases which explains why it does not appear in the typical .ino file. In this project, the header file MACs.h is also included so that the compiler will know how the functions declared in the header file can be called. The actual implementation is in the MAC.cpp file which will be compiled later. Those two files are included along with the project .ino and main.cpp files because the MAC address of a board is unique and that can be useful when making comparisons such as connect times or Wi-Fi throughput.
Some settings can be changed in a configuration block. If the board being used is a XIOA ESP32C6 to which an external antenna is connected the USE_EXTERNAL_ANTENNA macro must be defined. The setup() block will then switch the SoC antenna input to the external antenna.
The other setting is the SERIAL_BAUD which is the speed of the serial connection between the development board and the desktop machine. The value specified is a typical rate when the dev board has a discrete USB to serial adapter between the SoC and the USB connector. None of the XIAO boards have such an adapter, the USB connection is directly to the SoC which has a native USB peripheral as explained before. In that case, the SERIAL_BAUD is completely ignored. From there on, the source code should not need user modifications to work.
After the configuration, a couple of sanity checks are performed. Is an ESP32 based board is being used? While all the sketches are meant to be run on a XIAO ESP32C5, I find it useful to try them out with other devices for comparisons. The second check ensures that the ESP32 Arduino core installed in the IDE is recent enough.
The following section defines two macros: TITLE which purports to provide a readable label identifying the development board and ANTENNA which tries to identify the type of antenna on board.
As can be seen, only XIAO boards are identified explicitly, any other ESP32 based board will be labelled as "Unknown ESP32 board" and its antenna will be said to be "Unknown". It is probably a PCP printed antenna but many ESP32 boards have an external antenna connector.
Let's go straight to the setup() function which in all cases starts very much as shown below.
That is just the first part of the setup() function. It starts with the initialization of the Serial peripheral. There are two scenarios. The first is for SoC with native USB support such as the XIAO boards. There is no need to specify a baud in the Serial.begin() function. For other types of boards, the transmission rate has to be specified. There is a delay just after the peripheral is initialized. I set the delay to 2 seconds when a native USB peripheral is used and only 1 second if a USB-serial adapter is used. When a XIAO is reset, it is disconnected from the desktop. While the Arduino IDE is very fast in reestablishing the serial connection as soon as the XIAO has finished rebooting, output to the serial monitor will be lost if there is no delay. When a USB-serial adapter chip is involved, the serial connection is not broken when the SoC is reset and hence the shorter delay. However there is usually some more or less meaningful data that is transmitted which explains the extra empty line being printed.
After that the external antenna is connected to the SoC if that is required. Then a short header is printed to the serial monitor to identify the project or sketch, the board type and the board antenna. Often the pertinent MAC address is also printed to identify the actual development board being used.
All the above is meant for sketches that could be run on other development boards. Some of the sketches presented below, the blink.ino and pwm.ino sketches for example, are specifically for the XIAO ESP32C5. A test is done to ensure that board is being used, and most of the boilerplate code shown above is not necessary. Similarly, identifying the antenna type is not useful when the sketch does not use a radio. But none of this stuff hurts except for the flash memory usage.
Digital Output and Input
If you are impatient to run a program that does something you can run blink sketch from the Getting Started Guide. I would suggest changing the delay after turning the built-in LED on to 100 milliseconds to visually confirm that setting the pin connected to the built-in LED low turns the latter on.
You could also run the LED toggle sketch using the built-in LED and the boot button. In that case, I suggest turning the LED off initially in the setup() routine. The logic for turning the LED on or off in the last statement also needs to be inversed.
If you want the state of the built-in LED to follow the state of the boot push button, try the following code.
It's short, simple and the button debouncing is elementary but it works well enough.
These sketches show how the general digital input/output pins work. Logical the pins are logically either on or off. Electrically it is more complex but we don't need to worry about that here. I wanted to run similar tests for all pins. My electronic tinkering is limited by a frugal budget, but I spared no expense in creating the equipment to run these tests. The probe to test the digital write function was first built when looking at the XIAO SAMD21.

The two LED version was too expensive. The header pins have not been soldered on the XIAO ESP32C5, so the connection to the ground pin of the board is done with a mini alligator clip. The other end, used to probe each of the edge pads, is just a male Dupont wire. The second instrument, used to test the digital read function, is even simpler.

It's a male Dupont wire with an mini alligator clip at one end which can be connected to the ground pad or the 3.3 volt pad as needed. It may look like a wire, but it can easily double as a test push button.
Digital Output: blink
My version of the blink sketch, imaginatively named blink.ino, flashes on and off in a heartbeat pattern each gpio pin in turn. It goes around the board starting with the D0 pad in an anti-clockwise fashion. The built-in LED is also toggled. Connect the alligator clip of the LED probe to the ground pad of the XIAO and hold the other lead of the LED against the pad of the gpio pin being toggled. It will turn on in a heartbeat pattern confirm that the corresponding GPIO pin can be used as an output and that the digitalWrite function works with that pin.
Follow the output on the serial monitor to also confirm the correspondence between the Arduino names of the I/O pin and their GPIO number.
Digital Output: Pulse Width Modulation
This is basically the same as the blink sketch, but instead of an on-off pattern, a pulse width modulation signal is applied to each pin in a way that makes it seem as if the voltage is being slowly ramped up and down. If a LED is connected to a pin, it will appear to pulsate.
A pwm signal is a square (ON/OFF) wave. It's duty ratio is the proportion of the ON time to the period of the signal. In other words, it is the ratio (ON time)/(ON time + OFF time). The period a wave is 1/frequency. So the true length of time that the signal is on and off depends on the frequency of the signal. As far as I know, there is no function in the Arduino framework to set the PWM frequency. Perhaps there is a non-standard way to do that in the ESP32 Arduino core.
Using the duty ratio is set by using the analogWrite which accepts a value from 0 to 255. Consequently, the command analogWrite(pin, x) means that the pin will be high for x/256 of the time and it will be off for (256-x)/256 of the time. So the voltage at the pin is on average (x/256)*3.3 volts. This is the nearest one can come to a true analogue signal with the ESP32-C5.
Digital Input: Polling
In the first two examples the digital pin to which the boot button is connected was "polled" constantly to see if its state had changed. Polling means the value of the gpio pin connected to the boot button is read using the digitalRead(pin) function every time the loop() function is executed. My poll.ino sketch is just an extension of that. It saves each pin's level and monitors each pin in turn to see if the digitalRead() function return a different value compared to the saved value. In that case a report is printed to the serial terminal and the newly read pin value is saved to spot the next transition of that particular pin. Here is part of the output to the serial monitor as the test was performed.
Everything works as expected when the io pins are in either INPUT_PULLUP or INPUT_PULLDOWN mode. The OUTPUT seems counterintuitive but it does make sense because the ESP32-C5 general purpose io pins support reading the value written to them when in OUTPUT mode. That is a useful property which makes it possible to avoid saving in a variable the value written to an output pin for future reference.
It was not possible to read the value of the D6 (gpio 11) pin when the latter was put in OUTPUT mode. I think that may have to do with the fact that gpio 11 is the TX signal of Serial0 which is the interface to the hardware UART0 peripheral. It is used for debugging during the boot process. I have tried to decouple the pin from the UART0 port with the Serial0.stop() command but that does not work. This remains something to investigate.
Digital Input: Interrupts
Instead of polling a pin to see if its state has changed, an interrupt service routine (ISR) can be attached to the pin. Be careful with the ISR, there are things which may not be done while handling an interrupt. Also it is best to keep the ISR short. Consequently a common approach is to set a flag in the ISR. The loop() routine should check the flag and when it is set, it should do whatever is needed in response to the action that caused the interrupt. Don't forget to reset the flag after.
The sketch interrupt.ino illustrates how to handle a button press in the fashion described above. An ISR is attached to each button in turn. The interrupt will occur when the pin is grounded as it would be with an activated push button. To move on to test the next pin, press the boot button. Of course the boot button is handled by an interrupt service routine also.
As can be seen by the output to the serial monitor reproduced above, interrupts are very fast. If handling a true button in this fashion, some sort of debounce measure will be needed.
Analogue Input
The Getting Started Guide contains a sketch on reading analogue inputs based on the Grove-Rotary Angle Sensor. Initially I was a bit confused because that product is a potentiometer, a variable resistor if preferred. It is not a rotary encoder as I first thought. I may have that item, I certainly have a pot or two somewhere but it was easier to use another XIA0, the first one of them all, the SAMD21 which has a true analogue output. On connecting the A0 output from the SAMD21 to the A0 ADC input of the XIA0 ESP32C5, the following graph was obtained.

Here is the gist of the sketch running on the ESP32-C5
The actual code in the adc.ino sketch is slightly more complicated because it uses all analogue pads available on the XIAO ESP32C5. The D0 (gpio 1) edge connector is the only analogue input available on the board headers. There are four analogue inputs on the reverse side of the board: MTMS, MTDI, MTCK and MTDO which have the Arduino names A1 to A4 (gpio 2 to 5)respectively.
To run the program, I connected the SAMD21 and the ESP32C5 grounds together, clipped my simple probe to the A0 pin of the SAMD21 which was running the samd21_dac/dac.ino sketch. Then I pressed the other pin of the simple probe into the appropriate solder pad as instructed by adc.ino running on the ESP32C5. Don't be too shy about pressing the probe pin against the pad. A sharp DVM test lead pin would have been better because a good connection was not easily made with the Dupont male pin.
There's a second sketch entitled adc_dvm.ino. It is a bit of a misnomer, but a digital voltmeter was used to monitor the voltage from a small bench power supply as it was connected to the A0 analogue input of the XIAO ESP32C5. The voltage was measured with the dvm and the ESP32-C5 five times at voltages from 0.25 to 3.25 volts increasing 0.25 volts each time. The sketch kept track of the lowest and highest values and calculated the average measured by the XIAO.
| DVM (v) | Average (v) | Min (v) | Max (v) | Rel. Error | min delta (mv) | max delta (mv) |
|---|---|---|---|---|---|---|
| 0.256 | 0.259 | 0.248 | 0.272 | 1.17 % | -8 | 16 |
| 0.507 | 0.508 | 0.503 | 0.515 | 0.20 % | -4 | 8 |
| 0.756 | 0.755 | 0.743 | 0.763 | -0.13 % | -13 | 7 |
| 1.003 | 1.002 | 0.988 | 1.020 | -0.10 % | -15 | 17 |
| 1.253 | 1.256 | 1.243 | 1.268 | 0.24 % | -10 | 15 |
| 1.502 | 1.492 | 1.482 | 1.504 | -0.67 % | -20 | 2 |
| 1.752 | 1.756 | 1.752 | 1.765 | 0.23 % | 0 | 13 |
| 2.001 | 1.999 | 1.987 | 2.006 | -0.10 % | -14 | 5 |
| 2.252 | 2.253 | 2.231 | 2.270 | 0.04 % | -21 | 18 |
| 2.500 | 2.251 | 2.498 | 2.513 | -9.96 % | -2 | 13 |
| 2.751 | 2.759 | 2.743 | 2.780 | 0.29 % | -8 | 29 |
| 2.996 | 3.001 | 2.991 | 3.026 | 0.17 % | -5 | 30 |
| 3.245 | 3.251 | 3.249 | 3.256 | 0.18 % | 4 | 11 |
This is by no means a scientific test. The connections were not very solid, the leads were much too long and a run of 5 measurements was too short. I have no way of knowing how accurate the DVM is, but I do know that it was a very cheap instrument. Nevertheless, it's comforting to see that the relative error between the DVM and the XIAO is often less than half a percent. The 9.96% error at the nominal voltage 2.5 volts is clearly a transcription error. I will have to redo this test.
Deep Sleep
It can be stated with confidence that I have learned nothing more about the sleep modes of RISC-V microcontrollers since I looked at the XIAO ESP32C6. However, the Deep sleep mode code in the Staring Guide contains a very important comment.
That delay after enabling gpio wake-up or timer wake-up from deep sleep makes all the difference. There's no need to explicitly flush and close the serial port as was done in my previous post. Aside from that the logic of the code remains the same. It is based on two facts.
- The
setup()function is executed each time the SoC wakes up from deep sleep. The SoC is put into deep sleep mode near the end of thesetup()function. Consequently, theloop()function is never executed in the example sketches. - The real-time clock has memory which remains powered during deep sleep and is not erased when the system wakes up. Consequently it is the ideal memory in which to store any data that is needed across all wake periods such as a counter for the number of times the
setup()function has been executed since the system was powered up. In both programs, the counter is incremented once near the beginning of thesetup()functon. Then its value is printed to the serial monitor which is an indication that the SoC is awake, operating in normal mode, and that its serial peripheral is also functioning normally.
The ideal method to check if the XIAO is in deep sleep mode is to measure its current draw. Unfortunately, I don't have equipment to measure current at such low levels. So imitating others, I have resorted to other means to signal when the board is in deep sleep mode and when it returns to normal mode. Foremost of these are messages sent out via the serial port which works rather well in the Arduino IDE but fails when using the PlaformIDE terminal or a communication program such as picocom. Flashing the onboard LED is another signalling method that works in simple situations.
Finally before looking at the sketches, be warned: it will be necessary to put the XIAO is bootloader mode to download any new compiled firmware to the board if it is running a deep sleep mode sketch. That's because when running these sketches the board is in sleep mode most of the time and that means that its serial port is shut down.
Timer Wake-up
In this project, the XIAO is in deep sleep mode for a fixed period; 15 seconds long by default. This is done by enabling a wakeup timer before going into deep sleep mode. Here is a simplified version of the code used to put the board in deep sleep mode.
Running the sketch on the XIAO ESP32C5 within the Arduino IDE works quite well. Interestingly, the program works just as well with a first generation ESP32.

As can be seen the IDE signals quite clearly that it is not connected to the XIAO when the latter is in sleep mode. When the board wakes and restarts its serial peripheral, the IDE is very good at reestablishing the connection automatically, so that the serial output of the sketch continues to appear in the IDE serial monitor. By turning on timestamps in the serial monitor, the timing can be verified.
The ESP32-C5 woke up for the 3rd time at 17:51.58 after going into deep sleep at 17:51:40. That was a 17,979 second interval which seems reasonable when adding to the 15 second deep sleep period the 2 second delay after opening the serial port to which must be added the time to perform the boolean test to see if the boot counter is equal to 0, then the time to increment the counter and finally the time required to create the Boot number: xx message and the time to send the latter out to the serial monitor.
PlatformIO Notes
Frankly, running the project in the PlatformIO IDE is painful because, most of the time, it does not reconnect automatically quickly enough if at all. So I added feed back using the onboard user LED. It does two things. First, as the board wakes from deep sleep, it slowly flashes out the number of times the setup() function has been executed since the SoC was powered up. Then the just before the SoC is put back into deep sleep mode, the LED quickly flashes for 5 times. The LED provides just enough information to know if the system is in deep sleep mode or if it is operating normally.
That works well, but it is difficult to show on a static Web page. There is another trick in Linux to see when the board is in deep sleep mode using the watch utility to show the output of the ls -l /dev/ttyACM* command. continuously.
When the XIAO is in deep sleep, its serial peripheral is shut down. Linux therefore closes the device /dev/ttyACM0 which means that it cannot be listed.
When the XIAO returns to normal mode, its serial peripheral is turned on. Linux sees that it is connected to one of the serial ports of the desktop and recreates the /dev/ttyACM0 device. At that point, it will be listed and watch will display the result.
Exit watch with a CTRL+C keyboard shortcut.
This is why the SLEEP_PERIOD should not be very short. It takes time for Linux to shutdown the ttyACM0 device. If the serial port of the board is reconnected to the desktop during that time, a new serial device will be created, most likely ttyACM1. Then there's no hope that even the Arduino IDE can reconnect to the board.
GPIO Wake-up
Another way of waking the system in deep sleep is to apply a signal to a specific low power digital pin that is in INPUT mode. The signal can either be low (0 volts) or high (3.3 volts). If trigger to wake is to be low signal applied to a designated low power pin, then it must be otherwise maintained high with a high value resistor connected to 3.3V. This can be an internal pull up resistor (the pin mode should then be INPUT_PULLUP), or an external resistor. If the wake signal is to be high, then the logic must be inverted and the designated pin must be in INPUT_PULLDOWN mode, or an high value external resistor must connect the pin to ground. There are power usage consequences to using an internal versus an external resistor which will not be broached here because I don't know what they are.
Here is a simplified version of the code used to put the board in deep sleep mode.
The sketch is slightly more complex because it tests waking up the system from deep sleep with every low power pin available on the board one after the other. There are six low power pins available on the XIAO ESP32C5.
| Low Power Pin | Label |
|---|---|
| LP_GPIO0 | D1 |
| LP_GPIO1 | D0 |
| LP_GPIO2 | MTMS |
| LP_GPIO3 | MTDI |
| LP_GPIO4 | MTCK |
| LP_GPIO5 | MTDO |
As before this works well in the Arduino IDE.

All low power pins are tested in turn. Once all the pins have been tested, the signal needed to wake the board is inverted before another round of tests is performed. Just before going into deep sleep mode, the sketch sends to the serial monitor messages indicating which pin is the designated wake up pin and which signal must be applied to awake the board.
Unfortunately, I could not think of a practical way of running this test in the PlatformIDE. Adding LED blinks as before would not be sufficient. I tried remebering the sequence of the pins to activate and the signal that needs to be applied. It was too much to juggle.
Serial Communication
This section is about the serial communication peripherals that are available on all the XIAO boards:
- UART - universal asynchronous receiver-transmitter
- SPI - serial peripheral interface (synchronous)
- I2C - inter-integrated circuit (synchronous) also abbreviated IIC and I²C.
I can conceive of three ways to test the implementation of these communication protocols.
- Running a self-test called a loopback test. That means feeding the output of the serial peripheral into its input to verify that the date read corresponds to the data written to the communication port. This works with UART and SPI but not I2C.
- Connecting a compatible serial device to the port and checking that it works. One possibility would be to use another microcontroller with a known correct implementation of the serial communication under test. Obviously, not everyone will have the same device thus limiting the ability of others to run a similar test.
- Looking at the output with a logic analyzer. Again that is possible for all three protocols but it only tests the output function.
So I'll do my best in the short time available to look at these communication protocols on the XIAO ESP32C5.
UART
On my first attempt at running a loop back test, uart.uno, on the hardware serial port, I ran into difficulties. So before wasting too much time, I connected an input pin of a cheap knockoff logic analyzer to the TX (D6) of the XIAO and captured the output from the following short sketch.
As can be seen, the lower-case ASCII letters characters 'a' to 'z' are being transmitted.

It is a bit finicky to capture serial output, so here are the UARD decoder settings that I used.

The run was not completed; as soon as it seemed that enough serial data had been received by the analyzer, I stopped the capture to make it easier to zoom in on the data. In any case, this proved that the UART does transmit.
By the bye, this sketch made the XIAO work harder: on average 70 mA at 5.06 volts or 390 mWatts.
Knowing that the UART transmitted correctly, its TX pin and GND pin were connected to a second XIAO ESP32C5 which printed to its serial monitor any character received. That worked without problems. So I again ran the loop test and this time it worked as output to the serial monitor attests.
Just to make sure, I disconnected the link between the RX and TX signal line for a few seconds and then I reconnected.
I have no idea what the initial problem was; a bad connection perhaps? In any case not only did the loop test work but it was possible to set the baud to 5 megabits. I did not have time to try to capture that serial output with the logic analyzer to check if that rate was truly achieved. I don't even know if the analyzer can work at such a rate. However by setting the Core Debug Level to Verbose in the Tools menu and running the loop test again the following information about the initialization of the UART was sent to the serial monitor.
Clearly, the compiler did not have a problem with the specified speed. These phenomenal speeds may not be meaningful in a true application when a serial connection is made to a sensor that only communicates at 9600 baud.
SPI
The SPI serial port can be tested in a loop back configuration just like the UART. As with the UART, the serial output pin (MOSI=D10=GPIO10) is connected to the serial input pin (MISO=D9=GPIO9). There is no need to worry about the SPI clock and the chip select (SS) signals in this configuration. Here is the output of the little test program.
The received message was exactly what was sent which is what was expected. When the SPI data input pin was connected to the 3.3 volt rail so that the input value of each character was 0xFF corresponding to all bits being equal to 1. When the MISO pin was tied to ground and all received bytes were then equal to 0.
Note to self: find out what the clock dividers mean in terms of SPI transfer rates.
I2C
At this point, I will just say that the I2C peripheral to successfully used to display text on a 128x64 OLED screen using the Adafruit SH110X library.
Wi-Fi Communication
The three sketches in this section test the Wi-Fi functionality of the XIAO ESP32C5. Its support of the 5 GHz Wi-Fi band is the most obvious difference with other ESP32 modules. That is immediately obvious when running the Wi-Fi scan example.
Wi-Fi Scan
Here is the output from running the wifi_scan.ino sketch with the XIAO ESP32C5. Note that this sketch is
That's notably different from the results obtained with a XIAO ESP32C3 which can only connect to Wi-Fi networks on the 2.4GHz Wi-Fi band.
Here is a warning for those wondering what is the exact nature of the RSSI measurement: it's a rabbit hole. As far as I can make out, there is no standard. What RSSI measures is pretty much up to the manufacturer. It does seem that Espressif is measuring decibels-milliwatt. Here is an interpretation of what these values mean.
| RSSI | Interpretation |
|---|---|
| -30 dBm | Very strong signal, guaranteeing an excellent connection |
| -50 dBm | Excellent signal, suitable for most demanding uses (streaming, etc.) |
| -65 dBm | Acceptable signal for a stable and reliable connection |
| -90 dBm | Very weak signal, not allowing for a stable connection |
Sharp-eyed readers will have noticed the error message in the ESP32-C5 output. That message is printed if the Core Debug Level is set to something other than None. The error a minor bug which has no consequence on the execution of the code. The problem was reported in issue #12200 on January 4, 2026 and a fix was proposed on the next day. The latter has been merged into the first release candidate of version 3.3..6 of the ESP32 Arduino (3.3.6-RC1) (fix(5ghz): Rework WiFi LR logic for 5 GHz chips by @me-no-dev in #12202). I assume the error message will no longer appear in the next stable release of the ESP32 Arduino core.
The power consumption of the XIAO while running this scan is undetectable with my in-line USB DVM when the SoC is idling and it peaks near 90 mA or about 0.45 mW when the radio is used to scan. This was too much for the much solicited USB hub to which the XIAO was connected. The board seemed to be in a tight boot loop as it would reset very quickly after a few print statements in the setup. Setting the Core Debug Level to Debug to try to understand why the board was rebooting, revealed the error message seen above. That sent me on a wild goose chase because it was not the real problem. Eventually, I noticed that a brownout error was signalled at times. That is when I connected the XIAO directly to a USB port on the desktop and found that the scan completed without problems. I should have paid attention to the following warning in the README.md file that accompanied the WiFiScan.ino sketch from the esp32 WiFi examples.
Important: Be sure you're using a good quality USB cable and you have enough power source for your project.
Wi-Fi Connect
An important difference between the ESP32-C5 and the ESP32-C6 is the former's dual band Wi-Fi capability. Consequently, I feel that it is a bit of a shame that the example program for Wi-Fi connection in the Getting Started Guide makes no explicit mention of the 5 GHz band. Let's be clear, a connection on the 5 GHz band can be made because by default the ESP32-C5 automatically chooses which band it will use. Unfortunately, the sketch does not report that, nor can it be requested to use that band. The following is the source code with a little bit of tweaking to make the situation clearer.
This is the result when using a XIAO ESP32C3 when the USE_BAND macro is set to WIFI_BAND_MODE_5G_ONLY.
And here is the serial output of the same sketch running on the XIAO ESP32C5 which connected to a 5 GHz network.
Many access points have two wireless networks with the same SSID, one on the 2.4 band and the other on the 5 GHz bands. Indeed our ISP provided router/modem runs 3 wireless networks. I believe the second 5 GHz network is for the TV box sets. I wanted a sketch that would handle such a situation so I cobbled together the wifi_connect.ino sketch. When it begins, it lists all found wireless networks with the SSID specified in the secrets.h file. Running this code on the XIAO ESP32C5 gives the best results.
As can be seen, if more than one network can be found, the user is given the possibility to choose to which network to connect. In the Arduino IDE, enter the one digit index of the desired network in the message box at the top of the serial monitor and then strike the Enter key to send the index to the XIAO. Yes it's a bug. If there are 10 or more networks sharing the specified SSID, only one of the first nine can be chosen. Track that the XIAO connected to the correct network by looking at the BSSID.
When there's the initial scan finds only one network, then the user is not given a choice, the system will connect to that network if possible. The sketch has no problem with when a SoC does not handle 5 GHz band although the output is not quite clear that there is no scan of the 5 GHz band. Here is the output to the serial monitor when using a XIAO ESP32C3.
This sketch is clearly a work in progress. In particular the disconnect part is not yet implemented.
Wi-Fi Throughput
I was asked to measure the throughput of the Wi-Fi peripheral. That was something that I wanted to do for other devices but never got around to doing. So I am grateful for the impetus that finally got me to do something about this. As usual, I took the easy way out. I don't mean that I asked AI to write the code. Instead I asked a search engine to find an article on the subject and it came up with a good and recent (22 Dec. 2025) article by Tutoduino.fr entitled Analyze your ESP32 WiFi performance. Part 2 Wi-Fi throughput is most pertinent for us.
The article is quite clear and should be read, but here is the gist of the test. First a 20 MiB (20,971,520 bytes) file is created on a desktop on the same network as the ESP32 to test. Then a Python http server which can deliver the file is started. The firmware uploaded to the ESP32 creates an HTTP client that downloads the file. It does nothing with the downloaded data which would slow the process down. We are interested in the download speed only. After the download is completed, the sketch uses the time the download took to calculate the number of Mbits (1,000,000 bits) were transferred per seconds.
Tutoduino reports a speed of 7.29 Mbits/s with an ESP32-C3. I obtained similar results. Here is a table summarizing my first tests with the sketch.
| Board | Antenna | Band | Throughput (MBits) |
|---|---|---|---|
| XIAO ESP32C5 | External FPC | 5 GHz | 18.64 |
| XIAO ESP32C5 | External FPC | 2.4 GHz | 13.98 |
| XIAO ESP32C6 | Onboard ceramic | 2.4 GHz | 8.83 |
| XIAO ESP32C3 | External FPC | 2.4 GHz | 6.99 |
| LOLIN32 Lite | Onboard PCB | 2.4 GHz | 6.71 |
The rates are not accurate. They tend to underestimate the actual transmission rate, since there is more than the 20 MiB that was downloaded from the web server. Each IP packet contains a header and then the TCP packet which contains the IP packet also adds a header and both these headers are transmitted along with the data. Furthermore, TCP is a complex protocol with handshakes and acknowledgements designed to recover from transmission errors. All that overhead is not counted in our test. Finally, Tutoduino states that the throughput is appreciably higher when the code is developed in the ESP-IDF which is not surprising.
With that said, the results are valid from a comparative point of view. It is striking that the XIAO ESP32C5 using the slower 2.4 GHz band is twice as fast as the Lolin32 Lite which has a first generation ESP32 soc on board. The comparison would have been fairer if a similar board fitted with an FPC antenna had been tested.
The tests were done in optimal conditions given our wireless network. The development boards were only 4 metres from the Wi-Fi access point with no real obstacle. Those are the circumstances where the 5 GHz band is expected to be faster than the 2.4 GHz and that is what is observed. At the same time, optimal conditions is not where the antenna will make much difference. I did try replacing the external FPC antenna with a rod antenna but it didn't make much of a difference as expected.
More tests are clearly warranted.
Zigbee
Support for Zigbee in the ESP32 Arduino core continues to grow at a fast pace. The Zigbee examples directory now contains about 30 files covering all sorts of sensors and actuators. I have yet to look at most of these. Indeed, only the same two sketches tested with the XIAO ESP32C6 a few months ago are reviewed here. There is basically no change in the Zigbee_On_Off_Light sketch. A background task has been added in the Zigbee_On_Off_Switch firmware which polls regularly the state of the light which presumably makes it more responsive.
When compiling the sketches that are found in the 07_zigbee directory of my GitHub repository, it is necessary to set some options in the Tools menu correctly. The Readme.md in the subdirectory for each sketch display a screen capture of the appropriate settings which are not the same for both.
As with before with the XIAO ESP32C6, the XIAO ESP32C5 running Zigbee_On_Off_Light can join a Zigbee2MQTT controlled Zigbee network. Joining (or pairing, both terms seem to be used interchangeably) any new device can be daunting at first, but after a few success pairing will become much easier... trust me. Here are a few recommendations to make things easier.
- Before uploading the firmware to the XIAO enable the
Erase All Flash Before Sketch Uploadoption in the Arduino IDEToolsmenu. The rationale is to ensure that the XIAO is not attempting to reconnect to a coordinator used before. Well, that's how I think is going on when there are pairing problems. Of course the other Zigbee related options in the Tools menu must be correctly defined. - Enable joining (pairing) in the Zigbee2MQTT web interface. When testing a new device, I make it a rule to pair with the coordinator of our Zigbee network, not with any router. The following figure should make it clear how to go about this.
. Notifications will start popping up in the Zigbee2MQTT web interface.
- Place the XIAO very near the coordinator, just a few millimetres or even have the antennas kissing.
- Reset the XIAO, because when it restarts it goes into pairing mode. If all goes well, more notifications about the newly found device and about how the interview with it is proceeding should be appearing.
If everything went according to plan, and the interview was successful, the XIAO will show up at the bottom of the list of devices in the Zigbee2MQTT interface.

When there is a problem, which is pretty much the norm with a new device, there are a couple of things that may help. First I unplug a nearby router. It's an IKEA Zigbee bulb only two metres away and closer to my desktop machine than the coordinator. There's a possibility that the XIAO "talked" to it just after uploading the sketch and that might interfere. Secondly, I remove any device in that may have been created in the attempt to pair the XIAO.

Click on the trash can beside the undesired device and then check the Force remove option before clicking on the Delete button in the pop-up dialogue.
Try multiple times to pair the device. Be patient. As encouragement, I'll boast that every no-name Zigbee device purchased from questionable sources in far away places which offer no support at all has joined our Zigbee network thanks to Zigbee2MQTT. The unsupported device above the XIAO is a "Smart Sensor, Model C3007" according to the label on the box. It should be a PIR sensor according to some reports but it shows up as a ZG-102ZM which is a vibration and contact switch. Even IKEA zigbee devices have been paired although they tend to be even more difficult to work with initially. If problems persist, look at the FAQ Why does my device not ... pair? by Zigbee2MQTT.
While Zigbee2MQTT reports that the XIAO is paired, it is classed as unsupported. It was relatively easy to add an external converter to change that.

It would be too long to explain how that was done here (see Support new devices and External converters). However, it does not really matter. Both the XIAO and the vibration sensor show up in my home automation system based on Domoticz and work as expected.
I expect to add two more example sketches in the Zigbee section. Until then, I'll end by admitting that I can't say if pairing with the Zigbee2Tasmota bridge is possible; I "broke" my bridge trying to update it.
Bluetooth LE
Like most ESP32 SoC, the ESP32-C5 has Bluetooth® Low Energy (BLE) capabilities.
Scanning for Bluetooth LE Devices
As a first step, let's run a slightly modified version of the BLE scan sketch found in the Getting Started Guide. There seems to be an error near the end of the loop routine where a pointer is probably released a second time. That delete foundDevices; //Release pointer statement was not in the original Arduino example sketch by Evandro Copercini. The implementation of String BLEAdvertisedDevice::toString() in .../.arduino15/packages/esp32/hardware/esp32/3.3.5/libraries/BLE/src/BLEAdvertisedDevice.cpp handles most elements that may be found in BLE device advertisements except for serviceDataUUID. Consequently, a little bit of code was added to the MyAdvertisedDeviceCallbacks code to show that information when available. Don't misconstrue this, I don't know what I am doing, but it was interesting to see that when Bluetooth is activated on my relatively new Android phone ( it shows up during the scan and it does have as serviceDataUUID.
Here is the output of the modified sketch.
I can't identify the second device with a rather low rssi. Perhaps it is the set top box which has a Bluetooth remote. Even in the interest of science, I was not willing to turn off that device which is finicky and takes for ever to come back on line. If my shenanigans were to interrupt or prevent the taping of a programme by someone else in the household, it could be costly for me.
All that matters is that the scan was successful. However I must point out two things. When I first ran the sketch, the XIAO was caught in a boot loop, resetting almost immediately after the setup() was completed. Most times there was no hint as to the cause of the problem even if Core Debug Level was set to Warn in the Tools menu. However occasionally, an error message was visible.
It finally dawned on me that the culprit was the USB hub to which the XIAO was connected. The problem disappeared on connecting the XIA to another powered hub capable of powering the XIA when it uses its 2.4 GHz radio. This sketch requires more energy than when doing nothing with peaks up to 330 mWatts (60 mA at 5.06 or 5.07 volts). That is an important lesson: even Bluetooth is relatively power hungry.
The other warning that might be useful to others is that it was necessary to reboot the XIAO after uploading the sketch otherwise the scan appears to be working but the data shown is all wrong.
Implementing a Bluetooth Server
Moving on to setting up the XIAO as a BLE server using a slightly modified version of the BLE Server Program in the Getting Started Guide. The source code for the modified version is available in the GitHub repository. After uploading the firmware to the XIAO, I used nRF Connect for Mobile from Nordic Semiconductor ASA on an Android telephone to read and write the characteristic.
The XIAO_ESP32C5_Server is found when a scan is performed. Click on its Connect button. Click on the CLIENT tab and then on its Unkown service which has the UUID specified in server.ino : #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b". Then nRF Connect displays the unknown characteristic #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8". Unknown here means that the service and its characteristic are not predefined and registered values. But the program knows that the characteristic can be read and written. Click on the down arrow to read the characteristic.
The characteristic read is an array of bytes whose value is shown in hexadecimal and as ASCII characters. The phrase "Hello World from XIAO" is, of course, the value that was given to the characteristic in the sketch. Click on the up arrow of the characteristic to write a new value. Select the value type to be Text and enter a new string. I chose "Hi!". Click on the SEND button. The next screen shows that the value has indeed changed to "Hi!". While this was going on, the sketch displayed the following in the serial monitor.
If one does not have access to nRF Connect on a smart phone, then the equivalent can be done from a terminal in Linux with a machine that has a Bluetooth LE enabled card.
Of course, that value is "Hello world from XIAO" in hexadecimal. So the characteristic can be read. Let's write send "Hi!" which in hex is 48 69 21 to the server. Indeed it will be done twice, the second time waiting for confirmation.
That last read confirms that the "Hello world from XIAO" value was replaced with "Hi!". This is what the firmware displayed in the serial monitor.
The idea to use a Linux desktop comes from an article by Tony DiCola entitled Reverse Engineering a Bluetooth Low Energy Light Bulb written in 2015-03-01 and updated in 2024-03-08.
No matter which method is used, it has been confirmed that the XIAO ESP32C5 can be a Bluetooth server.
Adding a Bluetooth Client
A BLE Client Program from the Getting Started Guide running on the XIAO ESP32C5 can obtain read and write the data from the BLE server just as the nRF Connect application in Android and the gatttool utility in Linux. Here is the serial monitor output of the client XIAO.
At the same time, the BLE server running on an XIAO ESP32C6 was displaying corresponding messages on its serial monitor.
Thread and Matter
Justice can't be done to these topics here. Mostly because I know next to nothing about these technologies and our home automation system does not support Thread and Matter. Having said this, two XIAO ESP32C5 communicated with each other using Thread. The first, running the < example sketch, was able to find the other XIAO ESP32-C5 running the File/Examples/OpenThread/CLI/SimpleNode.ino sketch. This is the output for the simple node which, presumably, is broadcasting its existence.
And this is what the scan node displayed as it found the other board.
The only thing that was necessary to get these sketches to work was to enable PSRAM in the Arduino IDE Tools menu in both cases.
While leaving the Thread scanner running, the File/Examples/Matter/MatterOnOffLight.ino sketch was uploaded to the XIAO previously running the simple Thread node. Again PSRAM had to be enabled.
After a minute or so of complaining about not being commissioned, the Matter on/off node was discovered by the Thread scanner. As stated before, I do not have a Thread network and Matter hub, so it was surprising to receive an e-mail from Alexa daughter of a female warrior.
Loose translation: Alexa is reporting that a compatible device has signalled its presence and that it could be controlled by Alexa voice commands and automation routines once connected and activated. The only Alexa device operating is a Echo Dot purchased in 2020. I didn't even think it had a IEEE 802.15.4 radio; the Echo Dot description in my purchase history contains no mention of Zigbee, Thread or Matter. Let's not go down this rabbit hole. I just installed Amazon Alexa app on an Android phone, and followed the instructions. The short of it is that using the 4th gen Echo Dot, Alexa recognized the On/Off Matter node it could not connect to it. Finally, I looked at the manual, the README file accompanying the sketch and found this rather prominent table and notes?
SoC Wi-Fi Thread BLE
CommissioningLED Status ESP32 ✅ ❌ ❌ Optional Fully supported ESP32-S2 ✅ ❌ ❌ Optional Fully supported ESP32-S3 ✅ ❌ ✅ Optional Fully supported ESP32-C3 ✅ ❌ ✅ Optional Fully supported ESP32-C5 ❌ ✅ ✅ Optional Supported (Thread only) ESP32-C6 ✅ ❌ ✅ Optional Fully supported ESP32-H2 ❌ ✅ ✅ Optional Supported (Thread only) Note on Commissioning:
- ESP32 & ESP32-S2 do not support commissioning over Bluetooth LE. For these chips, you must provide Wi-Fi credentials directly in the sketch code so they can connect to your network manually.
- ESP32-C6 Although it has Thread support, the ESP32 Arduino Matter Library has been pre compiled using Wi-Fi only. In order to configure it for Thread-only operation it is necessary to build the project using Arduino as an IDF Component and to disable the Matter Wi-Fi station feature.
- ESP32-C5 Although it has Wi-Fi 2.4 GHz and 5 GHz support, the ESP32 Arduino Matter Library has been pre compiled using Thread only. In order to configure it for Wi-Fi operation it is necessary to build the project using Arduino as an ESP-IDF component and disable Thread network, keeping only Wi-Fi station.
It does appear that an ESP based Matter device can operate either on a Wi-Fi network or a Thread network. Running the sketch on XIAO ESP32C6 connected to Wi-Fi could be provisioned and Alexa was then able to control its LED.
Conclusions
The XIAO ESP32C5 is quite similar to the XIAO ESP32C6. I think either represent a relatively big step up from the XIAO ESP32C3. However, which one should be chosen? The obvious overused answer is to say that it depends on what's needed. The fact is that the tradeoffs are relatively subtle and more tests are needed to distinguish them. But what I really need is much more experience with them in practical projects. The conclusion may be available in a future version of this blog.
First Look at the Seeed Studio XIAO ESP32C6




