nRF5 SDK Application Timer Tutorial

2018-11-23: Updated tutorial to cover SDK version 15.2.

2019-02-08: Improved example project (SDK 15.2) and updated instructions accordingly.

Introduction

Scope

The following topics will be included in this tutorial:

  • Configuration of the application timer library
  • Repeated timers
  • Single shot timers

The tutorial will not cover all aspects of the application timer but will focus on the most important uses and API functions.

Necessary prior knowledge

It is expected that you have basic knowledge of how to use Keil or Segger Embedded Studio in order to build and download your application to your kit. Refer to Getting Started in the SDK documentation if this is unfamiliar territory. It is also expected that you are familiar with the concept of interrupts and event handlers. Please refer to the Pin Change Interrupt Example if you need to refresh this topic.

Necessary equipment and software

Hardware:

Software:

Other files:

Within the tutorial project folder there are subfolders for different boards:

  • pca10040 for the nRF52 DK.
  • pca10040e for the nRF52 DK emulating nRF52810.
  • pca10056 for the nRF52840 DK.

Preparations

To get started, download and extract the SDK and clone the example project under <SDK>/examples/peripheral/:

git clone https://github.com/NordicPlayground/nrf5-application-timer-tutorial.git

The example project configures GPIO inputs and outputs for the buttons and LEDs that will be used in the tutorial. Open the project that suits your target device and prefered toolchain. The application should build without any warnings or errors.

Erase the flash of the DK using nRF Connect Programmer or nrfjprog in order to ensure that no SoftDevice is installed. Program/download the application to the target board. You should be able to control LED 1 with button 1 and 2, and control LED 2 using button 3 and 4.

The Application Timer

The Application Timer library provides a user-friendly way of using the Real Time Counter 1 (RTC1) peripheral to create multiple timer instances. The RTC uses the Low Frequency Clock (LFCLK). Most applications keep the LFCLK active at all times. When using a SoftDevice the LFCLK is always active. Therefore there is normally very little extra power consumption associated with using the application timer. As the clock is 32.768 kHz and the RTC is 24 bit, the time/tick resolution is limited, but it takes a substantial amount of time before the counter wrap around (from 0xFFFFFF to 0). The frequency of the RTC can be lowered by using the 12 bit (1/x) prescaler.

In this part of the tutorial you will configure the library, and use it to create timers that call your timeout event handlers. These can be called repeatedly at a configurable interval or once at a configurable time from now.

Refer to the Application Timer API Reference as needed.

Add required files and includes

The application timer implementation file must be added to the project in order to use the application timer:

  • <SDK>/components/libraries/timer/app_timer.c

You might also need to add the following files if you are using this as a guide to add the application timer in your own application (unless allready present):

  • <SDK>/integration/nrfx/legacy/nrf_drv_clock.c
  • <SDK>/components/libraries/util/app_util_platform.c

Add the following directory to the include paths (the path is relative to project file):

  • ../../../../../../components/libraries/timer

You might also need to add the following includes if you are using this as a guide to add the application timer in your own application (unless already present):

  • ../../../../../../integration/nrfx/legacy
  • ../../../../../../modules/nrfx/drivers/include

Then include the required header files by adding the following lines below the existing include statements:

#include "app_timer.h"
#include "nrf_drv_clock.h"

Make sure that your projects sdk_config.h includes the configuration block for the application timer: This configuration is the default configuration in SDK 15.2, which is recommended in this tutorial. Refer to Application timer functionality configuration for details.

// <e> APP_TIMER_ENABLED - app_timer - Application timer functionality
//==========================================================
#ifndef APP_TIMER_ENABLED
#define APP_TIMER_ENABLED 1
#endif
// <o> APP_TIMER_CONFIG_RTC_FREQUENCY  - Configure RTC prescaler.
 
// <0=> 32768 Hz 
// <1=> 16384 Hz 
// <3=> 8192 Hz 
// <7=> 4096 Hz 
// <15=> 2048 Hz 
// <31=> 1024 Hz 

