Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Developing IoT Projects with ESP32
Developing IoT Projects with ESP32

Developing IoT Projects with ESP32: Unlock the full Potential of ESP32 in IoT development to create production-grade smart devices , Second Edition

eBook
$27.98 $39.99
Paperback
$49.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Colour book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Table of content icon View table of contents Preview book icon Preview Book

Developing IoT Projects with ESP32

Understanding the Development Tools

After getting a quick overview of Espressif’s ESP32 technology in the first chapter, we are now ready to start on some development with the real hardware. For this, we need to understand the basics and how to use the available tools for the job. It is a learning process and takes some time; however, we’ll have acquired the fundamental knowledge and gained hands-on experience to develop actual applications on ESP32 by the end of the chapter.

In this chapter, we’re going to install the development environment on our machines and use our development kits to run and debug the applications. The topics covered are as follows:

  • ESP-IDF
  • PlatformIO
  • FreeRTOS
  • Debugging
  • Unit testing

Let’s start by looking into the development framework by Espressif Systems, ESP-IDF.

Technical requirements

Throughout the book, we’re going to use Visual Studio Code (VSCode) as our Integrated Development Environment (IDE). If you don’t have it, you can find it here: https://code.visualstudio.com/. VSCode is updated monthly, but its version is 1.77.3 as of writing this book.

The main development framework that we will use in this chapter and in the book is ESP-IDF v4.4.4. It is available on GitHub here: https://github.com/espressif/esp-idf. If you are a Windows user, you can download the installer from this link as another installation option: https://dl.espressif.com/dl/esp-idf/.

ESP-IDF requires Python 3 to be installed on the development machine. We will also need Python in some of the examples. The Python version that is used is 3.10.11.

The examples in the book are developed on Ubuntu 22.04.2 LTS (Linux 5.15.0-71-generic x86_64). However, they compile regardless of the development platform after the installation of ESP-IDF on the development machine.

For hardware, we need both devkits in this chapter, ESP32-C3-DevKitM-1 and ESP32-S3-Box-Lite.

The source code in the examples is located in the repository found at this link: https://github.com/PacktPublishing/Developing-IoT-Projects-with-ESP32-2nd-edition/tree/main/ch2

ESP-IDF

ESP-IDF is the official framework by Espressif Systems. It comes with all the necessary tools and an SDK to develop ESP32-based products. After installing ESP-IDF, we will have:

  • An SDK for ESP32 development: ESP-IDF v4.4.4 supports the ESP32, ESP32-S2, ESP32-S3, and ESP32-C3 families of chips.
  • GCC-based toolchains for the Xtensa and RISC-V architectures.
  • GNU debugger (GDB).
  • Toolchain for the ESP32 Ultra Low Power (ULP) coprocessor.
  • cmake and ninja build systems.
  • OpenOCD for ESP32.
  • Python utilities: The most notable one is idf.py and is the one we will use throughout the book. It collects all the development tasks in the same Python script.

The installation of ESP-IDF differs from platform to platform, so make sure to follow the steps as described in the official documentation for your target development machine. The documentation is provided at this link: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/#installation

My personal choice of development environment is VSCode on Canonical Ubuntu 22.04 LTS. However, you can install ESP-IDF on any platform without any problem if you follow the guidance in the official documentation. The other IDE option is to use ESP-IDF through it.

On top of ESP-IDF, Espressif offers a VSCode extension in the Visual Studio Marketplace to manage ESP32 projects in a similar fashion to the command-line tools. It makes development in the VSCode environment easier but its capabilities are limited compared to the command-line tools. Its installation is explained here: https://github.com/espressif/vscode-esp-idf-extension. We will use this extension with VSCode to develop the very first ESP32 application.

The first application

