Программирование ARM nRF5 SDK v15 Application Timer API Fri, March 29 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

nRF5 SDK v15 Application Timer API Печать
Добавил(а) microsin   

Application Timer - этот модуль позволяет приложению создать несколько экземпляров работающих таймеров, работающих на основе периферийного устройства RTC1 [5]. Проверка таймаутов и запуск соответствующих обработчиков таймаутов пользователя (user time-out handlers) выполняется из обработчика прерывания (ISR) RTC1 (см. также описание драйвера RTC SDK [3]). Список запуска обрабатывается в программном прерывании (software interrupt SWI0). Оба этих ISR выполняются на уровне приоритета APP_LOW.

Примечание: здесь приведен перевод документации для модуля таймера приложения SDK v15. Вероятно, для более новых версий SDK информация также будет актуальна.

Когда вызывается API-функция app_timer_start() или app_timer_stop(), соответствующая операция по таймеру всего лишь помещается в очередь (queued), и запускается программное прерывание. Реальная операция start/stop таймера выполняется в коде SWI0 ISR. Прерывание SWI0 выполняется с приоритетом APP_LOW, поэтому если код приложения, который запустил API-функцию таймера, работает с приоритетом APP_LOW или APP_HIGH, то операция таймера из очереди не выполнится до тех пор, пока не произойдет возврат из обработчика приложения. Такой случай произойдет, например, если таймер останавливается из обработчика таймаута, когда планировщик (Scheduler) не используется.

Используйте параметр USE_SCHEDULER в макросе APP_TIMER_INIT(), чтобы выбрать, должен ли использоваться планировщик. Даже если планировщик не используется, app_timer.h будет подключать app_scheduler.h, поэтому при компиляции заголовок app_scheduler.h должен быть доступен в одном из настроенных путей поиска заголовков (compiler include paths).

[Перечисления]

Перечисление app_timer_mode_t определяет режимы работы Application Timer.

/**@brief Timer modes. */
typedef enum
{
    APP_TIMER_MODE_SINGLE_SHOT,  /**< Таймер сработает по таймауту
                                      только один раз. */
    APP_TIMER_MODE_REPEATED      /**< Таймер при таймауте будет
                                      автоматически перезапускаться. */
} app_timer_mode_t;

[Структуры]

// Структура, используемая для выделения данных таймера.
typedef struct app_timer_t {
    uint32_t data[CEIL_DIV(APP_TIMER_NODE_SIZE, sizeof(uint32_t))];
} app_timer_t;
 
// Структура, передаваемая в планировщик.
typedef struct
{
    app_timer_timeout_handler_t timeout_handler;
    void *                      p_context;
} app_timer_event_t;

[Определения типа]

typedef void(* app_timer_timeout_handler_t )(void *p_context);

Тип для обработчика таймаута таймера приложения.

typedef struct app_timer_t app_timer_t;
typedef app_timer_t * app_timer_id_t;

Тип для идентификатора таймера. Никогда не декларируйте переменную такого типа, вместо этого используйте макрос APP_TIMER_DEF (см. ниже врезку с описанием макросов).

[Макросы конфигурации Application Timer]

Эти макросы конфигурации находятся в sdk_config.h [4].

#define APP_TIMER_ENABLED 1

Разрешает функционал модуля app_timer. Установите в 1, чтобы были доступны Application Timer.

 

#define APP_TIMER_CONFIG_RTC_FREQUENCY n

Конфигурирует прескалер RTC [5] для работы его счетчика на одной из заданных частот. Здесь N устанавливается в одно из следующих значений:

n Частота, Гц
0 32768
1 16384
3 8192
7 4096
15 2048
31 1024

 

#define APP_TIMER_CONFIG_IRQ_PRIORITY n

Значение n конфигурирует приоритет прерывания. Имейте в виду, что приоритеты 0, 2 (nRF51) и 0, 1, 4, 5 (nRF52) зарезервированы для SoftDevice. Доступны следующие варианты выбора приоритета (чем значение меньше, тем приоритет выше):

n Приоритет
0 0 (самый высокий приоритет)
1 1
2 2
3 3
4 4 (только для Software Component)
5 5 (только для Software Component)
6 6 (только для Software Component)
7 7 (только для Software Component)

 

#define APP_TIMER_CONFIG_OP_QUEUE_SIZE n

