Digital frequency meter with ESP32 and TFT display: an economical and essential solution for the electronics laboratory


The digital frequency meter with ESP32 is a simple but very useful tool in every electronics laboratory. In the world of electronics and circuit design, the need to accurately measure the frequencies of digital signals is critical. However, professional measurement tools, such as digital frequency meters, can be expensive and out of reach for hobbyists, students and small laboratories. Fortunately, thanks to modern microcontrollers such as the ESP32, it is possible to make a low-cost digital frequency counter that offers good performance.

The project we present takes advantage of the ESP32, a powerful microcontroller equipped with multiple features, including the LEDC module for the generation of PWM signals and the ability to measure digital frequencies with high precision. Using a 2.8-inch TFT display with ILI9341 drivers, the result is a compact, versatile and, above all, economical measuring tool. This digital frequency meter is ideal for anyone who needs a reliable tool for testing and calibrating electronic circuits without having to invest in expensive equipment.

In general, a digital frequency counter finds application in a wide range of scenarios. In digital circuits, it can be used to check the clock frequency of microcontrollers, oscillators, and communication signals. In analog circuits, it can help characterize the frequency response of filters, amplifiers, and other components. Furthermore, it is useful in teaching and experimentation, offering students a practical tool to better understand the behavior of periodic signals and frequency theory.

Building this project does not require advanced skills in electronics or programming. Thanks to the open-source libraries and developer community, you can set up and use the ESP32 relatively easily. The project uses the TFT_eSPI library for managing the TFT display and the FreqCountESP library for frequency measurement, ensuring accuracy and ease of use.

In this article, we will explore in detail the necessary components, how the digital frequency meter works, and how to assemble and program the device. In the end, you will have a powerful and versatile measurement tool, built on a shoestring budget, that will allow you to explore and analyze a wide range of digital signals with ease and precision.

As usual we will use the Platformio IDE for creating the project.

The FreqCountESP library

The FreqCountESP library is an essential tool for building a digital frequency counter with the ESP32. Developed specifically to take advantage of the capabilities of the ESP32 microcontroller, this library allows you to precisely measure the frequency of incoming digital signals. Its design has been optimized to ensure high accuracy and to simplify implementation in code, making it ideal for hobbyists and developers.

FreqCountESP uses the ESP32’s internal timers to count the number of signal transitions (state changes from low to high or vice versa) that occur on a given GPIO pin in a predefined time interval. This technique allows you to calculate the frequency of the signal precisely and reliably. The library supports measurements in a wide frequency range, making it versatile for different applications.

One of the main advantages of FreqCountESP is its ease of use. The library’s interface is designed to be intuitive, allowing you to get started quickly with just a few lines of code. Once you’ve included the library in your project, you simply need to configure the input pin and start frequency counting. The library automatically handles the complex details of transition counting and frequency calculation, returning the measured value in hertz.

Additionally, the library is well documented and community supported, offering practical examples and tips for optimizing measurements. This support makes FreqCountESP an excellent choice for anyone looking to add frequency measurement functionality to an ESP32-based project.

In summary, the FreqCountESP library represents a powerful and convenient solution for measuring digital signal frequencies with the ESP32. Its accuracy, combined with ease of use, makes it an indispensable tool for developers and electronics enthusiasts who want to create an effective, low-cost digital frequency counter.

The FreqCountESP library does not rely on FFT (Fast Fourier Transform) to measure the frequency of digital signals. Instead, it uses a method of directly counting signal transitions (low to high or high to low) that occur on a GPIO pin of the ESP32. This approach is simple and straightforward, particularly suitable for square wave digital signals.

Measurement method details

  • Transition counting: the library takes advantage of the ESP32’s internal timers to count the number of signal transitions on a given GPIO pin in a predefined time interval. Each change in signal state (from LOW to HIGH or from HIGH to LOW) is recorded.
  • Time lapse: the time interval over which transitions are counted is critical to the accuracy of the measurement. A longer interval allows for more precise measurement of low frequencies, while for high frequencies the interval can be reduced.
  • Frequency calculation: at the end of the time interval, the number of counted transitions is used to calculate the signal frequency.