In this example, we will simply print 'Hello World' on the serial output of ESP32 – surprise! Let’s do it step by step:

  1. Make sure you have installed ESP-IDF v4.4.4. Follow the steps as described in the documentation (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/#installation).
  2. Make sure you have installed VSCode and the Espressif IDF extension (https://github.com/espressif/vscode-esp-idf-extension).
  3. Plug ESP32-S3-BOX-Lite into a USB port of your development machine and observe that it is shown on the device list (the following command is on a Linux terminal but you can choose any relevant relevant tool on your development machine):
    $ lsusb | grep -i espressif
    Bus 001 Device 025: ID 303a:1001 Espressif USB JTAG/serial debug unit
    
  4. Run VSCode and enable the ESP-IDF extension if it is not already enabled.

    Figure 2.1: Espressif IDF in VSCode

  1. Create an ESP32 project. There are two ways to do that. You can either select View | Command Palette | ESP-IDF: New Project or press the Ctrl + E + N key combination to open the New Project dialog. Fill in the input boxes as shown in the following screenshot (don’t use any whitespace in the project path, and select ESP32-S3 and the serial port that the devkit is connected to). Then click on the Choose Template button.

    Figure 2.2: New project

  1. On the next screen, select ESP-IDF from the drop-down box and sample_project from the list. Complete this step by clicking on the Create project using template sample_project button. On the bottom right of the screen, a pop-up window will show up asking to open the project in a new window. Answer Yes and a new VSCode window will appear with the new project.

    Figure 2.3: Select sample_project

  1. In the new VSCode window, you now have the environment for the ESP32 project. You can see the file names in Explorer on the left side of the window.

Figure 2.4: Explorer with VSCode files

  1. Rename main/main.c to main/main.cpp and edit the content with the following simple and famous code:
    #include <iostream>
    extern "C" void app_main(void)
    {
        std::cout << "Hello world!\n";
    }
    
  2. To compile, flash, and monitor the application, we can simply press the Ctrl + E + D key combination (there are other ways to the same thing: we can click on the fire icon in the bottom menu or we can select the same CP in the command palette in VSCode). The ESP-IDF extension will ask you to specify the flash method. There are three options: JTAG, UART and DFU. We select UART.

    Figure 2.5: Selecting the flash method

  1. Then the next step is to select the project – we only have first_example. VSCode will open a terminal tab where you can see the compilation output. If everything goes well, it will connect to the serial monitor and all the logs will be displayed as follows.

    Figure 2.6: The serial monitor output

  1. We have compiled the application, flashed it to the devkit, and can see its output in the serial monitor. To close the serial monitor, press Ctrl + ].

Let’s go back to the application code in step 8 and discuss it briefly. In the first line, we include the C++ iostream header file to be able to use the standard output (stdout) to print text on the screen:

#include <iostream>

In the next line, we define the application entry point:

extern "C" void app_main(void)
{

extern "C" means that we will next add some C code and the C++ compiler will not mangle the symbol name that comes after this. It is app_main here. The app_main function is the entry point of ESP32 applications, so we will have this function in every ESP32 application that we develop. We only print "Hello world!\n" on the standard output in app_main:

    std::cout << "Hello world!\n";
} // end of app_main

The standard output is redirected to the serial console by default, thus we see Hello world! printed on the screen in step 10 when we monitor ESP32 by connecting its serial port. We’ve completed our first ESP32 application. We can use these steps as a blueprint when starting a new project. Let’s discuss more about the internal workings of ESP-IDF and other files in a typical ESP32 project.

ESP-IDF uses cmake as its build configuration system. Therefore, we see the CMakeLists.txt files in various places. The one in the root defines the ESP-IDF project:

cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(first_project)

The other one is main/CMakeLists.txt. It registers the application component by providing the source code files and include directories. In our case, it is only main.cpp and the current directory of main.cpp where it will look for any header files:

idf_component_register(SRCS "main.cpp"
                    INCLUDE_DIRS ".")

We frequently edit this file to add new source code files and include directories in our projects.

If you are not familiar with the cmake tool, you can visit its documentation at this link: https://cmake.org/cmake/help/latest/. We will discuss the CMakeLists.txt files in the example projects but if you want a preview of how to configure an ESP-IDF component, the documentation is here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#minimal-component-cmakelists

You may have noticed that when we compiled the application, new additions appeared in the project root as can be seen in the following figure:

Figure 2.7: The files and directories after compilation

We can see a new directory, build, and a file, sdkconfig. As the name implies, all the artifacts from the build process are located under the build directory. The tools used during the build process generate their files in there. The most interesting files are probably the JSON files. Let’s list them:

Figure 2.8: JSON files in the build directory

The names of these files are self-explanatory regarding their content so I don’t want to repeat them. However, I suggest you check their content after building the project since they can be very helpful for troubleshooting purposes and understanding the build system in general.

sdkconfig in the project root is important. It is generated by ESP-IDF automatically during the build if there is none. This file contains the entire configuration of the application and defines the default behavior of the system. The editor is menuconfig, which is accessible from View | Command Palette | ESP-IDF: SDK Configuration editor (menuconfig) or simply Ctrl + E + G:

Figure 2.9: SDK configuration (sdkconfig)

We will use menuconfig often to configure our projects, and even provide our custom configuration items to be included in sdkconfig (menuconfig is a part of the Linux configuration system, Kconfig, and adapted in ESP-IDF to configure ESP32 applications).

ESP-IDF Terminal

One last thing worth noting with the ESP-IDF extension is the ESP-IDF Terminal. It provides command-line access to the underlying Python scripts that come with the ESP-IDF installation. To open a terminal, we can select View | Command Palette | ESP-IDF: Open ESP-IDF Terminal or press the key combination of Ctrl + E + T. It opens an integrated command-line terminal. The two powerful and popular tools are idf.py and esptool.py. We can manage the entire development environment and build process by only using idf.py and we will use this tool a lot throughout the book. Just go ahead and write idf.py in the terminal to see all the options. As a quick example:

$ idf.py clean flash monitor
Executing action: clean
Running ninja in directory <your_directory>/ch2/first_project/build
Executing "ninja clean"...
[0/1] Re-running CMake...
-- Project is not inside a git repository, or git repository has no commits; will not use 'git describe' to determine PROJECT_VER.
-- Building ESP-IDF components for target esp32s3
-- Project sdkconfig file <your_directory>/ch2/first_project/sdkconfig
-- App "first_project" version: 1
<The rest of the build and flashing logs. Next comes the application output.>
I (0) cpu_start: Starting scheduler on APP CPU.
Hello world!

This simple command cleans the project (removes the previous build output if any), compiles the application, flashes the generated firmware to the devkit, and finally starts the serial monitor to show the application output.

Similarly, you can see what esptool.py can do by writing its name and pressing Enter in the terminal. The main purpose of this tool is to provide direct access to the ESP32 memory and low-level application image management. As an example, we can use esptool.py to flash the application binary:

$ esptool.py --chip esp32s3 write_flash -z 0x10000 build/first_project.bin
esptool.py v3.2
Found 1 serial ports
Serial port /dev/ttyACM0
Connecting...
Chip is ESP32-S3
Features: WiFi, BLE
Crystal is 40MHz
MAC: 7c:df:a1:e8:20:30
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash will be erased from 0x00010000 to 0x00079fff...
Compressed 430672 bytes to 205105...
Wrote 430672 bytes (205105 compressed) at 0x00010000 in 4.5 seconds (effective 769.1 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin…

The esptool.py tool is especially useful for production. It directly communicates with the ROM bootloader of Espressif SoCs and is independently available on PyPI so we can install it on any machine for firmware binary management with a simple command, without using the entire ESP-IDF:

$ pip install esptool

The Python installation adds two other tools:

  • espefuse.py for reading/writing eFuses of ESP32
  • espsecure.py for managing secure boot and flash encryption

We will not use esptool.py and the other two that come with the installation in the examples but you will probably need them in production to flash the firmware images on your IoT devices and enable security features. The official documentation for these tools can be found at this link: https://docs.espressif.com/projects/esptool/en/latest/esp32/index.html

With this overview under your belt, you are now ready to start using VSCode and the ESP-IDF extension together to develop ESP32 projects. The next tool is PlatformIO.

PlatformIO

PlatformIO supports many different platforms, architectures, and frameworks with modern development capabilities. It comes as an extension in VSCode and so is very easy to install and configure with just a few clicks. After launching VSCode, go to the VSCode Extensions screen (Ctrl+Shift+X) and search for platformio in the marketplace. It appears in the first place in the match list. Click on the Install button, and that is it:

Figure 2.10: Installing PlatformIO

After a few minutes, the installation completes and we have PlatformIO installed in the VSCode IDE. PlatformIO has some unique features. The most notable one is probably the declarative development environment. With PlatformIO, we only need to specify what we’re going to use in our project, including the chip type (not limited to Espressif products), which framework and which version of that framework, other libraries with version constraints, and any combination of them. We’ll see what all these things mean and how to configure a project shortly. Apart from that, PlatformIO has all the utilities that you would need when developing an embedded project, such as debugging, unit testing, static code analysis, and firmware memory inspection. When I used PlatformIO for the first time roughly 8 years ago, the debug feature was not available in the free version. It is now a free and open-source project with all features at our disposal. Thank you, guys! Enough talking, let’s develop the same application with PlatformIO.

Hello world with PlatformIO

Now, we’re going to use PlatformIO. Here are the steps to develop the application:

  1. Go to the PlatformIO Home screen.

    Figure 2.11: PlatformIO Home

  1. Click on the New Project button on the right of the same screen.

    Figure 2.12: Quick access buttons on the PlatformIO Home screen

  1. A pop-up window appears. Set the project name, select Espressif ESP32-S3-Box for the board, and specify the framework as Espressif IoT Development Framework. You can choose a directory for the project or leave it at the PlatformIO default. Click on Finish to let PlatformIO do its job.

    Figure 2.13: PlatformIO Project Wizard

  1. When the project is created, we have the following directory structure.

    Figure 2.14: Project directory structure

  1. Rename src/main.c to src/main.cpp and copy-paste the same code that we have already developed with the ESP-IDF extension.
    #include <iostream>
    extern "C" void app_main()
    {
        std::cout << "Hello World!\n";
    }
    
  2. Edit the platformio.ini file to have the following configuration settings.
    [env:esp32s3box]
    platform = espressif32
    board = esp32s3box
    framework = espidf
    monitor_speed=115200
    monitor_rts = 0
    monitor_dtr = 0
    
  3. On the PlatformIO tasks list, you will see the Upload and Monitor task under the PROJECT TASKS | esp32s3box | General menu. It will build, flash, and monitor the application.

    Figure 2.15: PlatformIO project tasks

  1. You can observe the application output in the integrated terminal.

    Figure 2.16: Application output in the terminal

As you might have already noticed, we didn’t download or install anything except PlatformIO. It handled all these low-level configuration and installation tasks for us. PlatformIO uses the platformio.ini file for this purpose. Let’s investigate its content.

The first line defines the environment. The name of the environment is esp32s3box. We can write anything as the environment name:

[env:esp32s3box]

The second line shows the platform – espressif32:

platform = espressif32

As of writing this chapter, PlatformIO supports 48 different platforms. espressif32 is one of them. We can specify the platform version if needed and PlatformIO will find and download it for us. If none is specified, it will assume the latest version of the platform. Then the board that we use in the project is listed:

board = esp32s3box

The board in the project is esp32s3box. There are 11,420 different boards supported by PlatformIO, 162 of which are in the espressif32 platform. Next, we see the framework:

framework = espidf

The framework is espidf. This category contains 24 more frameworks in the PlatformIO registry.

The platform, board, and framework settings were added automatically in step 3 with the PlatformIO project wizard. PlatformIO collected them as user inputs at the project definition stage and set the initial content of platformio.ini with these values.

Then, we added the next three lines manually to define the serial monitor behavior:

monitor_speed=115200
monitor_rts = 0
monitor_dtr = 0

We set the serial baud rate to 115,200bps, and RTS and DTR to 0 in order to reset the chip when the serial monitor connects so that we can see the entire serial output of the application.

You can browse the PlatformIO registry at this link to see all platforms, boards, frameworks, libraries, and tools: https://registry.platformio.org/search.

Before moving on, let’s include our other board, ESP32-C3-DevKitM-1, in the project and see how easy it is to update the configuration of the project for different boards. To do that, just append the following lines at the end of platformio.ini and save the file:

[env:esp32c3kit]
platform = espressif32
board = esp32-c3-devkitm-1
framework = espidf
monitor_speed=115200
monitor_rts = 0
monitor_dtr = 0

When you save the file, PlatformIO will detect this and create another entry in the project tasks for the new environment as can be seen in the following figure:

Figure 2.17: New environment under PROJECT TASKS

After plugging the new devkit, you can upload and monitor the same application without making any other modifications in the project. Again, we didn’t manually download or install anything for ESP32-C3-DevKitM-1; it was all handled by PlatformIO. If you’re wondering where those downloads go, you can find them in the $HOME/.platformio/platforms/ directory of your development machine. The PlatformIO documentation provides complete information about what can be configured in platformio.ini with examples: https://docs.platformio.org/en/latest/projectconf/index.html.

PlatformIO Terminal

In addition to the GUI features, PlatformIO also provides a command-line tool – pio – which is accessible through PlatformIO Terminal. It can be quite useful in some cases, especially if you enjoy command-line tools in general. To start PlatformIO Terminal, you can click on the PlatformIO: New Terminal button in the bottom toolbar of VSCode.

Figure 2.18: VSCode bottom toolbar

This toolbar also has other quick-access buttons for the frequently used features, such as compilation, upload, monitor, etc. When you click on the Terminal button (the labels appear when you hover the mouse pointer over the buttons), it will redirect you to a command-line terminal where you can enter pio commands. Write pio and press the Enter key to display the pio options.

Figure 2.19: PlatformIO Terminal and the pio command-line tool

We can flash ESP32-C3-DevKitM-1 by using pio as the following:

$ pio run -t upload -e esp32c3kit
Processing esp32c3kit (platform: espressif32; board: esp32-c3-devkitm-1; framework: espidf)
----------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-c3-devkitm-1.html
PLATFORM: Espressif 32 (5.1.1) > Espressif ESP32-C3-DevKitM-1
HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash
…
Leaving...
Hard resetting via RTS pin...
============ [SUCCESS] Took 24.90 seconds =================
Environment    Status    Duration
-------------  --------  ------------
esp32c3kit     SUCCESS   00:00:24.902
=======================================

And we can monitor the serial output with the following command:

$ pio device monitor -e esp32c3kit
--- forcing DTR inactive
--- forcing RTS inactive
--- Terminal on /dev/ttyUSB0 | 115200 8-N-1
<removed>
ESC[0;32mI (324) cpu_start: Starting scheduler.ESC[0m
Hello World!

The pio tool has all the functions that you can do with the GUI. To see how to use any other command, just append the -h option after the command’s name. The online documentation provides more detailed information about the commands: https://docs.platformio.org/en/latest/core/userguide/index.html#commands.

This completes the introduction to PlatformIO. In the next topic, we will discuss FreeRTOS, the official Real-Time Operating System (RTOS) supported by ESP-IDF.

FreeRTOS

There are different flavors of FreeRTOS. FreeRTOS was originally designed for single-core architectures. However, ESP32 has two cores, and therefore the Espressif port of FreeRTOS is designed to handle dual-core systems. Most of the differences between vanilla FreeRTOS and ESP-IDF FreeRTOS stem from this. The following list shows some of those differences:

  • Creating a new task: There is a new function in ESP-IDF FreeRTOS where we can specify on which core to run a new task; it is xTaskCreatePinnedToCore. This function takes a parameter to set the task affinity to the specified core. If a task is created by the original xTaskCreate, it doesn’t belong to any core, and any core can choose to run it at the next tick interrupt.
  • Scheduler suspension: The vTaskSuspendAll function call only suspends the scheduler on the core on which it is called. The other core continues its operation. Therefore, it is not the right way to suspend the scheduler and protect shared resources.
  • Critical sections: Entering a critical section stops the scheduler and interrupts only on the calling core. The other core continues its operation. However, the critical section is still protected by a mutex, preventing the other core from running the critical section until the first core exits. We can use the taskENTER_CRITICAL(mux) and taskEXIT_CRITICAL(mux) macros for this purpose.

Another flavor of FreeRTOS is Amazon FreeRTOS, which adds more features. On top of the basic kernel functionality, with Amazon FreeRTOS developers also get common IoT libraries, such as coreHTTP, coreJSON, coreMQTT, and Secure Sockets, for connectivity. Amazon FreeRTOS aims to allow any embedded devices to be connected to the AWS IoT platform easily and securely. We will talk about Amazon FreeRTOS in more detail later in the book. For now, let’s stick to ESP-IDF FreeRTOS and see a classic example of the producer-consumer pattern.

Creating the producer-consumer project

In this example, we will simply implement the producer-consumer pattern to show some functionality of Espressif FreeRTOS. There will be a single producer and two consumer FreeRTOS tasks, one on each core of ESP32. As you might guess, the devkit is ESP32-S3-BOX-Lite (ESP32-C3 has a single RISC-V core). The producer task will generate numbers and push them to the tail of a queue. The consumers will pop numbers from the head. The following figure depicts what we will develop in this example:

Figure 2.20: Producer-consumer pattern

The producer task will have no affinity, meaning that the FreeRTOS scheduler will assign it to a core at runtime. We will pin a consumer task to each core. There will be a FreeRTOS queue to pass integer values between the producer and the consumers. FreeRTOS queues are thread-safe, so we don’t need to think about protecting the queue against reading/writing by multiple tasks. We will simply push values to the back of the queue and pop from the front (there is a good article here about how FreeRTOS queues work: https://www.freertos.org/Embedded-RTOS-Queues.html).

Let’s prepare the project in steps:

  1. Plug the devkit in a USB of your development machine and start a new PlatformIO project with the following parameters:
    • Name: espidf_freertos_ex
    • Board: Espressif ESP32-S3-Box
    • Framework: Espressif IoT Development Framework
  2. Edit platformio.ini and append the following lines (the last two lines will provide a nice, colorful output on the serial monitor):
    monitor_speed=115200
    monitor_rts = 0
    monitor_dtr = 0
    monitor_filters=colorize
    monitor_raw=yes
    
  3. Rename src/main.c to src/main.cpp and edit it by adding the following temporary code:
    #include <iostream>
    extern "C" void app_main()
    {
        std::cout << "hi\n";
    }
    
  4. Run menuconfig by selecting PLATFORMIO | PROJECT TASKS | esp32s3box | Platform | Run Menuconfig.

    Figure 2.21: Running menuconfig

  1. This is the first time we run menuconfig to configure ESP-IDF. We need to change a configuration value in order to enable a FreeRTOS function that lists the FreeRTOS tasks in an application. When menuconfig starts, navigate to (Top) Component config FreeRTOS Kernel and check the following options (the latter two are dependent on the first one, and will become visible when the first is enabled):
    • Enable FreeRTOS trace utility
    • Enable FreeRTOS stats formatting functions
    • Enable display of xCoreID in vTaskList

    Figure 2.22: Configuring FreeRTOS in menuconfig

  1. Build the project (PLATFORMIO | PROJECT TASKS | esp32s3box | General | Build).
  2. Flash and monitor the application to see the hi text on the serial monitor (PLATFORMIO/PROJECT TASKS | esp32s3box | General | Upload and Monitor).

    Figure 2.23: The serial monitor output when the application is configured successfully

Now that we have the project configured, we can develop the application, next.

Coding application

So far, so good. Now, we can implement the producer-consumer pattern in the src/main.cpp file. First, we clear the temporary code inside the file and then add the following headers:

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_log.h>

The freertos/FreeRTOS.h header file contains the backbone definitions based on the configuration. When we need a FreeRTOS function, we first include this header file, then the specific header where the needed function is declared. In our example, we will create tasks and a queue for the producer-consumer pattern; thus, we include freertos/task.h and freertos/queue.h respectively. The last header file, esp_log.h, is for printing log messages on the serial console. Instead of direct access to the serial output via iostream, we will use the ESP-IDF logging macros in this application. Then we can define the global variables in the file scope:

namespace
{
    QueueHandle_t m_number_queue{xQueueCreate(5, sizeof(int))};
    const constexpr int MAX_COUNT{10};
    const constexpr char *TAG{"app"};
    void producer(void *p);
    void consumer(void *p);
} // end of namespace

In the anonymous namespace, we define a FreeRTOS queue, m_number_queue. This will be the medium in which to exchange data between the producer and consumers. The xQueueCreate function (in fact, it is a macro) creates a queue to hold 5 integers. The producer will generate integers to push into the queue. The MAX_COUNT constant shows the maximum number of integers to be generated by the producer. TAG is required by the logging macros. We will use it as a parameter when we want to log something. A logging macro prints the provided tag before any message. producer and consumer are the functions to be passed to the FreeRTOS tasks. We will see how to do this next:

extern "C" void app_main()
{
    ESP_LOGI(TAG, "application started");
    xTaskCreate(producer, "producer", 4096, nullptr, 5, nullptr);

Now, we’re implementing the app_main function. Remember that this is the application entry point. The first statement is the ESP_LOGI macro call with TAG and a message. application started will be printed on the serial monitor when the application starts. There are other macros in the logging family, such as ESP_LOGE for errors and ESP_LOGW for warnings. In the next line after printing the log message, we create our first FreeRTOS task by calling xTaskCreate. It has the following syntax in the freertos/task.h header file:

xTaskCreate(task_function, task_name, stack_depth,
    function_parameters, priority, task_handle_address)

Looking at this prototype, xTaskCreate will create a FreeRTOS task that runs the producer function that we declared earlier. The task name will be producer with a stack size of 4096 bytes. We don’t pass any parameters to the task. The task priority is 5, and finally, we don’t provide any address for the task handle since we don’t need it in this example. The FreeRTOS scheduler will create the producer task with these parameters.

Then, we need the consumers:

    xTaskCreatePinnedToCore(consumer, "consumer-0", 4096, (void *)0,
                            5, nullptr, 0);
    xTaskCreatePinnedToCore(consumer, "consumer-1", 4096, (void *)1,
                            5, nullptr, 1);

We will have two consumers. For this, we use the xTaskCreatePinnedToCore function this time. It is very similar to xTaskCreate. Its prototype is:

xTaskCreatePinnedToCore(task_function, task_name, stack_depth,
    function_parameters, priority, task_handle_address, task_affinity)

In addition to the parameters that xCreateTask uses, xTaskCreatePinnedToCore needs a task affinity defined – i.e., on which core to run the task. In our example, the first consumer task will run on cpu-0, and the second one will run on cpu-1. This function is specific to ESP-IDF FreeRTOS in order to support dual-core processors as we mentioned earlier.

We have now created all the tasks. Let’s see the list of the FreeRTOS tasks that we have in this application with the following lines of code:

    char buffer[256]{0};
    vTaskList(buffer);
    ESP_LOGI(TAG, "\n%s", buffer);
} // end of app_main

To list the tasks, we call vTaskList with a buffer parameter. It fills the buffer with the task information and we print the buffer on the serial output. vTaskList has been enabled by a menuconfig entry during the project initialization phase. This completes the app_main function. Next, we will implement the producer task function in the anonymous namespace:

namespace
{
    void producer(void *p)
    {
        int cnt{0};
        vTaskDelay(pdMS_TO_TICKS(500));

In the producer function, we define a variable, cnt, to count the numbers that we push into the queue. Then, we implement a 500 ms delay in the task execution. We add a loop for enqueueing the numbers as follows:

        while (++cnt <= MAX_COUNT)
        {
            xQueueSendToBack(m_number_queue, &cnt, portMAX_DELAY);
            ESP_LOGI(TAG, "p:%d", cnt);
        }

In the loop, we use the xQueueSendToBack function of FreeRTOS to send the numbers into the queue. The xQueueSendToBack function takes the queue reference, a pointer to the value to be pushed into the queue, and the maximum time for which to block the task if the queue is full. The number that is passed to the queue is the value of the cnt variable itself. Therefore, we will see the numbers starting from 1 up to 10 in the queue. We finish the producer task function as follows:

        vTaskDelete(nullptr);
    } // end of producer

A FreeRTOS task cannot return, else the result would be an application crash. When we are done with a task and we don’t need it anymore, we simply delete it by calling the vTaskDelete function. This function takes the task handle as a parameter, and passing nullptr means that the current task is the one to be deleted. Since there is no task after that point, we can safely return from the producer function. Then we implement the consumer function:

    void consumer(void *p)
    {
        int num;

The consumer function will run on both cores of ESP32-S3. When we defined two consumer tasks in the app_main function, we passed the consumer function as the task function and the core number as the parameter to be passed to the consumer function. Therefore, the p argument of the function shows the core number. In the consumer function body, we first define a variable, num, to hold the values that come from the queue. Next comes the task loop:

        while (true)
        {
            xQueueReceive(m_number_queue, &num, portMAX_DELAY);
            ESP_LOGI(TAG, "c%d:%d", (int)p, num);
            vTaskDelay(2);
        }
    } // end of consumer
} // end of namespace

The task loop is an infinite loop, so the function will never return as it should be. The xQueueReceive function takes the same parameters as with the xQueueSendToBack function that we used in the producer function. However, the xQueueReceive function pops the value at the front of the queue. When all values in the queue are consumed, it will block the task until a new value arrives. If no value comes, then the xQueueReceive function will block forever since we passed portMAX_DELAY as its third argument. The application is ready to run on the devkit, let’s do it next.

Running the application

We can upload and monitor it by clicking on the Upload and Monitor project task of the PlatformIO IDE. Let’s discuss the output briefly:

<Previous logs are removed ...>
I (280) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (301) app: application started

After the start of the FreeRTOS schedulers on both CPUs, our application prints its first log as application started. Then we see the vTaskList output as follows:

I (301) app:
consumer-1      R   5       3580        9       1
main               X   1       1936        4       0
IDLE                R   0       892        6       1
IDLE                R   0       1012        5       0
producer           B   5       3500        7       -1
esp_timer           S   22     3432        3       0
ipc1                B   24      884         2       1
consumer-0      B 5       3412        8       0
ipc0                B   24      892         1       0

The columns in this table are:

  • Task name
  • Task state
  • Priority
  • Used stack in bytes
  • The order in which the tasks are created
  • Core ID

We can see our tasks in the list in addition to other default tasks. They are (in the order of creation) as follows:

  • The Inter-Processor Call (IPC) tasks (ipc0 and ipc1) for triggering execution on the other CPU
  • esp_timer for RTOS tick period
  • The main task that calls the app_main function (entry point) of the application
  • The IDLE tasks of FreeRTOS

After the default FreeRTOS tasks, our tasks start. When you look at the last column of the table, consumer-0 has started on cpu0, consumer-1 has started on cpu1, and for producer, the core ID value is displayed as -1, which means it can run on both CPUs.

The logs from the tasks come next on the serial output:

I (801) app: p:1
I (801) app: p:2
I (801) app: c1:1
I (801) app: p:3
I (801) app: c0:2
I (801) app: p:4
I (801) app: p:5
I (801) app: p:6
I (801) app: p:7
I (821) app: c1:3
I (821) app: p:8
I (831) app: c0:4
I (831) app: p:9
I (841) app: c1:5
I (841) app: p:10
I (851) app: c0:6
I (861) app: c1:7
I (871) app: c0:8
I (881) app: c1:9
I (891) app: c0:10

Because of the delays in the consumer tasks, the producer fills up the queue faster than the consumers remove numbers and the producer has to wait for the consumers to make some space so it can insert a new number. When consumer-1 removes 3 from the queue, then the producer can enqueue 8. It stops pushing new numbers when it gets to 10 as we coded. The rest of the job is only for the consumers to dequeue all numbers remaining in the queue.

This example demonstrated how to utilize FreeRTOS for a simple producer-consumer problem and the basic usage of the ESP32 cores with different tasks. We will continue to employ FreeRTOS in the examples of the upcoming chapters and learn about more of its features. The official ESP-IDF FreeRTOS API documentation is here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html.

In the next topic, we will discuss how we can debug our applications.

Debugging

All families of ESP32 MCUs support Joint Test Action Group (JTAG) debugging. ESP-IDF makes use of OpenOCD, an open-source software, to interface with the JTAG probe/adapter, such as an ESP-Prog debugger. To debug our applications, we use an Espressif version of gdb (the GNU debugger), depending on the architecture of ESP32 that we have in a particular project. The next figure shows a general ESP32 debug setup:

Figure 2.24: JTAG debugging

When we develop our own custom ESP32 devices, we can connect to the standard JTAG interface of ESP32 to debug the application. With this option, we need to use a JTAG probe between the development machine and the custom ESP32 device. The JTAG pins are listed on the official documentation for each family of ESP32 (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/jtag-debugging/configure-other-jtag.html).

The issue with JTAG debugging is that it requires at least 4 GPIO pins to carry the JTAG signals, which means 4 GPIO pins less available to your application. This might be a problem in some projects where you need more GPIO pins. To address this issue, Espressif introduces direct USB debugging (built-in JTAG) without a JTAG probe. In the preceding figure, the JTAG probe in the middle is not needed for debugging and OpenOCD running on the development machine talks directly to the MCU over USB. The built-in JTAG debugging requires only two pins on ESP32, which saves two pins compared to the ordinary JTAG debugging with a probe. This feature is not available in all ESP32 families but ESP32-C3 and ESP32-S3 do have it; thus, we will prefer this method in this example with our ESP32-S3-BOX-Lite devkit. We don’t need a JTAG probe but we still need a USB cable with the pins exposed outside to be able to connect them to the corresponding pins of the devkit. The connections are:

ESP32-S3 Pin    USB Signal
GPIO19            D-
GPIO20            D+
5V                V_BUS
GND            Ground

We can find a USB cable on many online shops with all lines exposed but it is perfectly fine to cut a USB cable and solder a 4-pin header to use its pins. You can see my simple setup below:

Figure 2.25: Built-in JTAG

We don’t need a driver for Linux or macOS to use the built-in JTAG debugging. The Windows driver comes with the ESP-IDF Tools Installer (https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/windows-setup.html#esp-idf-tools-installer).

Now, it is time to create the project and upload the firmware to see whether our setup works. In this example, we will see another way of creating an ESP-IDF project. We will work in the ESP-IDF environment from the command line and use the idf.py script to create the project and for other project tasks. Let’s do this in steps:

  1. If you are a Windows user, run the ESP-IDF Command Prompt shortcut from the Windows Start menu. It will open a command-line terminal with the ESP-IDF environment. If your development platform is Linux or macOS, start a terminal and run the export.sh or export.fish scripts respectively to have the ESP-IDF environment in the terminal:
    $ source ~/esp/esp-idf/export.sh 
    Detecting the Python interpreter
    Checking "python" ...
    Python 3.10.11
    "python" has been detected
    Adding ESP-IDF tools to PATH…
    <more logs>
    Done! You can now compile ESP-IDF projects.
    Go to the project directory and run:
    idf.py build
    
  2. Test the idf.py script by running it without any arguments. It will print the help message:
    $ idf.py
    Usage: idf.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
    ESP-IDF CLI build management tool. For commands that are not known to idf.py an attempt to execute it as a build system target will be made.
    <rest of the help message>
    
  3. Go to your project directory and run idf.py with a new project name. The script will create an ESP-IDF project with that name in a directory with the same name:
    $ idf.py create-project debugging_ex
    Executing action: create-project
    The project was created in <your project directory>/debugging_ex
    $ ls
    debugging_ex
    $ cd debugging_ex/
    $ tree
    .
    ├── CMakeLists.txt
    └── main
        ├──CMakeLists.txt
        └── debugging_ex.c
    1 directory, 3 files
    
  4. Download the sdkconfig file from the book repository into the project directory. It can be found here: https://github.com/PacktPublishing/Developing-IoT-Projects-with-ESP32-2nd-edition/blob/main/ch2/debugging_ex/sdkconfig.
  5. Run VSCode and open the debugging_ex directory.
  6. In the VSCode IDE, rename main/main.c to main/main.cpp and edit it to have the following code inside:
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    void my_func(void)
    {
        int j = 0;
        ++j;
    }
    extern "C" void app_main()
    {
        int i = 0;
        while (1)
        {
            vTaskDelay(pdMS_TO_TICKS(1000));
            ++i;
            my_func();
        }
    }
    
  7. Update the main/CMakeLists.txt file with the following content:
    idf_component_register(SRCS "debugging_ex.cpp" INCLUDE_DIRS ".")
    
  8. After these changes, we should have the following directory structure:
    $ tree
    .
    ├── CMakeLists.txt
    ├── main
    │   ├── CMakeLists.txt
    │   └── debugging_ex.cpp
    └── sdkconfig
    1 directory, 4 files
    
  9. We need to enable the debug options in the root CMakeLists.txt file. Edit it and set its content as the following:
    cmake_minimum_required(VERSION 3.16.0)
    include($ENV{IDF_PATH}/tools/cmake/project.cmake)
    project(debugging_ex)
    idf_build_set_property(COMPILE_OPTIONS "-O0" "-ggdb3" "-g3" APPEND)
    
  10. In the terminal, build the application by running idf.py:
    $ idf.py build
    Executing action: all (aliases: build)
    <more logs>
    Creating esp32s3 image...
    Merged 2 ELF sections
    Successfully created esp32s3 image.
    <more logs>
    
  11. See that the idf.py script has generated the application binary under the build directory:
    $ ls build/*.bin
    build/debugging_ex.bin
    
  12. Run the following command to see the basic size information of the application:
    $ idf.py size
    <some logs>
    Total sizes:
    Used static IRAM:   87430 bytes ( 274810 remain, 24.1% used)
          .text size:   86403 bytes
       .vectors size:    1027 bytes
    Used stat D/IRAM:   13941 bytes ( 158987 remain, 8.1% used)
          .data size:   11389 bytes
          .bss  size:    2552 bytes
    Used Flash size :  153979 bytes
          .text     :  113547 bytes
          .rodata   :   40176 bytes
    Total image size:  252798 bytes (.bin may be padded larger)
    
  13. Flash the application on the devkit (if flashing fails with a port error, just reverse the D+/D- connections of the devkit. This simple change will probably solve the problem):
    $ idf.py flash
    Executing action: flash
    Serial port /dev/ttyACM0
    Connecting...
    Detecting chip type... ESP32-S3
    <more logs>
    Leaving...
    Hard resetting via RTS pin...
    Done
    
  14. We now have an application running on the devkit, ready for debugging. Run a GDB server with the following command (idf.py will start an OpenOCD process for this):
    $ idf.py openocd --openocd-commands "-f board/esp32s3-builtin.cfg"
    Executing action: openocd
    OpenOCD started as a background task 477341
    Executing action: post_debug
    Open On-Chip Debugger v0.11.0-esp32-20221026 (2022-10-26-14:47)
    Licensed under GNU GPL v2
    <more logs> 
    
  15. Start another ESP-IDF command-line terminal as we did in step 1 and change the current directory to the project root directory.
  16. Run the following command to start a GDB client. It will open a web-based GUI in your default browser:
    $ idf.py gdbgui
    Executing action: gdbgui
    gdbgui started as a background task 476131
    

The idf.py script is the single point of contact to manage an ESP-IDF project. We can create, build, flash, and debug an application by only using this script. It has more features and we will have many chances to learn and practice those features throughout the book. The ESP-IDF build system documentation provides detailed information about the idf.py script and how it works with cmake to collect all the project components to compile them into an application. Here is the link for the documentation: https://docs.espressif.com/projects/esp-idf/en/v4.4.4/esp32s3/api-guides/build-system.html.

With a debugging session ready on the web GUI, we can now debug the application. The following screenshot shows this GUI.

Figure 2.26: Web-based debugger

The left panel shows the source code that is being debugged. We can set/remove breakpoints by clicking on the row numbers. The right panel shows the current status of the application, including threads, variables, memory, etc. On the top right, the buttons for the debug functions (restart, pause, continue, and step in/out/over) are placed. The debug functions also have keyboard shortcuts to ease the debugging process. Try the following debugging tasks on the GUI:

  • Click on line number 17 to set a breakpoint.
  • Press C (continue) on the keyboard and observe that the local variable i increases every time the execution hits the breakpoint.
  • Try pressing N (next) to run each of the lines consecutively.
  • When the execution comes to my_func, press S (step in) to enter the function. You can exit the function by pressing U (up).

This GUI is enough for an average debugging session and can be used to observe the behavior of the application when necessary. If you need to access other gdb commands, there is also another panel at the bottom where you can type these commands.

Unit testing

Whether you prefer Test-Driven Development (TDD) or just write unit tests as a safety net against regression, it is always wise to include them in the plans of any type of software project. Although the adopted testing strategy for a project depends on the project type and company policies, well-designed unit tests are the basic safeguard of any serious product. The time and effort you put into unit tests always pay off in every stage of the product life cycle, from the beginning of the development to the maintenance and upgrades.

For ESP32 projects, we have several unit-testing framework options. ESP-IDF supports the Unity framework but we can also use GoogleTest in our projects. We can configure PlatformIO to use any of them and run tests on a target device, such as an ESP32 devkit, and/or on the local development machine. Therefore, it is really easy to select different strategies for unit testing. For example, if the library that you are working on doesn’t need to use hardware peripherals, then it can be tested on the local machine and you can instruct PlatformIO to do this by simply adding some definitions to the platformio.ini file of the project.

We will create a sample project to see how unit testing is done in an ESP32 project next.

Creating a project

Let’s assume that we want to develop a simple light control class that sets a GPIO pin of ESP32-C3-DevKitM-1 to high/low in order to turn on and off the light that is connected to the GPIO pin. In this example, we will develop that class and write tests for it with the GoogleTest framework. We will also configure PlatformIO to run the tests on the devkit so that we know that the class works as intended on the real hardware. Let’s create the project as in the following steps:

  1. Start a new PlatformIO project with the following parameters:
    • Name: unit_testing_ex
    • Board: Espressif ESP32-C3-DevKitM-1
    • Framework: Espressif IoT Development Framework
  2. Open the platformio.ini file and set its content as follows:
    [env:esp32-c3-devkitm-1]
    platform = espressif32@6.2.0
    board = esp32-c3-devkitm-1
    framework = espidf
    build_flags = -std=gnu++11 -Wno-unused-result
    monitor_speed = 115200
    monitor_rts = 0
    monitor_dtr = 0
    monitor_filters = colorize
    lib_deps = google/googletest@1.12.1
    test_framework = googletest
    
  3. Build the project (PLATFORMIO | PROJECT TASKS | esp32-c3-devkitm-1 | General | Build).

The project is now configured and ready for development. However, before moving on, I want to briefly discuss the library management mechanism of PlatformIO. We have several options to add external libraries to our projects. The easiest one is probably just by referring to the PlatformIO registry. You can search the registry by navigating to PlatformIO Home/Libraries and typing the name of the library that you are looking for. When the library is listed, you select it and PlatformIO shows its detailed information. At this point, you can click on the Add to Project button to include the library in the project.

Figure 2.27: PlatformIO Registry

In our example, I preferred to specify googletest directly as in the following configuration line without using the graphical interface:

lib_deps = google/googletest@1.12.1

This line points to the PlatformIO registry. The format is <provider>/<library>@<version>. In this way, it is possible to add many other libraries consecutively in a project. With the lib_deps configuration parameter, we can also refer to other online repositories by providing their URLs.

The other popular option is to add local directories with lib_extra_dirs in platformio.ini. Any ESP-IDF-compatible library in these directories can be included in projects. I will talk about what compatible means in this context later in the book.

You can learn more about the PlatformIO Library Manager at this link: https://docs.platformio.org/en/latest/librarymanager/index.html.

You may have noticed that we can also set the platform version:

platform = espressif32@6.2.0

With this configuration, we set the platform version to a fixed value so that no matter when we compile the project, we know that it will compile without any compatibility issues with all other versioned libraries and of course with our code.

After this brief overview of library management, we can continue with the application.

Coding the application

Let’s begin with adding a header file, named src/AppLight.hpp, for the light control class and add the required header for GPIO control:

#pragma once
#include "driver/gpio.h"
#define GPIO_SEL_4 (1<<4)

Then we define the class as follows:

namespace app
{
    class AppLight
    {
    private:
        bool m_initialized;

In the private section of the class, we define a member variable, m_initialized, which shows if the class is initialized. The public section comes next:

    public:
        AppLight() : m_initialized(false) {}
        void initialize()
        {
            if (!m_initialized)
            {
                gpio_config_t config_pin4{
                  GPIO_SEL_4,
                  GPIO_MODE_INPUT_OUTPUT,
                  GPIO_PULLUP_DISABLE,
                  GPIO_PULLDOWN_DISABLE,
                  GPIO_INTR_DISABLE
                };
                gpio_config(&config_pin4);
                m_initialized = true;
            }
            off();
        }

After the constructor, we implement the initialize function. Its job is to configure the GPIO-4 pin of the devkit if it is not initialized yet and set its initial state to off. We use the gpio_config function to configure a GPIO pin as defined in the configuration structure that is provided as input. Here, it is config_pin4. The gpio_config function and the gpio_config_t structure are declared in the driver/gpio.h header file.

The off function is another member function of the class to be implemented next:

       void off()
        {
            gpio_set_level(GPIO_NUM_4, 0);
        }
        void on()
        {
            gpio_set_level(GPIO_NUM_4, 1);
        }
    };
} // namespace app

In the off member function, we call gpio_set_level with the parameters of GPIO_NUM_4 as the pin number and 0 as the pin level. Again, the gpio_set_level function is declared in the driver/gpio.h header file. Similarly, we add another function, on, in order to set the pin level to 1, or high.

The AppLight class is ready and we can write the test code for it next.

Adding unit tests

We create another source file, test/test_main.cpp, and add the header files that are needed for the unit tests:

#include "gtest/gtest.h"
#include "AppLight.hpp"
#include "driver/gpio.h"

For the AppLight testing, it would be a good idea to create a test fixture:

namespace app
{
    class LightTest : public ::testing::Test
    {
    protected:
        static AppLight light;
        LightTest()
        {
            light.initialize();
        }
    };
    AppLight LightTest::light;

The name of the test fixture is LightTest and it is derived from the ::testing::Test base class. In its protected area, we declare a static AppLight object and initialize it in the constructor of the fixture. With the fixture ready, we can now write a test as follows:

    TEST_F(LightTest, turns_on_light)
    {
        light.on();
        ASSERT_GT(gpio_get_level(GPIO_NUM_4), 0);
    }

The TEST_F macro defines a test on a test fixture. The first parameter shows the fixture name and the second parameter is the test name. In the test, we turn the light on, and assert whether it is really turned on. The ASSERT_GT macro checks whether the first parameter is greater than the second one.

Another test checks whether the off function is working properly or not. It is very similar to the previous test:

    TEST_F(LightTest, turns_off_light)
    {
        light.off();
        ASSERT_EQ(gpio_get_level(GPIO_NUM_4), 0);
    }
} // namespace app

This time, we turn the light off, and check whether it is actually turned off by using the ASSERT_EQ macro.

For each new test, a new fixture object will be created. That is why we defined the light object as static since we don’t want it to be initialized every time a new fixture is created. For more information about GoogleTest, see its documentation here:https://google.github.io/googletest/primer.html.

We still need an app_main function as usual. Here it comes:

extern "C" void app_main()
{
    ::testing::InitGoogleTest();
    RUN_ALL_TESTS();
}

The two lines in the app_main function initialize and run all of the test cases. This finalizes the test coding. Let’s run it on the devkit and see the test results.

Running unit tests

We can run the test application on the devkit and see the unit test results as in the following steps:

  1. Plug the devkit into one of the USB ports of your development machine.
  2. Navigate to PLATFORMIO | PROJECT TASKS | esp32-c3-devkitm-1 | Advanced and click on the Test option there.

    Figure 2.28: PlatformIO unit testing

  1. PlatformIO will compile the test application, upload it, and then run the tests. You can see the result on the terminal that popped up when you clicked on the Test option.

    Figure 2.29: Terminal output

The terminal lists the tests and the results. When a test fails, you can go back to the code, debug it, and run the tests again until they all pass.

With this topic, we conclude the chapter. However, I strongly suggest you don’t limit yourself to the explanations here and try other tools from both PlatformIO and ESP-IDF. I will continue to talk about them throughout the book and use them within the examples to help you get familiar with the tools and their features as much as possible.

Summary

In this chapter, we have learned about the tools and the basics of ESP32 development. ESP-IDF is the official framework to develop applications on any family of ESP32 series microcontrollers, maintained by Espressif Systems. It comes with the entire set of command-line utilities that you would need in your ESP32 projects. PlatformIO adds more IDE features on top of that. With its strong integration with the VSCode IDE and declarative project configuration approach, it provides a professional environment for embedded developers.

In the next chapter, we’ll discuss the ESP32 peripherals. Although it is impossible to cover all of them in a single chapter, we will learn about the common peripherals using examples so that we can easily carry out the tasks in real projects and the other experiments in the book.

Questions

Let’s answer the following questions to test our knowledge of the topics covered in this chapter:

  1. What is the name of the script file that the ESP-IDF build system uses to configure an ESP32 project?
    1. CMakeLists.txt
    2. platformio.ini
    3. main.cpp
    4. Makefile
  2. What is the name of the project-specific configuration file that is usually edited by running menuconfig?
    1. CMakeLists.txt
    2. sdkconfig
    3. pio
    4. platformio.ini
  3. Which of the following is the most fundamental tool that comes with ESP-IDF to manage an ESP32 project?
    1. pio
    2. openocd
    3. gdb
    4. idf.py
  4. Which of the following methods is the easiest to debug an ESP32-S3 board?
    1. JTAG
    2. SWD
    3. Built-in JTAG/USB
    4. UART
  5. Which file defines a PlatformIO project?
    1. CMakeLists.txt
    2. sdkconfig
    3. pio
    4. platformio.ini

Further reading

Learn more on Discord

To join the Discord community for this book – where you can share feedback, ask questions to the author, and learn about new releases – follow the QR code below:

https://discord.gg/3Q9egBjWVZ

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Build IoT projects from scratch using ESP32
  • Customize solutions, take them to cloud, visualize real-time data, implement security features
  • Practice using a variety of hands-on projects such as an audio player, smart home, and more

Description

ESP32, a low-cost and energy-efficient system-on-a-chip microcontroller, has become the backbone of numerous WiFi devices, fueling IoT innovation. This book offers a holistic approach to building an IoT system from the ground up, ensuring secure data communication from sensors to cloud platforms, empowering you to create production-grade IoT solutions using the ESP32 SoC. Starting with IoT essentials supported by real-world use cases, this book takes you through the entire process of constructing an IoT device using ESP32. Each chapter introduces new dimensions to your IoT applications, covering sensor communication, the integration of prominent IoT libraries like LittleFS and LVGL, connectivity options via WiFi, security measures, cloud integration, and the visualization of real-time data using Grafana. Furthermore, a dedicated section explores AI/ML for embedded systems, guiding you through building and running ML applications with tinyML and ESP32-S3 to create state-of-the-art embedded products. This book adopts a hands-on approach, ensuring you can start building IoT solutions right from the beginning. Towards the end of the book, you'll tackle a full-scale Smart Home project, applying all the techniques you've learned in real-time. Embark on your journey to build secure, production-grade IoT systems with ESP32 today!

Who is this book for?

If you are an embedded software developer, an IoT software architect or developer, a technologist, or anyone who wants to learn how to use ESP32 and its applications, this book is for you. A basic understanding of embedded systems, programming, networking, and cloud computing concepts is necessary to get started with the book.

What you will learn

  • Explore ESP32 with IDE and debugging tools for effective IoT creation
  • Drive GPIO, I2C, multimedia, and storage for seamless integration of external devices
  • Utilize handy IoT libraries to enhance your ESP32 projects
  • Manage WiFi like a pro with STA & AP modes, provisioning, and ESP Rainmaker framework features
  • Ensure robust IoT security with secure boot and OTA firmware updates
  • Harness AWS IoT for data handling and achieve stunning visualization using Grafana
  • Enhance your projects with voice capabilities using ESP AFE and Speech Recognition
  • Innovate with tinyML on ESP32-S3 and the Edge Impulse platform
Estimated delivery fee Deliver to South Africa

Standard delivery 10 - 13 business days

$12.95

Premium delivery 3 - 6 business days

$34.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Nov 30, 2023
Length: 578 pages
Edition : 2nd
Language : English
ISBN-13 : 9781803237688
Vendor :
Espressif Systems
Category :
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Colour book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Estimated delivery fee Deliver to South Africa

Standard delivery 10 - 13 business days

$12.95

Premium delivery 3 - 6 business days

$34.95
(Includes tracking information)

Product Details

Publication date : Nov 30, 2023
Length: 578 pages
Edition : 2nd
Language : English
ISBN-13 : 9781803237688
Vendor :
Espressif Systems
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 125.97
TinyML Cookbook
$43.99
Building Smart Home Automation Solutions with Home Assistant
$31.99
Developing IoT Projects with ESP32
$49.99
Total $ 125.97 Stars icon

Table of Contents

14 Chapters
Introduction to IoT development and the ESP32 platform Chevron down icon Chevron up icon
Understanding the Development Tools Chevron down icon Chevron up icon
Using ESP32 Peripherals Chevron down icon Chevron up icon
Employing Third-Party Libraries in ESP32 Projects Chevron down icon Chevron up icon
Project – Audio Player Chevron down icon Chevron up icon
Using Wi-Fi Communication for Connectivity Chevron down icon Chevron up icon
ESP32 Security Features for Production-Grade Devices Chevron down icon Chevron up icon
Connecting to Cloud Platforms and Using Services Chevron down icon Chevron up icon
Project – Smart Home Chevron down icon Chevron up icon
Machine Learning with ESP32 Chevron down icon Chevron up icon
Developing on Edge Impulse Chevron down icon Chevron up icon
Project – Baby Monitor Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.3
(45 Ratings)
5 star 66.7%
4 star 17.8%
3 star 4.4%
2 star 4.4%
1 star 6.7%
Filter icon Filter
Top Reviews

Filter reviews by




sgmustadio Feb 24, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Full disclosure: I was given a copy of the book to write a review.The ESP32 is a fan favorite among engineers, makers, and tinkerers looking to create various IoT projects. For the most part, I stick with the Arduino IDE and libraries when working with the ESP32. However, the non-Arduino option for development, ESP-IDF, is a wonderful suite of tools built to work with nearly any editor. If you are looking to develop professional, robust IoT projects with the ESP32, you will likely gravitate toward ESP-IDF.“Developing IoT Projects with ESP32” starts with a brief overview of the ESP32 hardware and ESP-IDF. In reality, the author assumes you have some working knowledge and experience with the ESP32, which is not a bad thing. This is not a book for beginners. Even though a “getting started” tutorial is provided for ESP-IDF, I would highly recommend using other online resources for setting up and using ESP-IDF. The official Espressif documentation covers more details and is plenty easy to follow.However, the book really shines after you have a basic grasp of ESP-IDF. It covers many topics required for developing professional projects that are sorely lacking in most online content. For example, it goes over the basics of FreeRTOS (the ESP-IDF flavor), step-through debugging, advanced libraries, REST, MQTT, security, IoT cloud services, and machine learning. If I were considering using the ESP32 for developing a professional project, this is the book I would want as my companion and reference guide.The biggest drawback is the lack of Bluetooth content. This chapter was apparently present in the first edition but sadly removed for this version. Even if WiFi is more popular, I think dismissing Bluetooth is a mistake, as plenty of developers still want to use low-energy, wireless connections to various devices (such as smartphones). The addition of machine learning chapters is welcome, but having both Bluetooth and ML would have been better.Additionally, the book switches between PlatformIO and native ESP-IDF tooling for installing third-party libraries. I recognize that PlatformIO is very popular, but I have generally struggled with it. My preference would be a focus on using the ESP-IDF and CMake for installing/linking such libraries, as that is a more portable approach.While the book is best used as a reference guide, it contains several projects that you can build to further grasp the concepts and tools: an audio player, a smart home plug, and a baby monitor. I highly recommend the book to anyone who wants to move beyond Arduino and level up their IoT skills using the ESP32.
Amazon Verified review Amazon
Dan G Jan 19, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book meticulously steps through the necessary tools and coding for building real IoT projects with the ESP32. I honestly can't think of a better book to learn and expand your knowledge on embedded and IoT. The examples are practical and provide a wide spectrum on the details of working with the internals, peripherals, cloud services, and user interfaces of a working solution. I really appreciate the syntax highlighting of the code samples. There is a lot packed into this book and it is well worth every step to follow along. Great job putting this second edition together with all new material.
Amazon Verified review Amazon
mperez Nov 30, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Disclaimer: I was given an early review copy of this book and did not purchase it.TLDR: Loved the book, definitely keeping it nearby while developing IoT projects This book does a fantastic job on many fronts:- succinctly highlighting details about the ESP32 family- thorough explanations on build setup (a major pain in many projects)- thorough code snippets that include full configuration parameters, making it a great reference- introducing first and third-party libraries that would cover a majority of needs (file systems, json, etc).- demonstrating projects of different scales, increasing the scope in large but exciting steps.- including sections on cloud integrations and ML- emphasizing different aspects of security in well-placed chunksComments:- I was really hoping to see sections involving bluetooth or LoRa- I like that the book used devices with both Xtensa and RISC-V architecturesIf I were to characterize the readers that would benefit most from this book, it would be Advanced Beginners in embedded projects. Programmers from other domains that are interested in IoT, engineering students, hobbyists that have already dabbled in microcontrollers, anyone in the "Ok, I can flash firmware and blink an LED, what's next?" phase or above.
Amazon Verified review Amazon
Chris Richards Jan 30, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
When I started my journey with the ESP32 the first edition of this book was the go to reference for everyone. Everywhere I looked to find guidance on how to begin with this awesome platform I was met with a clear "go read Vedat's book". The new second edition has improved on that reverential text in many ways. The examples are relevant, clear, and concise. The demonstration hardware was out of stock while I was reviewing the book but comparable kits are all over the place; if you know what to look for.And that is the rub, like the first edition before it this book assumes a level of familiarity with coding and microcontrollers you won't likely have if it is your first time. And while the first edition dominated the landscape because there were no beginner friendly alternatives, now we have a plethora of choices for all levels of experience. So, who is this book for?If you are just getting started in microcontrollers with no or very little coding experience, I suggest you start with Arduino. There are many websites and books devoted to you.If you are already comfortable in embedded C/C++ this book may be interesting but honestly you will probably get more value out of the Espressif IDF online documentation, it is fantastic.For C++ developers who have never played with a microcontroller before this book is exactly where you should begin. You will get elegant step by step guidance through the basic applications of the ESP32 using modern code structures.If you are not comfortable with C++, this book could be great as your first experience in microcontrollers as long as you take the time to work through the additional reference texts provided by the author. It will be a lot of work, but very fulfilling when you see the device light up for the first time. It is a magical feeling.Over all, highly recommended, as long as it is to the right audience.
Amazon Verified review Amazon
Brian Tol Dec 01, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I found "Developing Products with the ESP32" to be a valuable resource. The book provides a well-rounded introduction to the ESP32 line of chips, emphasizing their security features, which is crucial in the IoT space.The author's detailed overview of hardware debugging and unit testing is especially beneficial, given the complexity often encountered in these areas. Additionally, the solid coverage of Real-Time Operating Systems (RTOSes), with a focus on FreeRTOS, complements the technical depth of the book. The book assumes a basic level of electronics knowledge and a solid programming background.For developers delving into embedded systems, the book covers the essentials: storage, GPIO, GUI, and WiFi, alongside a useful introduction to MQTT and REST protocols. While it doesn't dive deep into other IoT protocols and GraphQL, this choice by the author streamlines the content without compromising its utility.The inclusion of discussions on the Rainmaker IoT Platform and AWS IoT Core, with insights into device shadows and time series data, is particularly pertinent. The book also touches upon AI and TinyML, bringing an added dimension to IoT development.What I appreciate most is how the author ties these diverse topics together with practical smart-home projects, offering plenty of examples and clear explanations. It's worth noting, though, that the book is lighter on protocols like BLE, LoRa, and NB-IoT. While these are conscious choices by the author, readers should be aware of the need to seek additional resources for these areas.Overall, it's an excellent introduction to the ESP32 and basic IoT development, but it's not a beginner's guide. For anyone looking to enter the IoT domain, this book provides a solid foundation, although it doesn't delve into the finer details of production-grade systems. Highly recommended for those wanting to get up to speed with the Espressif ecosystem and the essentials of IoT development.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela