PCNT for 2 input signals

EngineSpeed
Posts: 1
Joined: Mon Jun 19, 2023 4:53 pm

PCNT for 2 input signals

Postby EngineSpeed » Mon Jun 19, 2023 5:27 pm

Hello,

I want to use an ESP32 Dev Kit C V2 to read 2 separate analog signals. In my project, this is the vehicle engine RPM and vehicle speed.


I first used this project I found on Reddit as a guide: https://www.reddit.com/r/arduino/commen ... e_racecar/
But I realized I couldn't use pulseIn to read from multiple pins.

I couldn't find an example of how to read from two pins at once using the PCNT function.
I tried by "doubling" the code and use a different PCNT_UNIT, but when I try to read a signal from the second PCNT pin the ESP crashes.

Why is the ESP crashing when I try to read from 2 pins?
Also I am open if there is a better/easier way to do this.

Code: Select all

#include "driver/pcnt.h"                                 /* Include the required header file, Its working Arduion IDE 2.0*/

/* For detailed reference please follow the documentations in following Link:- 
  Link:- https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/pcnt.html
*/


#define PCNT_UNIT_UsedA      PCNT_UNIT_1                  /* Select the Pulse Count 0  as the unit..*/
#define PCNT_UNIT_UsedB      PCNT_UNIT_0                  /* Select the Pulse Count 1  as the unit..*/
#define PCNT_H_LIM_VAL      1                        /* Set the max limit to trigger the interrupt*/
#define PCNT_INPUT_SIG_IOA   18                            /* Pulse Input selected as GPIO 4 */
#define PCNT_INPUT_SIG_IOB   4                            /* Pulse Input selected as GPIO 34 */



int contadorOverflowA, diffA;                                    /* Variable to store the over flow count */
int contadorOverflowB, diffB;                                    /* Variable to store the over flow count */


pcnt_isr_handle_t user_isr_handleA = NULL;                /* User ISR handler for Interrupt */
pcnt_isr_handle_t user_isr_handleB = NULL;                /* User ISR handler for Interrupt */
/* *********************************************************************************
  ISR Function to trigen when ever over flow is detected.......
*************************************************************************************/
void IRAM_ATTR CounterOverflow_ISRA(void *arg)             
{
  /* Increment Over flow counter */
  contadorOverflowA = contadorOverflowA + 1;  
  /* Clear counter*/              
  PCNT.int_clr.val = BIT(PCNT_UNIT_UsedA);                 
  pcnt_counter_clear(PCNT_UNIT_UsedA);                     
}
void IRAM_ATTR CounterOverflow_ISRB(void *arg)             
{
  /* Increment Over flow counter */
  contadorOverflowB = contadorOverflowB + 1;  
  /* Clear counter*/              
  PCNT.int_clr.val = BIT(PCNT_UNIT_UsedB);                 
  pcnt_counter_clear(PCNT_UNIT_UsedB);                     
}

//------------------------------------------------------------
void Init_PulseCounterA (void)
{
  pcnt_config_t pcntFreqConfigA = { };                        // Declear variable for cinfig
  pcntFreqConfigA.pulse_gpio_num = PCNT_INPUT_SIG_IOA;         // Set the port ping using for counting
  pcntFreqConfigA.pos_mode = PCNT_COUNT_INC;                  // set Counter mode: Increase counter value
  pcntFreqConfigA.counter_h_lim = PCNT_H_LIM_VAL;             // Set Over flow Interupt / event value
  pcntFreqConfigA.unit = PCNT_UNIT_UsedA;                      //  Set Pulsos unit to ne used
  pcntFreqConfigA.channel = PCNT_CHANNEL_0;                   //  select PCNT channel 0
  pcnt_unit_config(&pcntFreqConfigA);                         // Configure PCNT.

  pcnt_counter_pause(PCNT_UNIT_UsedA);                        // Pause PCNT counter such that we can set event.
  pcnt_counter_clear(PCNT_UNIT_UsedA);                        // Clear PCNT counter to avoid ant mis counting.

  pcnt_event_enable(PCNT_UNIT_UsedA, PCNT_EVT_H_LIM);         // Enable event for when PCNT watch point event: Maximum counter value
  pcnt_isr_register(CounterOverflow_ISRA, NULL, 0, &user_isr_handleA);  // Set call back function for the Event.
  pcnt_intr_enable(PCNT_UNIT_UsedA);                          // Enable PCNT

  pcnt_counter_resume(PCNT_UNIT_UsedA);                       // Re-started PCNT.

 Serial.println("PCNT Init Completed A....");
}
void Init_PulseCounterB (void)
{
  pcnt_config_t pcntFreqConfigB = { };                        // Declear variable for cinfig
  pcntFreqConfigB.pulse_gpio_num = PCNT_INPUT_SIG_IOB;         // Set the port ping using for counting
  pcntFreqConfigB.pos_mode = PCNT_COUNT_INC;                  // set Counter mode: Increase counter value
  pcntFreqConfigB.counter_h_lim = PCNT_H_LIM_VAL;             // Set Over flow Interupt / event value
  pcntFreqConfigB.unit = PCNT_UNIT_UsedB;                      //  Set Pulsos unit to ne used
  pcntFreqConfigB.channel = PCNT_CHANNEL_0;                   //  select PCNT channel 0
  pcnt_unit_config(&pcntFreqConfigB);                         // Configure PCNT.

  pcnt_counter_pause(PCNT_UNIT_UsedB);                        // Pause PCNT counter such that we can set event.
  pcnt_counter_clear(PCNT_UNIT_UsedB);                        // Clear PCNT counter to avoid ant mis counting.

  pcnt_event_enable(PCNT_UNIT_UsedB, PCNT_EVT_H_LIM);         // Enable event for when PCNT watch point event: Maximum counter value
  pcnt_isr_register(CounterOverflow_ISRB, NULL, 0, &user_isr_handleB);  // Set call back function for the Event.
  pcnt_intr_enable(PCNT_UNIT_UsedB);                          // Enable PCNT

  pcnt_counter_resume(PCNT_UNIT_UsedB);                       // Re-started PCNT.

 Serial.println("PCNT Init Completed B....");
}

