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

nRF52 Pulse Duration Counter - going from 1 micro-second resolution to nano-seconds

I used the Adafruit Bluefruit NRF52840 LE and added some simple code to this existing timer/counter for pulses: https://devzone.nordicsemi.com/f/nordic-q-a/62633/nrf52-timer-as-counter-adafruit-feather   It counts the number of LOW pulses within a certain time period. I changed it to count HIGH pulses. it works in Arduino.

But I would like to measure the time between two pulses (the period between the RISING or HIGH peaks of two consecutive pulses).  There are only two pulses coming about every 2-3 seconds.  The expected duration of the two pulses is between 100-2000 us (micro-seconds).

I suspect that I need to read the  respective "time-stamp" of the Timer at the HIGH (RISING) of each of the two pulses and subtract the 1st reading from the 2nd reading.  Right now I am  using the "micros()" but that only gives me 1us resolution and defeats the purpose of the Timer' nano-second resolution.  

Is there a sample code someone can point me to add that "time-stamp" feature in there? 

#define FREQ_MEASURE_PIN 6u  // was 7u   PIN 11 on express 52840
#define PPI_CHANNEL 1u

void setup() {
  Serial.begin(115200);
  pinMode(LED_RED, OUTPUT);
  initCounter();
}

void loop() 
{  
  readCounter();
 // digitalToggle(LED_RED);
 // delay(1);
}

void readCounter()
{
  static int counts;
  NRF_TIMER2->TASKS_CAPTURE[0] = 1;
  counts = NRF_TIMER2->CC[0];
unsigned long counts1 = micros();
unsigned long counts2 = (counts - counts1);

  
  if (counts >0)  {
  //Serial.print("pulses: "); 
  //Serial.println(counts);
    Serial.println(counts1);
      Serial.println(counts2);
  counts1 =0;
  counts2 = 0;
  counts = 0;
 
  }
  
  
 NRF_TIMER2->TASKS_CLEAR = 1;                  
  NRF_TIMER2->TASKS_START = 1;
}

void initCounter()
{    
  NRF_P0->PIN_CNF[FREQ_MEASURE_PIN] = GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos |
                                      GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos |
                                      GPIO_PIN_CNF_PULL_Pulldown << GPIO_PIN_CNF_PULL_Pos |
                                      GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;

  NRF_PPI->CHEN |= 1 << PPI_CHANNEL;
  NRF_PPI->CH[PPI_CHANNEL].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[PPI_CHANNEL];
  NRF_PPI->CH[PPI_CHANNEL].TEP = (uint32_t)&NRF_TIMER2->TASKS_COUNT;

  NRF_GPIOTE->CONFIG[PPI_CHANNEL] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |
                                    FREQ_MEASURE_PIN << GPIOTE_CONFIG_PSEL_Pos |
                                    GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos; 

  NRF_TIMER2->TASKS_STOP = 1;   
  NRF_TIMER2->TASKS_CLEAR = 1;
  NRF_TIMER2->MODE = TIMER_MODE_MODE_LowPowerCounter << TIMER_MODE_MODE_Pos;
  NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
  NRF_TIMER2->TASKS_START = 1;
}

