This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Configuring nRF52840 for counting input frequency of upto 5MHz.

Hello,

I want to configure my nRF52840 development kit for counting input pulses on one of the GPIOs (Pin 13) in this case. I am using SEGGER Embedded Studio as IDE.

Here I am using PPI, GPIOTE and TIMER for this configuration. I have configured GPIOTE as an input event. As I want to count the rising edge on that particular GPIO, I have configured that GPIO to sense Low to High signal. According to this event, gpiote event handler is called and the static counter is incremented. 

As I searched in nordic forum, PPI is capable for operating in 8MHz. But when I apply 2MHz clock as input to the above mentioned GPIO, I don't get exact count in the watch window. As per the count, nRF kit is only able to count pulses at about 120kHz frequency.

If I apply clock below 120kHz it is counting perfectly. And when I increase the frequency, for example 1MHz or something, I get maximum count of 120kHz.

Is there anything missing here? Is my configuration correct? Is there a specific GPIO which has to be used for this purpose?

I want to capture the input frequency of about 5MHz. Is it possible with the nRF52840 dev kit?

Below is the configuration for PPI channel as well as timer0 settings.

static void gpiote_event_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
++m_gpiote_counter;
}

static void ppi_init(void)
{
uint32_t err_code = NRF_SUCCESS;
nrf_drv_gpiote_in_config_t config = NRFX_GPIOTE_RAW_CONFIG_IN_SENSE_LOTOHI(true);
config.pull = NRF_GPIO_PIN_NOPULL;
uint32_t gpiote_event_addr;
uint32_t PIN_NUMBER = 13;

err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
nrfx_gpiote_in_uninit(PIN_NUMBER);

err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel1);
APP_ERROR_CHECK(err_code);

err_code = nrf_drv_gpiote_in_init(PIN_NUMBER, &config, gpiote_event_handler);
APP_ERROR_CHECK(err_code);

nrf_drv_gpiote_in_event_enable(PIN_NUMBER, true);

gpiote_event_addr = nrf_drv_gpiote_in_event_addr_get(PIN_NUMBER);


err_code = nrf_drv_ppi_channel_assign(m_ppi_channel1,
gpiote_event_addr,
nrf_drv_timer_task_address_get(&m_timer0,
NRF_TIMER_TASK_COUNT));
APP_ERROR_CHECK(err_code);


// Enable configured PPI channels
err_code = nrf_drv_ppi_channel_enable(m_ppi_channel1);
APP_ERROR_CHECK(err_code);

}

static void timer0_init(void)
{
// Check TIMER0 configuration for details.
nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
timer_cfg.frequency = NRF_TIMER_FREQ_16MHz;
timer_cfg.mode = NRF_TIMER_MODE_COUNTER;
ret_code_t err_code = nrf_drv_timer_init(&m_timer0, &timer_cfg, timer0_event_handler);
APP_ERROR_CHECK(err_code);

nrf_drv_timer_extended_compare(&m_timer0,
NRF_TIMER_CC_CHANNEL0,
nrf_drv_timer_us_to_ticks(&m_timer0,
1),
NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
true);
}

Thanks.

Parents
  • hmm, you're applying nrf_drv_timer_us_to_ticks() to a timer in counter mode... in fact, you'll get  a value 16 here, so interrupt will be called for every 16th pulse, is this what you expected?

    Instead of using interrupt, you can configure a timer without CLEAR shortcut and read counter value directly from timer with nrf_drv_timer_capture().

  • Thanks for your reply,

    No the requirement is not of getting interrupt at every 16th pulse. We want interrupt for every pulse so that counter can get increment and we get the frequency of the input signal.

    I tried you suggestion above. Also used nrf_drv_timer_capture() in the timer interrupt handler. However I am not getting timer interrupt. However I am getting GPIOTE interrupt here and its counter gets incremented. But as I stated previously, it can only count frequency up to 120kHz instead applying frequency of 3MHz on the gpio configured for GPIOTE.

    Below are the changes we made as per your comment:

    static void timer0_event_handler(nrf_timer_event_t event_type, void * p_context)
    {
        m_counter = nrf_drv_timer_capture(&m_timer0,NRF_TIMER_CC_CHANNEL0);
    }
    
    static void timer0_init(void)
    {
        // Check TIMER0 configuration for details.
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        timer_cfg.mode = NRF_TIMER_MODE_LOW_POWER_COUNTER;
        ret_code_t err_code = nrf_drv_timer_init(&m_timer0, &timer_cfg, timer0_event_handler);
        APP_ERROR_CHECK(err_code);
    }
    
    

    Are the changes made fine?

    Why still I am not able to get timer interrupt? Is there any further configuration to be made?

    If possible, is there any demo for counting pulses of 3MHz on GPIO pin of nRF52840 dev kit from which we can take reference from?

    Thanks.

  • I didn't suppose you will get timer interrupt. Timer increments its value at GPIOTE event - why do you waste a lot of CPU cycles just to duplicate its work? When you need a counter value, call nrf_drv_timer_capture(), say, to channel 1 and then read CC[1].

    Concerning frequency limit, maybe the answer from this thread will help:

    "In the low power counter mode the hfclk is not requested until the COUNT task is trigerred, at that point the hfclk is requested for 1 clock clycle. So in practice this is a low power counter."

    So for each COUNT event, timer will start hfclk, wait until it get stabilized, increment counter, then stop hfclk. Try to call nrf_drv_clock_hfclk_request() at start, this will turn on hfclk forever (of course, power consumption will increase).

  • Thanks for your reply.

    Now we are able to count required frequency when we don't trigger an interrupt on every pulse. Thanks to your suggestions.

    However, for our end goal, we want to perform some task/logic on every rising edge of the pulse which we counted. Without using interrupt it doesn't seem to be achievable as due to interrupts, we are not able to count the input pulses accurately.

    Is there a way where we can achieve the end goal? Do we compulsorily need to use interrupt for performing some task when we detect rising edge or there is a way?

    Any logic or pseudo code or demo will be helpful here.

    Thanks.

  • You mention that your input frequency is about 5 MHz (about 12 CPU cycles). An interrupt requires 12 cycles for stacking + 12 cycles for unstacking + some time for your handling code. You have no CPU resources for your task...  but 52840 has a good task-event concept that allows to handle such cases directly by hardware. Could you give more details about required logic when you detect a pulse?

Reply
  • You mention that your input frequency is about 5 MHz (about 12 CPU cycles). An interrupt requires 12 cycles for stacking + 12 cycles for unstacking + some time for your handling code. You have no CPU resources for your task...  but 52840 has a good task-event concept that allows to handle such cases directly by hardware. Could you give more details about required logic when you detect a pulse?

Children
  • The requirement goes like this. We want to capture parallel data from camera sensor.

    The data from the camera sensor would be in sync with pixel clock. And we have to sample the 8 bit data on one of the nRF IO port's at every rising edge of the pixel clock. For our requirement, the pixel clock comes around 3-5MHz. This is the rate at which data would be coming to the kit.

    So in short, at every rising edge of the pulse, we want to get data from IO port and store it to an array or something.

    Is this possible by using task-event concept?

  • Unfortunately, there's no peripheral in nrf52 that is able to read parallel data and save it to RAM, you can only read GPIO register. This makes things more interesting )  If you're lucky and CPU power will suffice, you can write a pure software routine in assembly that will do this task. All interrupts should be disabled for the time of reading a stream; if you need to hold a BLE connection, request a timeslot (see Timeslot API). Pass CLK signal through a flip-flop to divide its frequency by two - thus you'll have to handle one CLK transition instead of rise+fall. 

    If data bus is connected to P1.0-P1.7, CLK to P1.8, the rough assembly routine will look like this:

    AcquireData
    	ldr r1, P1_IN
    	ldr r2, DestinationArray
    	ldr r3, #(ByteCount/2)
    wait1
    	ldrb r0, [r1]
    	tst r0, #(1 << 8)   ; waiting for CLK transition 0-1
    	beq wait1
    	strb r0, [r2],#1
    	subs r3, #1
    wait2
    	ldrb r0, [r1]
    	tst r0, #(1 << 8)   ; waiting for CLK transition 1-0
    	bne wait2
    	strb r0, [r2],#1
    	cbnz r3, wait1
        bx lr

Related