Программирование ARM nRF5 SDK Application Timer Mon, September 27 2021  

Поделиться

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

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

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

Модуль app_timer (app_timer.c, app_timer.h) позволяет приложению создавать несколько экземпляров программных таймеров на основе периферийного устройства RTC1 [10].

В этом руководстве (перевод [1]) мы обсудим следующие темы:

• Конфигурирование библиотеки Application Timer.
• Таймеры с автоматически повторяющимся запуском (repeated timers).
• Таймеры с однократным запуском (single shot timers).

Для экспериментов с таймерами понадобится следующее:

• Плата разработчика nRF52 DK или nRF52840 DK, или аналогичное оборудование с чипом nRF52xxx компании Nordic Semiconductor.
• IDE Segger Embedded Studio (SES), или любой другой поддерживаемый в SDK тулчейн (Keil, IAR), чтобы можно было выполнить сборку примеров кода.
• nRF5 SDK версии 15.2, хотя для библиотеки таймеров можно использовать SDK любой версии, начиная с версии 12 включительно [5].
• Проект примера  nrf5-application-timer-tutorial [4].

Чтобы начать работу, загрузите проект [4]. Для этого можно скачать архив репозитория в браузере, либо можно выполнить следующую команду Git:

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

В репозитории проекта содержатся три подкаталога, где находятся отдельные проекты для каждой из используемых плат разработчика:

Папка (кит) Описание
pca10040 nRF52 DK - плата разработчика для устройств BLE и сетей Bluetooth mesh на основе чипов nFR52810 и nRF52832. Содержит на борту встроенный отладчик J-Link (SWD), виртуальный COM-порт для вывода отладочных сообщений, 4 кнопки, 4 светодиода, слот для батарейки CR2032. Плата имеет форм-фактор, позволяющий установку shield-плат Arduino.
pca10040e Здесь находится проект для чипа nRF52810, который также может быть запущен и отлажен на плате разработчика nRF52 DK. Отдельного кита для nRF52810 не существует.
pca10056 nRF52840 DK - плата для разработки устройств BLE, Bluetooth mesh, NFC, Thread и Zigbee на основе чипа nRF52840. Содержит на борту встроенный отладчик J-Link (SWD), виртуальный COM-порт для вывода отладочных сообщений, 4 кнопки, 4 светодиода, слот для батарейки CR2032. Плата имеет форм-фактор, позволяющий установку shield-плат Arduino.

В проекте примера конфигурируются ножки GPIO как входы и выходы, чтобы подключить кнопки и светодиоды, используемые в этом руководстве в целях отладки. Откройте проект, соответствующий Вашей плате или MCU, и используемому тулчейну [6] и выполните его сборку (Build). Приложение должно скомпилироваться без каких-либо ошибок и предупреждений.

Сотрите flash MCU на плате DK, используя nRF Connect Programmer, или nrfjprog [11], или JLink Commander [12], чтобы гарантировать, что в нем не установлен SoftDevice. Запрограммируйте/загрузите скомпилированное приложение в MCU платы. Вы должны иметь возможность управлять светодиодом LED 1 с помощью кнопок Button 1 и Button 2, и светодиодом LED 2 с помощью кнопок Button 3 и Button 4.

[Application Timer]

Библиотека таймеров приложения (Application Timer) предоставляет дружественный для программиста метод использования таймера реального времени RTC1 (Real Time Counter 1, см. [10]), чтобы создать несколько одновременно работающих программных таймеров. RTC1 использует низкую тактовую частоту LFCLK (Low Frequency Clock) 32768 Гц, которая активно работает в большинстве приложений nRF5x (LFCLK всегда активна, когда используется SoftDevice). При этом в нормально работающем приложении активность LFCLK и таймера RTC будет потреблять очень незначительное количество энергии.

Поскольку частота LFCLK составляет 32.768 кГц, и разрядность счетчика таймера RTC1 составляет 24 бита, то разрешающая способность тика и максимальная длительность программных таймеров получаются ограниченными. Однако максимальная длительность программного таймера будет достаточно большой - 512 секунд, когда таймер считает на частоте 32.768 кГц, прежде чем его счетчик переполнится и не вернется в своем счете к 0 (от 0xFFFFFF до 0). Кроме того, тактовую частоту RTC можно понизить с помощью 12-разрядного прескалера (1/x).

