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

Capture then clear timer.

Is there a way to capture and clear a timer without being interrupted. This doesn't seem possible with shortcuts. also it looks like theres no capture event with which to trigger a clear task over ppi only compare events, or are the compare events also capture events (as compare registers also seem to be capture registers). What about an event that triggers both capture and clear tasks over ppi? what event could be used for code to trigger both tasks? what about a multiple store operation in asm (specifically I think the STMDB instruction might work, writing 1 on the capture register then zeros on the other registers, then finally a 1 on the clear register) community.arm.com/.../c-c-atomic-operation-on-arm9-and-arm-cortex-m4

	NRF_TIMER2->TASKS_CAPTURE[3] = 1;
	NRF_TIMER2->TASKS_CLEAR = 1;

can be interrupted?

  • Hi

    Where there is a will there is a way ;)

    In this case I think the simplest solution is to use one of the EGU units and the PPI to trigger both tasks in the timer at the same time.

    The EGU unit is a very simple peripheral that allow you to generate an internal event by activating a corresponding task (in software or hardware). The internal event can then be connected to the PPI controller, and since each PPI channel has a secondary FORK endpoint you can connect it to two different tasks.

    As an example, in the following code I am using PPI channel 0 to connect EGU0 channel 0 to the capture and clear tasks from your example:

    NRF_PPI->CH[0].EEP = (uint32_t)&NRF_EGU0->EVENTS_TRIGGERED[0];
    NRF_PPI->CH[0].TEP = (uint32_t)&NRF_TIMER2->TASKS_CAPTURE[3];
    NRF_PPI->FORK[0].TEP = (uint32_t)&NRF_TIMER2->TASKS_CLEAR;
    NRF_PPI->CHENSET = 1 << 0;
    

    To run the capture and clear operation, simply trigger the task of the EGU like this:

    NRF_EGU0->TASKS_TRIGGER[0] = 1; 
    

    Best regards
    Torbjørn

  • Torbjørn,

    If I execute periodically in the main loop:

    NRF_EGU0->TASKS_TRIGGER[0] = 1;

    last_cycle_count += (uint64_t) NRF_TIMER2->CC[3];

    timer2 is captured and cleared atomically and continues running. Is there any reason why time would be lost/gained every accumulation? It seems like it may be, as i think if it's captured and cleared mid-cycle, that cycle would not be counted and every time this happened the error would accumulate. Is this correct? Is there a better way of doing this?

  • Hi

    What is the prescaler setting of the timer, and how much variation are you seeing on the captured result?

    Since the CPU core is running at a higher frequency than the timer I would expect some variation in this case, but not more than a single count up or down.

    If you allow the timer to run freely, without resetting it for each capture, then you won't get any accumulation of error. You just need to calculate the difference between one capture and the next.

    May I ask what time you are trying to measure?

    Best regards
    Torbjørn

  • The prescaler is set to 1 MHz. I suspect it's only +/- 1 error Microsecond per capture and clear, but it captures and clears a lot.

    The first objective is to calculate offset and drift between the nRF52 timer and a remote 64 bit monotonic timer (on central) which arrives over ble (units are in microseconds). I calculate offset and a drift over many radio notifications logging the start and end of radio events (outliers get rejected). The algorithm is very complex, and uses arbitrary precision floating point math, but is a known working algorithm and I have tested those portions of the code. I believe the problem I am having lies with the fact that I am attempting to build a 64 bit timer from a 32 bit one and accumulating error over time as we have discussed.

    The second objective (and primary objective) is that the remote device (central) can send a future time stamp (64 bit) over ble to the nordic and have a task executed at that time. It currently does this by receiving the future timestamp, then runs:

    NRF_EGU0->TASKS_TRIGGER[0] = 1; last_cycle_count += (uint64_t) NRF_TIMER2->CC[2];

    next it applies offset and drift to last cycle count, this acquires a estimate of the 64 bit monotonic timer (which is on central) which can be subtracted from the future timestamp. No time is lost as it is accounted for by timer2 which is still running during calculation (it was cleared). The difference is loaded into a compare register on timer2 and triggers the start of a peripheral at that time over ppi (I also have another compare register set up for an error condition if the calculation has taken too long).

    I may be able to refactor things in such a way that the timer can run freely, the problem would be during the second objective then, time would be lost during applying offset and drift and loading the difference in the timer 2 register.

    The solution may be an additional piece of hardware: a 64bit timer ic or perhaps using an second timer as a overflow count register.

    This project has represented huge portions of my time and I very much need help on this issue.

    The Radio Notification Handler (I am using capture register 2 instead of 3 now):
    void SWI1_IRQHandler(void) {
    	m_radio_active = !m_radio_active;
    	if (m_radio_active && last_two_radio_start_events_count > 0) {
    		// Atomic capture and clear of timer 2 to the capture 3 register.
    		NRF_EGU0->TASKS_TRIGGER[0] = 1;
    		last_cycle_count += (uint64_t) NRF_TIMER2->CC[2];
    		last_two_radio_start_events_count--;
    		// 800 because we have 800 us before it starts.
    		last_two_radio_start_events[last_two_radio_start_events_count] = last_cycle_count + 800;
    	} else if (last_two_radio_end_events_count > 0) {
    		// Atomic capture and clear of timer 2 to the capture 3 register.
    		NRF_EGU0->TASKS_TRIGGER[0] = 1;
    		last_cycle_count += (uint64_t) NRF_TIMER2->CC[2];
    		last_two_radio_end_events_count--;
    		last_two_radio_end_events[last_two_radio_end_events_count] = last_cycle_count;
    	}
    }
    
  • Hi

    I definitely think a free running timer is the way to go.

    To simulate a 64-bit timer you can configure a second timer module in counter mode, and have the first timer trigger the count task over PPI every time it overflows.
    There is no dedicated overflow event, so you would have to use one of the CC registers (and the corresponding COMPARE event) for this. I haven't tested this, but if you set the CC register to 0 the event should fire when the timer overflows.

    To run an atomic capture on both timers you could use a trick similar to the capture/clear method I proposed earlier: Use one of the other task/events in the EGU, and use a PPI channel with the FORK feature to activate two capture tasks at the same time.

    Best regards

Related