Таймеры-счетчики

Таймеры-счетчики

На этом уроке мы познакомимся с таймерами-счетчиками микроконтроллера и реализуем задержку переключения светодиодов на периферийном модуле таймера.

Мы уже реализовывали задержку переключения светодиодов с помощью системного таймера и функции задержки HAL_Delay(). Но кроме системного таймера, который является частью ARM-ядра, микроконтроллер STM32F303VCT6 содержит 10 таймеров, перечень которых приводится в документации с указанием разрядности, возможного направления счета, коэффициента предварительного деления, количества каналов захвата-сравнения-ШИМ и количества комплиментарных выходов.

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

Основой таймера служит суммирующий счетчик, который считает количество импульсов генератора тактовой частоты и с приходом каждого импульса тактовой частоты увеличивает свое значение на 1. Поэтому таймер еще называют таймером-счетчиком.

Зная тактовую частоту, можно определить, сколько импульсов тактовой частоты пройдет за определенное время – это и будет значением таймера через это время. На основании этого легко реализовать задержку требуемой длительности.

Рассмотрим задачу реализации задержки 50 мкс, используя таймер с частотой тактирования 8 МГц. При этом период тактовой частоты составляет 125 нc. За 50 мкс пройдет ровно 400 импульсов тактовой частоты, и таймер-счетчик досчитает до 400.

Базовый таймер характеризуется тремя основными регистрами. Это

  • TIMx_CNT - счетный регистр, который хранит текущее значение счетчика;
  • TIMx_PSC - регистр предварительного деления тактовой частоты, который позволяет увеличивать значение счетного регистра не с приходом каждого импульса тактовой частоты, а через каждые несколько импульсов, количество которых как раз-таки и задается в этом регистре;
  • TIMx_ARR - регистр периода, который определяет максимальное значение, до которого считает таймер-счетчик.

Максимальное значение периода ограничено разрядностью таймера-счетчика и составляет

TIMx_ARR ≤ 2N-1

где N – количество разрядов таймера.
Для 16-разрядного таймера это значение составляет 65536. Указанный регистр может содержать любое целое положительное число, меньшее этого значение. То есть максимальное значение этого регистра составляет 65535.

Давайте попробуем реализовать задержку с использованием базового таймера TIM6 микроконтроллера. Пусть переключение светодиодов осуществляется по-прежнему раз в полсекунды.

Сначала рассчитаем значения основных регистров. Необходимо реализовать задержку 500 мс, имея в распоряжении тактовый сигнал с частотой 8 МГц и 16-битный таймер с коэффициентом предварительного деления, представляющим собой любое целое число от 1 до 216-1.
Отношение требуемого времени задержки к периоду тактовой частоты будет равно произведению коэффициента предварительного деления на содержимое регистра периода.
Расчет предварительного делителя
Для определения минимального коэффициента предварительного деления нужно указанное отношение разделить на максимальный период, который для 16-разрядного таймера составляет 216. Полученное значение необходимо округлить до ближайшего целого в большую сторону. Это минимальное значение составляет 62.
Расчет предварительного делителя
Выберем коэффициент предварительного деления равным 64. Это означает, что значение таймера-счетчика будет увеличиваться на 1 каждые 64 такта, то есть каждые 8 мкс.
Тогда период определится как отношение требуемого времени задержки к произведению периода тактовой частоты и коэффициента предварительного деления. В нашем случае это значение составит 62500.
Расчет периода

На самом деле содержимое регистра периода должно быть на 1 меньше указанного значение, поскольку нулевое значение таймера также сохраняется в течение 64 периодов тактовой частоты.

Создание проекта для работы с таймером

