Multiple PCNT at once

vvb333007
Posts: 8
Joined: Wed Jul 31, 2024 5:53 am

Multiple PCNT at once

Postby vvb333007 » Thu Dec 12, 2024 7:44 am

Hello,

I have a code, which uses PCNT to measure 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. Should it be 4 different interrupts? 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? 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

Code: Select all

#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


User avatar
ok-home
Posts: 116
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: Multiple PCNT at once

Postby ok-home » Thu Dec 12, 2024 8:33 am

the number of the channel that caused the interrupts can be viewed in the status register
intstatus.JPG
intstatus.JPG (64.01 KiB) Viewed 2955 times

MicroController
Posts: 2046
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Multiple PCNT at once

Postby MicroController » Thu Dec 12, 2024 11:21 am

vvb333007 wrote:
Thu Dec 12, 2024 7:44 am

Code: Select all

static void IRAM_ATTR pcnt_interrupt(void *arg) { 
  count_overflow++;
  PCNT.int_clr.val = BIT(pcnt_unit);
}
You may want to use the IDF-provided interrupt handler.

vvb333007
Posts: 8
Joined: Wed Jul 31, 2024 5:53 am

Re: Multiple PCNT at once

Postby vvb333007 » Thu Dec 12, 2024 10:52 pm

Thanks! It worked :)

vvb333007
Posts: 8
Joined: Wed Jul 31, 2024 5:53 am

Re: Multiple PCNT at once

Postby vvb333007 » Sat Dec 14, 2024 8:17 am

Solution.

Last two days spent trying to make it work with global PCNT ISR. Results are negative: only PCNT UNIT#0 seemed to generate interrupts. UNIT#1 counts but never generates interrupts. Thats what was with

Code: Select all

pcnt_isr_register()-style
iunterrupt.

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

Code: Select all

pcnt_isr_register()
if you use more than one PCNT unit at the same time.

Code: Select all

 // 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?
Is there a way to find out which channel caused the interrupt? There are 2 channels per unit and I am using them for frequency metering so why not to use both channels..

Who is online

Users browsing this forum: No registered users and 148 guests