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

  • Not quite what I was suggesting. You might explain the requirement in more detail before moving forward; not sure why you mention averaging 999 pulses. The stability of the measurement window is probably 20ppm (part per million) given you have HFCLK enabled as suggested, and over a short measurement that would be even better.

    Is the signal encoding an analogue (analog) waveform modulated about the center frequency? If so you don't care about the actual frequency, just the delta modulation which becomes a ratiometric measurement much easier to make (and more accurate) as there are some other techniques available.

    Is the signal instead a changing frequency which has to be measured absolutely, say for a hand-held frequency meter?

    Is the signal a series of digital step increments? Where does the 1Hz resolution requirement come from?

  • I incorrectly assumed the NRF52-DK automatically had that designed into it since it has an external xtal. Now I understand the external xtal has to manually be enabled (right?). When I do so (through enabling a BTLE SoftDevice) the frequency measurements are stable as a rock. It is off by about 2Hz in absolute accuracy, but I can live with that. It oscillates by about 1/2Hz to 1Hz. Very nice performance (on an NRF52-DK).

    So... Your idea worked well! Thank You. I'll post the final code here in case anyone else needs a frequency detector with a few Hz resolution that can measure up to the hundreds of KHz. Just make sure you use a very stable external CLK.

    #define FREQ_MEASURE_PIN  11												// P0.11
    #define kPulseDetectionCount 1000											// Detect this many +edges
    
    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
    
    		pulsesDetected = NRF_TIMER3->CC[0];		// Get detected pulses (count was captured during PPI event): total count for 1000 +edge events (in 0.0625us units)
    		freqDetected = (pulsesDetected == 0) ? 0 : (16000000.0 * (float)kPulseDetectionCount) / pulsesDetected;
    
    		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
    	}
    }
    
    
    // Must use a stable external CLK for accurate measurements
    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, when Timer 4 compares to kPulseDetectionCount +transitions events, capture the count of 0.0625us periods
    		// Add a 2nd task to stop 16MHz 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_STOP;
    	NRF_PPI->CHENSET = 1 << 0;
    
    		 // On PPI CH1, when a GPIOTE rise event happens start TIMER3 (the first time) and increment TIMER4 every time
    	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
    }
    

  • Hugh, I'll reply to your questions offline, OK?

  • Ok - and good job in getting your code running!

  • Hi Dmitry. This technique is working pretty well (thanks again!) but I seem to have an issue when the SoftDevice is transmitting data. It is almost as if the SoftDevice is dropping back to the internal LC oscillator then switching back to the external CLK when it returns control to me.

    When I transmit data via BT as a Characteristic, the readings vary +-150Hz (like it was before I knew about the internal vs. external oscillator selection above). This does NOT happen when I break at the point where the data is transmitted and look at the reading - the breakpoint seems to shut down the BT link and all subsequent readings are stable as a rock each time I continue with the breakpoint and break again the next time around (until the SoftDevice panics and throws an exception like it always does). 

    So basically when I break at my transmission point to see what data it is going to transmit, the link drops and the frequencies are stable. But when I let it run on it's own and transmit the frequency reading every second (from a 1 second timer interrupt), the data transmitted varies about +-150Hz or so. This variance also happens when I turn off BT and the SoftDevice all together which defaults the application to use the internal LC oscillator - that is expected.

    Thoughts about the SoftDevice toggling oscillator sources?

    Also, is there a way to tell the SoftDevice NOT to panic when I am single stepping through code?

    Thanks!

Reply
  • Hi Dmitry. This technique is working pretty well (thanks again!) but I seem to have an issue when the SoftDevice is transmitting data. It is almost as if the SoftDevice is dropping back to the internal LC oscillator then switching back to the external CLK when it returns control to me.

    When I transmit data via BT as a Characteristic, the readings vary +-150Hz (like it was before I knew about the internal vs. external oscillator selection above). This does NOT happen when I break at the point where the data is transmitted and look at the reading - the breakpoint seems to shut down the BT link and all subsequent readings are stable as a rock each time I continue with the breakpoint and break again the next time around (until the SoftDevice panics and throws an exception like it always does). 

    So basically when I break at my transmission point to see what data it is going to transmit, the link drops and the frequencies are stable. But when I let it run on it's own and transmit the frequency reading every second (from a 1 second timer interrupt), the data transmitted varies about +-150Hz or so. This variance also happens when I turn off BT and the SoftDevice all together which defaults the application to use the internal LC oscillator - that is expected.

    Thoughts about the SoftDevice toggling oscillator sources?

    Also, is there a way to tell the SoftDevice NOT to panic when I am single stepping through code?

    Thanks!

Children
  • Hi Kevin,

    Happy to know that your code works :)  With softdevice, sd_clock_hfclk_request() call should help.

    Also, is there a way to tell the SoftDevice NOT to panic when I am single stepping through code?

    It's annoying, but it seems there's no way to single-step while connection is active.

  • Thanks Dmitry. Any thoughts about my first question about the SoftDevice seemingly changing internal oscillators or something like that...?

  • Please ignore the above. I missed your first reply... Investigating now...

  • You need J-Link Monitor which allows you to single step debug while the SoftDevice runs happily in the background without dropping the BLE connection. Try this:

    #if defined(FEATURE_JLINK_MONITOR)
       // This allows all interrupts higher than _PRIO_SD_LOW (ie 4, BLE SD stuff) to continue execution even during a break
       // note Two J-Link commands are required in command file:
       // SetMonModeDebug = 1
       // SetMonModeVTableAddr = ADDR
       //  where ADDR is the application's vector table located in FLASH, ie the start address for the
       // application, probably either 0x1B000 or 0x1C000 or 0x26000 depending on SoftDevice
       // See https://github.com/NordicPlayground/j-link-monitoring-mode-debugging
       NVIC_SetPriority(DebugMonitor_IRQn, _PRIO_SD_LOW);
    #endif

    Enable FEATURE_JLINK_MONITOR in the project preprocessor (so it's easy to turn feature on and off). Add the commands noted above in Debug->J-Link->Command Options for SES; other IDEs are similar, I use both SES and IAR.

    Get code from monitor-mode-debugging

  • Thank you again Dmitry! Your idea worked. Again. Slight smile  This code is needed to maintain accurate frequency results when using a SoftDevice. If not using a SoftDevice, use other means to enable the external 32MHz oscillator.

    static void ensureExternalClkRunning(void)
    {
    	uint32_t isRunning = 0;
    	int cnt = 0;
    	sd_clock_hfclk_request();
    	do {
    		sd_clock_hfclk_is_running(&isRunning);
    		cnt++;
    		if (cnt > 1000000) {} // Handle non transition. Cnt is usually on the order of 80-ish
    	} while (isRunning == 0);
    }
    

Related