ESP32, Pulse counter (PCNT) & interrupts question

Hello,

I have a code, which uses PCNT to measure a frequency. The code uses interrupts to catch counter overflow (counter is 16bit only). Now I need to run 4 counters in parallel but it is not clear what to do with interrupts.

  1. Should it be 4 different interrupts? If so, how?
  2. Or, if it is single interrupt (like what I think it is) how can I find which unit has generated it and which counter is overflowing?
  3. Is it possible to get this information (unit & channel numbers which caused interrupt) while in ISR?

This is the code I currently use. It would not compile because it is a part of bigger library but it is ok as am not looking for any help with this code. This is the part which initializes PCNT & sets up interrupt.

Thanks!

#define PULSE_WAIT 1000      // default counting time, 1000ms
#define PCNT_OVERFLOW 20000  // PCNT interrupt every 20000 pulses



static unsigned int count_overflow = 0;             // counter overflow. incremented every 20000 pulses
static int pcnt_channel = PCNT_CHANNEL_0;
static int pcnt_unit = PCNT_UNIT_0;

// PCNT interrupt handler. Called every 20 000 pulses 2 times. TODO: Is this normal?
static void IRAM_ATTR pcnt_interrupt(void *arg) { 
  count_overflow++;
  PCNT.int_clr.val = BIT(pcnt_unit);
}


//TAG:count
//"count PIN [DELAY_MS [pos|neg|both]]"
// Count pulses on given pin for specified amount of time

static int cmd_count(int argc, char **argv) {

  pcnt_config_t cfg = { 0 };
  unsigned int pin, wait = PULSE_WAIT;
  int16_t count;

  if (!pin_exist((cfg.pulse_gpio_num = pin = q_atol(argv[1], 999))))
    return 1;

  cfg.ctrl_gpio_num = -1;  // don't use "control pin" feature
  cfg.channel = pcnt_channel;
  cfg.unit = pcnt_unit;
  cfg.pos_mode = PCNT_COUNT_INC;
  cfg.neg_mode = PCNT_COUNT_DIS;
  cfg.counter_h_lim = PCNT_OVERFLOW;

  // user has provided second argument
  if (argc > 2) {
    if ((wait = q_atol(argv[2], DEF_BAD)) == DEF_BAD)
      return 2;

    //user has provided 3rd argument
    if (argc > 3) {
      if (!q_strcmp(argv[3], "pos")) { /* default*/
      } else if (!q_strcmp(argv[3], "neg")) {
        cfg.pos_mode = PCNT_COUNT_DIS;
        cfg.neg_mode = PCNT_COUNT_INC;
      } else if (!q_strcmp(argv[3], "both")) {
        cfg.pos_mode = PCNT_COUNT_INC;
        cfg.neg_mode = PCNT_COUNT_INC;
      } else
        return 3;
    }
  }

  q_printf("%% Counting pulses on GPIO<*>%d</>...", pin);
  if (is_foreground_task())
    HELP(q_print("(press <i>[Enter]</> to stop counting)"));
  q_print(CRLF);


  pcnt_unit_config(&cfg);
  pcnt_counter_pause(pcnt_unit);
  pcnt_counter_clear(pcnt_unit);
  pcnt_event_enable(pcnt_unit, PCNT_EVT_H_LIM);
  pcnt_isr_register(pcnt_interrupt, NULL, 0, NULL);
  pcnt_intr_enable(pcnt_unit);

  // reset & start counting for /wait/ milliseconds
  count_overflow = 0;
  pcnt_counter_resume(PCNT_UNIT_0);
  wait = delay_interruptible(wait);
  pcnt_counter_pause(PCNT_UNIT_0);

  pcnt_get_counter_value(PCNT_UNIT_0, &count);

  pcnt_event_disable(PCNT_UNIT_0, PCNT_EVT_H_LIM);
  pcnt_intr_disable(PCNT_UNIT_0);

  count_overflow = count_overflow / 2 * PCNT_OVERFLOW + count;
  
  q_printf("%% %u pulses in %.3f seconds (%.1f Hz)\r\n", count_overflow, (float)wait / 1000.0f, count_overflow * 1000.0f / (float)wait);
  return 0;
}
#endif // #if COMPILING_ESPSHELL

how frequent are the pulses? do you really need to use interrupts?

so there must be some other device that is actually counting events and the code just needs to recognize when the count overflows?

I'm having trouble parsing that statement. If you're "not looking for any help with this code", then why are you asking a question here?

I'll assume for now you mean "I'm not looking for help outside of the PCNT question".

If so, you've posted code that does not compile. Please use that code to create a small project (an MRE) that actually compiles and demonstrates the issue at hand.

I am asking how I can find inside interrupt which PCNT uint/channel caused it.

BTW i found it. There's a register which contains a bitmask of PCNT caused interrupt. However theres no channel number :confused:

Unfortunately yes, because ESP32's pulse counter is just 16bit wide. It overflows fast. Frequencies are 1..100 000 Hz

so every ~ half second (65535 / 100000)

or did you mean the pulse rate into the counters is 6.5gHz?

See Register Event Callbacks in the documentation. The ISR is passed a handle to the PCNT unit, a pointer to the event data, and a pointer to user supplied data when the callback was registered. That might give you every thing you need to know.

EDIT:
Looking further, knowing the channel may not be possible. But, that may also not be an intended use case of the PCNT. The various channels can independently effect the counter (up / down), but the callbacks are based on the counter reaching one of its watchpoints regardless of which channel caused it to happen. So, what you're trying to do might be outside the intended purpose of the PCNT.

1 Like

1hz..100khz

Last two days spent trying to make it work with global PCNT ISR. Results are negative: only PCNT UNIT#0 seemed to generate interrupts.

However when I switched from this global isr to a per-unit service ISR it is all started to work.

I.e. functions below should be used instead of pcnt_isr_register() if you use more than one PCNT unit at the same time.

  // interrupt setup
  pcnt_event_enable(unit, PCNT_EVT_H_LIM); // your event of interest
  pcnt_event_disable(unit, PCNT_EVT_ZERO); // .. or we get 2x interrupts

  pcnt_isr_service_install(0); // install master PCNT ISR service (once)

  // install per-unit interrupt. 
  pcnt_isr_handler_add(unit, pcnt_unit_interrupt, (void *)unit);

Also noted that if I dont explicitly disable zero crossing event then I get 2x interrupts. Is zero event enabled by default?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.