You are here

Low Power Support: Tickless Idle Mode

Перевод может содержать ошибки. Читайте первоисточник: Low Power Support - Tickless Idle Mode

Назад: [Более подробно...] Вверх: [Более подробно...] Вперёд: [Более подробно...]

 

Энергосбережение

Смотрите также: Low Power Features For ARM Cortex-M MCUs.
Смотрите также: Tickless Demos на микропроцессорах SAM4L, RX100, STM32L, CEC1302 и EFM32.

 

Введение

Обычно потребление микроконтроллера, работающего под управлением FreeRTOS, снижается переводом микроконтроллера в малопотребляющий режим в вызове функции-ловушки задачи простоя. Но в этом случае снижение энергопотребления ограничено необходимостью каждый раз просыпаться, чтобы обрабатывать прерывание тиков ОС, и обратно уходить в режим снижения энергопотребления. Мало того, если частота тиков достаточно высока, затраты энергии и времени выход/вход в режим энергосбережения при обработке каждого тика будут превышать выгоду от любых режимов энергосбережения, кроме самых лёгких.

В режим простоя без прерывания тиков ОС FreRTOS отключает прерывания тиков на время простоя (на то время, когда нет прикладных задач, которые могут быть выполнены),  затем при включении прерывания тиков ОС корректирует значение счётчика тиков ОС.

Отключение прерываний по тикам ОС позволяет микроконтроллеру находиться в состоянии глубокого энергосбережения до тех пор, пока не произойдёт какое-либо внешнее прерывание, либо пока не придёт время ядру ОС перевести задачу в режим готовности к запуску.

 

Макрос portSUPPRESS_TICKS_AND_SLEEP()

portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime )

Встроенный функционал режима энергосбережения с отключением прерывания тиков ОС активируется установкой параметра configUSE_TICKLESS_IDLE в 1 в файле FreeRTOSConfig.h (разумется, только для тех портов ОС, которые это поддерживают). Пользовательская реализация режима энергосбережения с отключением прерывания тиков ОС может иметь место для любого порта FreeRTOS (включая и те порты, где присутствует встроенная реализация). Для этого параметр configUSE_TICKLESS_IDLE в файле FreeRTOSConfig.h устанавливается в 2.

Если  режим энергосбережения с отключением прерывания тиков ОС включён, ядро будет вызывать макрос portSUPPRESS_TICKS_AND_SLEEP() при выполнении двух условий:

  1. Задача простоя является единственной задачей, которая может быть запущена, поскольку все остальные задачи находятся либо в заблокированном, либо в приостановленном состоянии.
  2. По крайней мере n дополнительных полных тиков ОС пройдут до того, как ядро выполнит перевод задачи приложения из заблокированного состояния, где n задается константой configEXPECTED_IDLE_TIME_BEFORE_SLEEP в файле FreeRTOSConfig.h.

Значение единственного параметра макроса portSUPPRESS_TICKS_AND_SLEEP() эквивалентно полному числу тиков ОС перед тем, как задача будет переведена в состояние "Готова". Следовательно, значение параметра это время, в течение которого микроконтроллер может безопасно оставаться в режиме энергосбережения с остановленным прерыванием тиков ОС.

Замечание: если функция eTaskConfirmSleepModeStatus() возвращает eNoTasksWaitingTimeout, когда она вызвана из макроса portSUPPRESS_TICKS_AND_SLEEP(), то микроконтроллер может оставаться в состоянии глубокого сна бесконечно. Функция eTaskConfirmSleepModeStatus() возвращает значене eNoTasksWaitingTimeout только при выполнении следующих условий:

  1. Программные таймеры не используются, поэтому планировщик не должен выполнять функции обратного вызова таймеров в какой-либо момент в будущем.
  2. Все задачи приложения находятся либо в состоянии Suspended, либо в заблокированном состоянии с бесконечным ожиданием (т.е. значение таймаута указано portMAX_DELAY), поэтому планировщик не должен переводить задачи из заблокированного состояния по истечении некоторого фиксированного времени в будущем.

Чтобы исключить возможность "гонок", планировщик останавливается перед вызовом portSUPPRESS_TICKS_AND_SLEEP() и возобновляется, когда portSUPPRESS_TICKS_AND_SLEEP() завершается. Это гарантирует, что задачи приложения не могут выполняться между выходом микроконтроллера из режима энергосбережения и завершением макроса portSUPPRESS_TICKS_AND_SLEEP(). Кроме того, для макроса portSUPPRESS_TICKS_AND_SLEEP() необходима небольшая критическая секция между остановкой прерывания тиков ОС и входом микроконтроллера в режим энергосбережения. Функция eTaskConfirmSleepModeStatus() должна вызываться из этой критической секции.

Все порты для GCC, IAR и Keil на ARM Cortex-M3 и ARM Cortex-M4 предоставляют реализацию по-умолчанию макроса portSUPPRESS_TICKS_AND_SLEEP(). Важная информация об использовании реализации ARM Cortex-M находится на странице "Особенности режимов энергосбережения для микроконтроллеров с ядром ARM Cortex-M MCUs".

