ESP32 (7) – menuconfig

You’ve already learned in the previous tutorials that all the configuration of the esp-idf framework is done using a configuration menu:

esp32-menu01

esp32-menu00

You can use the same menu to set options/parameters for the program you’re developing: in today’s tutorial I’m going to explain how to do it!

Kconfig

The framework‘s config menu is configured via some text files. The syntax of those files is similar to the one used to configure the Linux kernel, known as the kconfig language.

To be able to add your own configuration items, first you have to create a file named Kconfig.projbuild in the same folder of your source code:

esp32-menu00b

All the content of the file is enclosed by the following two lines:

menu "Nome menu"
[...]
endmenu

which define the name of the menu that will group all your custom parameters. For example is you type

menu "Custom configuration"

you’ll see:

esp32-menu02

The most common configuration parameters have the following structure:

config <parameter name>
  <parameter type> <prompt>
  range min max
  default <default value>
  help
    <help text>

Parameter type can have the following values:

  • string, text parameters
  • int, parameters with integer values
  • hex, parameters displayed in hexadecimal format
  • bool, parameters with boolean values

If for example you need to configure the TCP port a server is listening on, you can define the corresponding parameter as it follows:

config TcpPort
  int "Server TCP port"
  range 1 65535
  default 80
  help
    Enter the TCP port the server is listening on

You can also create menu items with multiple choices:

choice
  config <choice_1>
    bool <prompt>
  config <choice_2>
    bool <prompt>
  ...
  default <default choice>

Once configured, the parameter values are saved in the sdkconfig file within the project’s main folder:

esp32-menu04

The name of the constants that correspond to the different parameters is CONFIG_<parameter name>. For the example above, the constant that corresponds to the TCP port you have to use in your program is CONFIG_TcpPort.

In my Github repository you can find a complete example. The Kconfig.projbuild file defines some parameters to demonstrate the use of the different types:

esp32-menu03

Once configured, you can find the parameters’ values in the sdkconfig file:

esp32-menu05

and if you run the program, the values are also displayed on the serial port:

esp32-menu06

Conclusion

The ability to extend the use of the menuconfig of the framework to include also custom parameters of your program allows to configure all the aspects of your project in a handy, unified way. In the previous tutorial the configuration of the wifi network (SSID and password) was hardcoded in the source of the program: a better choice is now to move  those parameters out of the code and insert them in the configuration menu… as exercise, try to do it!

How to make a simple 1 watt audio amplifier (LM386 based)

schematic7-750x422

A tutorial on making an LM386 audio amplifier circuit from Afrotechmods:

A tutorial on how to use the popular LM386 class AB audio amplifier IC to build a simple mono 1 watt audio amplifier. It costs less than $3 in parts! There is also a brief discussion of how to use high pass filters to prevent op-amp oscillation and subsequent noise.

More info at Afrotechmods tutorial page.

Check out the video after the break.

ESP32 (6) – How to connect to a wifi network

In this post I’m going to show you how to connect to a wifi network.

The esp-idf framework includes a wifi driver that manages the wifi interface of the esp32 chip. The driver exposes API calls the programmer can use to interact with it; you’ve already used some of those APIs in my previous tutorial:

