Таймер SysTick, реализация задержек в программе Печать
Добавил(а) microsin   

Важнейшей функцией почти всех приложений на микроконтроллерах является вычисление задержек времени в программе (отслеживание таймаутов во время передачи данных, периодический опрос клавиатуры и т. п.). Когда отслеживание времени является важным для приложения, Вы ничего не сможете сделать без хорошей функции задержки. Во всех моих проектах, начиная с простейших 8-битных MCU, функции задержек всегда было непросто реализовать, если нужно было точно отсчитывать время. Часто время правильно вычислялось только для определенной тактовой частоты, но чтобы код работал на всех тактовых частотах, требовалось применять продвинутое программирование на ассемблере. К счастью, как и у всех других микроконтроллеров ARM Cortex, семейство STM32F4 имеет отличный 24-битный системный таймер (system timer, SysTick), который считает вниз до нуля от предварительно загруженной в счетчик величины. Благодаря этому таймеру можно довольно просто и качественно реализовать отсчет реального времени в программе.

Функции для SysTick можно найти в заголовочном файле core_cm4.h, если Вы используете CoIDE. Если вы используете IAR, то функции для управления и использования таймера SysTick Вы сможете найти в примерах кода на основе стандартной библиотеки ST: в среде IAR 6.50, на стартовой странице выберите EXAMPLE PROJECTS -> ST -> STM32F4xx -> CMSIS and STM32F4xx stdperiph lib 1.1.0 (ищите функции SysTick_Config, SysTick_Handler). Все, что необходимо - просто вызвать функцию SysTick_Config, где в параметре будет указано количество тиков между прерываниями, и предоставить обработчик прерывания SysTick_Handler.

В ядро процессора STM32 встроен 24-битный системный таймер, так называемый SysTick (STK), который считает в обратном направлении от загруженной в таймер величины до нуля. В момент достижения значения 0 счетчик автоматически на следующем тактовом перепаде перезагружает сам себя значением из регистра STK_LOAD, и далее продолжает счет вниз, считая каждый приходящий импульс тактирования. Когда процессор остановлен (halted) для отладки, то счетчик не декрементируется.

Таймер SysTick (Cortex System Timer) специально предназначен для операционных систем реального времени (real-time operating system, RTOS), но может использоваться просто как стандартный счетчик вниз. Его возможности:

• 24-битный счетчик с обратным отсчетом.
• Возможность автозагрузки.
• Маскируемая генерация системного прерывания, когда счетчик достигает 0.
• Программируемый источник тактирования.

Базовый адрес для блока регистров SysTick равен 0xE000E010.

Таблица 54. Обзор регистров таймера SysTick.

Адрес Имя Тип Режим доступа Значение после сброса Описание
0xE000E010 STK_CTRL RW Привилегированный 0x00000000 Регистр статуса и управления SysTick.
0xE000E014 STK_LOAD RW Привилегированный не определено Значение для перезагрузки счетчика SysTick.
0xE000E018 STK_VAL RW Привилегированный не определено Текущее значение счетчика SysTick.
0xE000E01C STK_CALIB RO Привилегированный 0xC0000000 Регистр калибровки SysTick.

[Описание регистров SysTick]

STK_CTRL, SysTick control and status register (регистр статуса и управления).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved COUNT
FLAG
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
   Reserved CLK
SOURCE
TICK
INT
ENABLE
rw rw rw

Смещение адреса 0x00, значение после сброса 0x00000000. Требуемый режим доступа: привилегированный. Через регистр статуса и управления разрешаются фичи SysTick.

Биты 31..17, 15..3 зарезервированы, их состояние должно сохраняться в нулевом значении.

COUNTFLAG: этот бит читается как 1, если таймер досчитал до нуля после последнего чтения этого бита.

CLKSOURCE: выбор тактовой частоты для счетчика SysTick. 0: частота AHB/8, 1: частота процессора (AHB).

TICKINT: разрешение на запрос прерывания (exception request enable) SysTick. 0: счет продолжается вниз до нуля, но при этом не выставляется запрос на SysTick exception, 1: то же самое, но по достижении нуля выставляется запрос на SysTick exception. Примечание: программное обеспечение может использовать флаг COUNTFLAG, чтобы определить, что сосчитал ли когда-либо счетчик SysTick до нуля.