Parents
  • Hi,

    the best resolution you can achieve is 1/16us. For the maximum accuracy, use the hardware path (GPIOTE-PPI-CAPTURE) instead of software loop. Also don't confuse GPIOTE and PPI channels - though you can use channels with the same index for both, these are different things.

    The code will look like this:

    void GPIOTE_IRQHandler(void)
    {
      if (NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] == 1)
      {
        NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] = 0;
        counts = NRF_TIMER2->CC[0];
        ...
      }
    }
    
    void initCounter()
    {    
      NRF_P0->PIN_CNF[FREQ_MEASURE_PIN] = GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos |
                                          GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos |
                                          GPIO_PIN_CNF_PULL_Pulldown << GPIO_PIN_CNF_PULL_Pos |
                                          GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;
    
      NRF_PPI->CHEN |= 1 << PPI_CHANNEL;
      NRF_PPI->CH[PPI_CHANNEL].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL];
      NRF_PPI->CH[PPI_CHANNEL].TEP = (uint32_t)&NRF_TIMER2->TASKS_CAPTURE[0];
    
      NRF_GPIOTE->CONFIG[GPIOTE_CHANNEL] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |
                                        FREQ_MEASURE_PIN << GPIOTE_CONFIG_PSEL_Pos |
                                        GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos; 
      NRF_GPIOTE->INTENSET = (1 << GPIOTE_CHANNEL);
      NVIC_EnableIRQ(GPIOTE_IRQn);
    
      NRF_TIMER2->TASKS_STOP = 1;   
      NRF_TIMER2->TASKS_CLEAR = 1;
      NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
      NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
      NRF_TIMER2->PRESCALER = 0;
      NRF_TIMER2->TASKS_START = 1;
    }
    

  • Sorry for the slow reply.  Took me a while with my limited knowledge to digest the information.  Thank you very much for the suggestions and the sample code.  I incorporated the code above into the original.  And I added comments in the code the best I could understand it. 

    I suspect that I either missed something or I did not define the GPIOTE_CHANNEL (number?) correctly.  I do not have any readings on the Arduino IDE serial port, and it also locks up the port, requiring a manual reset. 

    I suspect that its very close to correct, but after reading the SDK I was not able to figure what setup or step I missed with the GPIOTE_CHANNEL.  Is there a missing link between GPIOTE_EVENT and the TIMER signal detection?

    Just to refresh:  Only two HIGH pulses are coming in about every 3-10 seconds on pin 6.  The two pulses are around 800 us (micro sec) apart from each other peak-to-peak.   I am hoping to capture the RISING or HIGH level of Pulse 1, and the same with Pulse 2. Then determine the gap between the two pulse tops (the period/interval between the two pulses).

    #define FREQ_MEASURE_PIN 6u  // actual PIN 11 on Adafruit Feather Express nRFf52840 LE
    #define PPI_CHANNEL 1u
    #define GPIOTE_CHANNEL 1   // I just picked channel # as a guess...  Tried 0, 1 , 2, 6
    
    volatile unsigned long counts; //volatile because inside IRQ
    
    void setup() {
      Serial.begin(115200);
      pinMode(LED_RED, OUTPUT);
      initCounter();
    }
    
    void loop() 
    {  
     //nothing here because IRQHandler does all the work and only when singal is detected
     
    }
    
    
    void GPIOTE_IRQHandler(void)
    {
      if (NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] == 1)    //if an input pulse is detected on pin 6 then...
      {
        NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] = 0;     //  reset event detection to zero to ready for next event detection? 
        counts = NRF_TIMER2->CC[0];                     // save Timer value at same time when signal was read on pin 6
        Serial.println(counts);
      }
    }
    
    
    void initCounter()
    {    
      NRF_P0->PIN_CNF[FREQ_MEASURE_PIN] = GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos |        //sets up PIN 6 for event read
                                          GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos |  //pos going signal
                                          GPIO_PIN_CNF_PULL_Pulldown << GPIO_PIN_CNF_PULL_Pos |   //pulldown LOW so no floating of pin voltage
                                          GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;      //a HIGH POS signal is the trigger
                                          
      NRF_PPI->CHEN |= 1 << PPI_CHANNEL;
      NRF_PPI->CH[PPI_CHANNEL].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL];
      NRF_PPI->CH[PPI_CHANNEL].TEP = (uint32_t)&NRF_TIMER2->TASKS_CAPTURE[0];   //sets up Timer channel
    
      NRF_GPIOTE->CONFIG[GPIOTE_CHANNEL] = GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos |         //sets up pin for event capture 
                                        FREQ_MEASURE_PIN << GPIOTE_CONFIG_PSEL_Pos |                   //sets up event capture on pin 6
                                        GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos;   //sets event capture signal trigger type 
                                        
      NRF_GPIOTE->INTENSET = (1 << GPIOTE_CHANNEL);    //  Interrupt routine setup on a pin
      NVIC_EnableIRQ(GPIOTE_IRQn);                     //  Interrupt routine setup if signal detected on pin 6
    
      NRF_TIMER2->TASKS_STOP = 1;    //stops timer
      NRF_TIMER2->TASKS_CLEAR = 1;    //clear timer to zero
      NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;   //sets up TIMEr mode as "Timer"
      NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;    // sets values for Timer input
      NRF_TIMER2->PRESCALER = 0;       //read at max resolution (at MCU speed)
      NRF_TIMER2->TASKS_START = 1;      // starts the Timer
    }

  • Not sure how that works in Arduino, or if you are building for a specific board, but by default GPIO P0.06 is used for the UART TX pin on our DKs. If the UART is not configured to use a different pin in your build, the "Serial.begin(115200);" line may interfere with the GPIOTE channel configuration which is set later. Have you tried removing this line, or used a different GPIO for the FREQ_MEASURE_PIN?

  • Thank you for the quick reply. The code compiles without a problem on Arduino IDE, but when I open the Serial COM port, it either does nothing , or freezes the port.    I think interrupt issues in the code may not necessarily prompt an error during compiling.   I will add in the code:   extern "C"      

    I am checking into it...  

  • Jorgen, just noticed your suggestion.  Thank you.  I will try another pin and see if the use of Pin 6 (being the  UART TX pin) maybe the source of the conflict. 

  • YES!!! YES!!!!! Works!!!   I added the Extern "C"  {}      Now I get readings of two pulses' timestamps.  

    I subtracted the first timestamp from the second timestamp and getting 12682 and 12876 values (my input pulses are not super steady).  Which seems to translate based on your 1/16 micro sec resolution to 792.625 and 804.75 us.   Which is exactly what I was expecting! 

    So the maximum resolution of the NRF52840 is 62.5 nano sec tick (16 Mhz MCU clock)?

    extern "C"
    {
    void GPIOTE_IRQHandler(void)
    {
      if (NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] == 1)    //if an input pulse is detected on pin 6 then...
      {
        NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL] = 0;     //  reset event detection to zero to ready for next event detection? 
        counts = NRF_TIMER2->CC[0];                     // save Timer value at same time when signal was read on pin 6
        Serial.println(counts);
      }
    }
    }

  • fe7565 said:
    So the maximum resolution of the NRF52840 is 62.5 nano sec tick (16 Mhz MCU clock)?

    Yes, that is correct. The TIMERs runs off the 16 MHz peripheral clock.

Reply Children
Related