Теперь давайте создадим новый проект в STM32CubeMX. Выбираем New Project. В появившемся окне выбираем микроконтроллер STM32F303VCTx, указывая соответственно серию, линейку и тип корпуса.
Далее снова конфигурируем линии PE8…PE15 как выходы, а линию PA0 как вход. И в периферийном модуле SYS указываем тип отладки Serial Wire.
Дополнительно включим модуль таймера TIM6. Открываем соответствующее выпадающее меню и ставим галочку в ячейке Activated.
Создание проекта в STM32CubeMX
Во вкладке Clock Configuration все оставляем по умолчанию и переходим во вкладку Configuration. Здесь мы видим, что у нас появилась возможность настройки выбранного модуля TIM6.
Конфигурация TIM6
Заходим внутрь этой настройки, щелкнув по модулю левой кнопкой мыши. В появившемся окне есть возможность установить значения предварительного делителя Prescaler и период Counter Period. Задаем рассчитанные значения – коэффициент предварительного деления равен 64, а модуль пересчета 62499.
Задание предварительного делителя и периода таймера

И генерируем код проекта – выбираем Project -> Generate Code.
В появившемся окне указываем имя проекта и место, где он будет сохранен. И нажимаем Ok.

Открываем проект в IAR.
В части инициализации у нас добавился вызов еще одной функции – MX_TIM6_Init(). Кроме того, в файле main.c появилось описание дополнительной переменной htim6 переопределенного типа TIM_HandleTypeDef. Это дескриптор таймера, инициализация которого производится указанной функцией инициализации. Если мы обратимся к заголовочному файлу драйвера, то увидим, что тип этого дескриптора представляет собой переопределенную структуру, включающую, в частности, базовый адрес в поле Instance. С его помощью мы сможем обратиться к интересующим нас регистрам.

Для начала использования таймера его необходимо включить. Для этого используется функция

 
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)

в качестве аргумента которой передается ссылка на дескриптор таймера.

Давайте воспользуемся самой первой реализацией бегущего огня. Я скопирую ее из предыдущего проекта и вставлю в бесконечный цикл. Сначала гасим все светодиоды, а затем проверяем значение i и включаем тот светодиод, которому соответствует значение i. Снова опишем переменную i с начальным значением, равным 0.


И в качестве задержки введем цикл ожидания. Цикл будет выполняться пока значение счетного регистра таймера больше 0. Как только таймер-счетчик переполнит сохраненное значение в регистре периода, его значение сбросится в 0 и осуществится выход из цикла. Доступ к значению счетного регистра можно получить через поле Instance дескриптора таймера htim6.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim6);
  uint8_t i = 0;
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
      | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
    switch (i)
    {
      case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
      case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
      case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
      case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
      case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
      case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
      case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
      case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
    }
    i++;
    i %= 8;
    while (htim6.Instance->CNT > 0);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


Откомпилируем проект и загрузим в отладочную плату. Конечно, введение цикла ожидания не является наилучшим решением для формирования задержки. Одна из некорректностей реализации данного решения проявляется при запуске программы на выполнение – светодиоды переключаются, но через один. Это связано с тем, что за 64 рабочих такта цикл успевает выполниться еще раз и снова попасть в цикл ожидания, то есть светодиоды, расположенные на линиях PE9, PE11, PE13, PE15 включаются на очень короткий промежуток времени, и мы этого не замечаем.

Чтобы побороться с этой некорректностью возможны несколько вариантов. Одним из них является введение вспомогательной переменной, как мы это реализовывали с кнопкой. Но мы попробуем использовать другой способ. Введем еще один цикл задержки, условием продолжения которого будет равенство счетного регистра таймера нулю. Выходом из этого цикла является уже значение счетного регистра, отличное от нуля.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start(&htim6);
  uint8_t i = 0;
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
      | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
    switch (i)
    {
      case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
      case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
      case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
      case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
      case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
      case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
      case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
      case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
    }
    i++;
    i %= 8;
    while (htim6.Instance->CNT > 0);
    while (htim6.Instance->CNT == 0);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строка 37.

Загрузим программу в отладочную плату и запустим на выполнение. И мы видим, что все светодиоды переключаются с задержкой в полсекунды. Только теперь задержка реализована на таймере TIM6. Аналогичным образом можно использовать другие таймеры для формирования задержки.

На следующем уроке мы поговорим о других недостатках такого способа реализации задержки и рассмотрим реализацию задержки с использованием прерываний.

Прокрутить вверх