/* *********************************************************************************
   Function to clean the Counter and its variables......
*************************************************************************************/
void Clean_Counters()                                       
{

  pcnt_counter_pause(PCNT_UNIT_UsedA);                      // Pause PCNT counter such that we can set event.
  contadorOverflowA = 0;                                     // Clear global Over flow counter.
  pcnt_counter_clear(PCNT_UNIT_UsedA);                       // Clean Pulse Counter...
  pcnt_counter_resume(PCNT_UNIT_UsedA);                       // Re-started PCNT.

  pcnt_counter_pause(PCNT_UNIT_UsedB);                      // Pause PCNT counter such that we can set event.
  contadorOverflowB = 0;                                     // Clear global Over flow counter.
  pcnt_counter_clear(PCNT_UNIT_UsedB);                       // Clean Pulse Counter...
  pcnt_counter_resume(PCNT_UNIT_UsedB);                       // Re-started PCNT.

}



void setup() {
   Serial.begin(115200);
   delay(500);
  // put your setup code here, to run once:
Init_PulseCounterA();
Init_PulseCounterB();
}

void loop() 
{
  /* Add static variable to store previous value*/
  static int16_t Previous_countA = 0;
  static int Previous_contadorOverflowA = 0;
  // put your main code here, to run repeatedly:
  int16_t countA = 0;

   pcnt_get_counter_value(PCNT_UNIT_UsedA, &countA);

  /* Add static variable to store previous value*/
  static int16_t Previous_countB = 0;
  static int Previous_contadorOverflowB = 0;
  // put your main code here, to run repeatedly:
  int16_t countB = 0;

   pcnt_get_counter_value(PCNT_UNIT_UsedB, &countB);

  //  /* Print only if there is any difference or change in tha value.*/
  //  if( (Previous_count != count) || (Previous_contadorOverflow != contadorOverflow) )
  //  {
    diffA = contadorOverflowA - Previous_contadorOverflowA;
   Serial.print(" Each Count A= ");
   Serial.print((int)countA);
   Serial.print(", Overflow Count A= ");
   Serial.println((int)diffA);

     //  /* Print only if there is any difference or change in tha value.*/
  //  if( (Previous_count != count) || (Previous_contadorOverflow != contadorOverflow) )
  //  {
    diffB = contadorOverflowB - Previous_contadorOverflowB;
   Serial.print(" Each Count B= ");
   Serial.print((int)countB);
   Serial.print(", Overflow Count B= ");
   Serial.println((int)diffB);

    Previous_countA = countA;
   Previous_contadorOverflowA = contadorOverflowA;
    Previous_countB = countB;
   Previous_contadorOverflowB = contadorOverflowB;

  //  }

delay(500);  /* Delay is given to give result properly..*/

}

ElectRick
Posts: 1
Joined: Wed Apr 24, 2024 4:32 pm

Re: PCNT for 2 input signals

Postby ElectRick » Wed Apr 24, 2024 4:48 pm

Hey!

I had a similar issue and solved it by using pcnt_isr_service_install and pcnt_isr_handler_add which allows you to use different ISRs for different units.
The way I understand it, pcnt_isr_register just registers the same interrupt handler for all units.

Here is my code:

Code: Select all

// Example code taken from
// https://esp32.com/viewtopic.php?t=14660

#include "driver/pcnt.h"      // ESP32 library for pulse counter
#include "soc/pcnt_struct.h"  // to avoid "'PCNT' was not declared in this scope" error

#define ENC_L 4                     // GPIO pin for Encoder left wheel
#define ENC_R 5                     // GPIO pin for Encoder right wheel
#define PCNT_H_LIM_VAL 30           // upper counting limit, max. 32767, write +1 to overflow counter, when reached 