Проверка таймаутов и запуск пользовательских обработчиков таймаута выполняется в обработчике прерывания RTC1. Управление списком обработчиков таймаута осуществляется с помощью программного прерывания (SWI0). Оба этих обработчика прерывания работают на уровне приоритета APP_LOW.

При вызове функций app_timer_start() или app_timer_stop() операция таймера просто ставится в очередь, и инициируется программное прерывание. Реальная операция запуска/остановки таймера выполняется в обработчике прерывания SWI0. Поскольку прерывание SWI0 выполняет код с приоритетом APP_LOW, если код приложения, вызывающий функцию таймера, работает с приоритетом APP_LOW или APP_HIGH, то операция таймера выполняться не будет до тех пор, пока не произойдет выход из обработчика приложения. Это будет иметь место, например, когда таймер останавливается из обработчика таймаута, когда он не использует планировщик.

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

В этой части руководства мы сконфигурируем библиотеку, и будем использовать её для создания таймеров, которые при истечении заданного интервала времени (таймаута) будут вызвать ваши подпрограммы обработки события таймаута (timeout event handlers). Эти вызовы подпрограмм обработчика таймаута с момента запуска могут быть как автоматически повторяющимися (repeated timer), так и однократными (single shot timer). При необходимости в дополнительной информации обращайтесь к справочному руководству по API таймеров приложения [2].

Рассмотрим процесс использования таймеров по порядку.

Добавление требуемых файлов и подключение заголовков. В проект необходимо добавить модуль app_timer.c, где реализован функционал таймеров приложения:

КаталогSDK/components/libraries/timer/app_timer.c

Примечание: здесь КаталогSDK представляет полный путь на диске, куда был распакован пакет SDK. Например, для SDK v15.2.0 это может быть путь наподобие c:/nRF5_SDK_15.2.0_9412b96.

Также может понадобиться добавить следующие файлы, если Вы используете это руководство для добавления таймеров приложения в свое собственное приложения (если они еще не добавлены):

КаталогSDK/integration/nrfx/legacy/nrf_drv_clock.c
КаталогSDK/components/libraries/util/app_util_platform.c

Добавьте следующий каталог в пути поиска заголовков компилятора include paths (путь здесь указан относительно папки, где находится файл проекта):

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

Также может понадобиться следующие пути поиска заголовков, если Вы используете это руководство для добавления таймеров приложения в свое собственное приложения (если они еще не добавлены):

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

Затем подключите необходимые файлы заголовка, вставив следующие строки ниже последней строки с операторами #include:

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

Проверьте, что заголовок sdk_config.h вашего проекта подключает блок конфигурации для application timer: такая конфигурация действует по умолчанию в SDK 15.2, который рекомендуется в этом руководстве. Также см. дополнительную информацию по ссылке [7].

// < e > APP_TIMER_ENABLED - app_timer - функционал таймера приложения
//====================================================================
#ifndef APP_TIMER_ENABLED
#define APP_TIMER_ENABLED 1
#endif
 
// < o > APP_TIMER_CONFIG_RTC_FREQUENCY - конфигурация прескалера.
// < 0= > 32768 Гц
// < 1= > 16384 Гц
// < 3= > 8192 Гц
// < 7= > 4096 Гц
// < 15= > 2048 Гц
// < 31= > 1024 Гц
 
#ifndef APP_TIMER_CONFIG_RTC_FREQUENCY
#define APP_TIMER_CONFIG_RTC_FREQUENCY 0
#endif
 
// < o> APP_TIMER_CONFIG_IRQ_PRIORITY - приоритет прерывания
 
// < i > Приоритеты 0,2 (nRF51) и 0,1,4,5 (nRF52) зарезервированы
//       для SoftDevice.
// < 0= > 0 (самый высокий приоритет)
// < 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 - емкость очереди запросов таймера.
 
// < i > Размер очереди зависит от того, как много таймеров используется
// < i > в системе, как часто таймеры запускаются, и от общей латентности
// < i > (загруженности) системы. Если размер очереди слишком мал, то
// < i > произойдет сбой вызовов app_timer.
#ifndef APP_TIMER_CONFIG_OP_QUEUE_SIZE
#define APP_TIMER_CONFIG_OP_QUEUE_SIZE 10
#endif
 
