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

Frequency detector possible?

Hi. Can anyone think of a way to use the nRF52832 (probably Timer) to detect approximately 125KHz-150KHz variable square wave down to about a 1Hz resolution? I'd like to detect frequency changes at about 100 times a second though. Ideally I'd like to do this without any external hardware (like a heterodyne mixer). Thoughts? Thanks!

(FINAL answer at very bottom. The answer is YES, the nRF52832 can be used even with a SoftDevice to accurately detect a few hundred KHz signal down to a few Hz resolution)

Parents
  • Hi,

    Yes, you can do this with GPIOTE, PPI, and two timers - for example, TIMER1 is a pulse counter, TIMER2 is a 16-MHz timer.
    - configure TIMER1->CC[0] for a number of pulses to measure plus 1 (in your case, 1000 pulses is about 0.08 sec that meets your requirements)
    - configure first PPI channel to start TIMER2 and increment TIMER1 by GPIOTE rise event
    - configure second PPI channel to capture TIMER2 value into CC[0] by counter's TIMER1->COMPARE[0] event (after 1000 pulses)
    - to start measurement, clear TIMER1 and TIMER2, then enable both PPI channels
    - after TIMER1->COMPARE[0] event, TIMER2->CC[0] will contain total time for 1000 pulses in 1/16 usec units.

    1Hz resoultion is a challenge. A difference between 149999 and 150000 Hz is about 0.04 usec at 1000 periods, resolution of nRF52 timer is 1/16 usec - I believe you can get about 2-3 Hz resolution if everything is done carefully.

  • Hi Dmitry,

    I got it basically working largely from code from https://devzone.nordicsemi.com/f/nordic-q-a/9036/measuring-input-gpio-pin-frequency-with-soft-device-running. My code looks like this:

    static void freqDetectorInit(void)
    {
        IOPinConfig(0, FREQ_MEASURE_PIN, 0, IOPINDIR_INPUT, IOPINRES_NONE, IOPINTYPE_NORMAL);
    
    	NVIC_SetPriority(TIMER3_IRQn, APP_IRQ_PRIORITY_LOW);
    	NVIC_EnableIRQ(TIMER3_IRQn);									// Calls TIMER3_IRQHandler
    
        	// Timer 4: Freq counter
    	NRF_TIMER4->TASKS_STOP = 1;
    	NRF_TIMER4->MODE = TIMER_MODE_MODE_Counter;
    	NRF_TIMER4->BITMODE = (TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos);
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_TIMER4->EVENTS_COMPARE[0] = 0;
    
    		// Timer 3: Timed gate
    	NRF_TIMER3->TASKS_STOP = 1;
    	NRF_TIMER3->MODE = TIMER_MODE_MODE_Timer;
    	NRF_TIMER3->PRESCALER = 0;										// Fhck / 2^0
    	NRF_TIMER3->CC[0] = 16000000ULL / 1000;							// Detect 1000 events - careful changing this!
    	NRF_TIMER3->BITMODE = (TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos);
    	NRF_TIMER3->TASKS_CLEAR = 1;
    	NRF_TIMER3->INTENSET = (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos);
    	NRF_TIMER3->EVENTS_COMPARE[0] = 0;
    
    		// GPIOTE init
    	NRF_GPIOTE->CONFIG[0] = 0x01 << 0; 								// Event mode
    	NRF_GPIOTE->CONFIG[0] |= FREQ_MEASURE_PIN << 8;					// Pin number
    	NRF_GPIOTE->CONFIG[0] |= GPIOTE_CONFIG_POLARITY_LoToHi << 16;	// Event rising edge
    
    		// PPI GPIOTE counter init on PPI CH1 set up to start the count
    	NRF_PPI->CHEN |= 1 << 1;										// Enable the channel - CH1
    	*(&(NRF_PPI->CH1_EEP)) = (uint32_t)&NRF_GPIOTE->EVENTS_IN[0];	// Event end point
    	*(&(NRF_PPI->CH1_TEP)) = (uint32_t)&NRF_TIMER4->TASKS_COUNT;	// Task end point
    	NRF_PPI->CHENSET |= 1 << 1;										// Enable the SET function
    
    		// PPI timer stop counter init on PPI CH0 set up to end the count
    	NRF_PPI->CHEN |= 1 << 0;
    	*(&(NRF_PPI->CH0_EEP)) = (uint32_t)&NRF_TIMER3->EVENTS_COMPARE[0];
    	*(&(NRF_PPI->CH0_TEP)) = (uint32_t)&NRF_TIMER4->TASKS_STOP;
    	NRF_PPI->CHENSET |= 1 << 0;
    
    	NRF_TIMER3->TASKS_START = 1;
    	NRF_TIMER4->TASKS_START = 1;
    }
    
    
    
    static volatile uint32_t freqDetected = 0;
    
    extern "C" void TIMER3_IRQHandler(void)
    {
    	if (NRF_TIMER3->EVENTS_COMPARE[0] != 0)
    	{
    		NRF_TIMER3->EVENTS_COMPARE[0] = 0;
    		NRF_TIMER4->TASKS_CAPTURE[0] = 1;
    
    		freqDetected = NRF_TIMER4->CC[0];		// Total count for 1000 events (in 0.0625us units)
    
    		NRF_TIMER3->TASKS_CLEAR = 1;
    		NRF_TIMER4->TASKS_CLEAR = 1;
    
    		NRF_TIMER4->TASKS_START = 1;
    	} else
    		hang(1);
    }
    

    I'm not sure I've fully wrapped my head around it though because the values I get for 

    freqDetected

    only report KHz and not down to the Hz - so a signal of 123456Hz returns 123 and I miss the 456 which is the important part. When I change to 

    NRF_TIMER3->CC[0] = 16000000ULL;

    then I get freqDetected values down to the hertz: 123456 but then the sampling takes a full 1000ms where I need it to take about 10ms.

    What, if anything, might I be doing wrong, if you can see it?

    Thanks!

    Kevin

  • This is generic ARM implementation and applies to most register-based hardware peripherals. It can be confusing, but has huge advantages in some circumstances. To set a bit write a '1' to the bit in the "set" register; to clear a bit write a '1' to the bit in the "clear" register. To read a bit read the "status" register. The underlying target register in all 3 cases is the same.

    Why would anyone design a system like that? Well any number of bits up to 32 can be set in a single instruction without affecting other bits; similarly up to 32 bits can be cleared in a single instruction without affecting other bits. Writing a '0' to any of these 3 registers does nothing.

    So to answer your question CHEN is the "status" register, CHENSET is the "set" register and CHENCLR is the "clear" register. Very effective; one just has to remember since this is different to many other architectures (PIC/MSP430/8051/Z80/Z8000/8086/68000/,,,).

  • Thanks for the very good explanation H.!  I didn’t know that the ARM had such a convention but it makes total sense. 
    Very much appreciated. Take care in these times.

  • OK, this is what I got. It doesn't work - the interrupt routine is never called. Can you take a look and let me know what I might be doing wrong? Thanks!

    static void freqDetectorInit(void)
    {
    		// Pin & GPIOTE init
        IOPinConfig(0, FREQ_MEASURE_PIN, 0, IOPINDIR_INPUT, IOPINRES_NONE, IOPINTYPE_NORMAL);
    	NRF_GPIOTE->CONFIG[0] = 0x01 << 0; 								// Event mode
    	NRF_GPIOTE->CONFIG[0] |= FREQ_MEASURE_PIN << 8;					// Pin number
    	NRF_GPIOTE->CONFIG[0] |= GPIOTE_CONFIG_POLARITY_LoToHi << 16;	// Event rising edge
    
    		// Calls TIMER4_IRQHandler
    	NVIC_SetPriority(TIMER4_IRQn, APP_IRQ_PRIORITY_LOW);
    	NVIC_EnableIRQ(TIMER4_IRQn);
    
    		// Timer 4: Rising edge event counter - detect 1000 +edges then generates an interrupt
    	NRF_TIMER4->TASKS_STOP = 1;
    	NRF_TIMER4->MODE = TIMER_MODE_MODE_Counter;						// Counting external pulses
    	NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;			// Only need 16 bits to count 1000 pulses
    	NRF_TIMER4->CC[0] = 1001;										// # pulses to detect (+1)
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_TIMER4->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;	// Generate int when done to get results
    
    		// Timer 3: 16MHz timer during 1000 events
    	NRF_TIMER3->TASKS_STOP = 1;
    	NRF_TIMER3->MODE = TIMER_MODE_MODE_Timer;
    	NRF_TIMER3->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    	NRF_TIMER3->PRESCALER = 0;
    	NRF_TIMER3->CC[0] = 0;
    	NRF_TIMER3->TASKS_CLEAR = 1;
    
    		// Using PPI CH0, connect NRF_TIMER4->EVENTS_COMPARE[0] (1000 +transitions) event to NRF_TIMER3->TASKS_CAPTURE[0] (capture the count of 0.0625us periods) task
    		// Add a 2nd task to clear counter and start timer. These together avoid a possible 'spurious' GPIO transition between clearing counter and starting timer
    	NRF_PPI->CH[0].EEP = NRF_TIMER4->EVENTS_COMPARE[0];
    	NRF_PPI->CH[0].TEP = NRF_TIMER3->TASKS_CAPTURE[0];
    	NRF_PPI->FORK[0].TEP = NRF_TIMER3->TASKS_CLEAR;
    	NRF_PPI->CHENSET = 1 << 0;
    
    		// On PPI CH1, when a GPIOTE rise event happens start TIMER3 and increment TIMER4
    	NRF_PPI->CH[1].EEP = NRF_GPIOTE->EVENTS_IN[0];
    	NRF_PPI->CH[1].TEP = NRF_TIMER3->TASKS_START;
    	NRF_PPI->FORK[1].TEP = NRF_TIMER4->TASKS_COUNT;
    	NRF_PPI->CHENSET = 1 << 1;										// Go
    }
    
    
    static volatile uint32_t freqDetected = 0;
    
    extern "C" void TIMER4_IRQHandler(void)
    {
    	NRF_PPI->CHENCLR = 1 << 1;				// Disable PPI channel CH1 that initiated counting
    	NRF_TIMER3->TASKS_STOP = 1;				// Stop our counter
    	freqDetected = NRF_TIMER3->CC[0];		// Get detected freq: total count for 1000 +edge events (in 0.0625us units)
    	NRF_TIMER3->TASKS_CLEAR = 1;			// Reset timers
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_PPI->CHENSET = 1 << 1;				// Re-enable PPI - go again
    }
    

  • You didn't start your counter (I suggested to remove manual start only for timer, but couter should be started manually).

    In interrupt handler, you should check what event is pending, and clear that event (otherwise interrupt handler will be called again and again):

    void TIMER4_IRQHandler(void)
    { 
        if(NRF_TIMER4->EVENTS_COMPARE[0])
        {
            NRF_TIMER4->EVENTS_COMPARE[0] = 0;
            ... your code ...
        }
    }

  • Thanks Dmitry. I actually tried enabling TIMER4 but that didn't work. I then (incorrectly) assumed the line

    NRF_PPI->FORK[1].TEP = NRF_TIMER4->TASKS_COUNT;

    was turning on timer 4 for me. Nope..

    So now I put in starting timer 4 and the other change you mentioned and still the interrupt is not being called. Currently I have:

    static void freqDetectorInit(void)
    {
    		// Pin & GPIOTE init
        IOPinConfig(0, FREQ_MEASURE_PIN, 0, IOPINDIR_INPUT, IOPINRES_NONE, IOPINTYPE_NORMAL);
    	NRF_GPIOTE->CONFIG[0] = 0x01 << 0; 								// Event mode
    	NRF_GPIOTE->CONFIG[0] |= FREQ_MEASURE_PIN << 8;					// Pin number
    	NRF_GPIOTE->CONFIG[0] |= GPIOTE_CONFIG_POLARITY_LoToHi << 16;	// Event rising edge
    
    		// Calls TIMER4_IRQHandler
    	NVIC_SetPriority(TIMER4_IRQn, APP_IRQ_PRIORITY_LOW);
    	NVIC_EnableIRQ(TIMER4_IRQn);
    
    		// Timer 4: Rising edge event counter - detect 1000 +edges then generate an interrupt
    	NRF_TIMER4->TASKS_STOP = 1;
    	NRF_TIMER4->MODE = TIMER_MODE_MODE_Counter;						// Counting external pulses
    	NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;			// Only need 16 bits to count 1000 pulses
    	NRF_TIMER4->CC[0] = 1001;										// # pulses to detect (+1)
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_TIMER4->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;	// Generate int when done to get results
    	NRF_TIMER4->TASKS_START = 1;
    
    		// Timer 3: 16MHz timer during 1000 events
    	NRF_TIMER3->TASKS_STOP = 1;
    	NRF_TIMER3->MODE = TIMER_MODE_MODE_Timer;
    	NRF_TIMER3->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    	NRF_TIMER3->PRESCALER = 0;
    	NRF_TIMER3->CC[0] = 0;
    	NRF_TIMER3->TASKS_CLEAR = 1;
    
    		// Using PPI CH0, connect NRF_TIMER4->EVENTS_COMPARE[0] (1000 +transitions) event to NRF_TIMER3->TASKS_CAPTURE[0] (capture the count of 0.0625us periods) task
    		// Add a 2nd task to clear counter and start timer. These together avoid a possible 'spurious' GPIO transition between clearing counter and starting timer
    	NRF_PPI->CH[0].EEP = NRF_TIMER4->EVENTS_COMPARE[0];
    	NRF_PPI->CH[0].TEP = NRF_TIMER3->TASKS_CAPTURE[0];
    	NRF_PPI->FORK[0].TEP = NRF_TIMER3->TASKS_CLEAR;
    	NRF_PPI->CHENSET = 1 << 0;
    
    		// On PPI CH1, when a GPIOTE rise event happens start TIMER3 and increment TIMER4
    	NRF_PPI->CH[1].EEP = NRF_GPIOTE->EVENTS_IN[0];
    	NRF_PPI->CH[1].TEP = NRF_TIMER3->TASKS_START;
    	NRF_PPI->FORK[1].TEP = NRF_TIMER4->TASKS_COUNT;
    	NRF_PPI->CHENSET = 1 << 1;										// Go
    }
    
    
    extern "C" void TIMER4_IRQHandler(void)
    {
    	if (NRF_TIMER4->EVENTS_COMPARE[0]) {
    		NRF_PPI->CHENCLR = 1 << 1;				// Disable PPI channel CH1 that initiated counting
    		NRF_TIMER3->TASKS_STOP = 1;				// Stop our counter
    		pulsesDetected = NRF_TIMER3->CC[0];		// Get detected pulses: total count for 1000 +edge events (in 0.0625us units)
    		NRF_TIMER3->TASKS_CLEAR = 1;			// Reset timers
    		NRF_TIMER4->TASKS_CLEAR = 1;
    		NRF_TIMER4->EVENTS_COMPARE[0] = 0;		// Reset for next
    		NRF_PPI->CHENSET = 1 << 1;				// Re-enable PPI - go again
    	}
    }
    

