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

  • Ok. Great. Thanks. What about the interruption routine? That should get the results, right? And what should it reset? What should it do to restart the whole process?

  • The best way is to disable PPI channel that starts counting, stop TIMER3, clear TIMER3 and TIMER4, then re-enable PPI.

  • Please explain the differences and when you would use PPI's CHEN, CHENSET, and CHENCLR. The documentation doesn't explain why there are 3 and when to use them (that I've found) and the graphic at the top only shows CHEN. Thanks!

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

Reply Children
No Data
Related