// < q > APP_TIMER_CONFIG_USE_SCHEDULER - разрешение обслуживания событий
//      app_timer с помощью планировщика (app_scheduler).
#ifndef APP_TIMER_CONFIG_USE_SCHEDULER
#define APP_TIMER_CONFIG_USE_SCHEDULER 0
#endif
 
// < q > APP_TIMER_KEEPS_RTC_ACTIVE - разрешить постоянную работу RTC
// < i > Если эта опция разрешена, то RTC остается в работе, даже если
//       нет активных программных таймеров.
// < i > Эта опция может использоваться, когда app_timer применяется
//       для генерации меток реального времени (timestamping).
#ifndef APP_TIMER_KEEPS_RTC_ACTIVE
#define APP_TIMER_KEEPS_RTC_ACTIVE 0
#endif
 
// < o > APP_TIMER_SAFE_WINDOW_MS - максимально возможная латентность
//       (в миллисекундах) для обработки события app_timer.
// < i > Максимально возможное время таймаута, которое может быть
// < i > установлено, сокращается за счет окна безопасности.
// < i > Пример: частота RTC 16384 Гц, максимально возможный таймаут
// < i > составит 1024 секунд - APP_TIMER_SAFE_WINDOW_MS.
// < i > Поскольку RTC не останавливается, когда MCU переводится
// < i > в состояние приостановки во время сессии отладки, это значение
// < i > должно захватывать нужный интервал, если требуется отладка.
// < i > Можно остановить MCU на интервал APP_TIMER_SAFE_WINDOW_MS
// < i > без повреждения поведения app_timer.
#ifndef APP_TIMER_SAFE_WINDOW_MS
#define APP_TIMER_SAFE_WINDOW_MS 300000
#endif
 
// < h > App Timer Legacy configuration - устаревшая конфигурация.
//================================================================
 
// < q > APP_TIMER_WITH_PROFILER - разрешение профайлинга app_timer
 #ifndef APP_TIMER_WITH_PROFILER
#define APP_TIMER_WITH_PROFILER 0
#endif
 
// < q > APP_TIMER_CONFIG_SWI_NUMBER - конфигурация используемого
//       экземпляра SWI.#ifndef APP_TIMER_CONFIG_SWI_NUMBER
#define APP_TIMER_CONFIG_SWI_NUMBER 0
#endif

Подобным образом проверьте, что подключается блок конфигурации для драйвера тактирования. В проекте примера [4] это уже сделано.

// < e > NRF_CLOCK_ENABLED - nrf_drv_clock - драйвер периферийного
//       устройства CLOCK - устаревший слой API.
//================================================================
#ifndef NRF_CLOCK_ENABLED
#define NRF_CLOCK_ENABLED 1
#endif
 
// < o > CLOCK_CONFIG_LF_SRC - источник низкочастотных тактов (LF Clock)
 
// < 0= > RC
// < 1= > XTAL
// < 2= > Synth
// < 131073= > внешний сигнал малой амплитуды
// < 196609= > внешний сигнал полной амплитуды
#ifndef CLOCK_CONFIG_LF_SRC
#define CLOCK_CONFIG_LF_SRC 1
#endif
 
// < o > CLOCK_CONFIG_IRQ_PRIORITY - приоритет прерывания
 
// < i > Приоритеты 0,2 (nRF51) и 0,1,4,5 (nRF52) зарезервированы
//       для SoftDevice
// < 0= > 0 (самый высокий приоритет)
// < 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

Инициализация. Поскольку в этом руководстве SoftDevice не используется, то такты LFCLK должны быть явно настроены в самом приложении. Для этого можно применить один из способов разрешения LFCLK - с помощью драйвера тактов (Clock driver). Добавьте следующую функцию куда-нибудь перед функцией main():

/**@brief Функция, запускающая внутренний генератор LFCLK.
 *
 * @details Это необходимо для того, чтобы работал таймер RTC1, используемый
 *          модулем Application Timer (когда SoftDevice разрешен, эта функция
 *          не нужна, потому что LFCLK уже работает).
 */
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);
}

Добавьте вызов этой функции в начало функции main(), до входа в её бесконечный цикл:

lfclk_request();