Reply
  • Thanks Dmitry. I actually tried enabling TIMER4 but that didn't work. I then (incorrectly) assumed the line

    NRF_PPI->FORK[1].TEP = NRF_TIMER4->TASKS_COUNT;

    was turning on timer 4 for me. Nope..

    So now I put in starting timer 4 and the other change you mentioned and still the interrupt is not being called. Currently I have:

    static void freqDetectorInit(void)
    {
    		// Pin & GPIOTE init
        IOPinConfig(0, FREQ_MEASURE_PIN, 0, IOPINDIR_INPUT, IOPINRES_NONE, IOPINTYPE_NORMAL);
    	NRF_GPIOTE->CONFIG[0] = 0x01 << 0; 								// Event mode
    	NRF_GPIOTE->CONFIG[0] |= FREQ_MEASURE_PIN << 8;					// Pin number
    	NRF_GPIOTE->CONFIG[0] |= GPIOTE_CONFIG_POLARITY_LoToHi << 16;	// Event rising edge
    
    		// Calls TIMER4_IRQHandler
    	NVIC_SetPriority(TIMER4_IRQn, APP_IRQ_PRIORITY_LOW);
    	NVIC_EnableIRQ(TIMER4_IRQn);
    
    		// Timer 4: Rising edge event counter - detect 1000 +edges then generate an interrupt
    	NRF_TIMER4->TASKS_STOP = 1;
    	NRF_TIMER4->MODE = TIMER_MODE_MODE_Counter;						// Counting external pulses
    	NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;			// Only need 16 bits to count 1000 pulses
    	NRF_TIMER4->CC[0] = 1001;										// # pulses to detect (+1)
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_TIMER4->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;	// Generate int when done to get results
    	NRF_TIMER4->TASKS_START = 1;
    
    		// Timer 3: 16MHz timer during 1000 events
    	NRF_TIMER3->TASKS_STOP = 1;
    	NRF_TIMER3->MODE = TIMER_MODE_MODE_Timer;
    	NRF_TIMER3->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    	NRF_TIMER3->PRESCALER = 0;
    	NRF_TIMER3->CC[0] = 0;
    	NRF_TIMER3->TASKS_CLEAR = 1;
    
    		// Using PPI CH0, connect NRF_TIMER4->EVENTS_COMPARE[0] (1000 +transitions) event to NRF_TIMER3->TASKS_CAPTURE[0] (capture the count of 0.0625us periods) task
    		// Add a 2nd task to clear counter and start timer. These together avoid a possible 'spurious' GPIO transition between clearing counter and starting timer
    	NRF_PPI->CH[0].EEP = NRF_TIMER4->EVENTS_COMPARE[0];
    	NRF_PPI->CH[0].TEP = NRF_TIMER3->TASKS_CAPTURE[0];
    	NRF_PPI->FORK[0].TEP = NRF_TIMER3->TASKS_CLEAR;
    	NRF_PPI->CHENSET = 1 << 0;
    
    		// On PPI CH1, when a GPIOTE rise event happens start TIMER3 and increment TIMER4
    	NRF_PPI->CH[1].EEP = NRF_GPIOTE->EVENTS_IN[0];
    	NRF_PPI->CH[1].TEP = NRF_TIMER3->TASKS_START;
    	NRF_PPI->FORK[1].TEP = NRF_TIMER4->TASKS_COUNT;
    	NRF_PPI->CHENSET = 1 << 1;										// Go
    }
    
    
    extern "C" void TIMER4_IRQHandler(void)
    {
    	if (NRF_TIMER4->EVENTS_COMPARE[0]) {
    		NRF_PPI->CHENCLR = 1 << 1;				// Disable PPI channel CH1 that initiated counting
    		NRF_TIMER3->TASKS_STOP = 1;				// Stop our counter
    		pulsesDetected = NRF_TIMER3->CC[0];		// Get detected pulses: total count for 1000 +edge events (in 0.0625us units)
    		NRF_TIMER3->TASKS_CLEAR = 1;			// Reset timers
    		NRF_TIMER4->TASKS_CLEAR = 1;
    		NRF_TIMER4->EVENTS_COMPARE[0] = 0;		// Reset for next
    		NRF_PPI->CHENSET = 1 << 1;				// Re-enable PPI - go again
    	}
    }
    