Advantages of the direct counting method

  • Simplicity: It does not require complex mathematical transformations like the FFT, making the code simpler and faster to execute. This is particularly useful for real-time applications where speed of response is crucial.
  • Precision: for square wave digital signals, the direct counting method can provide very precise measurements. This is because the method directly measures the number of cycles of the signal, reducing possible sources of error associated with approximating frequencies with the FFT.
  • Efficiency: by using the ESP32’s hardware timers, the load on the main processor is minimal. This allows the ESP32 to perform other tasks at the same time as measuring frequency, improving the overall efficiency of the system.
  • Ease of implementation: the FreqCountESP library is designed to be easy to use. With just a few lines of code, you can start measuring the frequency of digital signals, making it accessible even to those with little experience with microcontroller programming.

Introduction to FFT

Since we mentioned it, let’s say a few words about the FFT. FFT, short for Fast Fourier Transform, is a fundamental algorithm in many signal processing, data analysis and engineering applications. The FFT allows you to transform a signal from the time domain to the frequency domain, breaking it down into its sinusoidal components of different frequencies. This process is known as spectral analysis and allows you to understand which frequencies are present in a signal and with what intensity.

What is the Fourier Transform?

Before talking about the FFT, it is useful to understand the Fourier Transform. The Fourier Transform is a mathematical operation that transforms a continuous time-domain signal into a frequency-domain signal. The discrete version of this transform, called DFT (Discrete Fourier Transform), is applicable to digital signals, which are sampled at discrete intervals.

Advantages of FFT

The FFT is a fast implementation of the DFT. While DFT has a computational complexity of O(N2), where N is the number of points in the signal, FFT reduces this complexity to O(N log⁡ N). This makes FFT much more efficient and practical for the analysis of large signals, allowing real-time processing on devices with limited resources, such as microcontrollers and embedded systems.

Applications of the FFT

FFT finds application in a wide range of fields, including:

  • Audio signal processing: frequency analysis, noise reduction, equalization and sound compression.
  • Digital communications: signal modulation and demodulation, frequency spectrum analysis.
  • Images and videos: data compression, filtering and image enhancement.
  • Diagnostics and predictive maintenance: vibration analysis and condition monitoring in machinery and structures.

Limitations of the FFT

Despite its power, FFT has some limitations. The accuracy of the transform depends on the length of the signal and the sampling frequency. Furthermore, FFT assumes that the signal is periodic and infinitely repeated, which can introduce artifacts if the signal does not meet this condition. These artifacts can be mitigated through windowing techniques, which consist of applying a window to the signal to reduce discontinuities at the edges.

Comparison between direct counting and FFT

In the context of our ESP32-based digital frequency counter, the direct transition counting method is more appropriate for square wave digital signals. FFT, while extremely powerful and versatile, would be overkill and less efficient for this specific application. However, for applications that require spectral analysis of complex signals with multiple frequency components, the FFT is an indispensable tool.

In conclusion

FFT is a powerful signal analysis tool that transforms a signal from the time domain to the frequency domain. While not necessary for a simple digital frequency meter like the one described in this article, the FFT offers advanced analytical capabilities for a wide range of applications. Understanding the fundamental concepts of FFT provides a solid foundation for exploring more complex applications in the future when it is necessary to examine the frequency composition of complex signals.

What is ILI9341 2.8″ SPI LCD TFT Display?

The ILI9341 2.8″ SPI TFT LCD Display is a type of high-resolution color liquid crystal display (LCD) designed to provide a visual interface in electronic projects. It communicates with the microcontroller via a dedicated SPI interface. In addition to color display, it features of a resistive touchscreen that allows direct interaction with the user (via dedicated SPI interface), and of an SD (Secure Digital) card slot that allows you to easily store and access data (via dedicated SPI interface).

