Tags

, , , ,

There will be many times when I will want to fire off timer events.  I’m used to doing this at a level that is closer to “get back to me after one second five times.”  Where the one second and five times can easily be changed.  It’s so simple…I don’t need to understand anything about how the software gets this magic to happen.

I found getting a timer to fire off every second for five times required me to learn more about how the nRF51822’s clock + software drivers work.

The Goal

The goal of this post is to understand how to set up a timed event using the nRF51 DK and SDK.

Thanks To Those That Went Before

I like to take a moment to share my gratitude for the people who have provided me with the knowledge I needed to figure out what I wrote about in a post.

  • Chris Gammell – continues to be an excellent mentor.  I continue to take his Contextual Electronics course.  I am extremely grateful for the skills I have learned since starting his course in January of 2014.
  • The Nordic developers who tirelessly answer our questions on their DevZone.  It is VERY difficult to provide the level of respectful and useful support that comes from this team.  THANK YOU.

Open Source

  • The nRF51 Eclipse project (for Max OS X) for using the app_timer APIs is located at this GitHub repository.  Note: because it took me a bit of head knocking to get the Eclipse + nRF51 environment working on my Mac I wrote a post on how to get this environment up and running.

Choosing an API for Timed Events

Goldilocks had three bears.  The nRF51 appears to have three timer APIs that can be used to implement timed events.  Which approach should I take?

  • the APIs of app_timer.c – these are used in the BLE example apps.  After trying all methods, I ended up using the app_timer APIs.  The app_timer apis sit on top of the nRF51822’s RTC (Real Time Counter) APIs, providing a queue so that multiple timers can be set up on one of the RTC instances.  
  • the RTC APIs.  As noted in the nRF51 Series Reference ManualThe [Real Time Counter] is a 24 bit low-frequency clock with frequency prescaling and tick, compare, and overflow events.  There are two RTC instances available.
  • the hardware timer APIs.

The Hardware Timer

The timer example in the nRF51 SDK (timer_example) illustrates using a hardware timer to call an ISR every 1/2 a second.  This made me think the hardware timer was the way to go if I wanted a timer to fire off every second.  Using the hardware timer is simple and perhaps makes sense.  As noted to me by a Nordic developer:  If you are not concerned about power usage (maybe because you are experimenting or making a prototype) then you can just use TIMER1 and TIMER2 and do not get concerned about the HFCLK.

….BUT WAIT…

The HFCLK

The hardware timers use a high frequency clock source – either an external crystal or internal oscillator (see the Reference Manual for more detail).  While there are three timers – TIMER0, TIMER1, and TIMER2, buried within the reference manual for the BLE stack, TIMER0 is blocked for it’s use.

Power Hog

What’s more,  as noted in this Nordic DevZone postIf you use TIMER1 or TIMER2 then those will keep HFCLK enabled in sleep modes making your power go up…Any of the modules in the system that use HFCLK when started and not stopped before going to sleep will keep the HFCLK enabled. For example, if you enable and start Timer1 and then call sd_app_evt_wait(), then Timer1 will be on and it will keep the HFCLK on.

Given my newbie knowledge approach to these timers, there is a good chance I am missing other reasons why using the hardware timers is not the way to go for the scenario I seek.

The RTC

On to the Real Time Counter.  As noted in the nRF52 SDK documentation, “The RTC will run off the LFCLK.”  There are two RTC instances, RTC0 and RTC1.  The app_timer APIs use RTC1.  The BLE stack (called “the SoftDevice” – link to the S110 documentation) uses RTC0.  So if you plan to use the SoftDevice, the only available RTC instance is RTC1.  Or – said another way – if you use the Application Timer apis and the BLE SoftDevice stack there aren’t any RTC instances available.

The LFCLK

Similar to the HFCLK, the Low Frequency Clock source can be set to use either an external crystal or internal oscillator.  Even though I have been warned by Elicia in Making Embedded Systems : “Many small microcontrollers use an internal RC oscillator as their clock source. Although these make life easier for the hardware designer, their accuracy leaves a lot to be desired. Considerable drift can accumulate over time, and this can lead to errors in communication and some real-time applications.”  I decided I’d go with what is included until I can justify the addition of an external oscillator.  The Ladybug Blue timer scenarios use timed events to give spacing between ADC readings and not for maintaining a time or synchronization.

Power Saver

 this Nordic DevZone post notes: you can use RTC in system on low power mode. In this mode it’ll consume around 3uA = ION + IRTC + IX32k.

Another Nordic DevZone post notes a bit more detail: In system off mode, all clock sources and peripherals on the chip are turned off, and the only wakeup source is reset and pin change (for those that have this enabled)..Depending on whether you use a 32 kHz crystal or the internal RC oscillator, this gives a total consumption of I_on + I _RTC + I_X32k, typical 2.3 + 0.2 + 0.4 = 2.9 µA or i_on + I_RTC + I_RC32k = 2.3 + 0.2 + 0.8 = 3.3 µA

Repeating Timed Events

I was testing the  RTC nRF51 SDK example. While tick events fired regularly, I could not get the compare events to repeatedly fire.  While I do point out this is a “RTFC” where C = Code moment, I asked why this was happening on the Nordic DevZone.  Stefan kindly answered (and provided code): The internal event handler of the rtc driver, i.e. the nrf_drv_rtc_int_handler, handles the RTC0 peripheral interupt and then calls the registered event handler of the application. If you look into the nrf_drv_rtc_int_handler, you see that interrupts for any COMPARE[x] events are disabled, while TICK and OVERFLOW interrupts are not disabled. If you avoid disabling the COMPARE[x] interrupts, then the COMPARE[0] interrupt will be recurrent after you clear the RTC0 counter. (see the post for the code)