Children
  • This isn't a reply to the last question - dmitry is doing a great job there :-) - but a technique you can use to get enhanced resolution once your code is working.

    In essence once you have a count of the number of input signal cycles in the 10mSec measurement period, we need a way to interpolate the distance from the last input edge trigger (let's assume +ve edge) to the point in time when the 10mSec measurement period terminates. You mention limiting the external hardware, so how about a single capacitor? Well, maybe a resistor as well; using a series capacitor only risks exceeding the rails depending on the input signal, so safer to use a resistor feed to a different layout.

    Feed the input signal to a digital port pin (as now) where the input cycles are counted for the 10mSec sampling interval. Feed the same input signal through a series resistor to a capacitor (to Gnd, acting as an integrator) and a second analogue (analog) port pin. Bias the input signal ac coupling to 50% by enabling both pull-up and pull-down resistor ladders CONFIG.RESP and CONFIG.RESN, although you might get away not doing that. Now (hopefully) taking an SAADC measurement on this ADC channel provides an indication of the time since last +ve edge in conjunction with the knowledge of the current high or low status of the digital input pin (indicates rising or falling voltage) given a suitable capacitor selection (depends on source impedance). Some simple (not-so-simple) math or a simple look-up table converts this voltage into an accurate indication of the point in time in the last cycle when the 10mSec measurement interval terminated provided the SAADC sample is triggered by that same event. Adding that point in time to the coarse measurement and subtracting the deterministic SAADC sampling/conversion time  gives the required fine measurement. 16MHz asynchronous measurement issues ( I know you would ask that) falls out (hopefully) in the wash. Amazing, what?

  • Could you look in the debugger, does your counter ever counts something?

    The method suggested by  is very interesting, I just add that ADC measurement should be done at both edges (first and last), because GPIOTE event is synchronized with PCLK in both cases.

  • OK, finally got things working. Mostly... Turns out the problem I had was in misconfiguring the PPI's. This fixes that and now I am reading frequencies:

    #define FREQ_MEASURE_PIN  11												// P0.11
    #define kPulseDetectionCount 1000											// Detect this many +edges
    
    
    static void freqDetectorInit(void)
    {
    		// Pin & GPIOTE init
        IOPinConfig(0, FREQ_MEASURE_PIN, 0, IOPINDIR_INPUT, IOPINRES_NONE, IOPINTYPE_NORMAL);
    	NRF_GPIOTE->CONFIG[0] = 0x01 << 0; 								// Event mode
    	NRF_GPIOTE->CONFIG[0] |= FREQ_MEASURE_PIN << 8;					// Pin number
    	NRF_GPIOTE->CONFIG[0] |= GPIOTE_CONFIG_POLARITY_LoToHi << 16;	// Event rising edge
    
    		// Calls TIMER4_IRQHandler
    	NVIC_SetPriority(TIMER4_IRQn, APP_IRQ_PRIORITY_LOW);
    	NVIC_EnableIRQ(TIMER4_IRQn);
    
    		// Timer 4: Rising edge event counter - detect 1000 +edges then generate an interrupt
    	NRF_TIMER4->TASKS_STOP = 1;
    	NRF_TIMER4->MODE = TIMER_MODE_MODE_Counter;												// Counting external pulses
    	NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;			// Only need 16 bits to count 1000 pulses
    	NRF_TIMER4->CC[0] = kPulseDetectionCount + 1;											// # pulses to detect (+1)
    	NRF_TIMER4->TASKS_CLEAR = 1;
    	NRF_TIMER4->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;	// Generate int when done to get results
    	NRF_TIMER4->TASKS_START = 1;
    
    		// Timer 3: 16MHz timer during 1000 events
    	NRF_TIMER3->TASKS_STOP = 1;
    	NRF_TIMER3->MODE = TIMER_MODE_MODE_Timer;
    	NRF_TIMER3->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    	NRF_TIMER3->PRESCALER = 0;
    	NRF_TIMER3->CC[0] = 0;
    	NRF_TIMER3->TASKS_CLEAR = 1;
    
    		// Using PPI CH0, connect NRF_TIMER4->EVENTS_COMPARE[0] (1000 +transitions) event to NRF_TIMER3->TASKS_CAPTURE[0] (capture the count of 0.0625us periods) task
    		// Add a 2nd task to clear counter and start timer. These together avoid a possible 'spurious' GPIO transition between clearing counter and starting timer
    	NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER4->EVENTS_COMPARE[0];
    	NRF_PPI->CH[0].TEP = (uint32_t)&NRF_TIMER3->TASKS_CAPTURE[0];
    	NRF_PPI->FORK[0].TEP = (uint32_t)&NRF_TIMER3->TASKS_CLEAR;
    	NRF_PPI->CHENSET = 1 << 0;
    
    		 // On PPI CH1, when a GPIOTE rise event happens start TIMER3 and increment TIMER4
    	NRF_PPI->CH[1].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[0];
    	NRF_PPI->CH[1].TEP = (uint32_t)&NRF_TIMER3->TASKS_START;
    	NRF_PPI->FORK[1].TEP = (uint32_t)&NRF_TIMER4->TASKS_COUNT;
    	NRF_PPI->CHENSET = 1 << 1;										// Go
    }
    
    
    static volatile uint32_t pulsesDetected = 0;
    static volatile uint32_t freqDetected = 0;
    
    extern "C" void TIMER4_IRQHandler(void)
    {
    	if (NRF_TIMER4->EVENTS_COMPARE[0]) {
    		NRF_PPI->CHENCLR = 1 << 1;				// Disable PPI channel CH1 that initiated counting
    		NRF_TIMER3->TASKS_STOP = 1;				// Stop our counter (& do a capture)
    
    		pulsesDetected = NRF_TIMER3->CC[0];		// Get detected pulses: total count for 1000 +edge events (in 0.0625us units)
    		freqDetected = (pulsesDetected == 0) ? 0 : (16000000.0 * (float)kPulseDetectionCount) / pulsesDetected;
    		pulsesDetected = 0;
    
    		NRF_TIMER3->TASKS_CLEAR = 1;			// Reset timers
    		NRF_TIMER4->TASKS_CLEAR = 1;
    		NRF_TIMER4->EVENTS_COMPARE[0] = 0;		// Reset for next
    		NRF_PPI->CHENSET = 1 << 1;				// Re-enable PPI - go again
    	}
    }
    

    But there are 2 interesting anomalies:

    1) There is a fixed frequency offset of about 500-600hz. When I measure a dead accurate 100KHz signal, I get frequency readings ranging from 100400 to about 100600.

    2) There is a lot of jitter in the measurement even after 1000 pulses have transpired. I would expect a few Hz difference from reading to reading but I am seeing differences of around 200Hz in the readings.

    Thoughts?

  • Thanks again for the input Hugh. If I understand you correctly, your idea is to try to accurately resolve the very last pulse in the 1000 pulse train - basically to compare the phase difference between when the last pulse happens and when the last sampling of that pulse happens, right? 

    If that is a correct understanding, I don't know how much that solution might help given that I am sampling 1000 pulses in order to determine frequency and even if the last pulse was mis-measured the worst it could be (1/2 the pulse width), wouldn't the averaging of the other 999 pulses wash that mistaken measurement out for the most part? I could see where your idea would totally help if I were measuring only a single pulse, but given the large sampling and averaging that is happening, I think that might not result in such a huge accuracy improvement.

    Or maybe I don't fully understand what you are resolving with this refinement...

    In any case, thanks for the input none-the-less!

  • Did you start HFCLK? Don't expect an accurate result from an internal oscillator.

Related