ESP_ERROR_CHECK(esp_wifi_init(&wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

The driver is executed in parallel with the main program and communicates with it using events:

esp32-wifi02

In the main program you have to define a function that will be the event handler, i.e. a function called by the wifi driver every time it has to notify a new event:

static esp_err_t event_handler(void *ctx, system_event_t *event)
{...}

then you have to tell the framework the name of your function:

ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

The list of the events the framework may generate is included in the esp_event.h file. The events related to the wifi driver when working in station mode have SYSTEM_EVENT_STA as name prefix:

esp32-wifi03

The connection to a wifi network follows this flow diagram:

esp32-wifi04

  • the main program configures and starts the driver via API calls
  • after having completed its internal tasks, the driver notifies that it has successfully started triggering the event SYSTEM_EVENT_STA_START
  • the event handler, once received that event, can call the esp_wifi_connect() API to ask the driver to connect to the network specified during the configuration phase
  • when the connection is completed and after having obtained a valid IP address (if the DHCP service is used), the driver triggers the event SYSTEM_EVENT_STA_GOT_IP
  • now the event handler can inform the main program that the connection has been completed

Tasks synchronization

In the process described above, the main program waits for the connection to be completed before executing its own instructions. It’s therefore important to understand how to synchronize the different elements: the main program, the event handler and the wifi driver. FreeRTOS offers different ways to make the tasks communicate: one of the simplest is via events. Events, in FreeRTOS, are managed using event groups and event bits.

Event bits are similar to flags,  visible to the different tasks:

  • the programmer can create as many event bits as he needs
  • tasks can activate (set) o deactivate (clear) the different bits
  • a task can pause its execution waiting for one of more bits to be set

Event bits are grouped into event groups, each of them usually contains 8 event bits. Event bits in an event group are numbered depending on their “position” (BIT0, BIT1…):

esp32-wifi05

The program

The complete program is available in my Github repository. Let’s review the main sections:

static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
[...]
wifi_event_group = xEventGroupCreate();

The program defines an event group (wifi_event_group) and an event bit (CONNECTED_BIT). In the app_main() the event group is created using the FreeRTOS xEventGroupCreate() method.

#define WIFI_SSID "MYSSID"
#define WIFI_PASS "MYPASSWORD"
[...]
wifi_config_t wifi_config = {
  .sta = {
    .ssid = WIFI_SSID,
    .password = WIFI_PASS,
  },
};
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));

The configuration of the wifi network starts with the definition of two constants: the SSID name and the password (WIFI_SSID e WIFI_PASS). The constants are then used to initialize the wifi_config struct; struct which is then passed as parameter to the esp_wifi_set_config method.

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
 
  case SYSTEM_EVENT_STA_START:
    esp_wifi_connect();
    break;
  case SYSTEM_EVENT_STA_GOT_IP:
    xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
    break;  
  case SYSTEM_EVENT_STA_DISCONNECTED:
    xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
    break;

The event handler manages the different events triggered by the wifi driver as explained above. In particular, once the connection is established (STA_GOT_IP event), sets the CONNECTED_BIT event bit. On the contrary, in case of disconnection it clears the same event bit.

xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);

The main task pauses its execution until the connection to the wifi network is perfomed, waiting for the CONNECTED_BIT bit to be set. The portMAX_DELAY constant will cause the task to block indefinitely (without a timeout).

tcpip_adapter_ip_info_t ip_info;
ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
printf("IP Address:  %s\n", ip4addr_ntoa(&ip_info.ip));
printf("Subnet mask: %s\n", ip4addr_ntoa(&ip_info.netmask));
printf("Gateway:     %s\n", ip4addr_ntoa(&ip_info.gw));

When the connection is established, the main task prints out the network parameters (IP address, netmask and gateway). The TCPIP_ADAPTER_IF_STA constant represents (see tcpip_adapter.h) the network interface of the esp32 chip when working in station mode.

Here’s a screenshot of the program in execution:

esp32-wifi06

To make its output clearer, I turned off the default logging of the wifi driver with the instruction:

esp_log_level_set("wifi", ESP_LOG_NONE);

and disabled the buffering for the standard output:

setvbuf(stdout, NULL, _IONBF, 0);

ESP32 (5) – Wifi scanner

The main feature of the esp32 chip, as it was for its predecessor esp8266, is for sure the ability to connect to wifi networks. I’ve already blogged in a previous article about the different standards (802.11 b/g/n) and security methods (WEP, WPA-PSK…) the chip supports; today I’m going to explain how to develop a program to scan for available wifi networks.

The complete program is available in my Github repository; let’s comment the source code.

As we know, every program using the esp-idf framework is executed starting from the app_main() method. This method starts initializing the tcpip stack and the wifi event handler:

tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
ESP_ERROR_CHECK is a handy macro, defined in esp_err.h, to check the result of the different framework methods. If a call returns the constant ESP_OK, the execution of the program can proceed; if not the program is stopped and the line in error is displayed on the serial output.