Реализация по-умолчанию со временем будет добавлена во все порты ОС. В то же время,  описанные выше ловушки могут использоваться для добавления режимов энергосбережения без тиков ОС в любой порт.

 

Реализация portSUPPRESS_TICKS_AND_SLEEP()

Если порт FreeRTOS не предоставляет реализацио по-умолчанию макроса portSUPPRESS_TICKS_AND_SLEEP(), то разработчик приложения может предоставить свою собственную реализацию, определив portSUPPRESS_TICKS_AND_SLEEP() в файле FreeRTOSConfig.h.

Если используемый порт FreeRTOS  предоставляет реализацию по-умолчанию для portSUPPRESS_TICKS_AND_SLEEP(), то разработчик приложения всё равно может использовать свою реализацию, определив макрос portSUPPRESS_TICKS_AND_SLEEP() в файле FreeRTOSConfig.h.

Приведённый пример исходного кода показывает, как  макрос portSUPPRESS_TICKS_AND_SLEEP() может быть реализован разработчиком. Этот пример является базовым, и допускает некоторое "сползание" времени ядра относительно реального календарного времени. В официальных портах FreeRTOS этот неприятный эффект максимально возможно снижен, но для этого приходится усложнять реализацию.

Из вызовов функций, показанных в примере, только vTaskStepTick() и eTaskConfirmSleepModeStatus() являются частью API FreeRTOS. Остальные функции специфичны для используемого микроконтроллера и предназначены для управления часами и режимами энергосбережения, и, поэтому, должны предоставляться разработчиком приложения. 

 

Пример пользовательской реализации portSUPPRESS_TICKS_AND_SLEEP()

/*----------------------------------------------------------------------------*/
/* Сначала определим макрос the portSUPPRESS_TICKS_AND_SLEEP(). Параметр макроса
это время в тиках до следующего запуска ядра ОС. */
#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )

/*----------------------------------------------------------------------------*/
/* Теперь определим функцию, которая будет вызываться макросом portSUPPRESS_TICKS_AND_SLEEP(). */
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;
eSleepModeStatus eSleepStatus;

    /* Считываем текущее время из некоего узла, который будет продолжать
    работать во время нахождения микроконтроллера в режиме энергосбережения. */
    ulLowPowerTimeBeforeSleep = ulGetExternalTime();

    /* Останавливаем таймер, вырабатывающий прерывания тиков ОС. */
    prvStopTickInterruptTimer();

    /* Входим в критическую секцию, которая запретит выход микроконтроллера
    из спящего режима по прерываниям. */
    disable_interrupts();

    /* Убедимся, что всё ещё можно войти в спящий режим. */
    eSleepStatus = eTaskConfirmSleepModeStatus();

    if( eSleepStatus == eAbortSleep )
    {
        /* Некая задача вышла из заблокированного состояния с момента вызова
        макроса, или "контекст удерживается в ожидании" "a context siwth is
        being held pending (???). Поэтому не входим
        в режим энергосбережения. Перезапускаем таймер прерывания тиков ОС и
        выходим из критической секции. */
        prvStartTickInterruptTimer();
        enable_interrupts();
    }
    else
    {
        if( eSleepStatus == eNoTasksWaitingTimeout )
        {
            /* Нет необходимости настраивать прерывание для выхода
            микроконтроллера из спящего режима через некоторое
            фиксированное (определённое) время в будущем. */
            prvSleep();
        }
        else
        {
            /* Конфигурируем прерывние для вывода микроконтроллера из состояния
            энергосбережения во время следующего запуска ядра. Прерывание должно
            вырабатываться источником, который продолжает работать, когда
            микроконтроллер переходит в режим энергосбережения. */
            vSetWakeTimeInterrupt( xExpectedIdleTime );

            /* Входим в режим энергосбережения. */
            prvSleep();

            /* Определяем, как долго микроконтроллер находился в режиме
            энергосбережения. Это время будет меньше, чем xExpectedIdleTime,
            если микроконтроллер был выведен из режима энергосбережения
            прерыванием, отличным от настроенного вызовом
            vSetWakeTimeInterrupt(). Заметьте, что планировщик останавливается
            перед вызовом portSUPPRESS_TICKS_AND_SLEEP(), и возобновляет работу,
            когда portSUPPRESS_TICKS_AND_SLEEP() завершает работу. Поэтому
            никакие другие задачи не будут запускаться, пока эта функция
            выполняется. */
            ulLowPowerTimeAfterSleep = ulGetExternalTime();

            /* Корректируем счётчик тиков ядра для учёта времени, которе
            микроконтроллер провёл в состоянии энергосбережения. */
            vTaskStepTick( ulLowPowerTimeAfterSleep - ulLowPowerTimeBeforeSleep );
        }

        /* Выходим из критической секции - это возможно сразу после
        вызова prvSleep(). */
        enable_interrupts();

        /* Перезапускаем таймер, вырабатывающий прерывания тиков ОС. */
        prvStartTickInterruptTimer();
    }
}

 

Hobby's category: