I recently stumbled upon very small boards equipped with the ESP32-C3 chip, also called the Super Mini chip.
It's a small SBC with 4MB of flash. They were also super cheap on AliExpress (± €9 for 5 pieces!).
Note: if you buy them online, check if it's really the version with the 4MB of flash on board! There are versions that do not have flash on board too.
You can check this by verifying the number on the chip. There should be a number 4 somewhere at the end of the chip number (4MB of flash) or 8 (8MB of flash).
I'm using the Espressif ESP-IDF framework to create, compile, flash and run small applications on this device.
Below, you will find different chapters talking about a specific property of the chip:
The board: what's on it
The pins: definition
The product: details
The Super Mini: what's available
Mostly a picture, since a picture tells a thousand words...
Below are the product details:
USB-C connector
WiFi
Reset and bootloader buttons
Bluetooth
Below are two images, each explaining about the pins available on the ESP32-C3 development board, but from a different view...
Below is a picture showing what is available on the board: connector, buttons, chip, WiFi antenna,...
Below is an overview of the complete development board, including the accompanying headers.
The board can be powered in 3 ways:
Using the USB-C connector
Connecting +5V to the +5V pin of the board
Connecting +3V3 to the +3V3 pin of the board
So, if your board contains the correct software, you can use a +5V power supply to power the board. No need for a USB-C connector.
When the board is powered with +5V you also have +3V3 at the 3V3 pin.
Note: you CAN NOT have both USB-C as well as +5V connected at the same time!
This will send your board to the happy hunting grounds...
The ESP32-C3 has two UART channels: one for serial communication and one that can be used for anything else.
Serial communication:
Uses UART0
Uses pins 20 (U0RXD) and 21 (U0TXD)
Other serial communication:
Uses UART1
Should not use pins 20 and 21
Can use the following pins for communication: 4 => TX, 5 => RX.
See https://docs.espressif.com/projects/esp-idf/en/v6.0.1/esp32c3/api-reference/peripherals/uart.html
On of the possible applications that can be used to see serial communication is Putty. It has, next to SSH, the possibility to connect to a serial port.
However, if for one reason or another, the serial communication is broken (e.g. you press the RST button on the ESP32-C3 SuperMini development board) then the connection is lost and is not auto-restored. You have to restart the session yourself.
That's a pity because sometimes you want to see the very inital logs from an application. By the time you have restarted the session, those logs are gone.
I found a better way to "auto-reconnect" a serial communication after the session is broken: make a serial reader yourself that checks for broken communication and retries to reconnect, say, every second. This, until the connection is up and running again.
I know there are very sophysticated applications out there, but some of them are costing an arm and a leg. Not what I want. I prefer to build my own, very simple applications, if possible.
Here's the Python code for this tool:
import serial
import time
import sys
# Configuration
port = 'COM3' # Replace with your COM port (e.g., 'COM3', '/dev/ttyUSB0')
baudrate = 115200 # Adjust to match your device's baud rate
timeout = 1 # Timeout for reading data (in seconds)
def read_from_serial():
while True:
try:
# Attempt to open the serial port
ser = serial.Serial(port, baudrate, timeout=timeout)
print(f"✅ Connected to {port} at {baudrate} baud.")
while True:
try:
# Read a line from the serial port (adjust based on your device's protocol)
line = ser.readline().decode('utf-8', errors='replace').strip()
if line: # Only print if data is received
print(f"📡 Received: {line}")
except serial.SerialException:
print(f"⚠️ Serial port {port} disconnected. Reconnecting...")
break
except serial.SerialException:
print(f"❌ Port {port} not available. Retrying in 5 seconds...")
time.sleep(1)
if __name__ == "__main__":
print(f"🔄 Starting auto-reconnect for {port}...")
read_from_serial()
As you can see, not long, quite simple and it does what it has to do (at least, for me): showing the UART output and try to reconnect if it's detecting the serial communication is broken.
A few preconditions:
Install pyserial: pip install pyserial: this will install both serial as well as serial.tools
Note: do NOT install serial only as this is not installing serial.tools (see this thread)
Pass the serial port: COM3 in my case (YMMV)
Pass the baud rate: 115200 in my case (YMMV)
Pass a timeout: the recheck time the moment a broken serial communication is detected
That's all there is. Nothing more, nothing less. For the rest, the SW speaks for itself:
Import the necessary modules
Create an endless loop
Open the serial port with the parameters defined above (port, baudrate and timeout)
Read and decode the line from the serial port
Print the decoded line
Provide exception traps in case the serial communication is broken
Wait a bit and retry the connection in case a broken serial communication is detected
Since I try to reconnect within 1 second, chances are that you see the serial communication almost from the very beginning, if the device is alive and kicking again.
By the way: if you want to see those fancy icons, don't use a basic DOS command prompt. This one does not have the virtual terminal sequences code activated.
Use Windows Terminal, since that one has the virtual terminal sequences activated by default.
Nowadays, it's possible to create executables from a Python script. In the days of doom of Python, that was only a utopic wish.
First, you have to install the necessary Python modules. See this section on how to do that.
Once the modules are in place, go to the location where the Python script is located. In my example, it's quite simple since I only have one Python file: ReadComPort.py.
To create an executable from that file, run the following command:
pyinstaller --onefile ReadComPort.py
A series of procesing will start and the pyinstaller application will create files and subdirectories.
If the application tree is in place (see next section on how to get tree in Git Bash), open Git Bash and type tree -L 2 to restrict the treeview to 2 levels only. This is how I achieved the below tree view.
Geert@Win10 MINGW64 /e/AppData/Espressif/esp32_c3
$ tree -L 2
.
|-- ReadComPort.py
|-- ReadComPort.spec
|-- build
| `-- ReadComPort
|-- dist
| `-- ReadComPort.exe
`-- projects
|-- !nfo.txt
|-- FirstProject
|-- SoftAP
|-- blink
|-- blink_new
|-- dsmr_new
|-- gpio_via_web
|-- hello_world
|-- hello_world_new
|-- lvgl_web_gui
`-- mqtt_motion_sensor
14 directories, 4 files
I added the level restriction parameter -L 2 to the tree command since if you ommit this, you will get an extremely long list of directories and files, since a lot of stuff is generated.
As you can see in the tree view, one of the directories created, is dist. The pyinstaller is putting the final executable in that directory.
Running it is, well... just simply running the executable!
If you want to have the resulting executable at another location, you must use the --distpath option (run pyinstaller --help to see the zillion option possibilities):
pyinstaller --onefile ReadComPort.py --distpath .
This will put the resulting executable at the current location.
To be able to use the tree command with level restriction I had to:
Use Git Bash, since the Windows tree command cannot limit its output to a certain level
Add tree to the Git Bash environment, since Git Bash does not natively has the tree command on board.
To get the Git Bash tree, go to this location and download the binary file. You will be rerouted to SourceForge (unfortunately, since I don't like SourceForge because of historical reasons...) and the zip file will be downloaded after a few seconds.
Once the zip file is present, open it and get tree from the bin directory. Move it to the /usr/bin directory where you installed Git Bash (usually a subdirectory of the Git repository path).
If the application tree is in place, open Git Bash, go to the location where you want to see a tree view and type tree -L 2 to restrict the treeview to 2 levels only. This is how I achieved the tree view in the above section.
If you only want to see the tree view of one specific subdirectory, pass it as a parameter to the tree command.
Example: in the components subdirectory of the ESP-IDF installation I want to see the tree view of the directory esp_driver_uart. Not all the others.
To achieve this, go to the subdirectory that contains esp_driver_uart and then run the command
tree esp_driver_uart -L 3
As you can see, you can combine options...
The normal output view of the tree command is something like this:
$ tree -L 2
.
|-- CRC
| `-- DsmrCrc16.cs
|-- Logging
| `-- Logger.cs
|-- Models
|-- P1Simulator.csproj
|-- Program.cs
|-- Serial
| |-- ComPortDetectorHybrid.cs
| `-- SerialSender.cs
|-- Simulation
| |-- SimulationMode.cs
| `-- SimulationProfile.cs
|-- Telegrams
| |-- TelegramGenerator.cs
| `-- Templates
|-- bin
| |-- Debug
| `-- Release
`-- obj
|-- Debug
|-- P1Simulator.csproj.nuget.dgspec.json
|-- P1Simulator.csproj.nuget.g.props
|-- P1Simulator.csproj.nuget.g.targets
|-- Release
|-- project.assets.json
`-- project.nuget.cache
As you can see, backticks are used to indicate the last entry in a view.
However, backticks have special meanings in, for instance, markdown syntax. So, if you would put the above treeview literally inside a markdown document, you will see very strange effects.
To avoid this, you can force tree to use ASCII characters instead of backticks. Use the option -A to achieve this.
The above treeview with the option -A results in this:
$ tree -A -L 2
.
├── CRC
│ └── DsmrCrc16.cs
├── Logging
│ └── Logger.cs
├── Models
├── P1Simulator.csproj
├── Program.cs
├── Serial
│ ├── ComPortDetectorHybrid.cs
│ └── SerialSender.cs
├── Simulation
│ ├── SimulationMode.cs
│ └── SimulationProfile.cs
├── Telegrams
│ ├── TelegramGenerator.cs
│ └── Templates
├── bin
│ ├── Debug
│ └── Release
└── obj
├── Debug
├── P1Simulator.csproj.nuget.dgspec.json
├── P1Simulator.csproj.nuget.g.props
├── P1Simulator.csproj.nuget.g.targets
├── Release
├── project.assets.json
└── project.nuget.cache
Not only is this resolving the backtick issue, to me it's also a more nice representation. Anyway, that's my perception of course...
We've seen how to create an executable for the serial monitor, based on the absolute minimal settings.
However, you can do much more with the pyinstaller application. Among a lot of other things, we can give our application a name, irrespective of the Python file passed to the pyinstaller. And we can also give an icon to the application.
To give a name, that's the easiest part: just pass the --name parameter and give a name for the application.
Example: --name SerialMonitor
For the icon, however, you must have a .ico image available.
For my serial monitor application, I have a couple of nice icons available in PNG format.
On the website https://convertico.com you can pass one or more PNG files and it will give you back a zip file with converted PNG to ICO files. This, all for free!
Below, you can find the PNG files I have.
I've put those icons in a directory called icons and when running the pyinstaller I then pass the following options:
pyinstaller --onefile --name SerialMonitor --icon icons/<name_of_ico_file> <name_of_py_file>
The result is an executable with the name and icon given!
SPIFFS is short for SPI Flash File System and is an area in flash memory that is reserved for file storage.
If you have an application with a Web UI, for instance, then you will need at least a HTML file. Maybe some CCS files and JS files too. And maybe others...
Putting those in the SPIFFS flash memory area is the way to go. This way, if you change one of those files, you don't have to recompile the whole app. That can make a serious difference in compile time.
If you want to use SPIFFS and define the size of it in flash memory yourself, you have to create a file partitions.cvs in the root of your project.
By default, the ESP-IDF framework is taking the content of the file partitions_singleapp.csv, located in .\.espressif\v6.0.1\esp-idf\components\partition_table (or wherever you installed ESP-IDF).
This is defined in the default sdkconfig file that is generated while generating a new project. Search for Partition Table in the sdkconfig file and look for the following line:
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
You will also see the following line:
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
But this is not taken into account, because the following line is not set by default:
# CONFIG_PARTITION_TABLE_CUSTOM is not set
The file partitions_singleapp.csv has the following default layout:
# Name, Type, SubType, Offset, Size , Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs , , 0x6000,
phy_init, data, phy , , 0x1000,
factory, app , factory, , 1M ,
To change it, you have to create a file partitions.cvs yourself, as mentioned earlier.
PO