Модуль Application Timer должен инициализироваться вызовом функции app_timer_init(). Вызов этой функции должен быть выполнен после вызова lfclk_request(), и перед вызовом любой другой API-функции Application Timer. Добавьте следующую строку в функцию main, после вызова lfclk_request() и перед входом в бесконечный цикл:

app_timer_init();

[Создание repeated-таймера]

Таймер приложения в режиме автоматического повторения запуска (repeated mode) будет перезапускаться заново всякий раз, когда его таймаут истечет. При этом через равные, заданные пользователем интервалы времени будет запускаться обработчик таймаута (timeout handler). Такой режим таймера подходит для реализации регулярно повторяющихся действий - например мигание светодиодом или опрос датчика. Здесь мы изменим приложение так, чтобы нажатие на кнопку Button 1 запускало переключение светодиода LED 1 с помощью таймера приложения, запущенного в repeated-режиме. Кнопка Button 2 будет останавливать мерцание светодиода LED 1 путем вызова остановки таймера.

Таймеры приложения создаются вызовом API-функции app_timer_create(). Эта функция принимает 3 параметра:

p_timer_id: указатель на ID таймера, который будет заполнен после вызова этой функции.
mode: тут указывается режим работы таймера - либо однократный single shot (APP_TIMER_MODE_SINGLE_SHOT), либо автоматически повторяющийся repeated (APP_TIMER_MODE_REPEATED).
timeout_handler: указатель на обработчик таймаута. Это функция пользователя, которую будет запускать таймер после истечения заданного интервала - однократно или с повторениями (в зависимости от параметра mode).

Сначала создадим переменную, в которой будет храниться ID таймера, заполненный вызовом app_timer_create(). Добавьте следующие строки в код, можно в самое начало главного модуля приложения:

/**@brief Дескриптор для repeated-таймера, который будет
 *        переключать светодиод LED 1:
 */
APP_TIMER_DEF(m_repeated_timer_id);

Затем нужно создать функцию для обработчика события таймаута (timeout event handler). Код в ней будет переключать LED 1 в противоположное состояние, в результате светодиод будет мигать с регулярными интервалами времени. Добавьте следующую функцию обработчика куда-нибудь перед функцией main():

/**@brief Обработчик таймаута для repeated-таймера.
 */
static void repeated_timer_handler(void * p_context)
{
    nrf_drv_gpiote_out_toggle(LED_1);
}

Теперь нужно создать таймер вызовом API-функции app_timer_create. Создание таймера лучше всего обернуть в отдельную функцию, чтобы код функции main() был как можно компактнее и понятнее:

/**@brief Функция, где создаются таймеры приложения.
 */
static void create_timers()
{
   ret_code_t err_code;
 
   // Создание таймеров.
   err_code = app_timer_create(&m_repeated_timer_id,
                               APP_TIMER_MODE_REPEATED,
                               repeated_timer_handler);
   APP_ERROR_CHECK(err_code);
}

Добавьте вызов функции create_timers() в функцию main() перед входом в её главный цикл:

create_timers();

Таймер создан, но еще не запущен. Фактически мы также еще не указали время между таймаутами. Таймаут указывается в количестве тиков таймера RTC1, с учетом прескалера.

Чтобы с удобством задавать длительность интервала таймера в миллисекундах вместо тиков RTC, используйте макрос APP_TIMER_TICKS, который преобразует значение миллисекунд в значение количества тиков.

Теперь нужно изменить функцию обработчика нажатия на кнопку button_event_handler(), которая уже имеется в проекте примера [4]. Эта функция обрабатывает нажатие на кнопку Button 1 и запускает таймера. Удалите следующий код из button_event_handler():

case BUTTON_1:
   // Зажечь LED 1.
   nrf_drv_gpiote_out_clear(LED_1);
   break;

Вместо этого кода введите следующее:

case BUTTON_1:
   // Запуск repeated-таймера (LED начнет мигать).
   err_code = app_timer_start(m_repeated_timer_id,
                              APP_TIMER_TICKS(200),
                              NULL);
   APP_ERROR_CHECK(err_code);
   break;

Декларируйте переменную err_code перед оператором switch, добавив следующую строку:

ret_code_t err_code;

Функция app_timer_start запускает указанный таймер (первый параметр), и задает, сколько должно пройти тиков до момента таймаута (второй параметр). Третий параметр - традиционный указатель а функцию-обработчик события таймаута таймера (timeout handler). Если здесь указать NULL, то функция обработчика не используется. В этом руководстве указатель представлен параметром p_context обработчика repeated_timer_handler().

