На этом уроке мы познакомимся с таймерами-счетчиками микроконтроллера и реализуем задержку переключения светодиодов на периферийном модуле таймера.
Мы уже реализовывали задержку переключения светодиодов с помощью системного таймера и функции задержки 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.
Во вкладке Clock Configuration все оставляем по умолчанию и переходим во вкладку Configuration. Здесь мы видим, что у нас появилась возможность настройки выбранного модуля TIM6.
Заходим внутрь этой настройки, щелкнув по модулю левой кнопкой мыши. В появившемся окне есть возможность установить значения предварительного делителя Prescaler и период Counter Period. Задаем рассчитанные значения – коэффициент предварительного деления равен 64, а модуль пересчета 62499.
И генерируем код проекта – выбираем Project -> Generate Code.
В появившемся окне указываем имя проекта и место, где он будет сохранен. И нажимаем Ok.
Открываем проект в IAR.
В части инициализации у нас добавился вызов еще одной функции – MX_TIM6_Init(). Кроме того, в файле main.c появилось описание дополнительной переменной htim6 переопределенного типа TIM_HandleTypeDef. Это дескриптор таймера, инициализация которого производится указанной функцией инициализации. Если мы обратимся к заголовочному файлу драйвера, то увидим, что тип этого дескриптора представляет собой переопределенную структуру, включающую, в частности, базовый адрес в поле Instance. С его помощью мы сможем обратиться к интересующим нас регистрам.
Для начала использования таймера его необходимо включить. Для этого используется функция
в качестве аргумента которой передается ссылка на дескриптор таймера.
Давайте воспользуемся самой первой реализацией бегущего огня. Я скопирую ее из предыдущего проекта и вставлю в бесконечный цикл. Сначала гасим все светодиоды, а затем проверяем значение i и включаем тот светодиод, которому соответствует значение i. Снова опишем переменную i с начальным значением, равным 0.
И в качестве задержки введем цикл ожидания. Цикл будет выполняться пока значение счетного регистра таймера больше 0. Как только таймер-счетчик переполнит сохраненное значение в регистре периода, его значение сбросится в 0 и осуществится выход из цикла. Доступ к значению счетного регистра можно получить через поле Instance дескриптора таймера htim6.
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
{
/* 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 включаются на очень короткий промежуток времени, и мы этого не замечаем.
Чтобы побороться с этой некорректностью возможны несколько вариантов. Одним из них является введение вспомогательной переменной, как мы это реализовывали с кнопкой. Но мы попробуем использовать другой способ. Введем еще один цикл задержки, условием продолжения которого будет равенство счетного регистра таймера нулю. Выходом из этого цикла является уже значение счетного регистра, отличное от нуля.
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
{
/* 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. Аналогичным образом можно использовать другие таймеры для формирования задержки.
На следующем уроке мы поговорим о других недостатках такого способа реализации задержки и рассмотрим реализацию задержки с использованием прерываний.
Подскажите пожалуйста, а как вы включили осцилограф?
ПО осциллографа, который я использую, содержит кнопку (в нижней строчке), которая приостанавливает сбор данных.
Пользуюсь осциллографом АКИП-4114 (в Южно-Уральском государственном университете).
Подскажите пожалуйста, а почему данный код не зажигает светодиод?
2
3
4
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12, GPIO_PIN_SET);
…
По этому коду проблем не вижу.
Возможные причины
— на линии PD12 нет светодиода
— линия PD12 сконфигурирована не как цифровой выход
— линия PD12 сконфигурирована как цифровой выход с открытым коллектором
— далее в коде есть операции, которые не позволяют Вам увидеть, что светодиод зажигается.
Здравствуйте, разъяните пожалуйста запись htim6.Instance->CNT, не понятно здесь «->» . Я знаю, что «->» используется при обращении к методам и свойствам класса, когда с ним работают по указателю. А здесь не понятно для меня.)
htim6 — это дескриптор таймера.
Instance — это указатель на его внутреннюю структуру типа TIM_TypeDef, который, в свою очередь, в качестве полей содержит имена регистров конкретного микроконтроллера.
Чтобы разобраться в коде, установите курсор на интересующем имени, нажмите правую кнопку мыши и проследуйте по ссылке «Go to Definition of ‘***'». Будет открыт тот класс, где определено это имя.
Что касается стрелки (->), то это — стандартный способ обращения к полям указателя на структуру. Подробнее смотрите здесь.