Technical features:

Screen size: 2.8 inches (diagonal)
Risoluzione: 320 x 240 pixel
Controller: ILI9341, a popular TFT display controller that supports communication via SPI (Serial Peripheral Interface)
Interface: SPI (Serial Peripheral Interface) for communication with microcontrollers and other devices
Touchscreen: resistive, which allows direct interaction with the user through pressure
SD card slot: for accessing SD memory cards via the SPI interface, allowing data storage and access
Colors: Supports display of 65,536 colors (16-bit)
Viewing angle: good, with clear vision from different angles
Backlight: LED backlight for uniform and adjustable brightness
Compatibility: compatible with a wide range of microcontrollers and development boards

Functionality and use: the ILI9341 2.8″ SPI TFT LCD display with resistive touchscreen and SD card slot can be used in a variety of applications, including:

  • Creating interactive user interfaces for embedded devices and IoT projects
  • Display of text, graphics and images with the possibility of interaction via touchscreen
  • Implementation of data logging systems that use the SD card slot to store and access data
  • Making portable media devices, such as video players and image viewers, with the ability to store media files on the SD card


  • User interaction: the resistive touchscreen allows direct and intuitive interaction with the user interface.
  • External storage: the SD card slot offers the possibility of storing a large amount of data externally to the main device (for example images to be used as wallpaper or icons).
  • Versatility: thanks to its SPI interface and its advanced technical features, it is suitable for a wide range of applications in electronics and embedded computing.


  • Programming complexity: it may require some familiarity with embedded programming and graphics libraries to take full advantage of its features.
  • Limited screen size: display size may be limited for some applications that require a larger view.

In summary, the ILI9341 2.8″ SPI TFT LCD Display with resistive touchscreen and SD card slot is a versatile choice for embedded projects that require high-quality color display, user interaction and external storage capacity. With its advanced features and compatibility with a wide range of microcontrollers, it is ideal for a variety of applications in embedded electronics and computing.

The graphics libraries used: TFT_eSPI and TFT_eWidget

TFT_eSPI library: The TFT_eSPI library is a highly optimized graphics library designed to provide a simple and powerful interface for managing color TFT displays with microcontrollers based on ESP8266 and ESP32 architecture. Here are some of the main features of the TFT_eSPI library:

  • Compatibility: supports a wide range of TFT displays with SPI-compatible controllers, including the popular ILI9341 controller used in 2.8″ TFT displays.
  • High speed: takes advantage of the hardware capabilities of the ESP8266 and ESP32 microcontrollers to achieve high performance and fast screen refresh times.
  • Memory saving: it is designed to take up as little memory space as possible, allowing other microcontroller resources to be used for additional functionality.
  • Touchscreen support: integrates resistive touchscreen management functionality, allowing user interaction through touches and pressures on the display.
  • Advanced color management: offers a wide range of color management features, including viewing color images, creating graphs, and much more.
  • Graphics library: provides a variety of functions and methods for drawing geometric shapes, text and other graphics on the TFT display.

TFT_eWidget library: The TFT_eWidget library is an additional library designed to further simplify the development of interactive user interfaces on TFT displays using the TFT_eSPI library. Here are some of the main features of the TFT_eWidget library:

  • Default UI components: provides a wide range of user interface (UI) components, such as buttons, scrollbars, text boxes, and more, that can be easily integrated into your designs.
  • Advanced customization: allows full customization of UI components, including changing color, size, and style to fit specific project needs.
  • Easy integration: thanks to its intuitive interface and simple programming methods, it allows for quick and hassle-free integration of UI components into existing projects.
  • Touchscreen support: automatically manages user interaction via the resistive touchscreen, allowing intuitive use of UI components through taps and presses on the display.

In summary, the TFT_eSPI and TFT_eWidget libraries offer a complete and powerful development experience for creating interactive user interfaces on TFT displays with ESP8266 and ESP32 microcontrollers. With their advanced features and ease of use, they are ideal for a wide range of applications in embedded electronics and IoT.

SPI interface

The display, touchscreen and SD card reader each use dedicated SPI interfaces. It is a 4-wire serial communication protocol commonly used in embedded projects. These four wires are:

  1. MISO (Master In Slave Out): this is the pin through which the module receives data from the master device, which is usually the Arduino or another microcontroller.
  2. MOSI (Master Out Slave In): this is the pin through which the module sends data to the master device.
  3. SCK (Serial Clock): this is the clock pin that synchronizes data transmission between the module and the master device.
  4. CS (Chip Select): this pin is used to select the module and initialize data sending/receiving operations.

What components do we need for the digital frequency meter with ESP32?

The list of components is not particularly long:

The ESP32 model chosen for this project is that of the AZ-Delivery company.

Project implementation

The electrical diagram

Before creating the actual circuit let’s take a look at the pinout of the board:

Pinout of the ESP32
Pinout of the ESP32

TFT LCD display pinout: On the left side there are connections (top) for the touchscreen and (bottom) for the display. On the right side the connections for the SD card module
TFT LCD display pinout: On the left side there are connections (top) for the touchscreen and (bottom) for the display. On the right side the connections for the SD card module

The connections between the display and the ESP32 module will follow the indications in this table:

Connection table between the display and the ESP32

In this project we will not use either the touchscreen or the SD card module incorporated into the display.

In the following image you can see the electrical diagram created with Fritzing:

Digital frequency meter wiring diagram
Digital frequency meter wiring diagram

For those who prefer it, below we see the electrical diagram created using EasyEda:

Electrical diagram created with EasyEda
Electrical diagram created with EasyEda

The sketch

Let’s create the PlatformIO project

We have already seen the procedure for creating a PlatformIO project in the article How to create a project for NodeMCU ESP8266 with PlatformIO.

Although it refers to the ESP8266 board, the procedure is similar.
Simply, when choosing the platform, you will have to choose the AZ-Delivery ESP-32 Dev Kit C V4.

Do not install any of the libraries mentioned in the article.

Now edit the platformio.ini file to add these two lines:

monitor_speed = 115200
upload_speed = 921600

so that the file looks like this:

platform = espressif32
board = az-delivery-devkit-v4
monitor_speed = 115200
upload_speed = 921600
framework = arduino

The libraries (FreqCountESP, TFT_eSPI and TFT_eWidget) can be installed directly by modifying the platformio.ini file as follows:

platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =

We also need to replace a file in the TFT_eSPI library after it has been installed. This is the User_Setup.h file that you can download from the link below:

and, once unzipped, put this downloaded file in the project folder PROJECT_NAME/.pio/libdeps/az-delivery-devkit-v4/TFT_eSPI/ (after perhaps renaming the old file as User_Setup.h_OLD just to preserve it).

PLEASE NOTE: the path PROJECT_NAME/.pio/libdeps/az-delivery-devkit-v4/TFT_eSPI/ is the same for both Linux and Windows.

Finally, you need to download the Free_Fonts.h file from the link below and copy it into the project’s include folder:

You can download the project from the following link:

unzip it, take the main.cpp file and replace it with the one you have in the previously created project.

Now let’s see how the sketch works.

Initially the necessary libraries are included:

#include <Arduino.h>

#include <Arduino.h>
#include "Free_Fonts.h" // Include the header file attached to this sketch
#include <TFT_eSPI.h> 
#include <TFT_eWidget.h>           
#include <SPI.h>

#include "FreqCountESP.h"

The color scheme of the graphic indicator and the frequency of its updating are then defined:

// Meter colour schemes
#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5

#define TFT_GREY 0x2104 // Dark grey 16-bit colour
#define LOOP_PERIOD 100 // Display updates every 100 ms

int colour = TFT_BLUE;