Здесь n задает емкость очереди запросов таймеров (по умолчанию 10). Размер очереди зависит от того, сколько таймеров используется в системе, как часто таймеры запускаются, и от общей латентности системы. Если очередь слишком мала, то будут происходить неудачи с API-вызовами app_timer.

 

#define APP_TIMER_CONFIG_USE_SCHEDULER 0

Разрешает обработку событий app_timer на основе планировщика (app_scheduler). Установите в 1, чтобы разрешить использовать планировщик для таймеров приложения.

 

#define APP_TIMER_KEEPS_RTC_ACTIVE 0

Разрешает RTC работать всегда, установите в 1 для активации. Если эта опция разрешена, то RTC работает даже тогда, когда нет активных таймеров, что может использоваться, если app_timer используется для отсчета реального времени (timestamping).

 

#define APP_TIMER_WITH_PROFILER 0

Разрешает анализ производительности (профилирование) кода app_timer.

 

#define APP_TIMER_CONFIG_SWI_NUMBER 0

Конфигурирует используемый экземпляр SWI.

 

[Макросы для работы с Application Timer]

#define APP_TIMER_LOG_NAME app_timer

Задает имя модуля для сообщений лога.

 

#define APP_TIMER_CLOCK_FREQ 32768

Частота тактов таймера, Гц. Частота тактов RTC, используемого для реализации таймера приложения.

 

#define APP_TIMER_MIN_TIMEOUT_TICKS 5

Минимальная длительность таймаута в тиках, которую можно указать в качестве параметра для app_timer_start().

 

#define APP_TIMER_NODE_SIZE 32

Размер app_timer.timer_node_t (используется для выделения памяти).

 

#define APP_TIMER_SCHED_EVENT_DATA_SIZE sizeof(app_timer_event_t)

Размер данных события, когда используется планировщик.

 

#define APP_TIMER_MAX_CNT_VAL RTC_COUNTER_COUNTER_Msk

Максимальное значение счетчика, которое может быть возвращено из вызова app_timer_cnt_get.

#define APP_TIMER_TICKS (MS) ((uint32_t)ROUNDED_DIV(       \
            (MS) * (uint64_t)APP_TIMER_CLOCK_FREQ,         \
            1000 * (APP_TIMER_CONFIG_RTC_FREQUENCY + 1)))