The esp-idf wifi stack requires a method (= event handler) that is called every time an event related to the wifi interface is triggered (for example the connection to a new network…). We’ll talk in a future tutorial about event handling, for now it’s enough to define an empty handler method:

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
  return ESP_OK;
}

The program continues configuring, initializing and starting the wifi stack:

wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

You can create a default configuration with the WIFI_INIT_CONFIG_DEFAULT() macro and start the wifi interface in station mode with the constant WIFI_MODE_STA. Station mode is when the interface acts as a “client”, to connect to an access point.

Now you can configure and start the scan process:

wifi_scan_config_t scan_config = {
	.ssid = 0,
	.bssid = 0,
	.channel = 0,
        .show_hidden = true
};
ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true));

Using the scan_config variable you can configure some filters the scanning process will use (for example scan only a given channel) and ask to display or not the access points with hidden SSID. A zero value for each field means ALL (= no filter).

The scanning process can be executed in blocking (true) or non blocking (false) mode changing the second parameter of the function esp_wifi_scan_start. In this example I’m using the blocking mode: program execution is halted until the scan is completed.

When the scan is complete, you can obtain the list of the detected networks:

uint16_t ap_num = MAX_APs;
wifi_ap_record_t ap_records[MAX_APs];
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, ap_records));

The esp_wifi_scan_get_ap_records method returns an array of wifi_ap_record_t elements. You have to reserve some memory space to store that array, defining the maximum number of elements you want to retrieve. In my program, I defined therefore the MAX_APs constant:

#define MAX_APs 20
It may sound strange to pass the ap_num variable by reference to the esp_wifi_scan_get_ap_records method. The reason is that the method uses the variable in two ways (inout): as input to find the max capacity of the array passed as the second parameter and as output to return the effective number of networks found and stored in the array.

You can now print on the serial connection the details of the networks found:

printf("Found %d access points:\n", ap_num);
for(int i = 0; i < ap_num; i++)
	printf("%32s | %7d | %4d | %12s\n", 
	(char *)ap_records[i].ssid, ap_records[i].primary, ap_records[i].rssi,
	getAuthModeName(ap_records[i].authmode));

At the end, create an empty task to avoid a continuous reset of the chip:

xTaskCreate(&loop_task, "loop_task", 2048, NULL, 5, NULL);
[...]
void loop_task(void *pvParameter) {
    while(1) { 
        vTaskDelay(1000 / portTICK_RATE_MS);		
    }
}

During the program compilation, remember to activate the wifi module in the menuconfig (Component config – ESP32-specific config):

esp32-wifi01

Here’s the output of the program:

esp-scan02

ESP32 (4) – Flash, bootloader and FreeRTOS

In the previous posts I’ve described the new esp32 chip and explained how to setup the development environment, including the use of one – optional – graphical IDE, Eclipse.

Before starting to develop your programs, you need to understand three key components of the framework: flash, bootloader and FreeRTOS operating system. But don’t worry! At the end of this post you’ll write, compile and execute your first program (even if “minimal”) and starting from the next tutorial we’ll work together on more complex examples.

Flash

As I wrote in the first post of this tutorial, the esp32 chip requires an external flash memory to store programs, data, configuration parameters…

The external memory is connected to the chip via the SPI bus and the supported capacity is up to 16Mb. The official Espressif module (ESP-WROOM-32) includes a 4Mb flash memory made by GigaDevice (GD25Q32):

esp-flash01

The flash memory can store different elements: programs, data… hence it’s divided into sections (partitions). The list partitions, their size and position within the flash memory is stored in the memory itself (at address 0x8000) and it’s called partition table.

Two partition types are defined by default:

  • app (type 0) – partition that contains an application (program)
  • data (type 1) – partition that contains data

When writing a new program, the developer can decide how to organize the flash memory based on the program’s specific needs. The esp-idf framework offers two pre-configured memory layouts, you can choose from the configuration menu:

esp-flash02

You can also show the project’s partition table with the make partition_table command:

esp-flash03