Если все сделано правильно, то сейчас код должен скомпилироваться без каких-либо ошибок и предупреждений. Попробуйте загрузить скомпилированное приложение в память MCU кита разработчика (development kit, DK). Попробуйте нажать кнопку 1, это действие должно запускать мигание LED 1. Однако пока что не предусмотрена остановка мигания (кроме как сбросом MCU).

Чтобы остановить мигание светодиода, нам нужно остановить таймер. Это делается с помощью функции app_timer_stop(), которая принимает только один параметр - ID таймера. Измените button_event_handler() так, чтобы функция app_timer_stop() вызывалась при нажатии кнопки Button 2, путем замены существующего кода case BUTTON_2 следующим кодом:

case BUTTON_2:
   // Остановка repeated-таймера (перестанет мигать LED).
   err_code = app_timer_stop(m_repeated_timer_id);
   APP_ERROR_CHECK(err_code);
   break;

В этой последней редакции, после компиляции и загрузки кнопка 2 должна останавливать мигание светодиода. Таймер можно запустить заново нажатием на кнопку 1, и LED 1 снова начнет мигать.

[Создание однократного таймера]

Таймер приложения в режиме однократного запуска (single shot mode) запустит обработчик после истечения таймаута только один раз. Однако однократный таймер всегда можно перезапустить. Кроме того, количество тиков, через которое произойдет таймаут, может быть установлено на другое значение всякий раз при запуске таймера (это также относится и к repeated-таймерам).

В этой части руководства мы настроим кнопку 3 на запуск однократного таймера, который зажгет светодиод LED 2 при истечении таймаута. Первое нажатие на кнопку 3 таймаут таймера устанавливается на 1 секунуд. Затем таймаут будет увеличиваться на 1 секунду всякий раз при запуске этого таймера. Мы сохраним функционал кнопки 4, которая гасит светодиод.

Создайте новый ID таймера путем добавления следующих строк ниже создания m_repeated_timer_id:

// Дескриптор однократного таймера, используемого
// для зажигания LED2:
APP_TIMER_DEF(m_single_shot_timer_id);

Затем создайте обработчик таймаута, который каждый раз при вызове должен зажечь LED 2:

/**@brief Обработчик таймаута для одиночного таймера.
 */
static void single_shot_timer_handler(void * p_context)
{
   nrf_drv_gpiote_out_clear(LED_2);
}

Измените create_timers() добавлением следующих строк в конец этой функции (обратите внимание, что второй параметр теперь 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);

Обновите button_event_handler(), заменив код по ветке case BUTTON_3 на следующий:

case BUTTON_3:
   // Запуск однократного таймера, который зажжет LED2,
   // когда истечет таймаут. При этом каждый раз длительность
   // таймаута будет увеличиваться на 1 секунду.
   timeout += 1000;
   err_code = app_timer_start(m_single_shot_timer_id,
                              APP_TIMER_TICKS(timeout),
                              NULL);
   APP_ERROR_CHECK(err_code);
   break;

Определите статическую переменную timeout путем добавления перед оператором switch следующей строки:

static uint32_t timeout = 0;

Скомпилируйте и загрузите код. Если первый раз нажать кнопку 3, то светодиод LED 2 загорится через 1 секунду. Нажмите кнопку 4, светодиод погаснет. Снова нажмите кнопку 3, на этот раз светодиод зажжется через 2 секунды.

[Минимальный интервал 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 мкс.

[Ссылки]

1. nRF5 SDK Application Timer Tutorial site:nordicsemi.com.
2. nRF5 SDK v15 Application Timer API.
3. Pin Change Interrupt Example site:nordicsemi.com.
4. NordicPlayground / nrf5-application-timer-tutorial site:github.com.
5. nRF5_SDK.
6. Отличия примеров arm4 и arm5_no_packs из nRF5 SDK.
7. Simple application timer functionality configuration site:nordicsemi.com.
8. nRF5 SDK Scheduler Tutorial site:nordicsemi.com.
9. Timer library site:nordicsemi.com.
10. nRF52: счетчик реального времени RTC.
11nRF Command Line Tools.
12. J-Link Commander site:wiki.segger.com.

 

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


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

Top of Page