Преобразовывает миллисекунды в тики таймера. Этот макрос использует целочисленную 64-разрядную арифметику. Поэтому только пока в качестве параметра указана константа (например, определенная через #define), вычисления будут делаться препроцессором. Соответственно если в качестве параметра используется переменная, то для макроса сгенерируется программный код.

 

#define APP_TIMER_DEF(timer_id) _APP_TIMER_DEF(timer_id)

Создает идентификатор таймера и статически выделит для таймера память. Параметр макроса timer_id это имя для переменной идентификатора таймера, которая будет использоваться для управления этим таймером.

ret_code_t app_timer_init (void);

Функция для инициализации модуля таймера. Вернет значение NRF_SUCCESS, если инициализация прошла успешно.

 

ret_code_t app_timer_init (ret_code_t app_timer_create (app_timer_id_t const *p_timer_id,
                             app_timer_mode_t mode,
                             app_timer_timeout_handler_t timeout_handler);void);

Функция для создания экземпляра таймера. Параметры:

Тип Имя Описание
[in] p_timer_id Указатель на переменную, где будет сохранен идентификатор созданного таймера.
[in] mode Режим таймера (однократный или с автоповтором таймаутов).
[in] timeout_handler Функция обработчика, которая вызывается при истечении времени интервала таймера.

Возвращаемые значения:

NRF_SUCCESS Если таймер был успешно создан.
NRF_ERROR_INVALID_PARAM Если был указан недопустимый параметр.
NRF_ERROR_INVALID_STATE Если модуль Application Timer не был инициализирован, или таймер работает (запущен и идет отсчет его таймаута).

Функция app_timer_create выделяет ресурсы для таймера в контексте кода, который её вызвал. Также вызов этой функции не защищен критическим регионом. По этой причине следует позаботиться о том, чтобы не было вызова функции app_timer_create из нескольких контекстов одновременно (например, из нескольких уровней прерывания). Функция app_timer_create может быть вызвана повторно для одного и того же экземпляра таймера, если он в настоящий момент не работает.

Внимание: FreeRTOS и RTX реализация app_timer не позволяют вызвать app_timer_create на ранее инициализированном экземпляре таймера.

 

ret_code_t app_timer_start (app_timer_id_t timer_id,
                            uint32_t timeout_ticks,
                            void *p_context);

Функция для запуска таймера. Параметры:

Тип Имя Описание
[in] p_timer_id Указатель на переменную, где хранится идентификатор созданного таймера (полученный вызовом app_timer_create).
[in] timeout_ticks Количество тиков (RTC1, с учетом настройки прескалера) до события таймаута таймера (минимум 5 тиков).
[in] p_context Указатель общего назначения. Он будет передан в обработчик таймаута, когда произойдет событие таймаута таймера.

Возвращаемые значения:

NRF_SUCCESS Если таймер был успешно запущен.
NRF_ERROR_INVALID_PARAM Если был указан недопустимый параметр.
NRF_ERROR_INVALID_STATE Если модуль Application Timer не был инициализирован, или таймер не был создан.
NRF_ERROR_NO_MEM Если очередь операций таймера была переполнена.

Минимальное значение для timeout_ticks равно 5. Для нескольких активных таймеров таймауты, которые происходят в непосредственной близости друг от друга (в диапазоне от 1 до 3 тиков), получат положительный джиттер максимум в 3 тика. Когда эта функция вызывается на таймере, который уже работает, вторая операция запуска игнорируется.

 

ret_code_t app_timer_stop (app_timer_id_t timer_id);

Функция останавливает указанный таймер (переданный через параметр timer_id). Возвращаемые значения:

NRF_SUCCESS Если таймер был успешно остановлен.
NRF_ERROR_INVALID_PARAM Если был указан недопустимый параметр.
NRF_ERROR_INVALID_STATE Если модуль Application Timer не был инициализирован, или таймер не был создан.
NRF_ERROR_NO_MEM Если очередь операций таймера была переполнена.

 

ret_code_t app_timer_stop_all (void);

Функция останавливает все работающие таймеры. Возвращаемые значения:

NRF_SUCCESS Если все таймеры были успешно остановлены.
NRF_ERROR_INVALID_STATE Если модуль Application Timer не был инициализирован.
NRF_ERROR_NO_MEM Если очередь операций таймера была переполнена.

 

uint32_t app_timer_cnt_get (void);

Функция вернет текущее значение счетчика RTC1.

 

uint32_t app_timer_cnt_diff_compute (uint32_t ticks_to, uint32_t ticks_from);

Эта функция предназначена для вычисления разницы между двумя значениями счетчика RTC1. Параметры ticks_to и ticks_from это значения, полученные вызовами app_timer_cnt_get(). Возвращаемое значение - количество тиков RTC1 от ticks_from до ticks_to.

 

uint8_t app_timer_op_queue_utilization_get (void);

Эта функция предназначена для получения максимальной наблюдаемой загрузки очереди операций таймера. С помощью её можно настроить модуль таймера и определить значение OP_QUEUE_SIZE, чтобы оптимально использовать память RAM. Возвращаемое значение - максимальное количество событий в очереди, которое наблюдалось на данный момент.

Чтобы функция app_timer_op_queue_utilization_get была доступна, должна быть разрешена опция APP_TIMER_WITH_PROFILER (см. выше описание макросов).

 

void app_timer_pause (void);

Функция для постановки на паузу активности RTC, который управляет поведением модуля app_timer. Эта функция может использоваться в целях отладки, чтобы гарантировать, что приложение приостановлено при входе в breakpoint.

 

void app_timer_resume (void);

Функция для возобновления активности RTC, который управляет поведением модуля app_timer. Эта функция может использоваться в целях отладки для восстановления активности приложения.

[Ограничения при использовании Application Timer]

Минимальный интервал. В заголовочном файле app_timer.h определен макрос APP_TIMER_MIN_TIMEOUT_TICKS, который задает минимальное количество тиков, которое можно указать при вызове app_timer_start(). Также в этом заголовке определен макрос APP_TIMER_CLOCK_FREQ, который показывает частоту тиков в Гц (под тиками подразумеваются отсчеты тактов таймера RTC1). По умолчанию в примерах SDK используются следующие значения этих макросов:

#define APP_TIMER_CLOCK_FREQ         32768
#define APP_TIMER_MIN_TIMEOUT_TICKS  5

Таким образом, минимальный интервал app_timer в секундах получится 1 / (32768 / 5), что соответствует приблизительно 153.6 мкс.

Запуск API-функций. На практике я столкнулся с проблемами, когда вызываются функции запуска подряд, друг за другом. Например, нельзя вызывать app_timer_stop и затем сразу app_timer_start, это может привести иногда к сбою срабатывания запуска таймера.

[Пример использования Application Timer]

Программный таймер удобно использовать для создания автономных повторяющихся действий, работающих по принципу "запустил и забыл". Получается что-то похожее на многозадачную систему. Ниже показан простой пример модуля blinker - для реализации одинарного, двойного и тройного мигания светодиодом, работающего как фоновый процесс. Модуль может быть применен в приложении для индикации состояния или для отладки.

Заголовочный файл blinker.h:

#include "app_timer.h"
 
// Значения этого перечисления задает тип мигания - однократное,
// двойное, тройное. Может быть расширено для большего количества
// мерцаний.
typedef enum
{
   BLINK1,
   BLINK2,
   BLINK3
}TBlinkMode;
 
// Интервалы для создания миганий в тиках системы. BLINK_ON_TIME
// задает сколько светодиод горит, BLINK_OFF_TIME сколько погашен,
// BLINK_INTERVAL_TIME задает паузу между порцией миганий.
#define BLINK_ON_TIME         APP_TIMER_TICKS(  20/*мс*/, APP_TIMER_PRESCALER)
#define BLINK_OFF_TIME        APP_TIMER_TICKS( 200/*мс*/, APP_TIMER_PRESCALER)
#define BLINK_INTERVAL_TIME   APP_TIMER_TICKS(1000/*мс*/, APP_TIMER_PRESCALER)
 
// Инициализирует подсистему блинкера. Требует предварительного
// вызова APP_TIMER_INIT:
void BlinkerInit (void);
// Запускает выбранный тип мигания:
void BlinkStart (TBlinkMode bm);
// Полностью останавливает мигание:
void BlinkStop (void);

Реализация функций blinker.c:

#include "blinker.h"
#include "pins.h"
 
APP_TIMER_DEF(m_blink_timer_id);
static uint8_t blinkstate;
static TBlinkMode blinkmode;
 
static void blink_timer_handler (void * p_context)
{
   uint32_t err_code;
   UNUSED_PARAMETER(p_context);
 
   if ((blinkmode*2 + 1) == blinkstate)
   {
      blinkstate = 0;
      LED(0);
      err_code = app_timer_start(m_blink_timer_id, BLINK_INTERVAL_TIME, NULL);
      APP_ERROR_CHECK(err_code);
   }
   else if (0 == (blinkstate & 1))
   {
      LED(1);
      err_code = app_timer_start(m_blink_timer_id, BLINK_ON_TIME, NULL);
      APP_ERROR_CHECK(err_code);
      blinkstate++;
   }
   else
   {
      LED(0);
      err_code = app_timer_start(m_blink_timer_id, BLINK_OFF_TIME, NULL);
      APP_ERROR_CHECK(err_code);
      blinkstate++;
   }
}
 
void BlinkerInit (void)
{
   uint32_t err_code;
 
   err_code = app_timer_create(&m_blink_timer_id,
                               APP_TIMER_MODE_SINGLE_SHOT,
                               blink_timer_handler);
   APP_ERROR_CHECK(err_code);
}
 
void BlinkStart (TBlinkMode bm)
{
   uint32_t err_code;
 
   blinkstate = 0;
   blinkmode = bm;
   err_code = app_timer_start(m_blink_timer_id, BLINK_ON_TIME, NULL);
   APP_ERROR_CHECK(err_code);
}
 
void BlinkStop (void)
{
   uint32_t err_code;
   
   err_code = app_timer_stop(m_blink_timer_id);
   APP_ERROR_CHECK(err_code);
   LED(0);
}

[Ссылки]

1. nRF SDK Application timer site:nordicsemi.com.
2. nRF5 SDK Application Timer.
3. nRF5 SDK RTC driver site:nordicsemi.com.
4. Конфигурационный заголовок nRF5x SDK.
5. nRF52: счетчик реального времени RTC.

 

Добавить комментарий


Защитный код
Обновить

Top of Page