In the screenshot above, you can see the partition table layout that corresponds to the simplest configuration (Single factory app, no OTA):

  • a data partition (named “nvs“) with a size of 24Kb starting from address 0x9000
  • a data partition (named “phy_init“) with a size of 4Kb starting from address 0xf000
  • an app partition (named “factory“) with a size of 1Mb starting from address 0x10000

Bootloader

To better understand why the flash memory is divided into partitions, I have to describe you how the esp32 bootloader works, that is how the application is started and how a new firmware is uploaded.

In the esp32 ROM memory there’s a small program, named first-stage bootloader. This program is executed at each reset of the chip: it configures the access to the external flash memory and, if required, stores on it new data coming from the serial/USB port (flash process). Once finished, it accesses the flash memory (at address 0x1000) and loads and executes the second-stage bootloader.

Even if the standard behavior of the esp-idf framework is to use this second-stage bootloader, you can also develop a stand-alone application that, stored at address 0x8000 of the flash memory, is directly executed by the first-stage bootloader.

The second-stage bootloader reads the partition table at address 0x8000 and searches for app partitions. It decides which application has to be executed based on the content of the otadata partition: if this partition is empty or doesn’t exist, the bootloaded executes the application stored in the factory partition. This allows to implement an over-the-air (OTA) application update process: you send the new version of your application to the esp32 chip; the version is stored in a new app partition. Once the upload is completed, the id of the partition is saved in otadata and the chip is rebooted; the bootloader will execute the new version:

esp-flash04

FreeRTOS

The esp-idf framework is based on the FreeRTOS Real-Time Operating System. It may sound strange to talk about operating systems when working on a “small” chip like the esp32… but you don’t have to think that FreeRTOS is an operating system like Windows, Linux or MacOS. The main feature an embedded operating system offers – thanks to its internal scheduler, is multitasking, that is the ability to run different tasks in parallel. We know that a microprocessor core can execute an instruction at a time: it seems that different applications run simultaneously because of the scheduler is rapidly switching between their tasks.

esp-flash05

different task statuses in FreeRTOS

A real-time operating system is design to guarantee that task scheduling is deterministic: you can predict the behavior of its scheduler. FreeRTOS allows the developer to define a priority for each task: the scheduler uses priority values to define the execution pattern of the different tasks.

Our first program

Let’s write our first program that used the esp-idf framework. This program, available in my Github repository, will be the skeleton for all the next tutorials.

First we must include some libraries:

esp-flash07

The stdio.h library (Standard I/O) is used for input/output functions (printf…) and the two freertos header files are used to define and execute the different tasks.

Every program is executed starting from the app_main() function:

esp-flash08

In the app_main() a new task is created using the xTaskCreate method. This method requires the following parameters: the pointer to the function (&loop_task) that contains task code, the name of the task (“loop_task”), the size, in words, of the stack memory to be assigned to the task (512), additional parameters for the task (NULL), the task’s priority (5) and the pointer – optional – to retrieve an handler of the task (NULL).

Once the task is created, the FreeRTOS scheduler executes it based on the different tasks and priorities.

The task has the following code:

esp-flash09

it’s a continuous loop that every second prints the sentence Hello World!”. The vTaskDelay method pauses the task for the specified number of ticks. The portTICK_RATE_MS constant defines the duration, in milliseconds, of a tick; if you therefore divide 1000 for that constant you get the number of ticks in a second.

Additional files

To be able to compile your project, you need to add a couple of additional files:

  • a Makefile in the main folder of your project that contains the name of the project and an include for the main makefile of the framework:

esp-flash10

  • component.mk file – it can be empty – in the folder where the source code is saved into to tell the compiler to process the files in that folder:

esp-flash11

Test

When the program is ready, you can compile and load it on the board as explained in a previous post:

make
make flash

If everything is ok, when you connect to the board with a serial emulator you should see:

esp-flash12

 

ESP32 (3) – Eclipse

In the previous blog post, I explained how to install the official development framework (Espressif IoT Development Framework) and how to use it to compile your first example, Hello world.

