After about 18 months, it seemed appropriate to revisit the subject of Super Mini development boards based on the ESP32-C3 system-on-a-chip. The four Super Mini ESP32-C3 boards initially purchased have yet to be deployed because, as explained in the original version of this post, they cannot work reliably with Wi-Fi and not at all with Bluetooth. However, in the light of some positive results obtained by others, two other Super Mini ESP32-C3 boards were obtained. On first blush, they appear to have significantly improved wireless performance. A similar type of development board which sports an onboard 0.42" OLED display also fares better. This is in keeping with the following observation.
Until recently, many makers were cautious about SuperMini boards due to unreliable batches - USB issues, unstable boot modes, or flaky power rails were common with some no-name versions. That’s changed. Reputable vendors are now consistently delivering well-built SuperMinis, and suddenly these boards are becoming the go-to picks for many makers.Source: espboards
This is a full rewrite of the original article last updated in June of 2024. This rewrite was first posted on March 9, 2026 to reflect changes to the associated GitHub repository Super Mini ESP32C3 Arduino Sketches / PlatformIO Projects. Those changes were needed to keep up with the latest release of the Espressif ESP32 Arduino core and with the decision to switch to the pioarduino IDE fork of the PlatformIO IDE extension for code editors such as Visual Studio Code and VSCodium.
Originally, the Super Mini ESP32-C3 was examined as an alternative to the XIAO ESP32C3 investigated before. Consequently, the first look at the Super Mini was an implicit comparison with the Seeed Studio offering. One aim of this version is to make it a stand alone examination of the capabilities of the Super Mini.
Table of content
- Hardware
- Uploading Firmware to the Super Mini ESP32-C3 Board
- Exploring the Super Mini ESP32-C3
- Something Like "Hello World"
- Digital Input and Output
- Analogue Input
- Deep Sleep Mode
- Serial Communication
- Wi-Fi
- Bluetooth LE
- Upcoming
Hardware
Many versions of the Super Mini ESP32C3 boards are made. They are sold by numerous retailers who often do not identify the version or maker of the board on offer. A seller may offer a single board while displaying multiple photographs of different boards with no clue as to which one will be delivered. Some retailers may not know exactly what they are selling. The first batch of four boards obtained 18 months ago had silk screen markings on the top side of the board that were different from what was shown on the seller's web page. The almost illegible logo on the reverse side was a smudged 'ESP32C3' above 'Super Mini'. Nothing identified the maker. Another two boards, obtained about a month ago, are legibly identified on the reverse side by four lines: 'TENSTAR', 'ROBOT', 'ESP32-C3' and 'Super Mini'. I do not know if TENSTAR is the manufacturer of the boards, or if it designed them, or if it is just a retailer. Frankly it does not matter enough to me to spend time investigating this.
Luckily, all these boards adhere to a common form factor and pin numbering. Here is a representation of the top view with relatively accurate labels and with pin numbers and serial functions at the sides of the pin. Click on each image to see a bigger version with a legend.
The i/o numbers shown in white on a purple background are silk screened in white on the back side of the board. They are the pin numbers to use in Arduino programs. The standard Super Mini boards measure roughly 22.5 mm in length (along the axis defined by the USB connector and the ceramic antenna with the C3 label) by 18 mm across. The USB-C connector protrudes about 2 mm past the board so that the dimension of a bounding rectangle is 24x18 mm. A standard 8 pin 0.1" header can be soldered on each side. Of these 16 pins, 3 are dedicated to power connections (GND, 3.3 volts and 5 volts). The remaining 13 pins are input/ouput connections to the microprocessor. The boards have two push buttons (reset and boot), two LEDs (one is a red power indicator, the other is a user blue LED) and an onboard ceramic antenna. The bigger components on the top side are the SoC, the crystal oscillator and a voltage regulator. There are no components on the back side.
An ESP32-C3 based board with an onboard 0.42" OLED display can be considered to be a Super Mini. It is slightly bigger at 25x21 mm, 27x21 mm taking into account the USB-C connector. The same SoC pads are brought out to the header pins, but their ordering is completely different as can be seen. Most of the components, SoC, oscillator and so on, are on the back side of the board which makes it almost impossible to solder the board on top of a larger printed circuit board. Perhaps that's the reason there are no crenellated edge connectors. These physical differences are not very significant from a programmer's point of view, so that the two types of boards can be treated as one for the most part. However, there is an important distinction about the pins assigned to the I2C peripheral which, in my opinion, should correspond to the i/o pins that are connected to the OLED display. And in that case, if the OLED is connected to i/o pin other than 8 and 9, then it would be better to attach different pins to the SPI peripheral should there be an overlap between the SPI and I2C pin assignments. That is made quite obvious in the diagram above which corresponds to the board in my posession whose OLED display is connected to i/o pins 5 and 6. More on that latter.
There is a Super Mini Plus version which has an RGB user LED and a U.FL connector for an optional external antenna. I do not have such a board. There are other boards of the same size with a similar layout but based on a different SoC such as the ESP32-H2, ESP32-S3, ESP32-P4. These are not quite as common and seem more expensive and will not be discussed here.
Both diagrams show a grayed out A5 analogue input. The digital controller of the SAR ADC2 (SAR: successive approximation register , ADC: analogue-to-digital converter) does not work according to the ESP32-C3 Series SoC Errata. The suggested "workaround" is to use SAR ADC1. Thankfully ADC2 has only one channel. This affects revisions v0.0, v0.1, v0.2, v0.3, v0.4 and v1.1 of the ESP32-C3 Series SoCs.
In other words, do not use i/o 5 as an analogue input.
ESP32C3 Super Mini Schematic
Schematics are available from ICBbuy, nologo and Tenstar (open the Description section on the seller's store). All three appear to be identical. Unfortunately, there is no legend attributing the design to anyone and it's not even sure that the design corresponds to the boards received. The common schematic could be a straightforward implementation of a reference design by Espressif, but none could be found.
There is a schematic for an ESP32C3 super mini like development board with a 0.42" OLED display that is named the ESP32-C3-ABrobot-OLED. The schematic is similar to the other three with the addition of the I2C display which is connected to GPIO5 (SDA) and GPIO6 (SCL). This is confusing because the same source has an i/o pin diagram that shows that the I2C signals are assigned to GPIO8 and GPIO9 (as on the Super Mini) and also to GPIO5 and GPIO6. The ESP32-C3 has only one I2C controller so, clearly, only a single pair of i/o pins can be attached to it at any one time. To confuse things even further, the C3-ABrobot-OLED project has a platformio.ini configuration file with the entry board = airm2m_core_esp32c3 (AirM2M CORE ESP32C3 in the Arduino IDE). The corresponding variant pins_arduino.h contains yet a third I2C pin assignment: SDA = 4 and SCL = 5. In ESP32-C3 OLED 0.42" Display Development Board, ESPBoards says The integrated 0.42" OLED display uses I2C communication (typically GPIO 8 for SDA and GPIO 9 for SCL) and repeats that assertion at least twice more, yet it links to the ESP32-C3-ABrobot-OLED schematic. Are there boards with displays that use different pin assignments?
In projects 26_i2c_ds3231 and 27_i2c_oled, the SDA = 5 and SCL = 6 pin assignment is used and it works properly with the single ESP32C3 with 0.42" OLED board in my possession. Others have reported the same thing: ESP32-C3 0.42 OLED (Kevin's Blog Feb 12, 2025) and How to use onboard 0.42 inch OLED for ESP32-C3 OLED development board with micropython.
Powering the Board
The three sources for the schematic contain very similar language about powering the board via the USB-C connector (hence with a 5 V power source) or directly through the 5V/Gnd pins. In that case, they agree that 3.3 to 6 volts DC could be used. That lower figure is doubtful; the specified ME6211x33 LDO voltage regulator requires a minimum of 4.3 volts to generates the 3.3-volt power rail with a sustained 500 mA of current. According to the schematic, the VBUS pins of the USB-C socket are directly connected to the 5-volt pin of the header. Consequently, the USB port cannot be used in a normal fashion if an external power source is connected to the 5 V pin. If one wanted to power the Super Mini through the 5-volt and ground pins of the header and also wanted to use the USB CDC peripheral of the SoC, a special USB cable passing through only the GND, D- and D+ USB signals would be required.
The ME6211 voltage regulator is capable of producing a maximum output current of 500 mA in optimal conditions and that is more or less the minimum required current for the ESP32-C3 when using the radio (ref). Clearly, care must be exercised if the 3.3-volt pin of the header is used to power external loads such as sensors.
Source of the Wi-Fi Problem
In my first look at the Super Mini boards, I repeated some material found on a forum about the source of the problem being an antenna impedance mismatch. It seemed to make sense because the Super Mini would connect rapidly if my hand was touching or very near the board. In those initial tests, the board and the WiFi access point were quite near each other. In the current test environment, the access point is about 8 metres away, upstairs in a bathroom. Touching a Super Mini can often extends the connection time or even prevents the board from connecting at all.
On looking at more reports of problems with Wi-Fi connection I have found that the problem is not limited to the Super Mini.
- ESP32-C3 can not connect to WiFi AP (AUTH_EXPIRE) #6767 Board: Lolin C3-mini. Solution:
WiFi.setTxPower(WIFI_POWER_8_5dBm); - Lolin Wemos C3 mini needs hardware mod to work #15443 Board: Lolin C3-mini. Solution:
WiFi.setTxPower(WIFI_POWER_8_5dBm); - ESP-C3-Super-Mini Specs at ESP3D Board: Super Mini ESP32-C3. Solution:
WiFi.setTxPower(WIFI_POWER_15dBm);other valuesWIFI_POWER_15dBmorWIFI_POWER_8_5dBm - ESP32-C3 Mini Development Board not able to connect to WiFi #780 Board: Waveshare ESP32-C3-Zero. Solution:
WiFi.setTxPower(WIFI_POWER_8_5dBm); - ESP32-S3-Zero doesn’t connect to WiFi network Board: Waveshare ESP32-S3-Zero (2MB PSRAM). Solution:
WiFi.setTxPower(WIFI_POWER_8_5dBm);
More references of that type could be added I am sure. I especially like the solutions proposed by ESP3D because it is in line with the results of the experiments done here. There is nothing sacrosanct about the 8.5 dBm TX power level as project 05_wifi_tx_power will show. The consensus that emerges from the first reference, which is the longest discussion about the source of the problem, is that it is a design flaw. One problem often mentioned is the location of the ceramic antenna which is too close to other components on the board. That was a factor in choosing the Tenstar boards in my second purchase. There are other boards where the onboard antenna is even further away from the other components, they tend to be rare and more expensive.
A contributor to the repository, Beherith, added the observation.
... [Some] Super Mini boards have a 3.3V regulator with a peak current rating of 250 mA (SMD LLVB). This is insufficient to power everything given that the [official datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf) specifies Wi-Fi current consumption @18.5dbm as 276mA or greater.
I am unable to verify this as I cannot identify the LDO regulator on the Super Mini boards in my possession. As mentioned above, the schematic called for an LDO capable of supplying 500 mA which is the Espressif recommended value. The Texas Instrument LP5907 LDO would be insufficient. I should probably rerun the test with boards that are supplying current to external peripherals. Logically the Wi-Fi connection should be slower or altogether impossible with higher TX power levels as the total current load increases.
Before closing this topic, I must mention that the hardware solution ESP32-C3 SuperMini ANTENNA MODIFICATION by Peter Neufeld. I have not yet tested this. Adding a rather cumbersome antenna does appear to defeat the purpose of using a very small device.


Nevetheless, it might be feasible in some applications. The possibility of adding an FPC or rod antenna is also mentioned in some vendor documentation. The second figure is from the nologo specification. Peter Neufeld talked about this in a comment in his blog.
Uploading Firmware to the Super Mini ESP32-C3 Board
Anyone reading this will want to install new firmware on the board's flash memory. Indeed this post will refer to many projects, meant to investigate the capabilities of the Super Mini ESP32C3, that are contained in a GitHub repository named Super Mini ESP32C3 Arduino Sketches / PlatformIO Projects. As the catchy title foreshadows, it is assumed that either the Arduino IDE (integrated development environment) or the PlatformIO IDE extension in a code editor such as Visual Studio Code or VSCodium will be used to create or edit the source code, upload it to the Super Mini and to view the serial output from the board if there's any. Actually, all the projects do spit out messages on the serial monitor which would rarely be useful in production IoT applications.
Which IDE to use is really down to personal preferences.
- The Arduino IDE is user-friendly and decently powerful since version 2. The IDE is a single application which is very easy to set up. All the complication with toolchains and platforms is hidden from the user. It has a simple editor which is used to write Arduino code or to load examples. Once the code is in the editor, compiling or compiling and uploading are just mouse clicks. Documentation is plentiful but be careful with older material which refers to the legacy IDE (version 1.8.19 and older).
- When it comes to PlatformIDE the installation is more complicated and there's a bit less hand holding so one should expect a somewhat steeper learning curve. In exchange one gets a more powerful programming environment.
It is safe to venture that most would recommend the use of the Arduino IDE to a complete beginner. Once that has been mastered, it is not too hard to move on to PlatformIO if desired.
Arduino IDE Installation Notes
It can be obtained for Windows, macOS and Linux from a downloads page. Do make sure to get the current Aduino IDE 2.3.x release. The page will suggest the correct version for your operating system. Installation is not complicated at all but there is some additional information in Downloading and installing the Arduino IDE 2.
Once the Arduino IDE has been downloaded, a couple of settings have to be entered in the IDE Preferences window. Open the latter with the File/Preferences... menu or with the Ctrl, keyboard shortcut. Then in the Additional boards manager URLs drop down list add the following URL https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json.

Update the Sketchbook location which is also in the Preferences window. It should be set to the top-level directory of the downloaded GitHub repository for this blog. This will make it possible to go to each sketch through the File/Sketchbook menu. It is probably best to keep a record of the default sketchbook location to reset it after working with the example sketches.

The sketchbook location must be correct, otherwise the Arduino IDE will not be able to locate the local copies of third-party libraries that are included in the repository.
I downloaded the AppImage file to the .local/bin directory in my home directory thus ensuring that the application is in the search path. Then I created the following arduino.desktop file.
Of course, it will need to be modified to account for the actual user name. An icon will be needed. I just downloaded a 48x48 png found by searching on the Internet. Once the modifications are completed, save the desktop file in the .local/share/applications directory. After a short while, Linux Mint OS automatically added an Arduino entry was added in the menu system.
I should point out that the sketches in the repository were developed with version 2.3.7. I have tried version 2.3.8 without any problem and without installing the libfuse2 library and without adding the 99-arduino.rules file as instructed. On the other hand, libfuse3 is installed on my up-to-date Linux Mint system. I believe that the udev rule only handles actual Arduino boards and would not help at all with the Super Mini. However, my Linux user is a member of the dialout group. That was needed in the past and may still be required if uploading to the board fails for a permission error.
Pioarduino IDE Installation Notes
I started to use the PlaformioIO IDE extension in Visual Studio Code over five years ago. Despite the usual initial struggle with using a never before used IDE within a new editor, I soon settled on that environment. It was very much better than the legacy Arduino IDE available at that time. Since then, I have migrated from Visual Studio Code to a fork called VSCodium (Why Does This Exist). Lately, I have found that the PlatformIO extension could no longer be easily installed in VSCodium. While that was unfortunate, the consequence was not all that important because PlatformIO support for newer ESP32 boards was becoming spotty. The good news is that piarduino a PlatformIO fork was created by a team led by jason2866. That is a large undertaking that includes the pioarduino IDE extension that is available in the marketplaces of both Visual Studio Code and VSCodium. Another important part of this project is the platform-espressif32 fork of the Espressif 32: development platform for PlatformIO. Support for newer ESP32 boards is better in the pioarduino platform.
It should be obvious by now that using this development environment is more involved than installing a single application as in the case of the Arduino IDE. Nevertheless, for my use case, the installation is not overly difficult. In broad strokes, start by installing Visual Studio Code or VSCodium. Once that is done, install the pioarduino IDE extension in the code editor.

Hopefully, the IKEA inspired instruction diagram is clear enough about the three steps needed to perform that task.
That's basically it for the installation. There is no need to manually install the ESP32 arduino platform as in the Arduino IDE, it will be done automatically when the platform is needed. Please be aware that I have not tested this with Visual Studio Code. Version 1.2.5 of the pioarduino IDE extension has been running in VSCodium, currently version 1.108.10359, since the beginning of 2026 without issues.
Board definition
In the previous version of this post, I recommended using the MakerGo definition in the Arduino IDE on the grounds that its variant arduino_pin.h file is a superset of the Nologo definition. Now, I have changed my mind and the Nologo board definition is used in all the sketches found in the GitHub repository because it is available in both IDEs without need to do anything special.
Arduino IDE

Selecting the board in the Arduino IDE can be done with the Tools/Board "..."/esp32/ menu. However there are many ESP32 board definitions and they are not in alphabetical order. You can press on the "N" key to see in succession all boards whose name starts with that letter. It is also possible to open the board drop box in the top button bar and then click on the Select other board and port... option.

The search box is much more powerful and if nologo is entered, only two entries will show up. Once the board is selected, there are a number of options that pertain to the specific board which can be set in the Tools menu.

The default value of these options is acceptable for almost all the sketches found in the GitHub repository. The exception could be the port, which here is shown greyed out because the board is not plugged into a USB port. The other options that are sometimes modified are the Core Debug Level and the Erase All flash Before Sketch Upload. If a particular option is required, it will be indicated in the comment at the top of the sketch .ino file.
Platformio IDE
Selection of the board and most settings are defined in a configuration file called platformio.ini which must exist in the top-level directory of a project. Here is the configuration file for the 00_sys_info project.
As can be seen, the board is specified in the [env:nolog_esp32c3_super_mini], as well as the framework and the full URL of the platform to use. As stated before, both the framework and the platform will be downloaded and installed automatically by the pioarduinoIDE extension. There are other options which are shown as comments only. The default CORE_DEBUG_LEVEL is 0 as in the Arduino IDE. It's just easy to have the build options as a comment should it be necessary to enable core debugging messages. Similarly, the IDE is pretty good at finding the upload and monitor ports, but having the possibility to manually specify them is useful when working with more than one board. The rationale for the src_dir option in the [platformio] is given in the GitHub repository.
The mandatory configuration file in PlatformIO projects is in effect a form of project documentation. In the Arduino IDE, it is up to the user to document the configuration. Hopefully, I have done this correctly at the start of each the sketch.
Why use the nologo esp32c3 board definition?
The "no logo" name seems so appropriate for a development board that is often delivered without any markings identifying its source. However, there is more to using the nologo board definition than its name. In the pioarduino platform-espressif32 boards directory, I identified all the board definitions that use an ESP32-C3 and that enable the onboard USB CDC peripheral. Of course the nologo_esp32c3_super_mini.json manifest was in that list. The two esp32-c3-devkit were eliminated,
but other definitions were found and in the end there were ten .json manifests to consider.
Then I compared the pins_arduino.h variant file of each board with the definition for the nologo variant. The serial ports are assigned to different pins in most cases leaving only three variants with correct pin assignments for the serial ports and analogue pins. The bee_motion_mini.json does not contain a LED_BUILTIN macro and variable and it has extra I/O pin assignments for devices not found on the Super Mini. With the dfrobot_beetle_esp32c3.json manifest the USB CDC On Boot option is not enabled in the Arduino IDE and the LED_BUILTIN macro and variable are incorrectly defined. That leaves the weactstudio_esp32c3coreboard whose pins_arduino.h has exactly the same content as the nologo variant. Consequently, it seems to work in the Arduino IDE. Unfortunately, the "variant" value in weactstudio_esp32c3coreboard.json is "esp32c3". That is not the expected "weact_studio_esp32c3 value. Consequently the wrong pins_arduino.h variant is used in pioarduino and the LED_BUILTIN macro and variable are thus wrong. I should also point out that the weact json manifest has different values for "maximum_ram_size" and "maximum_size" in the "upload" section from all the other definitions. I do not know if that is meaningful and I haven't checked if the same discrepencies exists in the huge Arduino boards.txt configuration file.
Bootloader Mode
To upload new firmware to the non-volatile flash memory of the ESP32-C3, the latter must be in bootloader mode. Once the upload is completed, the board must be returned to normal mode to start executing the new firmware. In most circumstances, this is done automatically by the IDE. Sometimes the IDE cannot put the board in bootloader mode. While some may think that the board is bricked or soft bricked, it is probably just fine. All that is needed is to manually put the board in bootloader mode. This is done by keeping the boot button depressed while pressing and releasing the reset button. Alternatively, one can remove power from the board by disconnecting the USB cable, and then reconnecting it to a power source via the USB cable while keeping the boot button depressed. Once the firmware has been uploaded, clicking on the reset button returns the board to normal operation meaning that it will start executing the code uploaded to the flash memory.
There is one situation where the need to resort to manual selection of the bootloader mode is almost certain. It is when the board is spending most of the time in deep sleep mode. A board will wake from deep sleep mode at some point, otherwise it is not doing much of anything, and when it does, it may be possible for the IDE to switch it to bootloader mode to start uploading a new version of the firmware. Success really depends on how long the board is awake and how hard it is to time the IDE upload operation to coincide with that period. Most times, when using deep sleep mode, manually enabling bootloader mode to upload new firmware is less frustrating. Alternatively, one could have a long delay in the startup code before the board is put into sleep mode. That way, resetting the board would provide some time to upload the firmware from the IDE without manually setting the board in bootloader mode.
Exploring the Super Mini ESP32-C3
I have created a number of sketches to test the Super Mini board. Most of them do not require anything other than a proper data and power USB cable to connect the Super Mini to the computer on which the IDE is running. If more is required, such as an LED, it will be noted. The source code can be found in my Github account in the super_mini_esp32c3_sketches repository. Each project is structured in such a way that it can be compiled and run in the Arduino IDE and in PlatformIO without installing any other libraries. Many of the projects are remixes of sketches in xiao_esp32c3_sketches, xiao_esp32c6_sketches and xiao_esp32c5_sketches (I know how integers are ordered, this is their chronological order).
In the latest repository about the XIAO ESP32C5, the projects were grouped into categories, which in retrospect would have been a better way of organizing them in the Super Mini repository. Instead the projects are in chronological order using the numerical prefix to accomplish that. Grouping them will be done in the following presentation.
Something Like "Hello World"
The first group of sketches consists of two projects.
| Project | Purpose |
|---|---|
| 00_sys_info | Display information about the ESP32-C5 SoC |
| 01_pin_names | Display the I/O pin numbers and names and various macro definitions of Super Mini ESP32-C3 boards |
| 12_macs | Display the MAC addresses of all interfaces |
Project: 00_sys_info
This project just displays some information about the SoC including the amount of flash storage (4 MBytes), the amount of PSRAM (0) and SRAM on the serial monitor. That seems simple enough, but you may want to run that sketch with the Core Debug Level set to "Verbose" in the Tools menu of the Arduino IDE or with the build_flags = -DCORE_DEBUG_LEVEL=5 entry in the platformio.ini configuration file if using the pioarduino IDE. Here are the result when using the Arduino IDE.
I will admit that the Sketch and Heap sizes confuse me. It does not help that the sizes are not exactly the same when the project is compiled with the pioarduino IDE even if the ESP-IDF and Arduino versions are the same. I will not go into the details here; there's no point in confusing others. I'll just mention that the Espressif documentation specifies that the SoC has 400 KiB of SRAM divided into a 16 KiB of cache memory leaving 384 KiB of internal RAM for the heap, allocated global variables and the stack (ESP32-C3 Series Datasheet Version 2.3 (2025-12-24), p.4 and section 3.3.2. Internal Memory of the ESP32-C3 Technical Reference Manual Version 1.3 (2025-05-08) p. 94).
On the less ambiguous side, testing all the Super Mini ESP32C3 boards in my possession including the board with an onboard display, yielded the same output on the serial monitor except for the MAC address of course. An XIAO ESP32C3 only differs with respect to the SoC revision which was 0.3 instead of 0.4. Since my XIAO boards are older that made sense.
Project: 01_pin_names
The second project in this group is 01_pin_names displays the content of the relevant pins_arduino.h variant file that corresponds to the Super Mini. It also prints a number of predefined macros. Unfortunately, the definitions are not the same depending on the IDE. The following table presents the differences.
| Macro | Value in Arduino IDE | Value in pioarduino IDE |
|---|---|---|
| PLATFORMIO | n.a. | defined (60119) |
| ARDUINO | defined (10607) | defined (10812) |
| ARDUINO_BOARD | "NOLOG_ESP32C3_SUPER_MINI" | "nologo_esp32c3_super_mini" |
| ARDUINO_NOLOGO_ESP32C3_SUPER_MINI | defined | n.a. |
| ARDUINO_ESP32C3_DEV | n.a. | defined |
The notable absence of the ARDUINO_NOLOGO_ESP32C3_SUPER_MINI macro when using the PlatformIO IDE does complicate things a bit when it would be useful to add conditional bits in a firmware meant to run on various ESP32 modules. The ARDUINO_ESP32C3_DEV macro, which is defined in nologo_esp32c3_super_mini.json manifest, is unfortunately not unique.
Most projects will print a warning to the serial monitor when ARDUINO_ESP32C3 is defined but the variant is not nolog_esp32c3_super_mini.
I think that's acceptable for the projects presented here, but in production use, I would simply add an extra flag in the platformio.ini configuration file.
Then the following conditional code in the sketch should work no matter if the Arduino or the pioarduino IDE is used.
Project: 12_macs
This project is straightforward only showing the capabilities of the MACs library. That library provides easy access to the MAC addresses of the interfaces that an ESP32 SoC has. Since the ESP32-C3 does not support IEEE 802.15.4 protocols such as Zigbee, all the MAC addresses will be 6 bytes long and the 8-byte ESP_MAC_IEEE802154 and 2-byte ESP_MAC_EFUSE_EXT addresses do not exist.
An ESP32 can have a custom MAC address burned into the eFuse memory by a customer. If a requst is made for the custom MAC when none has been defined an unavoidable error will result. Luckily this does not cause a core panic. In principle be the manufactureur of the board on which the SoC is found could have burned a custom address, but I have never found one defined among my Super Mini boards. Below is the ouput of that program when the HAS_CUSTOM_MAC macro is defined in the user configuration thus show the E (2122) system_api: eFuse MAC_CUSTOM is empty error.
Digital Input and Output
There are currently six project in this group of sketches.
| Project | Purpose |
|---|---|
| 02_blink_pulse_led | Alternately blinks (heartbeat) and pulses the onboard LED of an Super Mini ESP32-C3 |
| 10_blink_leds | Toogles each i/o pin of the Super Mini ESP32-C3 high and low in turn |
| 11_pulse_leds | Uses pulse width modulation to pulse the intensity of a LED connected to each i/o pin of an Super Mini ESP32-C3 |
| 13_poll | Tests polling of all the i/o pins of an Super Mini ESP32-C3 |
| 14_interrupts | Test the interrupt capability of all the i/o pins of the Super Mini ESP32-C3. |
| 15_interrupts2 | Investigates three interrupt modes and debouncing techniques with an Super Mini ESP32-C3 |
Project: 02_blink_pulse_led
This project is a needlessly complicated version of the Blink sketch which is the Hello World! example for embedded systems. When running, the firmware flashes the onboard user LED in a heartbeat fashion which visually confirms that the LED_BUILTIN variable is correctly set to the IO pin connected to the LED and that the LED is turned on by a LOW (nominally 0 volt) output value on that pin.
Pressing the boot button switches the program between a heartbeat mode and a pulse mode.
In pulse mode, the intensity of the user LED is slowly ramped up and down continuously. The visual effect is that the LED pulsates. The intensity depends on the output voltage of the i/o pin connected to the LED. However, the ESP32-C3 does not have digital to analogue converters which would allow setting the output voltage at a specified level. Instead the i/o pin is being turned on and off at a high frequency and the average voltage is a function of the portion of the time the pin is on versus when it is off. This is known as pulse width modulation.
It seemed clever to verify the functionning of the digitalRead(), digitalWrite(), and analogueWrite() (which implements pwm) functions of the ESP32 Arduino core in a single test. In retrospect, a simple asymetric blink program would have sufficed here, because I later felt compelled to test these functions and others against all the i/o pins of the board.
Project: 10_blink_leds
This project is designed to test the digitalWrite() function against every i/o pin of the Super Mini ESP32-C3 board. It requires a test rig that, luckily, is affordable.

The image is sufficient, its only an LED, a Dupont wire, a mini alligator clip that can establish a good contact with the GND pin of the Super Mini without shorting the power pins on either side, and a current limiting resitor. Anything between 100 to 250 ohms should do. The Dupont pin should be pressed against the edge pad as instructed by firmware.

If everything is right, the test LED should blink in a heartbeat fashion. The pins are tested in an orderly fashion to make it easier to perform the test.


There is a HAS_OLED macro that can be defined in the configuration section at the beginning of the source code to run the same test with a Super Mini with OLED. Since the silk screen labels are printed in a different orientation and the i/o pins are not arranged around the edges on the other board, they are blinked in a different order.
At the end of each cycle, the onboard user LED is blinked before the next cycle is started. Since the user LED is connected to i/o pin 8, it will be blinked twice in each cycle.
Project: 11_pulse_leds
This project is basically the same as the previous one except that the function being tested is analogWrite(). Consequently the intensity of each pin will be ramped up and down in turn during the test. The same test probe can be used and the order in which the pins are pulsed is the same as above.
Project: 13_poll
The digitalRead() function was use in the 02_blink_pulse_led project to poll the state of the BOOT button each time the never-ending loop() function is executed. This new project does the same with all the i/o pins along the edges of the Super Mini. In the process, that most i/o pins work as expected, but not all.
In the simplest scenario a polled i/o pin is connected to one side of a momentary on switch whose other side is connected to either ground or Vcc (3.3 volts). On the i/o pin side, there has to be a pull-up resistor or a pull-down resistor to ensure that the pin is not floating when the switch is not closed. Hopefully, the image on the right makes the situations clear. The external pull-up or pull-down resistor can be replaced by an internal resistor in the SoC which is enabled when the pin mode is set to INPUT_PULLUP or INPUT_PULLDOWN. Polling a i/o pin connected to an active low switch, means checking if the input value of the pin is LOW which will be the case when the switch is closed. Conversely, polling an i/o pin connected to an active high switch, consists of checking if the input value of the pin is HIGH indicating that the switch is closed. It all looks nice and symmetric but it isn't because there are 3 i/o pins that are pulled high with external resistors (4 pins on the Super Mini with the OLED display are pulled high). According to one schematic, pins 2, 8 and 9 are pulled high by three 10 K resistors connected to 3.3 volts. Since these are the strapping pins which determine the boot mode of the SoC, it is not practical to add external pull-down resistors to overpower the 10 K resitors.
Let's look at the sketch as it starts by testing every i/o pin with a very simple active low switch.

All that needs to be done is to connect the alligator clip to the ground pin and to touch each pin in turn to simulate a push button press. As can be seen, the programs starts by putting every i/o pin in INPUT_PULLUP mode making each pin a digital input and activating its internal pull-up resistor. Then the value of each pin is read to ensure that it is HIGH. From there the program reads the input value of each pin and records when it is pulled down for the first time.
When all 13 pins have been tested, the sketch moves on into the next phase. All i/o pins are put in INPUT_PULLDOWN mode. The alligator clip of the simple probe should be connected to the 3.3 volt pin of the Super Mini to simulate an active high push button. Even if the internal pull-down resistor of all the input pins is activated, the three strapping pins remain high as explained before.
Touching pins 2, 8 and 9 with the probe does nothing; the pin was HIGH and it remains HIGH when a simple probe connected to 3.3 volts (HIGH) contacts the pin. Contact with the other pins is observed by the sketch. When those non-strapping 10 pins have been activated, the sketch moves on to another state.
It is not the case with all microcontrollers, but the output latch of the i/o pins in OUTPUT mode can be read as if the pins are inputs. So in this third test, the mode of all the i/o pins is set to OUTPUT and the value of all the pins is set to HIGH. Again the alligator clip of the simple probe is attached to the ground pad of the board and an active low switch is simulated as the probe contacts each pin in turn.
As can be seen, that works at least for momentary push button. I do not know what the consequences would be if an output pin set to HIGH is driven LOW for a long period of time. Indeed, I don't even know if it can be safely done for short periods of time. So far, I have played with this for dozens of tests without apparent negative outcomes but not guarantee is offered here.
Finally when all 13 pins have been tested, the sketch enters a fourth phase, where mode of all the i/o pins is set to OUTPUT and the pins are all set to LOW. Here we need to simulate an active high momentary push button, so the alligator clip of the simple probe is connected to the 3.3 volts pin of the Super Mini.
As can be seen that works just as well as the previous test. Let's assume that the same warning about driving an output pin applies as before.
The sketch cycles through these four tests endlessly.
Project: 14_interrupts
Instead of polling a pin to see if its state has changed, an interrupt service routine (ISR) can be attached to the pin which will be executed whenever the state of the pin changes in a specified way. When the interrupt occurs, the processor suspends its current task, saves its own state, and then executes the ISR. When the ISR is completed, the processor restores its saved state and resumes execution of the suspended task. One has to be careful when writing an ISR. Typically, some things (non reentrant functions, variables and so on) that are not accessible. Also it is best to keep the ISR short to minimize any impact on the suspended task which may be time sensitive. Consequently a common approach is to set a flag in the ISR. The loop() routine checks the flag and, when it is set, it does whatever is needed in response to the action that caused the interrupt. Of course, that flag has to be reset 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.
Project: 15_interrupts2
This project follows up on the concluding comment of the previous project by showing some possible debouncing techniques. It also presents a way of handling multiple buttons. Indeed, an ISR is attached to each i/o pin in the setup(). This is done by defining an array of Button struct, one for each pin.
The first member of the struct is the index into the iopins[] and padlabels[] arrays because the i/o pads will be indexed according to their position along the edges of the development boards as in previous sketches. The second member, actTime, is the button activation time obtained from millis() when the ISR is executed. This value can be set when the ISR is first invoked when the button was pressed or it can be reset each time the ISR is called according to the RESET_TIMER macro in the user configuration at the top of the source code. Presumably, the debounceTime, measured in millseconds, could be adjusted to support buttons of different types and physical properties. In this test, all buttons will have the same DEBOUNCE_TIME set in the user configuration block. As before the pressed boolean is the flag that is monitored in the loop() function to execute the actual code that needs to be performed such as controlling a relay. Finally, there is a counter which will contain the number of times the ISR has been executed since the button was first pressed and the pressed flag was set true. This is an aid in evaluating the bounce problem by recording how often the contact was made and broken. Usually, that counter would not be included in a working sketch.
Here is a typical output of this sketch. Note how the pins along the edge of the board are divided into three contiguous groups according to the interrupt mode. I am not sure if that reveal cleverness or laziness, but it is a simple way of investigating the difference in these modes.
The timestamp at the start of each button press is present if the TIME_STAMP macro is defined in the configuration block. The grounded Dupont wire as ersatz push button switch was used in this experiment. The counter values reveal that this simulated switch is very noisy. Looking at the events at 80.211 and 80.418 milliseconds, we see that there were 53 make (and break) bounces when the wire was pushed against the pad. About 200 milliseconds, 3 bounces occurred when the wire was pulled away from the pad. An oscilloscope image would be nice, but the counter is still eloquent. At the 96.402 and 112.083 time marks, the wire was firmly pushed and held into the crenelation of pad 9 and after a second or two it was pulled away as cleanly as possible from the pad. If anything that made the bounce even worse. The event at 101.4 milliseconds does stand out with it desirable single contact on the falling edge. That was achieved by pushing and releasing the boot button. Again, the physical characteristics of the hardware matter when it comes to debouncing.
Analogue Input
This group contains two projects at this time.
| Project | Purpose |
|---|---|
| 16_adc | Testing the analogue input pins of the ESP32-C3 Super Mini. |
| 17_adcc | Testing continuous analogue input of the ESP32-C3 Super Mini. |
A plotter such as the Arduino IDE Serial plotter is very useful to view an analogue signal. Unfortunately, the PlatformIO IDE extension and its fork the pioarduino IDE extension does not have such a facility. There is a VS Code extension, teleplot by nesnes, which is reputed to work well with Arduino projects in Visual Studio Code. Unfortunately, teleplot is not available in the VSCodium market place, but a VSIX package from a fork of teleplot by Josue Siva de Morais called LasecPlot can be downloaded and that package can then be installed in VSCodium with ease.
I should mention that vsixhub provides a teleport VSIX package claiming that it was obtained from the Visual Studio Marketplace. See the link associated with the Download Latest VSIX File. I have not downloaded that package and cannot say that it works in the VSCodium code editor.
Project: 16_adc
Connecting the wiper of a potentiometer, which is across ground and 3.3 volts, to an analogue pin of the Super Mini would suffice to test the analogue input capabilities of the ESP32-C3. However, that does not translate into something very meaningful on a static page. So as before, each analogue pin will be connected to the analogue output of the SAMD21 DAC (digital to analogue converter, see the sketch in the samd21_dac directory of the 16_adc project). To make things simple to follow the value of the output DAC is linearly ramped up from 0 volts to 3.3 volts and then linearly ramped down back to 0 volts. In other words, the signal on the DAC output is a triangular wave.

The plot is rather surprising. When using the analogReadMilliVolts() function to sample the input triangle signal, there are plateaus at 2870 millivolts instead of reaching a single peak at 3300 mv. The raw data obtained with analogRead() makes it clear that the plateaus correspond to the maximum 4095 raw value that can be obtained with a 12-bit resolution ADC. These results are surprising given how well the XIAO ESP32C5 tracked the triangular wave from the same XIAO SAMD21 running the same dac.ino firmware.

This will need further investigation if I ever want to use the analogue input for accurate measurement.
If the TEST_A5 macro at the top of the user configuration block in the source file is defined, then an attempt will be made to initialize gpio 5 as an analogue input. This will result in an error.
That confirms that only five gpio pins can be used as analogue inputs on the ESP32-C3: A0, A1, A2, A3, A4 or GPIO numbers 0, 1, 2, 3 and 4.
Finally, here is the plot when reading a 150 Hz cosine signal generated with an ESP32 cosine wave generator (see the esp32_dac directory in the 16_adc project).

According to the horizontal time axis, a cycle lasts about 8 milliseconds (43.7 - 43.692) which translates to an input cosine wave with a frequency of 125 Hz. Within that 8 ms period there are 8 readings which means that the sample rate is about 1,000 Hz which is certainly far above the 250 Hz Nyquist rate to avoid aliasing. That is just about avoiding getting the frequency of the input signal wrong. Visually, 8 sample points per cycle do not give a very pleasing sinusoid. For those interested, that 1 Khz sampling rate was achieved by not defining the MIN_MAX and READ_DELAY macros in the configuration block at the start of the sketch.
Project: 17_adcc
This project is just an exploration of the ESP32-C3 analog to digital converter in continuous mode which hopefully would allow for a better capture of the relatively slow 150 Hz sine wave input signal. The Espressif documentation has the following introduction:
In continuous mode, the ADC performs automatic, high-speed sampling on one or more analog input channels, with results efficiently transferred to memory via DMA. This mode is suitable for applications that require periodic or high-frequency data acquisition.
As far as I can make out the current adc continuous mode implementation replaces the former ADC Continuous (DMA) Read mode. I cannot claim that I understand all the complications of the adc continuous read mode. It seems that averaging of the ADC readings is performed as they are coming in and only the average is stored. Averaging the data can be a desirable way of smoothing noisy analogue signals. On the other hand, if the averaging is done over too many data points and hence over too long a period, there is a real possibility of hiding true variations behind the average value. The configuration of the continuous read function allows setting the number of averaged reading to 1 which in effect suspends averaging. So there is flexibility there.
Basically, I took the AnalogReadContinuous.ino example and restricted the set of input ADC pins to a single pin. To try to keep the overhead to a minimum, the ADC is read continuously in the sketch setup() function. While that is going on, the average of the readings are stored in an array. Once the array is filled, its data is sent out to the serial plotter to see if something like a decent cosine wave has been captured. Because the readings are sent out to the plotter offline, the time scale along the horizontal axis has no real meaning. In other words, the frequency of the incoming signal cannot be calculated from the plot.

The data for the above graph from the LasecPlot in VSCodium, comes from running the sketch with SAMPLE_FREQUENCY set to 78,000 Hz and CONVERSIONS set to 3. Since every three observations are averaged, the effective sample frequency is 26,000 Hz. We do get a smoother sine wave. However, as expected the truncation at the top remains.

The above graph is obtained when the SAMPLE_FREQUENCY is set to 26,000 Hz with no averaging because CONVERSIONS equals 1.
While this is just a judgment call, I would argue that ADC conversion in continuous mode does yield a wave form that is closer to a true sine wave because of the higher sampling rate. I leave it up to the reader to judge if averaging every three observations made at three times the frequency is very much better.
Deep Sleep Mode
There are two projects examining the deep sleep mode of the ESP32-C3 based on the method by which the SoC is waken from that mode.
| Project | Purpose |
|---|---|
| 18_deep_sleep_tmr | Wake an ESP32-C3 Super Mini from deep sleep mode with timer |
| 19_deep_sleep_io | Wake an ESP32-C3 Super Mini from deep sleep mode with a high or low signal to an i/o pin |
In deep sleep mode, the CPU, most of the RAM, and most digital peripherals are powered off. The only parts of the SoC that remain powered are the real time clock controller and memory (source). The current consumed by the SoC in deep sleep mode is typically 5 micro amperes which is so much smaller than the approximately 85 milli amperes consumed when a Wi-Fi signal is being received and the very considerable 275 to 225 milli ampere when transmitting a Wi-Fi signal (sources: Table 5-7. Wi-Fi Current Consumption Depending on RF Modes and Table 5-9. Current Consumption in Low-Power Modes pp 56 and 57 of ESP32-C3 Series Datasheet Version 2.3). Unfortunately, I cannot verify these values.
Project: 18_deep_sleep_tmr
Each time an ESP32-C3 programmed with an Arduino sketch wakes up from deep sleep, it starts executing the setup() function just as when the processor is powered up or restarted. Near the end of the setup() function in the sketch, the board is put into deep sleep. Consequently the loop() function in this sketch is never executed. Similarly anything in the setup() function after putting the processor in deep sleep mode with the esp_deep_sleep_start() function is also not executed. Consequently anything that needs to be done when the board is awake must be performed at the start of the setup() procedure.
In this sketch the only thing that is done when the board is awake is to signal that fact. First the LED flashes on and off to indicate the number of times the setup() function has been executed since the last restart of the board. This flashing is slow so that it is easy to follow the count. At the same time, the count is also printed out to the serial port as well as the cause that awoke the board. After a short delay, the LED is quickly flashed on and off five times to signify that the board is about to return into deep sleep mode. A message indicating how long the board will be in that mode is also printed out to the serial monitor.
The counter used to keep track of the number of times that the setup() function has been executed since the last restart of the board has to be stored in memory that belongs to the real-time clock peripheral.
Non-volatile in this case means that the content of the RTC memory is preserved when the CPU is in deep sleep but not when the CPU is restarted physically or programmatically. Define the RESTART_COUNT macro to verify that assertion.
Running the sketch within the Arduino IDE works quite well and it is easy to follow the progress of the sketch in the serial monitor if the USE_SERIAL macro is defined in the user configuration at the beginning of the sketch.
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-C3 woke up for the 3rd time at 20:38:10 after going into deep sleep at 20:37:52. That was a 17.493 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. In other words, the timer looks to be accurate enough for IoT applications.
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 one has to mostly rely on the LED flashes to understand what is going on. 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/ttyACM0 command. continuously.
When the Super Mini is in deep sleep, its serial peripheral is shut down. Linux therefore closes the /dev/ttyACM0 device which means that it cannot be listed.
When the Super Mini 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.
I believe that this project would work with most ESP32 based board. It has been tested with success using a WEMOS Lolin32 Lite dev board based on an ESP32-WROOM-32 module with a dual-core Xtensa microcontroller.
Project: 19_deep_sleep_io
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 a 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 to be woken by setting i/o pin 2 LOW.
According to the ESP32-C3 Technical Reference Manual (Verson 1.3 2025-05-08 section 5.10.1 Power Supplies of GPIO Pins, p.167) only the pins (GPIO0 ~ GPIO5) in VDD3P3_RTC domain can be used to wake up the chip from Deep-sleep mode. 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 the deep sleep mode, the sketch sends to the serial monitor messages indicating which pin is the designated wake up pin and if a LOW or HIGH signal must be applied to that pin to awake the board.
As before this works well in the Arduino IDE.
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 remembering the sequence of the pins to activate and the signal that needs to be applied. It was too much to juggle.
Contrary to the previous deep sleep project, I believe that this project will only work with Espressif chips based on the RISC-V architecture. It has been tested with three C-series SoC and it probably works with H-series devices. P-series SoC also use a RISC-V microcontroller but I have no experience with suche devices.
Serial Communication
The group of sketches about the UART, SPI and I2C serial peripherals contains two projects at this point in time.
| Project | Purpose |
|---|---|
| 20_uart | Test the ESP32-C3 UART0 peripheral with a loop back test |
| 21_uart2 | Test the ESP32-C3 UART0 and UART1 peripherals with loop back tests |
| 22_spi | Test the ESP32-C3 SPI peripheral with a loop back test |
| 23_spi2 | ESP32-C3 SPI loop back tests with 12 different pin assignments |
| 24_spi_master | Test of SPI communication; SPI master device firmware |
| 25_spi_slave | Test of SPI communication; SPI slave device firmware |
| 26_i2c_ds3231 | Test the ESP32-C3 I2C peripheral with an RTC/EEPROM module |
| 27_i2c_oled | Test the I2C display of an ESP32-C3 Super Mini with onboard 0.42" OLED |
The ESP32-C3 has two UART interfaces, i.e. UART0 and UART1, which support IrDA and asynchronous communication (RS232 and RS485) at a speed of up to 5 Mbps (source: ESP32-C3 Datasheet Version 2.3 p. 46). These controllers are accessed through two HardwareSerial class objects called Serial0 and Serial1. The first sketch is a simple loop back test of the Serial0 port while the second sketch tests both UARTs with either two independent loop back circuits or two cross-wired loop backs. There is a hardware abstraction layer loopback test function uart_internal_loopback in .../esp32/esp32-hal-uart.c, but it is not used here.
The ESP32-C3 has three SPI interfaces but two of these are reserved for the system: the DMA controller uses the SPI0 interface to access flash memory, the CPU uses the SPI1 interface to also access flash memory. That leaves only the third SPI2 interface for general use. That is a characteristic shared across all SoCs in the ESP32-C family. The ESP32 family SoCs have 4 general use SPI ports, the newer ESP32-S and ESP32-P family have two SPI controllers available for general use. At least that is what is defined in the SPI hardware abstraction layer of the ESP32 Arduino core (Search for SPI_COUNT esp32-hal-spi.c).
The ESP32-C3 has a single I²C bus interface that can be used in master or slave mode. Only master mode is tested in this review of the Super Mini. Since this is a bus interface, there is no possibility of testing this serial device in a loop back mode. Instead the test is performed by communicating with a DS32R31 real-time clock module with an onboard AT24C32C EEPROM which are both I²C devices.
Project: 20_uart
By default the Serial0 instance of the HardwareSerial class is constructed with some default values. We can grasp what they by looking at the initializing begin function of the class.
Quoting the comment above the declaration found in .../esp32/HardwareSerial.h, if [rxPin or txPin] negative, it won't be set/changed and will be kept as is. Of course, that does not say what those values are. However, we already know that TX (= 21) and RX (= 20) are defined in the pins_arduino.h variant file. So, except for baud,all the other parameters have default values that do not need to be specified when initializing the serial port.
Consequently all the ways of invoking the begin function shown as comments are equivalent to the last simple Serial0.begin(BAUD) call. After connecting pins 20 and 21, here is the serial output of the sketch.
The 5 Mbaud rate is the maximum. A different value can be set in the user configuration block at the start of the sketch. The values listed are typical, but there are many other possible values. The baud cannot be arbitrary since it is generated by dividing a given clock with a set frequency with an integer, but the function does its best. For example, Serial0.begin(212345); results in an actual baud set to 212412.
If a very low UART baud is chosen such as 9600 bits per second, then the result will look like this.
This is because the while loop
executes so quickly that the "e" character is still being transmitted when the Serial0.available() test is performed after printing the "m" character. Consequently the test fails and the loop() function is executed again before "e" is finally completely received. A single millisecond delay at the bottom of the while loop is enough time to receive the next character. This can be enabled with the RX_DELAY macro in the configuration block.
Project: 21_uart2
On the face of it, there's not much to learn with this project. A loop back test runs on the second UART as another loop back test runs on the first UART.
Nevertheless, there are two things worth noting. First, the baud of the two UARTs can be set independently. Second, the Rx and Tx pin assignment of the second UART may seem a bit unusual. The ESP32-C3 is flexible in this respect, the Tx and Rx signals of either UART can be routed to any gpio pin. For this sketch, the default pins 20 and 21 are attached to the Serial0 port and the Serial1 Tx and Rx signals are connected to GPIO 10 and 0 respectively. However, any two distinct i/o pins on the edge connector of the board other than 20 and 21 could be used. So that is any two distinct pins in the range 0 to 10. If the SPI and I2C peripheral are needed, then the choice of pins to connect to the Tx and Rx signals of the Serial1 port is restricted to the set {0, 1, 2, 3, 10}. To change from 0 and 10 to another couple of pins, use the SER1_TX and SER1_RX macros in the configuration block at the beginning of the sketch.
To cross connect the two UART, and expect the sketch to work, their baud must be the same.
If the core debug level is set to 5 or verbose either in the Tools menu of the Arduino IDE or in a build flag in the platformio.ini configuration file if the program is run in the pioarduino IDE extension, then information about attached GPIO pins is printed as the setup() function is exited.
There we can see the two UART Tx and Rx assignments. In between two i/o pins are attached to the USB D- and USB D+ signal of the built-in USB CDC peripheral. That is meaningful, because those are the pins that are assigned to UART1 if the port is initialized with the default invocation Serial1.begin(BAUD).
Displaying that assignment in the serial monitor does require a bit of work because attaching pins 18 and 19 to Serial1 does mean that Serial is no longer connected to IDE serial monitor via the USB-C connector. Basically, Serial1 has to be stopped which releases the Tx and Rx pins and then Serial has to be initialized to reattach them the USB-CDC peripheral. To see that output, define the PRINT_DEFAULT_SERIAL1_PINS macro in the configuration block at the start of the sketch.
Project: 22_spi
This is a simple project that tests the only serial peripheral interface (SPI) available for the user with a loop back circuit much as was done with the UART. The SPI controller is used as a master device which is the default mode. Every second, it transmits a short 32-byte array which contains the string "Message ##" where ## is the count of messages transmitted since the last boot. This message is sent out, bit by bit, on the MOSI (master out, slave in) signal. The master also reads the incoming serial transmission on the MISO (master in, slave out) line. Typically, the slave would be simultaneously reading the serial data on the MOSI line and transmitting a reply on the MISO line. All this is synchronous based on the SCK signal from the master device. So to perform a loop back test, the MOSI and MISO pins of the Super Mini are connected together.
To get an idea of how quickly this is done, turn on timestamps in the Arduino IDE and it will be clear that the transmission of the 32-byte array is done within a millisecond.

Prove that this is truly a test by disconnecting the MISO and MOSI pins while the firmware is running. You could connect the MISO signal to 3.3 volts and the same result would be obtained.
Then connect the MISO pin (gpio 5) to ground.
That is a simple test which does not verify the functioning of the clock and select pins. That will require using a slave device. Before doing that, let’s investigate the ability to attach other gpio pins to the SPI controller.
Project: 23_spi2
Since the Super Mini board makes 13 gpio pins available to the user, there are 17,160 (13x12x11x10) ways to assign 4 gpio pins to the 4 SPI signals if any gpio pin can be used as any SPI signal. There is no way that it would be possible to test so many possibilities. The second SPI loopback project will test 12 SPI pin configurations. It would not be too hard to expand the program to 156 configurations but the time to run such a test would not be very practical.
This test is similar to the 10_blink_leds and 11_pulse_leds, where the Dupont wire with alligator clip is used to connect to the MOSI pin all the other pins in turn as they are attached to the MISO signal.
While this is a very incomplete test, it does seem to indicate that any four gpio pins could be used to implement the serial peripheral interface.
Project: 24_spi_master
Instead of performing a loop back test, this project and the following one involve two Super Mini boards communicating using their SPI controllers. Remember that in SPI communication, one device must be a master which communicates with one or more slaves. The sketch of the master device does not introduce much that is new with respect done in the previous two sketches. As before, when the board reboots, it displays some information to the serial monitor which includes instructions on how to connect the slave device.
As before, the sketch sends 32 bytes of data per transaction every second or so. In this version, we will look more closely at the full 32-byte record that is written and read during each transaction. To do that, the input buffer is filled with the letter 'I' and the output buffer will contain the 'Message ##' (with terminating '\0') padded with the letter 'O' before the transmission occurs. Then the sketch transmits the content of the 32-byte output buffer alternatively using the transferBytes() or transfer() functions. Here is what happens when the sketch runs without a connection to another device.
The incoming data will be
0xff instead of 0x00 (i.e. '\0').
To understand what is happening, remember that a master SPI controller is always reading a byte of data on the MISO input signal whenever it sends out a byte of data on the MOSI output signal. With the transferBytes(outBuffer, inBuffer, 32) function, the incoming data is written to the input buffer which is separate from the output buffer which remains intact. Only the output buffer is in play when using the transfer(outBuffer, 32) function which means that each byte of the output buffer when transmitted is replaced with the byte of data just read and nothing happens to the input buffer.
Because I clumsily incorrectly connected the slave device and got bizarre results, I checked that the master device on its own with a cheap logic analyzer. I found that everything was working as expected; here is the start of one transmission as captured with the analyzer.

After correcting the wiring, here is the spi_master serial output when connected to a second Super Mini running the spi_slave sketch described in the next section.
The replay message of the slave is similar but it is padded with the lower case 'o' to confirm that all 32 bytes of the reply is transmitted by the slave and read by the master. There is no link between the master message counter and the slave reply counter. In the above example, the master had just been restarted while the slave had been running for more than an hour. Again the reply is either copied to the independent input buffer or copied over the output buffer depending on which transfer function is used.
Project: 25_spi_slave
Two SPI devices connected together cannot be both masters. This is the project that sets up a ESP32-C3 Super Mini as a SPI slave that can communicate with the SPI master from the previous project. The ESP32 Arduino core does not contain a SPI slave library. Consequently, the ESP32SPISlave by Hideaki Tai, which appears to be the de facto library for ESP32 devices is used to implement the slave device.
For the most part this sketch is almost the same as the master sketch. While the SPI.transfer() function in the master sketch sends out a 32-byte record and reads the incoming data stream from the slave in each iteration of the loop function, the slave.transfer() reads a record up to 32 bytes in length in each iteration while transmitting its output buffer. If everything went well, it will read exactly 32 bytes and simultaneously transmit the content of its 32 bytes output buffer back to the master. Similarly to what is done in the master sketch, the input and output buffer of the slave device are filled with the letters 'i' and 'o' respectively before the slave output message "Reply ##" is copied to the start of the output buffer.
The serial output of the slave sketch is similar to the output of the master sketch.
Setting up the slave SPI device is slightly different from using the predefined SPI peripheral.
The ESP32SPSlave class object slave can be initialized with a simple slave.begin() as long as the SPI bus is correctly defined (FSPI = 0) and the SCK, MOSI, MISO, and SS pins as defined in the pins_arduino.h are used. Otherwise other pins can be attached to the SPI controller using non default values.
Project: 26_i2c_ds3231
I2C is a serial communication protocol that runs over a 2-wire (data and clock) bus. The data signal is used to read or write on the bus, but it cannot do both simultaneously. Consequently there is no possibility of performing a loop back test with the I2C controller. Instead, I tested the latter by connecting a ubiquitous I2C DS3231 real time clock with AT24C32 EEPROM module to the Super Mini.

Many Arduino libraries support the DS3231 RTC chip. I did not investigate all of them by any means. I chose to use the DS3231_Simple library by James Sleeman because it lives up to its name and it also supports the EEPROM chip. The support for the latter, is limited to a logging facility, but that is sufficient to test the RTC module and hence the I2C controller.
The SDA and SCL pads of the RTC/EEPROM module were connected to pins 8 and 9 respectively of a ESP32-C3 Super Mini. Then the user configuration of the start of the sketch was set as shown next.
After compiling and uploading the firmware to the Super Mini, the latter printed out the following on the IDE serial monitor.
The sketch will work exactly the same on a Super Mini with an OLED display when pins 8 and 9 are connected to the RTC/EEPROM module and the nologo ESP32C3 super mini board definition is used. Not surprisingly the I2C OLED display will not be found when scanning for I2C devices at the beginning. Connect the RTC/EEPROM module to pins 5 and 6 (SDA and SCL respectively) of the development board and change the user configuration as shown below.
Once the firmware is compiled and running on the Super Mini with 0.42" OLED, the serial output will be just about the same as before except that this time the I2C OLED, which as address 0x3C, is found.
Testing the OLED display will be covered in the next section.
Project: 27_i2c_oled
The sketch and a modified 72x40oled_lib library are available in the repository.
-- Details about the library and the sketch are forthcoming --
Wi-Fi
This group of Wi-Fi related sketches consists of three projects.
| Project | Purpose |
|---|---|
| 03_scan_wifi | Prints a list of available wifi networks every five seconds |
| 04_wifi_connect | Wi-Fi station connect example |
| 05_wifi_tx_power | Time to connect to the Wi-Fi network with all TX power levels |
Project: 03_scan_wifi
Comments abound on forums about the Wi-Fi problems with some Super Mini ESP32-C3 boards. I suspected that it could partly be caused by the onboard ceramic antenna. Indeed, Espressif rates ceramic antennas as used on the Super Mini as the only "low gain" type. The FPC antenna used on the XIAO ESP32C3 is said to have "medium gain". The results when running this are consistent with that evaluation. Only two networks were found with a SuperMini ESP32-C3.
The same code on a XIAO ESP32C3 found four networks.
To be fair, a couple of the first batch Super Mini boards fared better and the Tenstar boards and the board with the OLED display consistently had results comparable to the XIAO. However, it should be pointed out that 1 and 2 are our home networks and that the test was run with the microcontrollers less than a metre away from the Wi-Fi router. I am assuming that the other two networks are from our nearest neighbour which may be about 80 metres away.
Project: 04_wifi_connect
Relatively bad Wi-Fi scan results is not that important; what matters for a Wi-Fi device is its ability to establish and maintain a stable connection. Egregiously, one of the Super Mini could not connect to the local Wi-Fi network within two minutes. A XIAO ESP32C3 usually connects in about half a second. Luckily, n Arduino Forum message by al1fch which said that "I solved my WiFi connection problems by reducing the transmission power with a [WiFi.setTxPower] command to put before WiFI.begin()" yielded something of a solution.
If a Super Mini is having problems connecting to a Wi-Fi network, then adding the #define TX_POWER WIFI_POWER_11dBm directive in the user configuration section at the top of the source code may result in the success shown above. The initial connection attempt #0 is always done with the default Wi-Fi transmit power level. Only in subsequent attempts will the transmit power be set to the TX_POWER level. It was good news is that it was possible to get the Super Mini connected to the Wi-Fi network, but it could be that another WIFI_POWER_xxx value would be needed or would be better. The list of possible values can be found in the WiFiGeneric.h header file.
There is another directive in the user configuration section: #define TEST_DISCONNECT which by default is not enabled. If it is enabled, then the WiFi.disconnect(true, true) function is invoked before using the WiFi.begin() to connect to the network from the 5th attempt. When WiFi.begin() is called without an SSID and password, the connection is attempted using the last valid SSID and password used. That should fail because WiFi.disconnect(true, true) should wipe all Wi-Fi credentials from the SoC non-volatile memory. All tests so far have shown that is what happens. That test was included because that was a thing with the ESP8266 at one point as I recall.
Project: 05_wifi_tx_power
Picking a random transmit power is not the best way to get reliable Wi-Fi connections. The wifi_tx_power project tries to ascertain the best setting for reliable results. All the action in the sketch is in the setup() function. After identifying the board using its Wi-Fi station MAC address, the program reads the current default TX power level. Then the TX power level is set to each possible wifi_power_t value as defined in the latest version of WiFiGeneric.h. As can be seen below values over 20 dBm cannot be set and the last enum WIFI_POWER_MINUS_1dBm is an invalid argument for the setTxPower() function for ESP32-C3 SoCs. Then the TX power is set to each valid wifi_power_t value and the time to connect to the specified Wi-Fi network, the time required to obtain an IP address from the network DHCP server and the received signal strength indication (RSSI) are reported.
After all these measurements are made, a summary is presented on the serial monitor.
It is important to understand that these results are quite variable. This is what was measured with the same board a few hours later.
The set column is the actual value of the corresponding wifi_power_t enumerated type. The dBm is the corresponding power level in decibels per milliwatt. The ratio between these values is 4 to 1. The following three columns are the measurements.
Formally rssi is a unitless relative mesure of the strength of the received signal from the access point in 802.11 implementations. However, there appears to be a consensus that for Espressif SoC the RSSI is a measure of power whose units are decibels per milliwatt (dBM). See for example the discussion on the sort_method field under the Station Basic Configuration header. The closer to 0 the rssi, the stronger the signal.
| RSSI Range | Interpretation |
|---|---|
| -30 dBm to 0 dBm | Very strong signal, guaranteeing an excellent connection |
| -50 dBm to -30 dBm | Excellent signal, suitable for most demanding uses (streaming, etc.) |
| -65 dBm to -50 dBm | Acceptable signal for a stable and reliable connection |
| -90 dBm to -65 dBm | Very weak signal, not allowing for a stable connection |
| -127 dBm to -90 dBm | Extremely weak signal, unusable/td> |
The time column is the milliseconds from theWiFI.begin() instruction to the WiFi.STAT.connected() function returning true. The last column is the time from the WiFi.begin() instruction to WiFi.connected() returning true. Labelling that column dhcp is somewhat of a misnommer, because the Wi-Fi 'Got IP' Phase which is the time of interaction with the network DHCP server after the Wi-Fi connection is established (the Wi-Fi Connect Phase) is the difference between the two columns.
Raw results obtained from running the est with nine boards are available as a LibreOffice Calc file or as CSV file. The next table is a summary of the results.
| Super Mini ESP32C3 | XIAO ESP32- | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| No name | Tenstar | OLED | |||||||
| 5B:00 | 46:88 | 2D:20 | E5:C0 | EF:44 | F5:40 | C6 | C3 | C5 | |
| WIFI_POWER_20dBm | 1259 | 1251 | - | 49 | 86 | 51 | 49 | 1298 | 91 |
| WIFI_POWER_19_5dBm | 1256 | 1250 | 1439 | 48 | 45 | 53 | 49 | 1251 | 50 |
| WIFI_POWER_19dBm | 1253 | 1250 | 1260 | 45 | 44 | 48 | 92 | 1255 | 47 |
| WIFI_POWER_18_5dBm | 1251 | 1254 | 1252 | 144 | 49 | 71 | 52 | 1256 | 48 |
| WIFI_POWER_17dBm | 1250 | 1296 | 1251 | 47 | 45 | 45 | 48 | 1253 | 50 |
| WIFI_POWER_15dBm | 1250 | 1260 | 1254 | 46 | 48 | 46 | 48 | 1254 | 47 |
| WIFI_POWER_13dBm | 1250 | 1253 | 1252 | 45 | 46 | 49 | 51 | 1251 | 47 |
| WIFI_POWER_11dBm | 1323 | 1253 | 1253 | 46 | 46 | 57 | 53 | 1252 | 50 |
| WIFI_POWER_8_5dBm | 1294 | 1264 | 1301 | 50 | 85 | 53 | 128 | 1255 | 48 |
| WIFI_POWER_7dBm | 1300 | 1267 | 1312 | 48 | 87 | - | - | 1253 | 47 |
| WIFI_POWER_5dBm | 1265 | - | 1258 | 1157 | 52 | - | - | 1251 | 49 |
| WIFI_POWER_2dBm | - | - | 1360 | - | 59 | - | - | 1254 | 48 |
| RSSI – Average | -68.6 | -75.0 | -70.1 | -64.9 | -64.3 | -84.7 | -71.2 | -56.7 | -65.5 |
| RSSI – Standard deviation | 0.67 | 2.11 | 1.97 | 1.70 | 1.92 | 3.32 | 3.23 | 4.01 | 2.68 |
Be careful when comparing these results with the times from the previous project and the previous post about this project because the times given were for the time to connect and to acquire the IP address and only the connection time is given in the summary table above.
Where there's a dash (-), the board had not been able to connect in 2 minutes. The good news is that all boards managed to make a connection with a Wi-Fi access point at most TX power levels. The bad news it that the optimal transmission power setting is different from board to board and not that easily determined for any one board given the variability of the connect times.
As can be seen the Tenstar Super Minis do considerably better than the no name Super Mini purchased more than a year ago. The connection times for the OLED display version are quite good until the TX power level drops below 8.5 dBm. Indeed that performance is comparable with the XIAO ESP32C6 when using the latter's onboard ceramic antenna. The XIAO ESP32C3 and XIAO ESP32C5 both have an FPC external antenna which would lead one to believe that they should perform better. Putting aside the XIAO ESP32C3, the connection times of the Tenstar, the OLED and the XIAOs when they manage to connect to the access point are rather similar. This may be an indication that the bottleneck is the access point which is an old Rosewill single band Wi-Fi router running an old version of OpenWRT. I can't make sense of the XIAO ESP32C3 results. When the TX power is never changed from its default value (20 dBm), it systematically has very fast connection times near 43 ms.
Bluetooth LE
The ESP32-C3 supports Bluetooth Low Energie (LE) and is certified for Bluetooth LE 5.4. As stated before I do not have any real experience with this technology. So for the Super Mini, I only adapted two similar projects first developed for the XIAO ESP32-C3.
| Project | Purpose |
|---|---|
| 07_ble_led | Toggle an external LED on and off with an IOS or Android app using the ArduinoBLE library |
| 08_ble_led2 | Toggle an external LED on and off with an IOS or Android app using the BLE library |
As indicated in the table, the important difference between these projects is the Bluetooth library. In the first case a third-party library named ArduinoBLE from the arduino-libraries is used. In the other version, the whereas in the second project the BLE for ESP32 Arduino Core which as its name suggests is an integral part of the arduino-esp32 core for ESP32 SoCs.
Again my lack of experience does not allow me to pass an informed judgment about the relative merits of these libraries. However it should be mentioned that the ArduinoBLE library claims to support all board architectures with Bluetooth LE capabilities including Arduino MKR WiFi 1010, Arduino UNO WiFi Rev2, Arduino Nano 33 IoT, Arduino Nano 33 BLE, Nicla Sense ME and UNO R4 WiFi and, one assumes, all BLE enabled ESP32 boards. When it comes to the BLE, the situation is more complex and evolving. Currently, the default Bluetooth stack implementation is Bluedroid which supports ESP32 devices. Bluedroid supports Bluetooth Classic and LE. However, development of Bluedroid has stopped and, in time, it will be replaced by NimBLE which supports Bluetooth Light only for ESP32 devices and other SoCs. If that's not complicated enough, let's add that parts of the esp32 NimBLE implementation is based on the NimBLE-Arduino library from h2zero.
Project: 07_ble_led
When it comes down to using the ArduinoBLE library, the situation is murky at best. Currently (2026-02-26) the 07_ble_led project is not working. When using an ESP32-C3 based board it reboots after a core panic which occurs when the BLE.begin() function is invoked. More details are found at the top of the ble_led/main.cpp file. When it was possible to compile the project with older versions of the library and of the ESP32 arduino core, there were problems with conflicting name spaces especially when using the Arduino IDE. They could be resolved by arcane procedures such as deleting caches and moving the BLE library out of the source tree.
As a consequence of the problems presented above, I will not be supporting the 07_ble_led project for the time being.
This project will be revisited if I notice that the situation improves.
Project: 08_ble_led2
As for project 08_ble_led2, it can be compiled in the Arduino IDE and in PlatformIO. That project showed that the performance of the original Super Mini ESP32-C3 boards was terrible. Sometimes the connection between the microcontroller and the Android tablet took a considerable. At other times, the tablet had to be very close to, practically touching, the microcontroller for the connection to be made. There were times when a connection could not be established at all, even though LightBlue found the Super Mini when scanning for devices. The XIAO-ESP32C3, on the other hand, was reliable showing that the problem cannot be attributed to the ESP32-C3. Not surprisingly given the results with the Wi-Fi radio, the Tenstar Super Mini did considerably better than the no name boards as the composite nRF Connect scan graph illustrates.

The capability to set setting the BLE power level did not result in obvious improvements in Bluetooth connectivity in the original version. I now realize that may have been caused by a problem in the BLE implementation or more probably an operator problem. I expect to be investigating this further in the not too distant future. Until then, the program does function. Here is the output to the serial monitor as it starts.

The screen capture above shows the nRF Connect log as the user LED of the Tenstar Super Mini was turned on and off by sending the (Text UTF8) values "on" and "off" to the Light Output characteristic of the Automation IO service. Click on the up arrow to the right of Light Output to get to the send dialogue. It's not possible to show the LED changing state in a static page, but here is the monitor output as the BLE data was transmitted to the Super Mini board.
If one does not have access to nRF Connect or LightBlue 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.
If gatttool will not connect, remember that only one client can be connected to the Super Mini BLE server at a time.
Upcoming
Once the 27_i2c_oled details are filled in this version of this very long post will be completed. There are may be another post specifically on the ESP32C3 Mini with 0.42" OLED development board.
First Look at the Super Mini ESP32-C3 (original post)