ENABLE: разрешение работы счетчика, 0: счетчик запрещен, 1: разрешен. Когда в 1, то счетчик загружает в себя значение RELOAD из регистра STK_LOAD, когда счет достигает 0. В этот момент также устанавливается в 1 флаг COUNTFLAG, и может быть выставлен запрос от прерывания, в зависимости от значения флага TICKINT. После перезагрузки счетчика значением RELOAD счет продолжается, и цикл повторяется.

STK_LOAD, SysTick reload value register (регистр для значения перезагрузки).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved RELOAD[23:16]
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
RELOAD[15:0]
rw

Смещение адреса 0x04, значение после сброса не определено. Требуемый режим доступа: привилегированный. Биты 31..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

RELOAD: в этих битах содержится значение для перезагрузки счетчика SysTick. Это начальное значение, которое автоматически загружается в регистр STK_VAL, когда счетчик разрешен, и когда счет достигает 0.

Значение для RELOAD вычисляется в соответствии с целями программы:

• Для генерации multi-shot таймера (т. е. у которого срабатывания повторяются снова и снова) с периодом N циклов процессора, используйте значение для RELOAD, равное N-1. Например, когда нужно, чтобы прерывание SysTick было каждые 100 тактовых импульсов, то установите значение RELOAD на величину 99.
• Для генерации одиночного прерывания SysTick после задержки в N циклов процессора, используйте для RELOAD значение N. Например, когда нужно, чтобы прерывание SysTick было каждые 100 тактовых импульсов, то установите значение RELOAD на величину 100. 

STK_VAL, SysTick current value register (регистр текущего значения счетчика).

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
Reserved CURRENT[23:16]
rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
CURRENT[15:0]
rw

Смещение адреса 0x08, значение после сброса не определено. Требуемый режим доступа: привилегированный. Биты 31..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

CURRENT: текущее значение счетчика SysTick. Чтение возвратит это значение. Запись любого значения очистит это поле в 0, и также очистит в 0 бит COUNTFLAG регистра STK_CTRL. 

STK_CALIB, SysTick calibration value register (регистр значения калибровки). Содержимое регистра показывает свойства калибровки SysTick.

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
NOREF SKEW Reserved TENMS[23:16]
r r r
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
TENMS[15:0]
r

Смещение адреса 0x0C, значение после сброса 0xC0000000. Требуемый режим доступа: привилегированный. Биты 29..24 зарезервированы, их состояние должно сохраняться в нулевом значении.

NOREF: флаг NOREF, читается как 0. Показывает, что предоставляется отдельная опорная частота тактов. Частота этих тактов HCLK/8.

SKEW: флаг SKEW (уход), показывает, точно ли установлено значение TENMS. Читается как 1. Значение калибровки для 1 мс не определено, потому что значение TENMS не известно. Это может повлиять на пригодность SysTick в качестве программных часов реального времени.

TENMS: значение калибровки. Показывает значение калибровки, когда счетчик SysTick работает от тактов HCLK max/8. Реальное значение зависит от конкретного чипа и определяется в процессе производства, за подробностями обращайтесь к Product Reference Manual, секция SysTick Calibration Value. Когда HCLK запрограммирован на максимальную частоту, период таймера SysTick равен 1 мс. Если информация калибровки не известна, вычислите значение калибровки по частоте тактов процессора или внешней частоте тактов.

Регистры SysTick в среде IAR, в режиме отладки можно посмотреть в группе регистров STK. Кто бы мог подумать?..

[Общие указания по использованию таймера SysTick]

Счетчик SysTick работает от тактов процессора. Если тактовый сигнал остановлен в режиме пониженного энергопотребления, то счетчик SysTick прекращает счет.

Убедитесь, что программа использует адрес, выровненный по слову для доступа к регистрам SysTick.

После сброса текущее состояние счетчика и значение его перезагрузки не определены, и корректная последовательность инициализация для счетчика SysTick следующая:

1. Нужно запрограммировать значение для перезагрузки счетчика.
2. Затем очистить текущее значение счетчика.
3. Program Control and Status register.

Вектор прерывания таймера SysTick находится по адресу 0x000003C. RCC подает тактовую частоту на System Timer (SysTick) от частоты шины AHB (HCLK), поделенной на 8. SysTick может работать либо от этой частоты, либо от частоты тактов Cortex (HCLK), что конфигурируется через регистр статуса и управления SysTick.

SysTick-clock

Более подробно о таймере SysTick можно прочитать в руководстве PM0214 компании ST (STM32F3 and STM32F4 Series Cortex®-M4 programming manual).

