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

nRF51: Timer Capture Task

Hello,

I want to measure a frequency of a PWM signal like this:

GPIOTE-Event --> PPI --> Timer1-Capture-Task

So every time the event occurs, the counter value is copied into the respective CC-Register (in my case channel 0).

Question: Where is the ideal place to copy the content of the CC-Register into, lets say a data array? Normally, I would assume, that the Timer gererates an interrupt as soon as it has finished the capture. So my first idea is to use the timer ISR to copy the value. BUT: What is the advantage of that technique compared to a normal GPIO-Interrupt-ISR and reading/capturing the timer value manually in that ISR? (without using GPIOTE and PPI)?

Second question: How can I achive a timer clear as soon as the capture is done?

This is my code so far:

  uint32_t err_code;
  
  err_code = nrf_drv_ppi_init();
  APP_ERROR_CHECK(err_code);
  
  err_code = nrf_drv_gpiote_init();
  APP_ERROR_CHECK(err_code);
  
  nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
  err_code = nrf_drv_timer_init(&timer, &timer_cfg, timer_dummy_handler);
  APP_ERROR_CHECK(err_code);
  
  
  #ifdef NRF51
    //Workaround for PAN-73.
    *(uint32_t *)0x40008C0C = 1;
  #endif
  
  
  uint32_t gpiote_evt_addr;
  uint32_t capture_task_addr;
  nrf_ppi_channel_t ppi_channel;
  nrf_drv_gpiote_in_config_t config;
  
  config.sense = NRF_GPIOTE_POLARITY_LOTOHI;
  config.pull = NRF_GPIO_PIN_NOPULL;
  config.is_watcher = false;
  config.hi_accuracy = true;
  
  nrf_drv_gpiote_in_init(SIG_PIN,&config, gpiote_evt_handler);
  APP_ERROR_CHECK(err_code);
  
  err_code = nrf_drv_ppi_channel_alloc(&ppi_channel);
  APP_ERROR_CHECK(err_code);
  
  capture_task_addr = nrf_drv_timer_task_address_get(&timer, NRF_TIMER_TASK_CAPTURE0);
  gpiote_evt_addr = nrf_drv_gpiote_in_event_addr_get(SIG_PIN);
  
  err_code = nrf_drv_ppi_channel_assign(ppi_channel, gpiote_evt_addr, capture_task_addr);
  APP_ERROR_CHECK(err_code);
  
  err_code = nrf_drv_ppi_channel_enable(ppi_channel);
  APP_ERROR_CHECK(err_code);
  
  nrf_drv_gpiote_in_event_enable(SIG_PIN, true);
  
  nrf_drv_timer_enable(&timer);
  
  while(1)
  {
    
  };

Thank you very much in advance!

  • FormerMember
    0 FormerMember

    There is no specific ideal place to copy the content of the CC register, typically, the content in the CC register can be copied to a variable, a data array should work fine.

    The benefit of using PPI instead of normal GPIO interrupt handling is that when using PPI, the CPU is not being used:

    The Programmable Peripheral Interconnect (PPI) enables different peripherals to interact autonomously with each other using tasks and events without use of the CPU. The PPI provides a mechanism to automatically trigger a task in one peripheral as a result of an event occurring in another. A task is connected to an event through a PPI channel.

    (nRF51 series reference manual chapter 3.3.4)

    Clearing timer as soon as the capture is done:

    1. Capture value (will be copied to the CC register).

    2. Clear timer

    3. Read captured value from the CC register.

    This should work as long as every reading of captured values is finished before the next captured value.

  • Thank you very much for your support. In order to get that working accurately, it seems necessary to clear the timer immediately after the capture is done. Is the timer generating an interrupt after finishing the capture?

  • FormerMember
    0 FormerMember in reply to FormerMember

    Unfortunately, there is no event when the capture is finished, so I would think that it will be executed immediately. If you are not using the softdevice, the GPIOTE event can be set to have the highest priority, and you know that you won't have any other interrupt.

    You can check if the following will work:

    1. GPIOTE event --> PPI --> TASKS_CAPTURE

    2. GPIOTE event handler: clear timer, then read the CC register.

    If the above doesn't work, i.e the capture has not finished before clearing the timer, you can test the following:

    1. GPIOTE event --> PPI --> TASKS_CAPTURE

    2. GPIOTE event handler: dummy read CC values, clear timer, then read the CC register.

    Even though the capture/clear timing may be a little delayed, this delay will always be the same, so it should be possible to measure the frequency.

  • Thank you for your answer. It works both ways. However, I have encountered another problem. I can measure frequencies only up to 60 kHz with this method. I also tried another approach: devzone.nordicsemi.com/.../ but with exactly the same result. Everything is ok when the frequency is under 60 kHz (e.g. 50 kHz is measured as 49992 Hz), but the result is totally wrong above (70 kHz is measured as 4330 Hz).

    Is there a maximum frequency the GPIOTE/PPI-combination can handle? The maximum frequency I want to measure is about 300 kHz.

    Thank you very much in advance!

  • Think I found the problem (using the linked example above):

    300 kHz -> Capture Value is 0x919B (37,2 kHz). I found out, that 0x4919B would match the frequency (299419 Hz).

    Question: Why is Timer2 (used as counter) only delivering 16-Bit CC-values, despite it is initialized in 32 Bit-Mode:

    NRF_TIMER2->BITMODE = (TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos);
    
Related