#ifndef APP_TIMER_CONFIG_RTC_FREQUENCY
#define APP_TIMER_CONFIG_RTC_FREQUENCY 0
#endif

// <o> APP_TIMER_CONFIG_IRQ_PRIORITY  - Interrupt priority
 

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice
// <0=> 0 (highest) 
// <1=> 1 
// <2=> 2 
// <3=> 3 
// <4=> 4 
// <5=> 5 
// <6=> 6 
// <7=> 7 

#ifndef APP_TIMER_CONFIG_IRQ_PRIORITY
#define APP_TIMER_CONFIG_IRQ_PRIORITY 6
#endif

// <o> APP_TIMER_CONFIG_OP_QUEUE_SIZE - Capacity of timer requests queue. 
// <i> Size of the queue depends on how many timers are used
// <i> in the system, how often timers are started and overall
// <i> system latency. If queue size is too small app_timer calls
// <i> will fail.

#ifndef APP_TIMER_CONFIG_OP_QUEUE_SIZE
#define APP_TIMER_CONFIG_OP_QUEUE_SIZE 10
#endif

// <q> APP_TIMER_CONFIG_USE_SCHEDULER  - Enable scheduling app_timer events to app_scheduler
 

#ifndef APP_TIMER_CONFIG_USE_SCHEDULER
#define APP_TIMER_CONFIG_USE_SCHEDULER 0
#endif

// <q> APP_TIMER_KEEPS_RTC_ACTIVE  - Enable RTC always on
 

// <i> If option is enabled RTC is kept running even if there is no active timers.
// <i> This option can be used when app_timer is used for timestamping.

#ifndef APP_TIMER_KEEPS_RTC_ACTIVE
#define APP_TIMER_KEEPS_RTC_ACTIVE 0
#endif

// <o> APP_TIMER_SAFE_WINDOW_MS - Maximum possible latency (in milliseconds) of handling app_timer event. 
// <i> Maximum possible timeout that can be set is reduced by safe window.
// <i> Example: RTC frequency 16384 Hz, maximum possible timeout 1024 seconds - APP_TIMER_SAFE_WINDOW_MS.
// <i> Since RTC is not stopped when processor is halted in debugging session, this value
// <i> must cover it if debugging is needed. It is possible to halt processor for APP_TIMER_SAFE_WINDOW_MS
// <i> without corrupting app_timer behavior.

#ifndef APP_TIMER_SAFE_WINDOW_MS
#define APP_TIMER_SAFE_WINDOW_MS 300000
#endif

// <h> App Timer Legacy configuration - Legacy configuration.

//==========================================================
// <q> APP_TIMER_WITH_PROFILER  - Enable app_timer profiling
 

#ifndef APP_TIMER_WITH_PROFILER
#define APP_TIMER_WITH_PROFILER 0
#endif

// <q> APP_TIMER_CONFIG_SWI_NUMBER  - Configure SWI instance used.
 

#ifndef APP_TIMER_CONFIG_SWI_NUMBER
#define APP_TIMER_CONFIG_SWI_NUMBER 0
#endif

// </h> 
//==========================================================

// </e>

Similarly, make sure to include the configuration block for the clock driver. This is already there if you are using the example project.

// <e> NRF_CLOCK_ENABLED - nrf_drv_clock - CLOCK peripheral driver - legacy layer
//==========================================================
#ifndef NRF_CLOCK_ENABLED
#define NRF_CLOCK_ENABLED 1
#endif
// <o> CLOCK_CONFIG_LF_SRC  - LF Clock Source
 
// <0=> RC 
// <1=> XTAL 
// <2=> Synth 
// <131073=> External Low Swing 
// <196609=> External Full Swing 

#ifndef CLOCK_CONFIG_LF_SRC
#define CLOCK_CONFIG_LF_SRC 1
#endif

// <o> CLOCK_CONFIG_IRQ_PRIORITY  - Interrupt priority
 

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice
// <0=> 0 (highest) 
// <1=> 1 
// <2=> 2 
// <3=> 3 
// <4=> 4 
// <5=> 5 
// <6=> 6 
// <7=> 7 