The app_timer (Application Timer) APIs

Given what I have learned, the app_timer APIs are most suitable to meet my needs.  Because:

  • they are an abstraction above the RTC APIs – the Ladybug Blue timer events would be shielded from the uses of the hardware and RTC timers.
  • they are easy to use.
  • many of the BLE examples use the app_timer APIs.  The use case of these examples are similar to the Ladybug Blue scenarios.
If you look at the main.c after downloading the app_timerTest.zip file and unzipping, notice how the main() routine:
  • Initializes the low frequency clock and the app_timer queue:

static void timers_init(void)

{

  //initialize the low frequency cloc

  uint32_t err_code = nrf_drv_clock_init(NULL);

  APP_ERROR_CHECK(err_code);

  nrf_drv_clock_lfclk_request();

  // Initialize timer module.

  APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);

 

  // Create timers.

  err_code = app_timer_create(&m_timer_id,

      APP_TIMER_MODE_REPEATED,

      timeout_handler);

  APP_ERROR_

CHECK(err_code);

}

  • start the timer(s):

static void timers_start(void)

{

  uint32_t err_code;

  // Start application timers.

  err_code = app_timer_start(m_timer_id, TIMER_TICKS, NULL);

  APP_ERROR_CHECK(err_code);

}

  • handle the callback:

static void timeout_handler(void * p_context)

{

  //  UNUSED_PARAMETER(p_context);

  SEGGER_RTT_WriteString (0, “–> in timeout handler\n”);

  • stop the timer(s):

  uint32_t err_code;

  err_code = app_timer_stop(m_timer_id);

  APP_ERROR_CHECK(err_code);

}

 Simple.  It took me awhile to get this working since I kept forgetting to initialize the low frequency clock source.

The PRESCALER VALUE

One variable that needs to be set is the value of the nRF51’s PRESCALER register.

From wikipediaThe prescaler takes the basic timer clock frequency (which may be the CPU clock frequency or may be some higher or lower frequency) and divides it by some value before feeding it to the timer.  

Another way to say this – the PRESCALER lumps the ticks into a grouping so that timed events can happen at larger time periods before an overflow occurs.

The  nRF52 SDK documentation has a nice visualization for when the PRESCALER is set to 1:

PRESCALER

Tick events happen every time the clock ticks. Counter events happen after (tick*(prescalar+1)) ticks.  We’re interested in counter events.

The  nRF52 SDK documentation has the following table:

PRESCALERtable

From this Nordic DevZone post

  • I noted at the beginning of this post the RTC (and hence app_timer which sits on top of RTC1) uses a 24 bit counter.  Thus the maximum value that can be held is 0xFFFFFF.
  • The clock source is the low frequency RC oscillator which runs at 32,768 HZ.
  • When the PRESCALER is 0, the maximum time before an overflow occurs is 0XFFFFFF/32768 seconds = 512 seconds.
  • The PRESCALER is a 12 bit register so the prescalar value can go from 0 to 212-1 = 4095.  The longest amount of time that can pass before the RTC overflows is then 512 s *(4095+1) = 582.542 hours.
  • When the PRESCALER is 0, the counter and ticks are the same, so the counter resolution = 1/32768 = 30.5µs.
  • When the PRESCALER is 4095 the counter resolution = 1/(32768/4095) = 125ms.  (i.e.: as the wikipedia article noted, take the clock frequency and divide it by the PRESCALER to get the counter frequency.  1/f = time resolution).

Overflow

For averaging ADC sampling (of ~100 at a frequency of 5HZ), overflow won’t be a problem and a PRESCALER value of 0 can be used.

For background pH and EC measurement timed events, there will be overflow since I plan to sample continually.  Luckily for me, the app_timer APIs handle overflow for me!

Time Drift

This Nordic DevZone post discusses the low frequency clock accuracy.  “PPM” is thrown around a bit, but I was only familiar with this term as it related to chemistry.  For example, the amount of nutrients I might give a plant is usually given in either Siemens or Parts Per Million (PPM).  This document from the digikey.com site says: Long term stability is usually expressed in parts per million or ppm. A typical specification of 10 ppm means that over a 1 ms interval the clock period can change by 10 ns: Dt=1ms*(10/1,000,000) =10 ns.

In the DevZone post, Ole and Stefan tell us: The…RC oscillator…has an accuracy of 250 ppm when calibrated.  In a 1 second interval, the clock can drift 1 second *(250/1,000,000) = 250µs.  Ole and Stefan go on to point out:  The only thing you can choose through this enum for the RC is the calibration interval. As given in the nRF51822 PS, the accuracy is specified when the temperature is relatively stable, and it is calibrated every 4 seconds, so this is the calibration interval that should be used for most (all?) applications.  When the RC is calibrated, the 16 MHz clock must run while calibration is ongoing, which causes an increase in the average current consumption of about 6-7 µA with a 4 s interval…

I might not understand this correctly…my interpretation:  When the PRESCALER is 0, the time elapsed before overflow is 512s.  Given the 250µs drift/s, the clock will drift 512*.00025 = .128s.  When the PRESCALER is 4095, the time elapsed before overflow is 2097152s, the clock drift will be 2097152s*.00025 ~= 524s or ~8.73 minutes.

Until proven otherwise, I do not see clock accuracy as being important for pH and EC measurements since it is more important to have a length of time between measurements that ensures stable readings. 

……….

Whew.  That’s it for now.  Ooh…YIPPEE!! I got boards back from OSHPark….a bit of soldering and hopefully I’ll be able to run these tests on the Ladybug Blue Alpha 1 board….

 

Advertisements