ESP32 C6 PCNT

alepagliaccio
Posts: 4
Joined: Sat Jun 29, 2024 7:42 am

ESP32 C6 PCNT

Postby alepagliaccio » Sat Jun 29, 2024 7:44 am

Hello, I'm using a pcnt to count pules from a 10mhz ocxo. I get pretty stable results using a gps pps, sometimes 9,999,999 instead of 10 million but thats acceptable. But sometimes after about 1 minute I get a reading thats off by like 40000 pulses (approx), two readings off like this so 2 seconds and then it stabilizes to a new value. I guess there's something else goin on in the background that gets priority over my counter? How can I prevent this? I want to use the ocxo for accurate time keeping. To do this I mainly edited the pcnt example from esp itself. Do i need an esp32 with a second core to just make it count pulse? What do you suggest?
Thanks in advance.

Code: Select all

#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/pulse_cnt.h"
#include "driver/gpio.h"
#include "esp_sleep.h"

static const char *TAG = "example";

#define EXAMPLE_PCNT_HIGH_LIMIT 32000
#define EXAMPLE_PCNT_LOW_LIMIT  -32000

#define EXAMPLE_EC11_GPIO_A GPIO_NUM_13
#define EXAMPLE_GPIO_INTERRUPT_PIN  GPIO_NUM_10
int pulse_count = 0;
static int overflow_count = 0;

static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    
    pcnt_unit_get_count((pcnt_unit_handle_t)arg, &pulse_count);
    int total_count = pulse_count + (overflow_count * EXAMPLE_PCNT_HIGH_LIMIT);
    ets_printf("Total count: %d\n", total_count);
}

static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx)
{
    BaseType_t high_task_wakeup;
    QueueHandle_t queue = (QueueHandle_t)user_ctx;

    // Increment overflow counter on overflow
    if (edata->watch_point_value == EXAMPLE_PCNT_HIGH_LIMIT) {
        overflow_count++;
    }

    // send event data to queue, from this interrupt callback
    xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup);
    return (high_task_wakeup == pdTRUE);
}

extern "C" void app_main(void)
{
    ESP_LOGI(TAG, "install pcnt unit");
    pcnt_unit_config_t unit_config = {
        .low_limit = EXAMPLE_PCNT_LOW_LIMIT,
        .high_limit = EXAMPLE_PCNT_HIGH_LIMIT,
    };
    pcnt_unit_handle_t pcnt_unit = NULL;
    ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));

   
    ESP_LOGI(TAG, "install pcnt channel");
    pcnt_chan_config_t chan_config = {
        .edge_gpio_num = EXAMPLE_EC11_GPIO_A,
        .level_gpio_num = -1, // No level GPIO used
    };
    pcnt_channel_handle_t pcnt_chan = NULL;
    ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));

    ESP_LOGI(TAG, "set edge action for pcnt channel");
    ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));

    ESP_LOGI(TAG, "add watch points and register callbacks");
    int watch_points[] = {EXAMPLE_PCNT_LOW_LIMIT, -50, 0, 50, EXAMPLE_PCNT_HIGH_LIMIT};
    for (size_t i = 0; i < sizeof(watch_points) / sizeof(watch_points[0]); i++) {
        ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, watch_points[i]));
    }
    pcnt_event_callbacks_t cbs = {
        .on_reach = example_pcnt_on_reach,
    };
    QueueHandle_t queue = xQueueCreate(10, sizeof(int));
    ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));

    ESP_LOGI(TAG, "enable pcnt unit");
    ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
    ESP_LOGI(TAG, "clear pcnt unit");
    ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
    ESP_LOGI(TAG, "start pcnt unit");
    ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));

    // Configure GPIO interrupt on pin 10
    ESP_LOGI(TAG, "configure GPIO interrupt on pin 10");
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pin_bit_mask = (1ULL << EXAMPLE_GPIO_INTERRUPT_PIN);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf));

    // Install ISR service
    ESP_ERROR_CHECK(gpio_install_isr_service(0));
    ESP_ERROR_CHECK(gpio_isr_handler_add(EXAMPLE_GPIO_INTERRUPT_PIN, gpio_isr_handler, (void *)pcnt_unit));

