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)

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

Related