pcnt_unit_t units[2] = {PCNT_UNIT_0, PCNT_UNIT_1};      // select ESP32 pulse counter units (out of 0 to 7)
// PCNT_UNIT_0 for left side encoder, PCNT_UNIT_1 for right side

int16_t PulseCounters[2] = {0,0};           // pulse counters, max. value is 65536
int OverflowCounters[2] = {0,0};            // overflow counters for pulse counters       
uint16_t PCNT_FILTER_VAL=  1000;            // filter value for avoiding glitches in the count, max. 1023
                                            // length of ignored pulses in APB_CLK clock cycles (running at 80 MHz)
pcnt_isr_handle_t user_isr_handle = NULL;   // interrupt handler - not used

void CounterOverflow_Left(void *arg) {		// Interrupt for overflow of pulse counter
  OverflowCounters[0] = OverflowCounters[0] + 1;    		// Increase overflow counter
  PCNT.int_clr.val = BIT(PCNT_UNIT_0);                   	// Clean overflow flag
  pcnt_counter_clear(PCNT_UNIT_0);                       	// Zero and reset of pulse counter unit
}

void CounterOverflow_Right(void *arg) { 	// Similar, just for pulse counter of right encoder
  OverflowCounters[1] = OverflowCounters[1] + 1;    
  PCNT.int_clr.val = BIT(PCNT_UNIT_1);                  
  pcnt_counter_clear(PCNT_UNIT_1);                     
}

// Initialise pulse counters to detect rising edges on GPIOs defined by ENC_L and ENC_R
void initPulseCounters(){  
  int GPIOs[2] = {ENC_L, ENC_R};                          // select GPIO pins

  pcnt_config_t pcntFreqConfig = { };                     // Instance of pulse counter
  pcntFreqConfig.pos_mode = PCNT_COUNT_INC;               // Count only rising edges as pulses
  pcntFreqConfig.counter_h_lim = PCNT_H_LIM_VAL;          // Set upper counting limit
  pcntFreqConfig.channel = PCNT_CHANNEL_0;                // select channel 0 of pulse counter unit (for both pulse counters)

  for (int i=0; i<2; i++){
    pcntFreqConfig.pulse_gpio_num = GPIOs[i];             // Pin assignment for pulse counter
    pcntFreqConfig.unit = units[i];              		// select ESP32 pulse counter unit
    pcnt_unit_config(&pcntFreqConfig);                    // configure registers of the pulse counter

    pcnt_counter_pause(units[i]);                         // pause pulse counter unit
    pcnt_counter_clear(units[i]);                         // zero and reset of pulse counter unit

    pcnt_isr_service_install(0);           			// Install PCNT ISR service, non-shared interrupt of level 1, 2 or 3

    if(i==0){ // pulse counter for left wheel
      pcnt_isr_handler_add(units[i], CounterOverflow_Left, NULL);
    }
    else{ // pulse counter for right wheel
      pcnt_isr_handler_add(units[i], CounterOverflow_Right, NULL);
    }
 
    pcnt_event_enable(units[i], PCNT_EVT_H_LIM);          // enable event for interrupt on reaching upper limit of counting
    pcnt_set_filter_value(units[i], PCNT_FILTER_VAL);     // set damping, inertia 
    pcnt_filter_enable(units[i]);                         // enable counter glitch filter (damping)
    pcnt_counter_resume(units[i]);                        // resume counting on pulse counter unit
  }
}

// Reads both pulse counters and resets them
void Read_Reset_PCNTs() {        
  for (int i=0; i<2; i++){
  pcnt_get_counter_value(units[i], &PulseCounters[units[i]]); // get pulse counter value - maximum value is 16 bits
  OverflowCounters[units[i]] = 0;                             // set overflow counter to zero
  pcnt_counter_clear(units[i]);                               // zero and reset of pulse counter unit
  }
}

// Reads both pulse counters, results saved in Pulsecounters and Overflowcounters
void Read_PCNTs() {                 
  pcnt_get_counter_value(PCNT_UNIT_0, &PulseCounters[0]);   // get pulse counter value on unit 0
  pcnt_get_counter_value(PCNT_UNIT_1, &PulseCounters[1]);   // get pulse counter value on unit 1
}

void setup() {
  Serial.begin(115200);         // Start serial connection
  initPulseCounters();
}

void loop() {
  Read_PCNTs();
  Serial.print("Pulse_count_1:");
  Serial.print(PulseCounters[0]);
  Serial.print(", Overflow_value_1:");
  Serial.print(OverflowCounters[0]);
  Serial.print(", Pulse_count_2:");
  Serial.print(PulseCounters[1]);
  Serial.print(", Overflow_value_2:");
  Serial.println(OverflowCounters[1]);
  delay(1000);
}
It is currently only using two PCNT units but can easily be modified to handle more.
I also set the counting limit intentionally low to test that the overflow is handled correctly.
I hope this helps someone looking for a solution to the same problem (:

Cheers,
ElectRick

Who is online

Users browsing this forum: No registered users and 36 guests