Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2751
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Таймеры

296

 

Таблица 4: Доступные режимы отсчета таймера

Режим отсчета

Описание

TIM_COUNTERMODE_UP

Таймер считает от нуля до значения Period (которое не

 

может быть выше, чем разрешение таймера – 16/32-раз-

 

рядного), а затем генерирует событие переполнения.

TIM_COUNTERMODE_DOWN

Таймер считает вниз от значения Period до нуля, а затем

 

генерирует событие опустошения.

TIM_COUNTERMODE_CENTERALIGNED1 В режиме выравнивания по центру счетчик считает от 0 до значения Period – 1, генерирует событие переполнения, затем считает от значения Period до 1 и генерирует событие опустошения счетчика. Затем он возобновляет отсчет с 0. Флаг прерывания сравнения выходного сигнала

(Output compare interrupt flag) каналов, сконфигурирован-

ных в режиме выхода, устанавливается при отсчете счетчика вниз.

TIM_COUNTERMODE_CENTERALIGNED2

TIM_COUNTERMODE_CENTERALIGNED3

То же, что и TIM_COUNTERMODE_CENTERALIGNED1, но флаг пре-

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

То же, что TIM_COUNTERMODE_CENTERALIGNED1, но флаг преры-

вания сравнения выходного сигнала каналов, сконфигурированных в режиме выхода, устанавливается, когда счетчик отсчитывает вверх и вниз.

Таблица 5: Доступные режимы ClockDivision для таймеров общего назначения и расширенного управления

Режимы делителя тактового сигнала таймера

Описание

 

 

TIM_CLOCKDIVISION_DIV1

Вычисляет 1 выборку входного сигнала на

 

выводах ETRx и TIx

TIM_CLOCKDIVISION_DIV2

Вычисляет 2 выборки входного сигнала на выводах ETRx и TIx

TIM_CLOCKDIVISION_DIV4

Вычисляет 4 выборки входного сигнала на выводах ETRx и TIx

11.2.1. Использование таймеров в режиме прерываний

Прежде чем увидеть полный пример, лучше всего подвести итог тому, что мы видели до сих пор. Базовый таймер:

это автономный счетчик, который отсчитывает от 0 до значения, указанного в поле Period9 в структуре инициализации TIM_Base_InitTypeDef, которое может принимать максимальное значение 0xFFFF (0xFFFF FFFF для 32-разрядных таймеров);

частота отчета зависит от частоты шины, к которой подключен таймер, и ее можно разделить на 65536, установив регистр Prescaler в структуре инициализации;

9 Period используется для заполнения регистра автоперезагрузки (Auto-reload register, ARR) таймера. Я не знаю, почему инженеры ST решили назвать его таким образом, поскольку ARR – это имя регистра, используемое во всех технических описаниях от ST. Это может привести к путанице, особенно когда вы новичок в CubeHAL, но, к сожалению, мы ничего не можем поделать с этим.

Таймеры

297

когда таймер достигает значения Period, он переполняется и устанавливается флаг события обновления (Update Event, UEV)10; таймер автоматически перезапускает отсчет от начального значения (которое всегда равно нулю для базовых таймеров)11.

Регистры Period и Prescaler определяют частоту таймера, то есть сколько времени проходит до переполнения (или, если вы предпочитаете так, то как часто генерируется событие обновления), в соответствии с этой простой формулой:

Событие обновления =

Тактовый сигнал таймера

(Prescaler +1)(Period +1)

 

[1]

Например, предположим, что таймер подключен к шине APB1 микроконтроллера STM32F030 с сигналом HCLK, установленным на 48 МГц, значением Prescaler, равным 47999, и значением Period, равным 499. Получим таймер, который будет переполняться каждые:

Событие обновления =

48000000

 

= 2 Гц =

1

с = 0,5 с

(47999

+1)(499

+1)

2

 

 

 

Следующий код, разработанный для работы на Nucleo-F030R8, показывает полный пример использования TIM612. Пример – не более чем классический мигающий светодиод, но на этот раз мы используем базовый таймер для вычисления задержек.

Имя файла: src/main-ex1.c

7 TIM_HandleTypeDef htim6;

8