#if CONFIG_EXAMPLE_WAKE_UP_LIGHT_SLEEP
    // EC11 channel output high level in normal state, so we set "low level" to wake up the chip
    ESP_ERROR_CHECK(gpio_wakeup_enable(EXAMPLE_EC11_GPIO_A, GPIO_INTR_LOW_LEVEL));
    ESP_ERROR_CHECK(esp_sleep_enable_gpio_wakeup());
    ESP_ERROR_CHECK(esp_light_sleep_start());
#endif

    // Report counter value
    int pulse_count = 0;
    int event_count = 0;
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

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

Re: ESP32 C6 PCNT

Postby MicroController » Mon Jul 01, 2024 2:57 pm

You may well be witnessing the effects of a race condition between two events/interrupts: If the PCNT overflow occurs while a PPS GPIO interrupt is on its way to being handled (or vice-versa), the GPIO ISR may see an old/stale value in overflow_count (low by 1) and combine that value with the current (post-overflow) PCNT value, resulting in total_count being 32000 less than it should be.

Btw: Variables shared between an ISR and other code (/ISRs) should at least be declared volatile, or better yet be declared and accessed as atomics.

alepagliaccio
Posts: 4
Joined: Sat Jun 29, 2024 7:42 am

Re: ESP32 C6 PCNT

Postby alepagliaccio » Mon Jul 01, 2024 8:17 pm

MicroController wrote:
Mon Jul 01, 2024 2:57 pm
You may well be witnessing the effects of a race condition between two events/interrupts: If the PCNT overflow occurs while a PPS GPIO interrupt is on its way to being handled (or vice-versa), the GPIO ISR may see an old/stale value in overflow_count (low by 1) and combine that value with the current (post-overflow) PCNT value, resulting in total_count being 32000 less than it should be.

Btw: Variables shared between an ISR and other code (/ISRs) should at least be declared volatile, or better yet be declared and accessed as atomics.
Well that was what I was thinking was happening. But how do I avoid this race condition? In my new code I do use the flag accum_count and have only the pps isr that uses a queue to make the main ask the class for the counter value. Still have this issue. There's anything I can do?

alepagliaccio
Posts: 4
Joined: Sat Jun 29, 2024 7:42 am

Re: ESP32 C6 PCNT

Postby alepagliaccio » Mon Jul 01, 2024 8:26 pm

MicroController wrote:
Mon Jul 01, 2024 2:57 pm
You may well be witnessing the effects of a race condition between two events/interrupts: If the PCNT overflow occurs while a PPS GPIO interrupt is on its way to being handled (or vice-versa), the GPIO ISR may see an old/stale value in overflow_count (low by 1) and combine that value with the current (post-overflow) PCNT value, resulting in total_count being 32000 less than it should be.

Btw: Variables shared between an ISR and other code (/ISRs) should at least be declared volatile, or better yet be declared and accessed as atomics.
Also, the value is not 32000 less, is a lot less, example of pulse outputs from the last second of 5 seconds in a row when the issue occurs:
10000000
9999999
9872000
9679999
10000000

The issue more than often lasts for at least 2 seconds, and the values are always different, but you made me notice just now that they are always indeed (+-1 pulse) multiples of 32000 which is my high limit. But I do not have a race condition, I think? As I only have 1 isr for the pps only.

I do not have experience with volatiles and atomic so I will look into that later for sure thanks for the tip on that.

alepagliaccio
Posts: 4
Joined: Sat Jun 29, 2024 7:42 am

Re: ESP32 C6 PCNT

Postby alepagliaccio » Thu Jul 04, 2024 9:44 am

The issue that was occuring was due me having a watchdog to other values other than the High limit (forgot them from the example). Now the counter Is flawless. Now i wanted to also have ble scanning in the background. It Will be constant, when Active It often interfers with the pcnt and makes me lose pulses (then regain them the next cycle) which make the registered time at the interrupt incorrect. I tried setting It up on a S3 board running the pcnt on core 1. Still something disturbs the counter itself (only with bluetooth Scan Active). Any suggestion? I would Need pcnt core 1 to not have anything interferring with It at all

Who is online

Users browsing this forum: No registered users and 89 guests