Вместе с таймером SysTick написание хорошей функции задержки становится простой задачей. Просто заранее установите SysTick на удобную для Вас величину, чтобы получить в результате задержки наподобие 1 мс, 100 мкс, 10 мкс и даже 1 мкс. Выбранное значение должно соответствовать потребностям Вашего приложения; например, если требуемые задержки должны быть в диапазоне единиц и десятков миллисекунд, то подходящим значением для настройки SysTick будет интервал 100 мкс или 1 мс. В файле main.c примера [2] приведен код, который инициализирует SysTick следующим образом:

/* SysTick закончит счет каждые 1 мс */
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);

Здесь в поле HCLK_Frequency переменной RCC_ClocksTypeDef загружается значение текущей основной тактовой частоты, затем в функцию SysTick_Config передается количество тиков, которое должен сделать таймер SysTick до своего окончания счета.

Чтобы отслеживать моменты срабатывания таймера SysTick в программе, для обработчика прерывания SysTick нужно определить функцию SysTick_Handler, примерно так:

volatile uint32_t timestamp = 0;
  void SysTick_Handler (void)
{
   //сюда попадаем каждую 1 миллисекунду
   timestamp++;
}

Для того, чтобы удостовериться в корректной работе SysTick, для бесконечного цикла main можно написать такой код (здесь не приведен код настройки, полностью код можно скачать по ссылке [2]):

/* Бесконечный цикл мигания светодиодом STAT1 на макетной плате  Olimex STM32-P407 */uint32_t delaystamp;while(1)
{
   if (delaystamp < timestamp)
   {
      if (GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_6))
         GPIOF->BSRRH = GPIO_Pin_6; //STAT==0, светодиод погас
      else
         GPIOF->BSRRL = GPIO_Pin_6; //STAT==1, светодиод горит
      delaystamp = timestamp + 500; //полупериод мигания 0.5 сек.
   }
}

Принцип работы понятен: переменная timestamp постоянно увеличивает свое значение каждые 1 мс. Как только пройдет 500 мс, значение timestamp станет больше переменной delaystamp, и выполнится код, который переключит светодиод в противоположное состояние. Потом переменная delaystamp снова загружается на величину, на 500 превышающую timestamp, чем обеспечивается отслеживание очередного интервала задержки 500 мс. Исходный код проекта IAR 6.30, демонстрирующего использование SysTick, можно скачать по ссылке [2].

[Задержка на основе DWT]

// Функции задержки взяты из статьи [3]
#include "delay.h"
#include "stm32f429xx.h"
 
#define    DWT_CYCCNT    *(volatile unsigned long *)0xE0001004
#define    DWT_CONTROL   *(volatile unsigned long *)0xE0001000
#define    SCB_DEMCR     *(volatile unsigned long *)0xE000EDFC
 
void delay_us(uint32_t us)
{
   int32_t us_count_tick =  us * (SystemCoreClock/1000000);
   //разрешаем использовать счётчик
   SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
   //обнуляем значение счётного регистра
   DWT_CYCCNT  = 0;
   //запускаем счётчик
   DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; 
   while(DWT_CYCCNT < us_count_tick);
   //останавливаем счётчик
   DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk;
}
 
void delay_ms(uint32_t ms)
{
   int32_t ms_count_tick =  ms * (SystemCoreClock/1000);
   //разрешаем использовать счётчик
   SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
   //обнуляем значение счётного регистра
   DWT_CYCCNT  = 0;
   //запускаем счётчик
   DWT_CONTROL|= DWT_CTRL_CYCCNTENA_Msk; 
   while(DWT_CYCCNT < ms_count_tick);
   //останавливаем счётчик
   DWT_CONTROL &= ~DWT_CTRL_CYCCNTENA_Msk;
}

[HAL_Delay]

В библиотеках HAL есть функция HAL_Delay, реализованная на основе миллисекундного таймера. В качестве параметра она принимает значение задержки, указанное в миллисекундах.

[osDelay]

В среде FreeRTOS реализована функция osDelay, в качестве параметра она принимает значение задержки, указанное в тиках системы (по умолчанию длительность тика 1 мс).

[Ссылки]

1. STM32F407 Delay with SysTick site:patrickleyman.be.
2. 140824SysTick-example.zip - демонстрация задержек в программе с помощью таймера SysTick STM32.
3STM32: аббревиатуры и термины.
4. ФУНКЦИЯ ЗАДЕРЖКИ STM32 site:hubstub.ru.