ESP32 Pulse Counter PCNT with ESP-IDF and Rotary Encoder Example

In this tutorial, we will learn to use pulse counter or PCNT module of ESP32 using ESP-IDF and introduce you to rotary encoder usage. We will briefly introduce rotary encoders and their working mechanism. Then include an introductory example provided in ESP-IDF under peripherals for PCNT (Pulse Counter). This will help you to understand how to read rising and falling edges input signals from the rotary encoder.

ESP32 Pulse Counter PCNT Introduction

ESP32 has dedicated multiple PCNT (Pulse Counter) modules. It can be used to count the number of rising or falling edges of digital input signals. Each PCNT module contains an independent counter and provides multiple channels. Each channel can be configured and used separately to measure rising/falling edges and values of counter increases or decreases accordingly.

Each pulse counter signal can detect edge or level of signals such as positive and negative edges or active high and low level. Hence, we can program each PCNT channel to trigger based on different signal types. We can combine these types to detect both edge and level signals. Combining both types will make each channel acts as a quadrature decoder. Most importantly each module also has a separate glitch filter which is useful to filter out noise from input signals.

Rotary Encoders Introduction

Rotary encoders are commonly used to measure the angular position of rotating objects eg shafts. The angular position gives a determination of the amount as well as type of rotation. Depending on the movement of rotation, an analog or digital signal is produced. It is useful in controlling servo motors and is used by microelectronic hobbyists quite frequently because of its ease of use.

Rotary Encoders are of two types. Either they are distinguished as output signal or sensing technology. The picture below shows a rotary encoder module that is commonly available in the market. It is a simple output rotary encoder.

Rotary Encoder Module
KY-040 Rotary Encoder Module

When you rotate the knob of the rotary encoder, it consists of small steps that feel like bumps. Commonly rotary encoders have 12 steps but that not always true. A rotary encoder can have 200 steps as well. One interesting feature of these types of encoders is that we can rotate them endlessly as there is no starting or stopping position.

Rotary Encoders Working

When the knob of the rotary encoder is moved, it provides us with an output signal A and B that consists of two square waves which are 90 degrees out of phase which each other. When signal A goes form positive to zero then the value of pulse B is read. Rotating the knob clockwise, results in positive pulse for signal B and rotating the knob anti-clockwise results in a negative pulse for signal B. This way the direction of rotation can be monitored by comparing both output signals through the microcontroller and also calculating the total number of pulses. The total number of pulses for every turn do not remain same and vary for each turn. Moreover, the speed can also be calculated by counting the (number of pulses per unit time (frequency). Therefore, rotary encoders prove to be more useful due to its features.

The figure below shows the output signal format for commonly used rotary encoders. You can view how the rotational direction of shaft is affecting the two output signals according to the graphs provided.

Rotary Encoder Working Mechanism

KY-040 Pins

The figure below shows the pinout of a commonly available rotatory encoder KY-040.

Rotary Encoder Module Pinout

As you may notice, this module consists of five pins. These are CLK, DT, SW, + and GND.

PinDescription
CLKThe CLK pin gives out Output A which is the main output pulse. When the rotary encoder is moved one step/click, the output changes from HIGH to LOW (one cycle). This pin determines the direction of rotation along with the DT pin.
DTThe DT pin gives out Output B which is 90 degrees out of phase with Output A. It also plays a part in determining the direction of rotation.
SWThe SW pin is the Switch. It allows the rotary encoder to work as a push button. This pin gives an output of the active low push button switch. This means that the voltage goes low when the knob of the rotary encoder is pressed from the top (push button effect).
+The + pin is also known as the VCC pin. Supply voltages in the range of 3.3-5V.
GNDThis is the Ground pin that is connected to the common ground.

Note: The CLK and DT pins produce square waves (Output A and Output B respectively) that are 90 degree out of phase with each other. These two signals are used to measure the direction of rotation (Clockwise/Anti-clockwise).

ESP32 Rotary Encoder with ESP-IDF

Let’s look at an example sketch provided by ESP-IDF to learn how to configure the rotary encoder and use it to display encoder values while the knob is rotated.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. We have named our project ‘ESP_IDF_PCNT_ROTARY_ENCODER.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module.

Click ‘Choose Template’ button to proceed forward.

ESP32 Rotary Encoder with ESP-IDF Pr

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘rotary_encoder’ under the pcnt tab under Peripherals. Now click ‘Create project using template rotary_encoder.’

ESP32 Rotary Encoder with ESP-IDF Pr

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens our ESP_IDF_PCNT_ROTARY_ENCODER project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer. The main folder contains the source code meaning the main.c file will be found here.

Now go to main > rotary_encoder_example_main.c and open it.

ESP32 Rotary Encoder with ESP-IDF Pr

The following code opens up. This example code will display the encoder value in the terminal, as the knob of the rotary encoder is rotated. This is done by first setting the rotary encoder configuration and then starting it.

/* PCNT example -- Rotary Encoder

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "rotary_encoder.h"

static const char *TAG = "example";

void app_main(void)
{
    // Rotary encoder underlying device is represented by a PCNT unit in this example
    uint32_t pcnt_unit = 0;

    // Create rotary encoder instance
    rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG((rotary_encoder_dev_t)pcnt_unit, 14, 15);
    rotary_encoder_t *encoder = NULL;
    ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &encoder));

    // Filter out glitch (1us)
    ESP_ERROR_CHECK(encoder->set_glitch_filter(encoder, 1));

    // Start encoder
    ESP_ERROR_CHECK(encoder->start(encoder));

    // Report counter value
    while (1) {
        ESP_LOGI(TAG, "Encoder value: %d", encoder->get_counter_value(encoder));
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

How the Code Works?

Firstly, we will start by including the necessary libraries that includes the FreeRTOS libraries to generate delays, esp_log.h to log the informational messages and rotary_encoder.h for the functionality of the rotary encoder.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "rotary_encoder.h"

This code uses Informational Logging. The log function takes in two arguments. The first argument is the tag and the second argument is a formatted string. Therefore this global variable will be useful while calling the ESP_LOGI() functions. Here, “example” is the tag that will be used while logging.

static const char *TAG = "example";

Inside the app_main() function, we first create a variable called ‘pcnt_unit’ and set its value to 0. In this example, the rotary encoder underlying device is depicted by this PCNT unit.

uint32_t pcnt_unit = 0;

Next, we create the rotary encoder instance using rotary_encoder_new_ec11(). This function takes in two parameters.

  • The first parameter is the address to the already initialized configuration structure config which is set to ROTARY_ENCODER_DEFAULT_CONFIG((rotary_encoder_dev_t)pcnt_unit, 14, 15) so that the initialization of the configuration is at the values specified as parameters. It sets the GPIOA as 14 for CLK and GPIOB as 15 for DT. Here rotary_encoder_config_t is a structure that is used for rotary encoder configuration.
  • The second parameter is the address of the pointer to encoder whose stored value is NULL.
   rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG((rotary_encoder_dev_t)pcnt_unit, 14, 15);
    rotary_encoder_t *encoder = NULL;
    ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &encoder));

The, filter out glitch (1us) using encoder->set_glitch_filter(encoder, 1). Here, we use an arrow operator because we have to access the set_glitch_filter() through a pointer.

ESP_ERROR_CHECK(encoder->set_glitch_filter(encoder, 1));

Finally, start the encode using encoder->start(encoder).

 ESP_ERROR_CHECK(encoder->start(encoder));

Inside the while loop, the encode value is printed in the terminal. It is accessed after every second using encoder->get_counter_value(encoder).

while (1) {
        ESP_LOGI(TAG, "Encoder value: %d", encoder->get_counter_value(encoder));
        vTaskDelay(pdMS_TO_TICKS(1000));
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor
ESP32 Rotary Encoder with ESP-IDF Project Flash chip

After the code flashes successfully, connect your ESP32 with the rotary encoder. Use the following connections to connect both the devices together.

Rotary EncoderESP32
+3.3V
GNDGND
CLKGPIO14
DTGPIO15
ESP32 with Rotary Encoder schematic diagram
ESP32 with Rotary Encoder schematic diagram

Now rotate the knob of the rotary encoder and see the values changing in the terminal.

ESP32 Rotary Encoder with ESP-IDF Project Terminal

You may also like to read:

Leave a Comment