Then you define the tft object that manages the display:

TFT_eSPI tft = TFT_eSPI(); 

This is followed by the rainbow function which establishes the colors of the various quadrants of the circular indicator shown on the display and indicating more or less large frequency values:

// #########################################################################
// Return a 16-bit rainbow colour
// #########################################################################
unsigned int rainbow(byte value)
  // Value is expected to be in range 0-127
  // The value is converted to a spectrum colour from 0 = blue through to 127 = red

  byte red = 0; // Red is the top 5 bits of a 16-bit colour value
  byte green = 0;// Green is the middle 6 bits
  byte blue = 0; // Blue is the bottom 5 bits

  byte quadrant = value / 32;

  if (quadrant == 0) {
    blue = 31;
    green = 2 * (value % 32);
    red = 0;
  if (quadrant == 1) {
    blue = 31 - (value % 32);
    green = 63;
    red = 0;
  if (quadrant == 2) {
    blue = 0;
    green = 63;
    red = value % 32;
  if (quadrant == 3) {
    blue = 0;
    green = 63 - 2 * (value % 32);
    red = 31;
  return (red << 11) + (green << 5) + blue;

The ringMeter function physically draws the circular indicator of the frequency value on the screen:

// #########################################################################
//  Draw the meter on the screen, returns x coord of righthand side
// #########################################################################
int ringMeter(int value, int vmin, int vmax, int x, int y, int r, const char *units, byte scheme, int flag)
  // Minimum value of r is about 52 before value text intrudes on ring
  // drawing the text first is an option
  x += r; y += r;   // Calculate coords of centre of ring

  int w = r / 3;    // Width of outer ring is 1/4 of radius
  int angle = 180;  // Half the sweep angle of meter (300 degrees)

  int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v

  byte seg = 3; // Segments are 3 degrees wide = 100 segments for 300 degrees
  byte inc = 6; // Draw segments every 3 degrees, increase to 6 for segmented ring

  // Variable to save "value" text colour from scheme and set default
  colour = TFT_BLUE;
  // Draw colour blocks every inc degrees
  for (int i = -angle+inc/2; i < angle-inc/2; i += inc) {
    // Calculate pair of coordinates for segment start
    float sx = cos((i - 90) * 0.0174532925);
    float sy = sin((i - 90) * 0.0174532925);
    uint16_t x0 = sx * (r - w) + x;
    uint16_t y0 = sy * (r - w) + y;
    uint16_t x1 = sx * r + x;
    uint16_t y1 = sy * r + y;

    // Calculate pair of coordinates for segment end
    float sx2 = cos((i + seg - 90) * 0.0174532925);
    float sy2 = sin((i + seg - 90) * 0.0174532925);
    int x2 = sx2 * (r - w) + x;
    int y2 = sy2 * (r - w) + y;
    int x3 = sx2 * r + x;
    int y3 = sy2 * r + y;

    if ((i < v) && (flag == 0)) { // Fill in coloured segments with 2 triangles
      switch (scheme) {
        case 0: colour = TFT_RED; break; // Fixed colour
        case 1: colour = TFT_GREEN; break; // Fixed colour
        case 2: colour = TFT_BLUE; break; // Fixed colour
        case 3: colour = rainbow(map(i, -angle, angle, 0, 127)); break; // Full spectrum blue to red
        case 4: colour = rainbow(map(i, -angle, angle, 70, 127)); break; // Green to red (high temperature etc.)
        case 5: colour = rainbow(map(i, -angle, angle, 127, 63)); break; // Red to green (low battery etc.)
        default: colour = TFT_BLUE; break; // Fixed colour
      tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
      tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
      //text_colour = colour; // Save the last colour drawn
    else // Fill in blank segments if flag = 1
      tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREY);
      tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREY);
  // Convert value to a string
  char buf[10];
  byte len = 3; if (value > 999) len = 7;
  dtostrf(value, len, 0, buf);
  buf[len] = ' '; buf[len+1] = 0; // Add blanking space and terminator, helps to centre text too!
  // Set the text colour to default

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  // Uncomment next line to set the text colour to the last segment value!
  tft.setTextColor(colour, TFT_BLACK);
  // Print value, if the meter is large then use big font 8, othewise use 4
  if (r > 84) {
    tft.setTextPadding(55*2); // Allow for 3 digits each 55 pixels wide
    tft.drawString(buf, x, y, 4); // Value in middle
  else {
    tft.setTextPadding(3 * 14); // Allow for 3 digits each 14 pixels wide
    tft.drawString(buf, x, y, 4); // Value in middle
  // Print units, if the meter is large then use big font 4, othewise use 2
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  // if (r > 84) tft.drawString(units, x, y + 60, 4); // Units display
  // else tft.drawString(units, x, y + 15, 2); // Units display
  tft.drawString(units, x, y + 25, 4);

  // Calculate and return right hand side x coordinate
  return x + r;

We then encounter the variable frequency which will store the measured frequency value, the constant inputPin which defines the GPIO 33 (which we will use as the signal input), the variable timerMs which defines a timer for the FreqCountESP library and the constants minimumFrequency and maximumFrequency which report the minimum and maximum measurable frequency value that will be passed to the ringMeter function to indicate the extremes (so that it adapts the graphic representation of the circular indicator to the value of the measured frequency):

uint32_t frequency = 0;

static const int inputPin = 33;
int timerMs = 1000;

static const int minimumFrequency = 1; // (HZ)
static const int maximumFrequency = 1000000; // (HZ)   (max 1MHz)

From tests carried out with a square wave signal generator I found that the frequency meter manages to reach the value of 1 MHz and above even with a certain deviation from the real value. Therefore, as the frequency increases beyond certain limits, the measured value loses accuracy. This value may vary due to any component tolerances and also to the length of the connections. Therefore we could have different prototypes which, with the same input frequency, will show different measured frequency values ​​(with a deviation of a few tens/hundreds of Hz).

We then encounter the setup function which initializes the serial port, initializes the display and sets its orientation, background color and font to use and the FreqCountESP is initialized:



FreqCountESP.begin(inputPin, timerMs);

We then encounter the loop function which begins with a block that deals with the measurement of the input frequency:

  if (FreqCountESP.available())
    frequency =;

defines the position of the graph:

// Set the the position, gap between meters, and inner radius of the meters
int xpos = 0, ypos = 5, gap = 4, radius = 52;

// Draw a large meter
xpos = 480/2 - 160, ypos = 0, gap = 15, radius = 100;

and, finally, draw the circular analog meter and print, always with the same function, the measured frequency value:

// Comment out above meters, then uncomment the next line to show large meter
ringMeter(frequency,minimumFrequency,maximumFrequency, xpos,ypos,radius," Hz",BLUE2RED, 0); // Draw analogue meter 

Video of the operation

In the following video we will test our frequency meter with a laboratory signal generator. The square wave with its 3Vpp amplitude and an offset of 1.5V is selected on the generator in order to have a square wave that varies between 0V (which represents the logical 0) and 3V (which represents the logical 1):

In a previous article, Square wave generator with ESP32: an economical solution for circuit testing, we created a simple square wave generator with the ESP32 which generates square waves of different frequencies (up to 1 kHz in the version proposed in the article). We can use it to test our frequency meter by connecting the two circuits in this way and bringing the maximumFrequency variable to 1000 to adapt the full scale of the frequency meter to the maximum frequency generated which is (if the sketch has not been modified) equal to 1 kHz.

Test diagram of the digital frequency meter with square wave generator with ESP32
Test diagram of the digital frequency meter with square wave generator with ESP32

The following video shows how it works:


If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)

If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.

Enter your name
Enter your email
0 0 votes
Article Rating
Inline Feedbacks
View all comments
Would love your thoughts, please comment.x
Scroll to Top