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
    }

  • I didn't check the code on hardware, but I don't see anything missing, of course maybe I forgot something.. What are your results? If you're using Arduino IDE, there may be issues with interrupt handlers that are already defined in the IDE. I have no experience with Arduino IDE with nRF52, sorry.

  • 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?

Reply
  • 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?

Children
Related