This first post dives into one of the most fundamental peripherals of any microprocessor: The timer. Let us examine this part very closely. All the information was gathered from looking through an example file and the datasheet for the nRF52832.

The code with the examples can be found in the following github gist:

To start, we need to find an example that makes use of the timer. After some digging I found the peripherals/ppi example to be a good candidate. Reading through the main function and going from there shows the general pattern that we should follow if we were to use this peripheral:

– Generate timer config (This looks interesting so we make a mental note)
– Initialize timer with that config and pass event handler callback
– Convert a period in ms to a number of ticks (A quick glance at the function reveils that it does what we’d expect)
– Configure the compare register (This also looks interesting)
– Enable the timer

The event handler is just a function that is called every time a certain event is triggered by the timer. We will see this in more depth shortly.

Now that we have a broad overview, let’s dig deeper into the different bits. The constant NRFX_TIMER_DEFAULT_CONFIG pops into view and it’s interesting to see what a “default config” for such a timer contains. We follow its definition and see that it is made up of more constants that are provided by the sdk_config.h.

The first parameter is the frequency. The maximum frequency is 16 MHz  and it can be reduced by setting this parameter. Interesting to note is that, when a frequency below or equal to 1MHz is chosen, the slower CLK PCLK1M (1 MHz internal clock)  is chosen instead of PCLK16M (16 MHz internal clock), in order to reduce power consumption. This should be kept in mind when choosing the right timer values for certain tasks.

Next, mode can be either Timer or Counter. When mode is configured to be “Timer”, the module acts like an actual timer. The timer’s internal counter register is incremented for every tick. The time between ticks is the period given by the timer frequency. However, when setting the mode to “Counter”, the internal counter register is incremented whenever the COUNT-task is triggered. We will see an example of both.

Bit width sets the width of the variable that represents the number of ticks that have passed. This can be 8, 16, 24 or 32 bit wide. We can easily compute the largest timeline that these constraints allow. For example 2^32 / 31.25 kHz = 137439 seconds (38 hours and 10 minutes) is the longest possible timeline when using the slowest frequency (remember, that this puts constraints on the accuracy of the timer). When using the full 16MHz we only get 4 minutes and 28 seconds worth of timeline. We can extend these timelines by keeping track of time itself, updating a variable in software whenever an the timer is about to overrun.

IRQ priority sets the interrupt priority. Nested interrupts are a thing in ARM Cortex-M4, so the priority indicated, by which other interrupts the interrupt service routine for timer interrupts can be … interrupted.

Now that we know the settings, which describe a timer, let us see what this mysterious nrf_drv_timer_extended_compare function is doing.

Every timer in the nRF52 has multiple capture/compare registers associated with them. Timers 0 to 2 have 4, timers 3 and 4 have 6 so called CC-registers. The concepts of these functionalities is straight-forward and will be briefly explained here:

“Capture” means, that the current value of the timeline is written into the specified CC-register. Capturing can be used for example for time-stamping certain events and interrupts or for precisely measuring the time between two instants, as setting a capture task has such a small amount of overhead. In order to use a certain CC-register in capture mode, simply call the function nrfx_timer_capture with the timer and channel you are interested in. This will trigger the CAPTURE-Task and return the captured value. The function nrfx_timer_capture_get is a wrapper that simply returns the captured value for a certain channel. This function should be used when you do not want to capture the value again, but just want to read from it. Whenever the CAPTURE-task is triggered via the PPI (Programmable Peripheral Interface), this function must be used, as we don’t want to trigger the CAPTURE-Task again when reading the value.

“Compare” means, that an event occurs whenever the timeline is equal to the value in that CC-register. The timer counts up to the value specified in the CC-register and triggers an interrupt when it reaches that value. This can be useful when you want periodic things to happen, such as an LED blinking or a sensor measurement being taken. However, when implementing such functionality, make sure to keep your ISRs as short as possible and defer all complex processing to later times. Timer interrupts  are also used by preemptive schedulers: A timer interrupt occurs every X milliseconds and checks which task is supposed to run next. To setup a compare timer interrupt, simply call nrfx_timer_compare or nrfx_timer_extended_compare with a timer instance, an appropriate channel, the value at which the interrupt should be triggered as well as a flag, if interrupts should be enabled or disabled. In addition, nrfx_timer_extended_compare also takes an nrf_timer_short_mask_t can be added, which allows to specify that the CLEAR- and / or the STOP-Task should be executed, whenever the corresponding COMPARE-event is raised.

The index of the capture/compare register is equivalent to what is called “channel” in the example code.

Now that we have a good overview of what these timers are capable of, it’s time to play around with them. I created three small examples that showcase what we have learned. The gist of these examples is obviously not that this can’t be done differently, but it’s fun to tinker with these kind of things to deepen the understanding.

The examples are based on the example project peripherals/bsp in the nRF5 SDK, because it already includes button and LED initialization as well as UART logging. The only thing we need to import is nrfx_timer.h and nrfx_timer.c. In addition, some fields in the sdk_config.h must be added and reconfigured (The part found under “nrfx_timer – TIMER peripheral driver”). After this is done, our playground is ready for action.

There are 3 tasks implemented, which can be cycled through by pressing Button 1 on the nRF52-DK.

Task 1:
The first task creates a single counter. Whenever Button 0 is pressed, the counter is incremented by 1 and the value of the counter is displayed in form of its bits by the LEDs. When the compare value of 16 is reached, the counter is supposed to be resetted. This is achieved by setting the mask to NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK in the call to nrfx_timer_extended_compare. The timer is now cleared every time the compare value of channel 0 is reached.

Task 2:
In the second task, we create another timer. This timer increments the first timer (the counter), every time it reaches a certain compare value. The compare value is computed, such that it takes exactly 1 second between events. The result is the display of a binary number which is incremented every second.

Task 3:
The final task contains two timers. The timer from task 2 enables the new timer every time it hits its compare value. The second timer has several compare events attached to it. The datasheet tells us, that we need to use timer 3 or 4 if we need more than 4 compare registers. Instead of computing the compare values ourselves, we use the function nrfx_timer_ms_to_ticks, which does the computation for us, based on the frequency of the timer. Note that we only set the masking shortcut for the last compare registers: We only want to reset the timer once it cycled to the end. We finally disable the timer inside of its own event handler.

This wraps up this first post on timers in the nRF52-DK. We will come back to timers again and again, as they are vital for all kinds of embedded devices.