Even if you can write your programs with a simple text editor (on Windows I always suggest to use the opensource Notepad++), it’s very easier to do it with an IDE (Integrated Development Environment), that is a graphical application which you can use to write the code (it often includes syntax highlighting and auto-completion), to compile it and to upload the binary on you development board. Among the available IDEs, one of the most famous and adopted is without doubts Eclipse.

Installation

Eclipse installation is really straightforward: connect to the official site and download Eclipse IDE for C/C++ Developers for your operating system (as in the previous post, I decided to use Windows):

eclipse-01

Eclipse is shipped as a zip archive. When the download is complete, unzip the archive in a folder of your hard drive. To make it easy, I chose the same folder (the user’s home folder) where I’ve already installed the esp-idf:

eclipse-02

Run Eclipse with a double-click on eclipse.exe. At first, you are prompted to specify the path of your workspace (= the folder where your projects will be saved in). I chose to create the workspace as a subfolder of Eclipse’s installation folder:

eclipse-03

Project configuration

To be able to develop a project based on esp-idf with Eclipse, you need to do some configuration. First, import the project in the IDE. For this tutorial, I’m going to use the 01_hello_world example; in a future post I’ll show you how to start with an empty project.

Choose File – Import, then run the Existing Code as Makefile project wizard:

eclipse-04

Give the project a name, choose the folder that contains the code and make sure that the selected toolchain isCross GCC:

eclipse-05

When the import is complete, open the project Properties:

eclipse-06

First select C/C++ Build – Environment. Add (using the Add… button) two new variables:

  • name V, value 1
  • name IDF_PATH, value the path where esp-idf is installed (note: you have to use / instead of \)

On Windows, in addition, change the PATH variable with the following value (if msys32 is installed in the default path):

C:\msys32\usr\bin;C:\msys32\mingw32\bin;C:\msys32\opt\xtensa-esp32-elf\bin

eclipse-07

If you’re under Windows, now choose C/C++ Build, unflag Use default… and type the following build command:

bash ${IDF_PATH}/tools/windows/eclipse_make.sh

eclipse-08

The final step is to choose C/C++ General – Preprocessor Include Paths and open the Providers tab.

Click on CDT Cross GCC Built-in Compiler Settings and change the text ${COMMAND} with xtensa-esp32-elf-gcc:

eclipse-09

Then click on CDT GCC Build Output Parser and add xtensa-esp32-elf- at the beginning of the command:

eclipse-10

You’re now ready to compile the project:

eclipse-11

It may happens that, after the compile process, Eclipse warns about some “unresolved inclusion”:

eclipse-12

The warnings should appear only under Windows and they are caused by a known bug that doesn’t block the compile process. You may solve it manually adding the different include folders found starting from the folder esp-idf/components in the project properties (C/C++ General – Paths and Symbols – GNU C):

eclipse-13

Flash

You can also configure Eclipse to run the make flash command, to load the compiled version of your program on the development board.

Choose, on the right panel, the Build Targets tab:

eclipse-14

Right-click on the project’s folder and choose New.

Type flash as target name, then confirm with Ok:

eclipse-15

If now you right-click on the new command, you can start the flash process choosing Build Target:

eclipse-16

The Console tab, in the lower panel of the IDE, shows flash command output and result:

eclipse-17

ESP32 (2) – The development environment

After having introduced, in the previous post, the ESP32 chip and the official development board by Espressif, today I’m going to show you how to install and use the official development environment.

I’ll explain how to install the IDE under Windows because of it is the most complex procedure. The development environment its how-to-install guides are available also for Linux and MacOS.

The official development framework for the ESP32 chip is published by Expressif on Github, with an opensource license (Apache 2.0) and named Espressif IoT Development Framework (shortly idf).

To be able to use it under Windows, you need some GNU commands and toolchains (make, bash…). Espressif chose to offer a pre-configured version of MSYS2 that includes everything is required.

Download the ZIP archive from the Espressif website and unzip it in the root of your C drive; MSYS2 will be installed in the C:\msys32 subfolder:

esp32-07

Double-click on msys2_shell.cmd to run a bash shell:

esp32-08

We can now clone the idf Github repository to have a local copy of it. First we must choose a folder where it will be copied; to keep it simple let’s use the user’s home folder.

Move into that folder and run the clone command:

$ cd
$ git clone --recursive https://github.com/espressif/esp-idf.git

esp32-09

During the git clone command execution, you may get some error messages like: not a valid identifier; those errors are caused by a known bug and can be therefore ignored.

After the process is completed, the development framework is available in the esp32-idf subfolder:

esp32-10

To use if when compiling your projects, you have to export the path as an environment variable (IDF-PATH):

$ export IDF_PATH=~/esp-idf

Let’s connect the board!

You’re now ready to connect the development board to your computer.

If you already have the correct drivers, once connected it will appear as a new serial port:

esp32-12

On the contrary, the needed drivers are available on the Silicon Labs official website.

Hello World

As usual when learning a new language or framework, let’s start with a simple program that displays (using the serial-usb connection) the sentence Hello world.

The framework is distributed with some examples, among them you can find the 01_hello_world program. To compile it, move to its folder and run the make command:

$ cd
$ cd esp-idf/examples/01_hello_world/
$ make

A configuration menu will be displayed: we’ll talk more about it in a next post. Now you only need to tell the flasher which is the serial port your board is connected to.

Choose Serial flasher config, then /dev/ttyUSB0 (the default port):

esp32-14

Type the name of your serial port (as identified above), then confirm with OkSave and Exit:

esp32-16

When the compile process is complete, you can upload the program on the board with the following command:

make flash

esp32-17

Because of a bug, under Windows it may happen that the flash process is unsuccessful. The easiest solution is to keep the button boot pressed during all the flashing process.

If you now use a terminal emulator to connect to the serial port with the correct speed (115200 baud) you should see the program output:

esp32-18

(the program prints the sentence “Hello world!”, counts 10 seconds and then restart the chip…)

ESP32 (1) – Introduction

You probably already know the esp8266wifi chip, made by Espressif. It appeared on some Chinese webstores in the middle of 2014 and in the beginning it was used as a “bridge” to connect microcontrollers (Arduino…) to wifi networks thanks to its very low cost (about 5$ for a module).

esp32-01

Because of the original firmware was not well documented, it has some bugs and offered only “standard” features (via AT commands), the maker community developed some alternative firmwares  (the most famous of which is surely NodeMCU), to fully exploit the chip power and to build complete systems, without the need of external microcontrollers:

esp32-02

ESPToy, by Rayshobby

In September 2016, after a beta testing phase lasted some months, Espressif announced and made available the successor of esp8266, named ESP32.

The main features of the new chip are the following:

  • Tensilica LX6 dual core processor at 240 MHz
  • 520Kb of SRAM memory
  • Wifi 802.11 b/g/n connectivity with support for WEP, WPA/WPA2 PSK/Enterprise
  • Bluetooth connectivity (classic and LE)
  • 32 I/O pins with several builtin peripherals
  • hardware acceleration for security algorithms (AES, SHA2, RSA-4096)

With this post, I’m going to start a tutorial that will explore how to use this new chip to develop IoT systems and projects.

The ESP32 needs some additional components to work: a flash memory (to store firmware and data), a crystal (for the RTC), an antenna and some passive components. For this reason, you can find on sale ready-to-use modules:

esp32-03

On the left, the official module by Espressif (ESP-WROOM-32) while on the right the module made by Ai-Thinker (ESP-32S).

In addition, on some webstores you can start to find different development boards that include the ESP32 module, the power supply, an USB connector… everything you need to start building your project!

The esp32.net website keeps an updated list of the available boards; for my tutorials I chose the official Espressif development kit, named ESP32-DevKit3 or ESP32-CoreBoard. I brought mine from Olimex:

esp32-06 esp32-05

If you give a look to its schematics, you can find the ESP32 module, a voltage regulator (NCP1117) to lower the 5V coming from the USB bus to the 3.3V required by the chip, a CP2102 USB-to-serial adapter and two buttons. Moreover, all the most important PINs are available on the side connectors::

esp32-04

In the next post, you’ll learn how to install the development environment and how to write your first program!