Идея программных таймеров уже встречалась нам в статьях этого цикла. Таймеры являются объектами ядра, предоставляющими задачам простой способ запуска событий по времени, или, чаще всего, способ выполнять действия на регулярной основе. Все детали функционала, связанного со временем (точность, обработка прерываний и т.д.) в Nucleus SE были рассмотрены в предыдущей статье (#27).
Программные таймеры могут быть настроены на однократное срабатывание, то есть они запускаются, а затем, после указанного периода времени, просто завершают цикл. Либо таймер может быть настроен на перезапуск: после завершения счета таймер автоматически перезапускается. Время работы после перезапуска может отличаться от первичного времени работы. Кроме того, таймер можно опционально настроить на выполнение специальной функции завершения, которая выполняется когда (или каждый раз когда) таймер завершает рабочий цикл.
Как и для большинства аспектов Nucleus SE, настройка таймеров управляется директивами #define в nuse_config.h. Основным параметром является NUSE_TIMER_NUMBER, который определяет сконфигурированные в приложении таймеров. По умолчанию это значение равно нулю (то есть в приложении таймеры не используются), и может принимать значения вплоть до 16. Некорректное значение приведет к ошибке компиляции, которая будет сгенерирована проверкой в файле nuse_config_check.h (этот файл входит в nuse_config.c и компилируется вместе с ним), что приведет к срабатыванию директивы #error.
Выбор ненулевого значения является главным активатором таймеров. Этот параметр используется при определении структур данных, и от его значения зависит их размер. Кроме того, ненулевое значение активирует настройки API.
В Nucleus SE я пытался найти возможность сделать функционал опциональным, там, где это позволит сэкономить память. Хорошим примером является поддержка функций завершения таймеров. Помимо того, что эта возможность опциональна для каждого таймера, механизм может быть активирован (или нет) для всего приложения при помощи параметра NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT в nuse_config.h. Присвоение этому параметру значения FALSE блокирует определение двух структур данных в ПЗУ, которые будут подробно описаны в этой статье.
Каждая функция API (служебный вызов) в Nucleus SE имеет активирующую директиву #define в nuse_config.h. Для таймеров к таким символам относятся:
По умолчанию, всем активаторам присвоено значение FALSE, таким образом, все служебные вызовы отключены, блокируя включение реализующего их кода. Для настройки таймеров в приложении нужно выбрать необходимые служебные вызовы API и присвоить им значение TRUE.
#define NUSE_TIMER_NUMBER 0/* количество таймеров приложения в системе 0-16 */
/* Активаторы служебного вызова */
#define NUSE_TIMER_CONTROL FALSE
#define NUSE_TIMER_GET_REMAINING FALSE
#define NUSE_TIMER_RESET FALSE
#define NUSE_TIMER_INFORMATION FALSE
#define NUSE_TIMER_COUNT FALSE
Если функция API, связанная с таймерами, активирована, а в приложении нет сконфигурированных таймеров (кроме функции NUSE_Timer_Count(), которая разрешена всегда), возникнет ошибка компиляции. Если ваш код использует вызов API, который не был активирован, произойдет ошибка компоновки, так как код реализации не был включен в приложение.
Фундаментальные операции, которые можно выполнять с таймером, это управление (запуск и остановка) и считывание текущего значения. Nucleus RTOS и Nucleus SE предоставляют два базовых служебных вызова API для этих операций.
Служебный вызов Nucleus RTOS API для управления таймером позволяет активировать и деактивировать таймер (запускать и останавливать). Nucleus SE предоставляет аналогичный функционал.
NUSE_CS_Enter();
if (enable == NUSE_ENABLE_TIMER)
{
NUSE_Timer_Status[timer] = TRUE;
if (NUSE_Timer_Expirations_Counter[timer] == 0)
{
NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer];
}
else
{
NUSE_Timer_Value[timer] = NUSE_Timer_Reschedule_Time[timer];
}
}
else /* enable == NUSE_DISABLE_TIMER */
{
NUSE_Timer_Status[timer] = FALSE;
}
NUSE_CS_Exit();
Если была указана функция NUSE_DISABLE_TIMER, статусу таймера (параметр NUSE_Timer_Status[]) присваивается значение FALSE, что приводит к игнорированию таймера обработчиком прерываний.
При выборе функции NUSE_ENABLE_TIMER счетчик таймера (NUSE_Timer_Value[]) устанавливается в значение NUSE_Timer_initial_Time[], при условии что таймер ни разу не останавливался с момента последнего сброса. В противном случае ему присваивается значение NUSE_Timer_Reschedule_Time[]. Затем статусу таймера (параметр NUSE_Timer_Status[]) присваивается значение TRUE, что приводит к тому, что таймер обрабатывается обработчиком прерываний.
Для получения оставшегося времени таймера служебный вызов Nucleus RTOS API возвращает количество тактов до истечения срока его действия. Nucleus SE предоставляет аналогичный функционал.
Вариант кода функции API NUSE_Timer_Get_Remaining() (после проверки параметров) тривиально прост. Значение NUSE_Timer_Value[] получается, а затем возвращается в критической секции.
Nucleus RTOS имеет четыре вызова API, которые предоставляют вспомогательные функции, связанные с таймерами: сброс таймера, получение информации о таймере, получение количества таймеров в приложении и получение указателей на все таймеры в приложении. Первые три функции реализованы в Nucleus SE.
Этот вызов API сбрасывает таймер в исходное, неиспользуемое состояние. Таймер может быть активирован или деактивирован после завершения этого вызова. Его можно использовать только после того, как таймер был отключен (при помощи NUSE_Timer_Control()). В следующий раз когда таймер будет активирован, он будет инициализирован с параметром NUSE_Timer_Initial_Time[]. Nucleus RTOS позволяет предоставить новое исходное состояние и провести перепланирование времени, а также указать функцию завершения при сбросе таймера. В Nucleus SE эти значения устанавливаются во время настройки и не могут быть изменены, поскольку они хранятся в ПЗУ.
NUSE_CS_Enter();
NUSE_Init_Timer(timer);
if (enable == NUSE_ENABLE_TIMER)
{
NUSE_Timer_Status[timer] = TRUE;
}
/* иначе enable == NUSE_DISABLE_TIMER а статус остается FALSE */
NUSE_CS_Exit();
Вызов NUSE_Init_Timer() инициализирует значение времени и очищает счетчик завершения. После этого при необходимости проверятся значение требуемого состояния и включен ли таймер.
Этот служебный вызов позволяет получить набор информации о таймере. Реализация Nucleus SE отличается от Nucleus RTOS тем, что она возвращает меньше информации, так как именование объектов не поддерживается.
timer – указатель на таймер, о котором запрашивается информация;
name – указатель на 8-символьную область для имени таймера;
enable – указатель на переменную, принимающую текущее состояние активатора таймера: NU_ENABLE_TIMER или NU_DISABLE_TIMER;
expirations – указатель на переменную, принимающую счетчик количества завершений цикла таймера с момента его последнего сброса;
id – указатель на переменную, принимающую значение параметра, переданного в функцию завершения цикла таймера;
initial_time – указатель на переменную, принимающую значение, в которое будет инициализирован таймер после сброса;
reschedule_time – указатель на переменную, принимающую значение, в которое будет инициализирован таймер после завершения.
STATUS NUSE_Timer_Information (NUSE_TIMER timer, OPTION *enable, U8 *expirations, U8 *id, U16 *initial_time, U16 *reschedule_time);
timer – индекс таймера, о котором запрашивается информация;
enable – указатель на переменную, принимающую значение TRUE или FALSE в зависимости от того, активирован таймер или нет;
expirations – указатель на переменную типа U8, принимающую значение количества завершений таймера с момента его последнего сброса;
id – указатель на переменную типа U8, принимающую значение параметра, переданного в функцию завершения таймера (вернет пустое значение, если функции завершения отключены);
initial_time – указатель на переменную типа U16, принимающую значение, которым будет инициализирован таймер после сброса;
reschedule_time – указатель на переменную типа U16, принимающую значение, которым будет инициализирован таймер после завершения.
NUSE_CS_Enter();
if (NUSE_Timer_Status[timer])
{
*enable = NUSE_ENABLE_TIMER;
}
else
{
*enable = NUSE_DISABLE_TIMER;
}
*expirations = NUSE_Timer_Expirations_Counter[timer];
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT
*id = NUSE_Timer_Expiration_Routine_Parameter[timer];
#endif
*initial_time = NUSE_Timer_Initial_Time[timer];
*reschedule_time = NUSE_Timer_Reschedule_Time[timer];
NUSE_CS_Exit();
Функция возвращает статус таймера. Значение параметра функции завершения возвращается, только если их поддержка была активирована в приложение.
Этот служебный вызов возвращает количество таймеров, сконфигурированных в приложении. В Nucleus RTOS это значение может меняться со временем, и возвращаемое значение будет отображать текущее количество таймеров. В Nucleus SE возвращаемое значение задается на этапе сборки и не может изменяться.
Таймеры используют пять или семь структур данных (находящихся в ОЗУ или в ПЗУ) которые (как и другие объекты Nucleus SE) являются набором таблиц, размер и количество которых соответствует количеству сконфигурированных таймеров и выбранных параметров.
Настоятельно рекомендую, чтобы код приложения не использовал прямой доступ к этим структурам данных, а обращался к ним через предоставляемые функции API. Это позволит избежать несовместимости с будущими версиями Nucleus SE и нежелательных побочных эффектов, а также упростит портирование приложений на Nucleus RTOS. Ниже приведен подробный обзор структур, чтобы упростить понимание работы кода служебных вызовов и для отладки.
NUSE_Timer_Initial_Time[] – массив типа U16, имеющий одну запись для каждого сконфигурированного таймера и хранящий значение каждого таймера.
NUSE_Timer_Reschedule_Time[] – массив типа U16, имеющий одну запись для каждого сконфигурированного таймера и хранящий значение, в которое таймер будет установлен после завершения. Нулевое значение говорит о том, что таймер «одноразовый» и не должен перезапускаться автоматически.
NUSE_Timer_Expiration_Routine_Address[] – массив типа ADDR, содержащий адрес процедур истечения таймеров. Этот массив существует, только если поддержка процедуры истечения таймера была активирована.
NUSE_Timer_Expiration_Routine_Parameter[] – массив типа U8, содержащий значения параметра, который передается в функции завершения таймеров. Этот массив существует, только если поддержка функций завершения была активирована.
Эти структуры данных объявляются и инициализируются (статически) в файле nuse_config.c, таким образом:
ROM U16 NUSE_Timer_Initial_Time[NUSE_TIMER_NUMBER] =
{
/* исходное время таймера------ */
};
ROM U16 NUSE_Timer_Reschedule_Time[NUSE_TIMER_NUMBER] =
{
/* время таймеров после первого завершения ------ */
};
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT || NUSE_INCLUDE_EVERYTHING
/* прототипы функций завершения */
ROM ADDR
NUSE_Timer_Expiration_Routine_Address[NUSE_TIMER_NUMBER] =
{
/* адреса функций завершения таймеров ------ */
/* может быть NULL */
};
ROM U8
NUSE_Timer_Expiration_Routine_Parameter[NUSE_TIMER_NUMBER] =
{
/* параметры функции завершения таймера ------ */
};
#endif
Объём данных в ПЗУ (в байтах) для всех таймеров в приложении, если поддержка функций завершения отключена, может быть вычислен следующим образом:
timer – указатель на предоставляемый пользователем блок управления таймером; он будет использоваться для управления таймерами в других вызовах API;
name – указатель на 7-символьное имя таймера с терминирующим нулем;
expiration_routine – указывает функцию, которая должна выполняться после завершения таймера;
id – элемент данных типа UNSIGNED, передаваемый на функцию завершения: этот параметр может использоваться для идентификации таймеров с одинаковой функцией завершения;
initial_time – указывает исходное количество тактов таймера до завершения таймера;
reschedule_time – указывает количество тактов таймера до завершения второго и последующих циклов; если этот параметр равен нулю, таймер останавливается только один раз;
enable – этот параметр может принимать значения NU_ENABLE_TIMER и NU_DISABLE_TIMER; NU_ENABLE_TIMER активирует таймер после его создания;
NU_DISABLE_TIMER оставляет таймер отключенным; таймеры, созданные с параметром NU_DISABLE_TIMER, должны быть активированы при помощи вызова NU_Control_Timer.
Этот вызов API удаляет ранее созданный таймер. В Nucleus SE в нем нет необходимости, так как таймеры создаются статически и не могут быть удалены.
Этот вызов API формирует последовательный список указателей на все таймеры в системе. В Nucleus SE в нем нет необходимости, так как таймеры определяются простым индексом, а не указателем.
Как и в случае со всеми другими объектами Nucleus SE, моей целью было обеспечение максимальной совместимости кода приложений с Nucleus RTOS. Таймеры не являются исключением и, с точки зрения пользователя, они реализованы так же, как и в Nucleus RTOS. Есть и определенная несовместимость, которую я посчитал допустимой, с учетом того, что в результате код станет более понятным и более эффективным с точки зрения объема требуемой памяти. В остальном, вызовы API Nucleus RTOS могут быть практически напрямую перенесены на Nucleus SE.
В Nucleus RTOS, все объекты описываются структурой данных — управляющим блоком, который имеет определенный тип данных. Указатель на этот блок управления служит идентификатором таймера. Я решил, что в Nucleus SE для эффективного использования памяти необходим другой подход: все объекты ядра описываются набором таблиц в ОЗУ и/или ПЗУ. Размер этих таблиц определяется количеством сконфигурированных объектов каждого типа. Идентификатор конкретного объекта – индекс в этой таблице. Таким образом, я определил NUSE_TIMER в качестве эквивалента U8, переменная (а не указатель) этого типа служит идентификатором таймера. С этой небольшой несовместимостью легко справиться, если код портируется с Nucleus SE на Nucleus RTOS и наоборот. Обычно над идентификаторами объектов не выполняются никакие операции, кроме перемещения и хранения.
Nucleus RTOS также поддерживает присвоение имен таймерам. Эти имена используются только при отладке. Я исключил их из Nucleus SE, чтобы сэкономить память.
В Nucleus RTOS таймеры реализованы при помощи 32-битных счетчиков. Я решил уменьшить это значение до 16 бит в Nucleus SE. Это привело к значительному улучшению эффективного использования памяти и времени выполнения. Nucleus SE может быть модифицирована, если приложению требуется более длительное время выполнения.
Nucleus SE реализует функции завершения похожим с Nucleus RTOS образом, только они могут быть отключены полностью (что позволяет сохранить память), а также они определяются статически. Функцию завершения нельзя изменить, когда таймер сброшен.
Nucleus RTOS поддерживает восемь служебных вызовов для работы с таймерами. Из них три не реализованы в Nucleus SE. Подробное описание этих вызовов, а также причины такого решения можно найти выше в этой статье, в разделе «Нереализованные вызовы API».
В следующей статье будут рассматриваться прерывания.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com