9int main(void) {

10HAL_Init();

12 Nucleo_BSP_Init();

13

14htim6.Instance = TIM6;

15htim6.Init.Prescaler = 47999; // 48 МГц / 48000 = 1000 Гц

16htim6.Init.Period = 499; // 1000 Гц / 500 = 2 Гц = 0.5 с

18__HAL_RCC_TIM6_CLK_ENABLE(); // Разрешение тактирования периферийного устройства TIM6

20HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0); // Разрешение IRQ периферийного устройства TIM6

21 HAL_NVIC_EnableIRQ(TIM6_IRQn);

22

23HAL_TIM_Base_Init(&htim6); // Конфигурирование таймера

24HAL_TIM_Base_Start_IT(&htim6); // Запуск таймера

10Флаг события обновления (UEV) защелкивается тактовым сигналом от предделителя и автоматически сбрасывается на следующем фронте тактового сигнала. Не путайте UEV с флагом прерывания обновления (Update Interrupt Flag, UIF), который необходимо сбрасывать вручную, как и любой другой IRQ. UIF устанавливается только при разрешении соответствующего прерывания. Как мы увидим в Главе 19, событие UEV, как и все флаги событий, установленные для других периферийных устройств, позволяет пробудить микроконтроллер, когда он перешел в режим пониженного энергопотребления, используя инструкцию WFE.

11Это является важным отличием от других архитектур микроконтроллеров (особенно 8-разрядных), где таймеры необходимо «перестроить» вручную, прежде чем они смогут повторно начать отсчет.

12Владельцы плат Nucleo, оснащенных микроконтроллерами STM32 F411, F401 и F103, найдут несколько

иной пример с использованием таймера общего назначения. Однако концепции остаются прежними.

Таймеры

298

25

26while (1);

27}

28

29void TIM6_IRQHandler(void) {

30// Передача управления HAL, который обрабатывает IRQ

31HAL_TIM_IRQHandler(&htim6);

32}

33

34void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

35// Этот обратный вызов автоматически вызывается HAL при возникновении события UEV

36if(htim->Instance == TIM6)

37HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

38}

Строки [14:16] конфигурируют TIM6, используя значения Prescaler и Period, вычисленные ранее. Затем таймер включается с помощью макроса в строке 18. То же самое относится к его IRQ. Затем таймер конфигурируется в строке 23 и запускается в режиме прерываний при помощи функции HAL_TIM_Base_Start_IT()13. Остальная часть кода действительно похожа на ту, которую видели до сих пор.

ISR TIM6_IRQHandler() срабатывает, когда таймер переполняется, а затем вызывается HAL_TIM_IRQHandler(). HAL автоматически обработает для нас все необходимые операции для правильного управления событием обновления и вызовет процедуру HAL_TIM_PeriodElapsedCallback(), чтобы сообщить нам, что таймер был переполнен.

Производительность процедуры HAL_TIM_IRQHandler()

Для таймеров, работающих очень быстро, HAL_TIM_IRQHandler() имеет незначительные накладные расходы. Эта функция предназначена для проверки до девяти различных флагов состояния прерывания, для выполнения которой требуется несколько ассемблерных инструкций ARM. Если вам нужно обработать прерывания за меньшее время, вероятно, лучше всего обработать IRQ самостоятельно. Еще раз, HAL разработан, чтобы абстрагировать многие детали от пользователя, но он вводит ограничения на производительность, которые должен знать каждый разработчик встраиваемых систем.

Как выбрать значения для полей Prescaler и Period?

Прежде всего, обратите внимание, что не все комбинации значений Prescaler и Period приводят к круглому делению тактовой частоты таймера. Например, для таймера, работающего на частоте 48 МГц, Period, равный 65535, понижает частоту таймера до 732 421 Гц. Для деления основной частоты таймера автор использует округляющий делитель для значения Prescaler (например, 47999 для таймера 48 МГц – помните, что, согласно уравнению [1], частота вычисляется добавлением 1 к обоим значения Prescaler и Period), а затем играет со значением Period для достижения желаемой частоты. MikroElektronica предоставляет удобный инструмент14 для автоматического вычисления этих значений с

13Достаточно распространенная ошибка новичков заключается в том, что они забывают запустить таймер с помощью одной из функций HAL_TIM_xxx_Start(), предоставляемой CubeHAL.

14http://www.mikroe.com/timer-calculator/