#ifndef CLOCK_CONFIG_IRQ_PRIORITY
#define CLOCK_CONFIG_IRQ_PRIORITY 6
#endif

// </e>

Initialization

As a SoftDevice is not enabled in this tutorial, the LFCLK must be requested explicitly. One way of doing this is by using the Clock driver. Add the following function somewhere before main():

/**@brief Function starting the internal LFCLK oscillator.
 *
 * @details This is needed by RTC1 which is used by the Application Timer
 *          (When SoftDevice is enabled the LFCLK is always running and this is not needed).
 */
static void lfclk_request(void)
{
    ret_code_t err_code = nrf_drv_clock_init();
    APP_ERROR_CHECK(err_code);
    nrf_drv_clock_lfclk_request(NULL);
}

Add a line calling lfclk_request() in the start of the main() function:

    lfclk_request();

The Application Timer should be initialized with the app_timer_init() function. This must be called before any other calls to the Application Timer API.

Put this line in main() in order to initialize the app timer:

    app_timer_init();

Create a repeated timer

An application timer in the repeated mode will restart each time it expires. Every time it expires the timeout handler will be called. This makes it suitable for performing a task at a regular interval, such as toggling a LED, which is what you will do now. In this section, we will modify the application so that button 1 starts toggling of LED 1 using an application timer in repeated mode. Button 2 will stop the toggling by stopping the timer.

Application timers are created using app_timer_create(). This function takes three parameters:

  • p_timer_id: pointer to the ID of the timer, which will be populated during the execution of this call.
  • mode: either single shot (APP_TIMER_MODE_SINGLE_SHOT) or repeated (APP_TIMER_MODE_REPEATED).
  • timeout_handler: pointer to the timeout handler.

First, create a variable that can hold the timer ID to be populated by app_timer_create(). Add the following line to your code, close to the top of the file:

APP_TIMER_DEF(m_repeated_timer_id);     /**< Handler for repeated timer used to blink LED 1. */

Then you will have to create the timeout event handler. This will toggle LED 1 every time it is called. Add the following function to your code somewhere before the main() function:

/**@brief Timeout handler for the repeated timer.
 */
static void repeated_timer_handler(void * p_context)
{
    nrf_drv_gpiote_out_toggle(LED_1);
}

I suggest you wrap the creation of the timer in a function, in order to keep a minimal main() function. Then the next timer you create in this tutorial can be created within the same function.

Add the following function to your code:

/**@brief Create timers.
 */
static void create_timers()
{
    ret_code_t err_code;

    // Create timers
    err_code = app_timer_create(&m_repeated_timer_id,
                                APP_TIMER_MODE_REPEATED,
                                repeated_timer_handler);
    APP_ERROR_CHECK(err_code);
}

Add the following to main() before the main loop in order to call the function you just made:

    create_timers();

Now the timer is created, but not started. In fact, we have not specified the time between timeouts yet. The timeout is specified in the number of ticks of the RTC1 including prescaling.

It is normally easier to relate to milliseconds than RTC ticks, and the APP_TIMER_TICKS macro is useful for converting a value in milliseconds to the corresponding number of ticks.

You will now have to modify the button_event_handler() that is present in the example project in order to let button 1 start the timer. Remove the following code from button_event_handler():

    case BUTTON_1:
        // Turn on LED 1.
        nrf_drv_gpiote_out_clear(LED_1);
        break;

..and replace it with this snippet:

    case BUTTON_1:
        // Start repeated timer (start blinking LED).
        err_code = app_timer_start(m_repeated_timer_id, APP_TIMER_TICKS(200), NULL);
        APP_ERROR_CHECK(err_code);
        break;

Declare the err_code variable before the switch statement by inserting the following line:

    ret_code_t      err_code;

