Application Timer Tutorial

2016-04-08: Updated tutorial to cover SDK version 11.

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 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 of 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:

  • pca10028 project is to be used with the nRF51 DK.
  • pca10040 project is to be used with the nRF52 DK.

Preparations

To get started, download the example code. It configures GPIO inputs and outputs for the buttons and LED's that will be used in the tutorial. To build it, extract the compressed project to <SDK>\examples\peripheral. The example is intended for the zipped version of the SDK. It will not work with the Pack installer. If you need help with this please have a look at this thread on DevZone: Compiling github projects. Open the project file (.uvprojx) and hit Build. The application should compile without any errors or warnings.

Erase the flash of the DK using nRFgo Studio or nrfjprog in order to ensure that no SoftDevice is installed. You only have to do this once. The application can be downloaded to the DK from Keil by pressing Download. 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. Therefor 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

Two C files have to be added to the project in order to use the application timer. Right click on the nRF_Drivers group in the project window. Click Add existing files to group 'nRF_Drivers' and add:

  • ..\..\..\..\..\components\drivers_nrf\clock\nrf_drv_clock.c

Then right click on the nRF_Libraries group in the project window. Click Add existing files to group 'nRF_Libraries' and add:

  • ..\..\..\..\..\components\libraries\timer\app_timer.c
  • ..\..\..\..\..\components\libraries\util\app_util_platform.c

Now go to Project -> Options for target ... -> C/C++. From there, add the following directories to the Include Paths:

  • ..\..\..\..\..\components\drivers_nrf\clock
  • ..\..\..\..\..\components\libraries\timer
  • ..\..\..\..\..\components\drivers_nrf\delay

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"

Initialization

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

// Function starting the internal LFCLK oscillator.
// 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)
{
    uint32_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:

    // Request LF clock.
    lfclk_request();

The Application Timer should be initialized with the APP_TIMER_INIT() macro. This must be called before any other calls to the Application Timer API. The four parameters must be selected so that they meet the requirements of the application:

  • PRESCALER: will be written to the RTC1 PRESCALER register. This determines the time resolution of the timer, and thus the amount of time it can count before it wrap around. On the nRF52 the RTC is a 24-bit counter with a 12 bit prescaler that run on the 32.768 LFCLK. The counter increment frequency (tick rate) fRTC [kHz] = 32.768/(PRESCALER+1). For example, a prescaler value of 15 means that the tick rate or time resolution is 32.768 kHz * 1/(15+1) = 2.048 kHz and the timer will wrap around every (2^24) * 1/2.048 kHz = 8192 s.
  • OP_QUEUES_SIZE: determines the maximum number of events that can be queued. Let's say you are calling the API function several times in a row to start a single shot timer, this determines how many times you can have queued before the queue gets full.
  • SCHEDULER_FUNC should be set to false when scheduler is not used, as is the case in this tutorial. See the Scheduler tutorial for how to use the Scheduler with the application timer.

In this tutorial you will create 2 timers. Add the following defines (which represent the parameters described above) close to the top of your code:

// General application timer settings.
#define APP_TIMER_PRESCALER             15    // Value of the RTC1 PRESCALER register.
#define APP_TIMER_OP_QUEUE_SIZE         3     // Size of timer operation queues.

Then put this line in main() in order to initialize the app timer (after the call to lfclk_request()):

    // Initialize the application timer module.
    APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false);

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_led_a_timer_id);

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:

// Timeout handler for the repeated timer
static void timer_a_handler(void * p_context)
{
    nrf_drv_gpiote_out_toggle(LED_1_PIN);
}

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:

// Create timers
static void create_timers()
{   
    uint32_t err_code;

    // Create timers
    err_code = app_timer_create(&m_led_a_timer_id,
                                APP_TIMER_MODE_REPEATED,
                                timer_a_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 application timers.
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, depending on the prescaler settings.

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

    case BUTTON_1_PIN:
        nrf_drv_gpiote_out_clear(LED_1_PIN);
        break;

..and replace it with this snippet:

    case BUTTON_1_PIN:
        err_code = app_timer_start(m_led_a_timer_id, APP_TIMER_TICKS(200, APP_TIMER_PRESCALER), NULL);
        APP_ERROR_CHECK(err_code);
        break;

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

    uint32_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 timer_a_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_handler() so that app_timer_stop() is called when button 2 is pressed, by replacing the following code in the button_handler():

    case BUTTON_2_PIN:
        nrf_drv_gpiote_out_set(LED_1_PIN);
        break;

...with this:

    case BUTTON_2_PIN:
        err_code = app_timer_stop(m_led_a_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_led_a_timer_id by adding the following line:

APP_TIMER_DEF(m_led_b_timer_id);

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

// Timeout handler for the single shot timer
static void timer_b_handler(void * p_context)
{
    nrf_drv_gpiote_out_clear(LED_2_PIN);
}

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_led_b_timer_id,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                timer_b_handler);
    APP_ERROR_CHECK(err_code);

Then update the button_handler() by removing:

    case BUTTON_3_PIN:
        nrf_drv_gpiote_out_clear(LED_2_PIN);
        break;

...and replacing it with:

    case BUTTON_3_PIN:
        // Start single shot timer. Increase the timeout with 1 second every time.
        timeout += 1000;
        err_code = app_timer_start(m_led_b_timer_id, APP_TIMER_TICKS(timeout, APP_TIMER_PRESCALER), 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

Anonymous