The app_timer_start function starts the specified timer (first parameter), and specifies how many ticks from now it shall time out (second parameter). The third parameter is a general-purpose pointer that is passed to the timeout handler. It is set to NULL in this case as we do not use it. (It is represented by p_context in the timeout handler repeated_timer_handler() in this tutorial).

With this code in place, you should be able to build the application without any errors or warnings. Try to build and download it to the development kit (DK). Then you should be able to start toggling of LED 1 by pressing button 1. However, once the LED is toggling there is no way to stop it (other than resetting the chip).

In order to stop the LED from blinking you have to stop the timer. This is done using the app_timer_stop() function, which takes the timer ID as the only parameter. Modify the button_event_handler() so that app_timer_stop() is called when button 2 is pressed, by replacing the following code in the button_event_handler():

    case BUTTON_2:
        // Turn off LED 1.
        nrf_drv_gpiote_out_set(LED_1);
        break;

...with this:

    case BUTTON_2:
        // Stop the repeated timer (stop blinking LED).
        err_code = app_timer_stop(m_repeated_timer_id);
        APP_ERROR_CHECK(err_code);
        break;

With the latest addition, build the code and download it to the target. You should now be able to stop the toggling of the LED by pressing button 2. The timer can be re-started any time by pressing button 1.

Create a single shot timer

A application timer in single shot mode will expire only once. However, it can always be restarted. Moreover, the number of ticks before it times out can be set to a different value every time it is called (this is also the case for repeated timers).

In this part of the tutorial we will let button 3 start a single shot timer that lights LED 2 when it times out. The first time button 3 is pressed the timer will have a timeout of 1 second. Then the timeout will be incremented by 1 second every time the timer is started. We will keep the functionality of button 4 that it is used to turn off the LED.

Add a new timer ID below the existing m_repeated_timer_id by adding the following line:

APP_TIMER_DEF(m_single_shot_timer_id);  /**< Handler for single shot timer used to light LED 2. */

Then create the timeout handler shall light LED 2 every time it is called. Add this snippet to your code:

/**@brief Timeout handler for the single shot timer.
 */
static void single_shot_timer_handler(void * p_context)
{
    nrf_drv_gpiote_out_clear(LED_2);
}

Then modify the create_timers() by appending the following lines to the end of the function (note that the mode is APP_TIMER_MODE_SINGLE_SHOT):

    err_code = app_timer_create(&m_single_shot_timer_id,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                single_shot_timer_handler);
    APP_ERROR_CHECK(err_code);

Then update the button_event_handler() by removing:

    case BUTTON_3:
        // Turn on LED 2.
        nrf_drv_gpiote_out_clear(LED_2);
        break;

...and replacing it with:

    case BUTTON_3:
        // Start single shot timer which turns on LED2 when it expires.
        // Increase the timeout with 1 second every time.
        timeout += 1000;
        err_code = app_timer_start(m_single_shot_timer_id, APP_TIMER_TICKS(timeout), NULL);
        APP_ERROR_CHECK(err_code);
        break;

Declare the static timeout variable before the switch statement by inserting the following line:

    static uint32_t timeout = 0;

Build the code and download it to the target. If you press button 3 you will see that LED 2 is lit after 1 second. Then press button 4, and the LED turns dark. Now press button 3 again and observe that it takes 2 seconds before the LED is lit.

Further Reading

P.S.: Please post any questions you might have on the forum and not as a comment below the tutorial. This makes it easier for other users and Nordic employees to see and search for your questions and you will most likely get a faster response.

Parents Comment
  • Hi Prasad, This is because nrf_nvic_state is declared in multiple files. While adding app-timer I added app_utilplatform. nrf_nvic_state is defined in mesh_softdevice_init.c as well as app_utilplatform.c. So I added this code snippet in app_utilplatform.c where nrf_nvic_state was already defined, and it worked.

    /* Global nvic state instance, required by nrf_nvic.h */
        #ifdef USE_EXTERN
            extern nrf_nvic_state_t nrf_nvic_state;
        #else
            nrf_nvic_state_t nrf_nvic_state;
    #endif
    #endif

Children
No Data