Stm32 таймеры hal. Что такое таймер? Разберёмся с прерываниями

В любом современном контроллере есть таймеры . В этой статье речь пойдёт о простых (базовых) таймерах stm32f4 discovery .
Это обычные таймеры. Они 16 битные с автоматической перезагрузкой. Кроме того имеется 16 битный программируемый делитель частоты . Есть возможность генерирования прерывания по переполнению счётчика и/или запросу DMA.

Приступим. Как и раньше я пользуюсь Eclipse + st-util в ubuntu linux

Первым делом подключаем заголовки:

#include #include #include #include #include

Ничего нового в этом нет. Если не ясно откуда они берутся либо читайте предыдущие статьи, либо открывайте файл и читайте.

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

Const uint16_t LEDS = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; // все диоды const uint16_t LED = {GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15}; // массив с диодами

Скорее всего уже знакомая вам функция-инициализации периферии (то есть диодов) :

Void init_leds(){ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // разрешаем тактирование GPIO_InitTypeDef gpio; // структура GPIO_StructInit(&gpio); // заполняем стандартными значениями gpio.GPIO_OType = GPIO_OType_PP; // подтяжка резисторами gpio.GPIO_Mode = GPIO_Mode_OUT; // работаем как выход gpio.GPIO_Pin = LEDS; // все пины диодов GPIO_Init(GPIOD, &gpio);

Функция инициализатор таймера:

Void init_timer(){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // включаем тактирование таймера /* Другие параметры структуры TIM_TimeBaseInitTypeDef * не имеют смысла для базовых таймеров. */ TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBaseStructInit(&base_timer); /* Делитель учитывается как TIM_Prescaler + 1, поэтому отнимаем 1 */ base_timer.TIM_Prescaler = 24000 - 1; // делитель 24000 base_timer.TIM_Period = 1000; //период 1000 импульсов TIM_TimeBaseInit(TIM6, &base_timer); /* Разрешаем прерывание по обновлению (в данном случае - * по переполнению) счётчика таймера TIM6. */ TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); TIM_Cmd(TIM6, ENABLE); // Включаем таймер /* Разрешаем обработку прерывания по переполнению счётчика * таймера TIM6. это же прерывание * отвечает и за опустошение ЦАП. */ NVIC_EnableIRQ(TIM6_DAC_IRQn); }

Я прокомментировал код, так-что думаю всё ясно.
Ключевыми параметрами тут являются делитель (TIM_Prescaler) и период (TIM_Period) таймера. Это параметры, которые собственно и настраивают работу таймера.

К примеру, если у вас на STM32F4 DISCOVERY тактовая частота установлена в 48МГц, то на таймерах общего назначения частота 24МГц. Если установить делитель (TIM_Prescaler) в 24000 (частота счёта = 24МГц/24000 = 1КГц), а период (TIM_Period) в 1000, то таймер будет отсчитывать интервал в 1с.

Обратите внимание, что всё зависит от тактовой частоты. Её вы должны выяснить точно.

Так же отмечу, что на высоких частотах переключение светодиода по прерыванию существенно искажает значение частоты. При значении в 1МГц на выходе я получал примерно 250КГц, т.е. разница не приемлима. Такой результат видимо получается из-за затрат времени на выполнение прерывания.

Глобальная переменная - флаг горящего диода:

U16 flag = 0;

Обработчик прерывания, которое генерирует таймер. Т.к. этоже прерывание генерируется и при работе ЦАП, сначала проверяем, что сработало оно именно от таймера:

Void TIM6_DAC_IRQHandler(){ /* Так как этот обработчик вызывается и для ЦАП, нужно проверять, * произошло ли прерывание по переполнению счётчика таймера TIM6. */ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { flag++; if (flag>3) flag = 0; /* Очищаем бит обрабатываемого прерывания */ TIM_ClearITPendingBit(TIM6, TIM_IT_Update); GPIO_Write(GPIOD, LED); // зажигаем слудующий диод } }

Функция main:

Int main(){ init_leds(); init_timer(); do { } while(1); }

Цикл оставляем пустым. Счётчик выполняет свою работу асинхронно, а прерывание на то и прерывание, чтобы не зависеть от выполняемой в данный момент операции.

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

Для чего это надо?
С помощью этого режима можно измерить длительность импульса или период сигнала.

Режим захвата у STM32 обладает некоторыми особенностями:

  • возможность выбрать какой фронт будет активным
  • возможность изменить частоту входного сигнала с помощью предделителя (1,2,4,8)
  • каждый канал захвата оснащён встроенным входным фильтром
  • источником сигнала захвата может служить другой таймер
  • для каждого канала предусмотрено по два флага, первый выставляется если произошёл захват, второй если произошёл захват при установленном первом флаге

Для настройки режима захвата предназначены регистры CCMR1 (для 1 и 2 канала) и CCMR2 (для 3 и 4), а также регистры CCER , DIER .

Давайте рассмотрим подробнее битовые поля регистра CCMR2 , отвечающие за настройку 4 канала таймера, именно его мы будем настраивать в примере. Ещё хотелось бы отметить, что в этом же регистре находятся битовые поля, которые используются при настройке таймера в режиме сравнения.

CC4S - определяет направление работы четвёртого канала(вход или выход). При настройке канала как вход сопоставляет ему сигнал захвата

  • 00 - канал работает как выход
  • 01 - канал работает как вход, сигнал захвата - TI4
  • 10 - канал работает как вход, сигнал захвата - TI3
  • 11 - канал работает как вход, сигнал захвата - TRC
IC4PSC – определяют коэффициент деления, для сигнала захвата
  • 00 - делитель не используется, сигнал захвата IC1PS формируется по каждому событию
  • 01 - сигнал захвата формируется по каждому второму событию
  • 10 - сигнал захвата формируется по каждому четвёртому событию
  • 11 - сигнал захвата формируется по каждому восьмому событию
IC4F - предназначен для настройки входного фильтра, кроме количества выборок, в течение которых микроконтроллер не будет реагировать на входные сигналы, также можно настроить частоту выборок. По сути мы настраиваем время задержки с момента прихода фронта до "подтверждающей" выборки.

Теперь давайте рассмотрим регистр CCER .

CC4E - включает/выключает режим захвата.
CC4P - определяет фронт по которому будет производиться захват, 0 - передний, 1 - задний.

И регистр DIER .

CC4DE - разрешает формировать запрос к DMA.
CC4IE - разрешает прерывание по захвату.

После того как произошёл захват формируется событие захвата, которое устанавливает соответствующий флаг. Это может привести к генерации прерывания и запросу DMA , если они разрешены в регистре DIER . Кроме того, событие захвата может быть сформировано программно, установкой битового поля в регистре генерации событий EGR :

Битовые поля CC1G, CC2G, CC3G и CC4G позволяют генерировать событие в соответствующем канале захвата/сравнения.

Кстати, CCR1, CCR2, CCR3 и CCR4 - регистры захвата, в которых сохраняется значение таймера по сигналу захвата.

Для того чтобы контролировать формирование сигнала захвата, в регистре SR для каждого канала выделено по два флага.

CC4IF - устанавливается когда формируется сигнал захвата, сбрасываются эти флаги программно или чтением соответствующего регистра захвата/сравнения.
CC4OF - устанавливается если флаг CC4IF не был очищен, а пришёл очередной сигнал захвата. Этот флаг очищается программно записью нуля.

Теперь давайте применим полученные знания на практике, с генератора сигналов на вход TIM5_CH4 подадим синусоиду с частотой 50Гц и попробуем измерить её период. Для того чтобы ускорить процесс предлагаю использовать DMA. Какой вывод МК соответствует 4 каналу TIM5 можно найти в даташите на МК в разделе Pinouts and pin description .

Для DMA необходим адрес регистра CCR4 , вот как его найти. Открываем RM0008 и в таблице Register boundary addresses находим начальный адрес TIM5.


смещение для регистра CCR4 можно найти в том же документе в разделе register map .

#include "stm32f10x.h" #define TIM5_CCR4_Address ((u32)0x40000C00+0x40) #define DMA_BUFF_SIZE 2 uint16_t buff;//Буфер uint16_t volatile T; void DMA2_Channel1_IRQHandler (void) { T = (buff > buff) ? (buff - buff) : (65535+ buff - buff); DMA2->IFCR |= DMA_IFCR_CGIF1; } void Init_DMA(void) { RCC->AHBENR |= RCC_AHBENR_DMA2EN; //Разрешаем тактирование первого DMA модуля DMA2_Channel1->CPAR = TIM5_CCR4_Address; //Указываем адрес периферии - регистр результата преобразования АЦП для регулярных каналов DMA2_Channel1->CMAR = (uint32_t)buff; //Задаем адрес памяти - базовый адрес массива в RAM DMA2_Channel1->CCR &= ~DMA_CCR1_DIR; //Указываем направление передачи данных, из периферии в память DMA2_Channel1->CNDTR = DMA_BUFF_SIZE; //Количество пересылаемых значений DMA2_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируем после каждой пересылки DMA2_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируем после каждой пересылки. DMA2_Channel1->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии - 16 бит DMA2_Channel1->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти - 16 бит DMA2_Channel1->CCR |= DMA_CCR1_PL; //Приоритет - очень высокий DMA2_Channel1->CCR |= DMA_CCR1_CIRC; //Разрешаем работу DMA в циклическом режиме DMA2_Channel1->CCR |= DMA_CCR1_TCIE;//Разрешаем прерывание по окончанию передачи DMA2_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу 1-го канала DMA } int main(void) { Init_DMA(); //включаем тактирование порта А, альтернативных функций и таймера RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; TIM5->PSC = 56000-1;//новая частота 1Khz TIM5->CCMR2 |= TIM_CCMR2_CC4S_0;//выбираем TI4 для TIM5_CH4 TIM5->CCMR2 &= ~(TIM_CCMR2_IC4F | TIM_CCMR2_IC4PSC);//не фильтруем и делитель не используем TIM5->CCER &= ~TIM_CCER_CC4P;//выбираем захват по переднему фронту TIM5->CCER |= TIM_CCER_CC4E;//включаем режим захвата для 4-го канала TIM5->DIER |= TIM_DIER_CC4DE;//разрешаем формировать запрос к DMA //TIM5->DIER |= TIM_DIER_CC4IE; //разрешаем прерывание по захвату TIM5->CR1 |= TIM_CR1_CEN; //включаем счётчик //NVIC->ISER |= NVIC_ISER_SETENA_18; //TIM5 Interrupt NVIC->ISER |= NVIC_ISER_SETENA_24; //DMA Interrupt while(1) { } }

Таймеры в STM32, как в принципе и вся периферия, являются очень навороченными. От обилия разных функций, которые могут выполнять таймеры может даже закружиться голова. Хотя, казалось бы, таймер он на то и таймер, чтобы просто считать. Но на деле все гораздо круче)

Мало того, что таймеры обладают такими широкими возможностями, так их еще несколько у каждого контроллера. И даже не два и не три, а больше! В общем, нахваливать все это можно бесконечно. Давайте уже разбираться, что и как работает. Итак, микроконтроллер STM32F103CB имеет:

  • 3 таймера общего назначения (TIM2, TIM3, TIM4)
  • 1 более продвинутый таймер с расширенными возможностями (TIM1)
  • 2 WDT (WatchDog Timer)
  • 1 SysTick Timer

Собственно таймеры общего назначения и таймер TIM1 не сильно отличаются друг от друга, так что ограничимся рассмотрением какого-нибудь одного таймера. К слову я остановил свой выбор на TIM4. Без особой причины, просто так захотелось =). Таймеры имеют 4 независимых канала, которые могут использоваться для:

  • Захвата сигнала
  • Сравнения
  • Генерации ШИМ
  • Генерации одиночного импульса
  • Переполнение
  • Захват сигнала
  • Сравнение
  • Событие-триггер

При наступлении любого из этих событий таймеры могут генерировать запрос к DMA (DMA – прямой доступ к памяти, уже скоро мы будем разбираться и с ним =)). Теперь немного подробнее о каждом из режимов работы таймеров.

Режим захвата сигнала. Очень удобно при работе таймера в этом режиме измерять период следования импульсов. Смотрите сами: приходит импульс, таймер кладет свое текущее значение счетчика в регистр TIM_CCR. По-быстрому забираем это значение и прячем в какую-нибудь переменную. Сидим, ждем следующий импульс. Опа! Импульс пришел, таймер снова сует значение счетчика в TIM_CCR , и нам остается только вычесть из этого значения то, которое мы предварительно сохранили. Это, наверное, самое простое использование этого режима таймера, но очень полезное. Отлавливать можно как передний фронт импульса, так и задний, так что возможности довольно велики.

Режим сравнения. Тут просто подключаем какой-нибудь канал таймера к соответствующему выводу, и как только таймер досчитает до определенного значения (оно в TIM_CCR ) состояние вывода изменится в зависимости от настройки режима (либо выставится в единицу, либо в ноль, либо изменится на противоположное).

Режим генерации ШИМ. Ну тут все скрыто в названии) В этом режиме таймер генерирует ШИМ! Наверно нет смысла что-то писать тут еще сейчас. Скоро будет примерчик как раз на ШИМ, там и поковыряем поподробнее.

Режим Dead-Time. Суть режима в том, что между сигналами на основном и комплементарном выводах таймера появляется определенная задержка. В интернете есть довольно много информации о том, где это можно и нужно применять.

Ну вот в принципе ооочень кратко об основных режимах работы таймера. Если будут вопросы про другие режимы, более специфические, пишите в Комментарии 😉

Надо бы потихоньку написать программку для работы с таймерами. Но сначала посмотрим, что есть в библиотеке Standard Peripheral Library. Итак, за таймеры несут ответственность файлы – stm32f10x_tim.h и stm32f10x_tim.c . Открываем первый и видим, что структура файла повторяет структуру файла для работы с GPIO, который мы рассматривали в предыдущей статье. Здесь описаны структуры и поля структур, которые нужны для конфигурирования таймеров. Правда здесь уже не одна, а несколько структур (режимов, а соответственно и настроек то у таймеров побольше, чем у портов ввода-вывода). Все поля структур снабжены комментариями, так что не должно тут возникать никаких проблем. Ну вот, например:

uint16_t TIM_OCMode; // Specifies the TIM mode.

Здесь будем задавать режим работы таймера. А вот еще:

uint16_t TIM_Channel; // Specifies the TIM channel.

Здесь выбираем канал таймера, ничего неожиданного) В общем все довольно прозрачно, если что спрашивайте =) С первым файлом понятно. А в файле stm32f10x_tim.c – готовые функции для работы с таймерами. Тоже все в целом ясно. Мы уже использовали библиотеку для работы с GPIO, теперь вот работаем с таймерами, и очевидно, что для разной периферии все очень похоже. Так что давайте создавать проект и писать программу.

Итак, запиливаем новый проект, добавляем все необходимые файлы:

Пишем код:

Необходимо отметить, что в поле TIM_Prescaler нужно записывать значение, на единицу меньшее, чем то, которое мы хотим получить.

/****************************timers.c*******************************/ #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" //При таком предделителе у меня получается один тик таймера на 10 мкс #define TIMER_PRESCALER 720 /*******************************************************************/ //Переменная для хранения предыдущего состояния вывода PB0 uint16_t previousState; GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; /*******************************************************************/ void initAll() { //Включаем тактирование порта GPIOB и таймера TIM4 //Таймер 4 у нас висит на шине APB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) ; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE) ; //Тут настраиваем порт PB0 на выход //Подробнее об этом в статье про GPIO GPIO_StructInit(& port) ; port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, & port) ; //А тут настройка таймера //Заполняем поля структуры дефолтными значениями TIM_TimeBaseStructInit(& timer) ; //Выставляем предделитель timer.TIM_Prescaler = TIMER_PRESCALER - 1 ; //Тут значение, досчитав до которого таймер сгенерирует прерывание //Кстати это значение мы будем менять в самом прерывании timer.TIM_Period = 50 ; //Инициализируем TIM4 нашими значениями TIM_TimeBaseInit(TIM4, & timer) ; } /*******************************************************************/ int main() { __enable_irq() ; initAll() ; //Настраиваем таймер для генерации прерывания по обновлению (переполнению) TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE) ; //Запускаем таймер TIM_Cmd(TIM4, ENABLE) ; //Разрешаем соответствующее прерывание NVIC_EnableIRQ(TIM4_IRQn) ; while (1 ) { //Бесконечно тупим) Вся полезная работа – в прерывании __NOP() ; } } /*******************************************************************/ //Если на выходе был 0.. timer.TIM_Period = 50 ; TIM_TimeBaseInit(TIM4, & timer) ; //Очищаем бит прерывания TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } else { //Выставляем ноль на выходе timer.TIM_Period = 250 ; TIM_TimeBaseInit(TIM4, & timer) ; TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } }

В этой программе мы смотрим, что было на выходе до момента генерации прерывания – если ноль, выставляем единицу на 0.5 мс. Если была единица – ставим ноль на 2.5 мс. Компилируем и запускаем отладку =)

Небольшое, но очень важное отступление… Наш пример, конечно, будет работать и для теста он вполне сгодится, но все-таки в “боевых” программах нужно следить за оптимальностью кода как с точки зрения его объема, так и с точки зрения производительности и расхода памяти. В данном случае нет никакого смысла использовать структуру timer, а также вызывать функцию TIM_TimeBaseInit() каждый раз при смене периода. Правильнее менять всего лишь одно значение в одном регистре, а именно в регистре TIMx->ARR (где х – это номер таймера). В данном примере код трансформируется следующим образом:

/*******************************************************************/ void TIM4_IRQHandler() { //Если на выходе был 0.. if (previousState == 0 ) { //Выставляем единицу на выходе previousState = 1 ; GPIO_SetBits(GPIOB, GPIO_Pin_0) ; //Период 50 тиков таймера, то есть 0.5 мс TIM4-> ARR = 50 ; } else { //Выставляем ноль на выходе previousState = 0 ; GPIO_ResetBits(GPIOB, GPIO_Pin_0) ; //А период теперь будет 250 тиков – 2.5 мс TIM4-> ARR = 250 ; } TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } /****************************End of file****************************/

Итак, продолжаем, на пути у нас очередные грабли) А именно ошибка:

..\..\..\SPL\src\stm32f10x_tim.c(2870): error: #20: identifier “TIM_CCER_CC4NP” is undefined

Не так страшно как может показаться, идем в файл stm32f10x.h, находим строки

Вот теперь все собирается, можно отлаживать. Включаем логический анализатор. В командной строке пишем: la portb&0x01 и наблюдаем на выходе:

Что хотели, то и получили) Другими словами все работает правильно. В следующей статье поковыряем режим генерации ШИМ, оставайтесь на связи 😉

Не пропустите хорошую статью про таймеры в целом – .

Добрый день. Сегодня набросаю первую статейку по таймерам в STM32. Вообще таймеры в STM32 настолько круты, что даже Шварцнегер нервно курит по крутости))) И изучать их придётся не в одной, и не в двух и не в трёх статьях. Но для начала не будем забивать себе сильно головы, а просто изучим первые простые таймеры и поработаем с ними.

В STM32 вообще существует три вида таймеров
1) базовые (basic timers)
2)общего назначения (general-purpose timers)
3)продвинутые (advanced-control timers)

Продвинутые таймеры самые крутые и в себе сочитают возможности двух предыдущих групп, плюс к этому ещё множество дополнительных функций типа работа с трёхфазными моторами и т.д. и т.п. До них нам ещё далеко, поэтому в данной части мы будем рассматривать работу с базовыми (basic timers).
Для начала давайте рассмотрим, какие есть таймеры на нашем процессоре STM32F407VG (вы смотрите про свои процессоры с которыми работаете)). В моём процессоре 14 таймеров — 12 — 16ти битных и 2 32 битных

Как мы видим на картинках к шине АРВ1 подключены таймеры TIM2, TIM3, TIM4, TIM5, TIM6, TIM7, TIM12
А к шине АРВ2 — TIM1, TIM8, TIM9, TIM10, TIM11
Теперь давайте рассмотрим картинку настройки нашего тактирования в программе CubeMX. Систему тактирования я ещё отдельно опишу, так как без неё никуда, но просто пока покажу как можно затактировать наши таймеры используя внутренний источник тактирования HSI.
Вот наша стандартная настройка тактирования без всяких перемножителей частот и т.д. Её мы и будем использовать.

А вот вариант ускорения работы)) Но советую шаловливыми ручёнками туда сильно не лазить, а то может уложить процессор на лопатки)) Это всё мы потом изучим и рассмотрим.

Итак, открываем Reference Manual на F4 серию микроконтроллеров, и начинаем курить мануал. ДА, в STM32 не всё так просто, поэтому товарищи учите английский, и читайте мануалы, потому что без этого будете долго искать что к чему. Я раньше как то очень тяжко к чтению документации относился (видать потому что задачи были простыми и мне хватало обычных примеров из интернета). Ну а теперь читаем… читаем…читаем…
Продолжим…
Итак таймеры 6 и 7 являются базовыми таймерами. Сидят они на шине АРВ1 как мы видим на картинке из reference manual.

Базовые таймеры 6 и 7 — 16ти битные, имеют настраиваемый предделитель от 0 до 65535. Для этих таймеров есть вот такие регистры доступные для чтения\записи.
Counter Register (TIMx_CNT) — счётчик
Prescaler Register (TIMx_PSC) — предделитель
Auto-Reload Register (TIMx_ARR) — регистр перезагрузки

Не будем сильно углубляться в подробности работы, так как там страниц 10 описания доступных нам регистров и т.д, нам хватит трёх — написанных выше
Итак, что это за регистры и для чего они нам нужны. Да вот для чего. Решили мы тут срочно помигать светодиодом, удивить товарищей AVR-щиков например, и говорим — а давай кто быстрее настроит мигание одним светодиодом с периодом пол секунды, а вторым с периодом в секунду тот и выиграл. (кстати можно проделать подобный эксперимент))))
Для того чтобы это нам реализовать нужно всего 5 шагов — 1
1) Запустить CubeMX и создать проект под наш контроллер.
2)в CubeMX выставить работу таймеров
3) сгенерировать проект и открыть его в Keil uVision
4)проинициализировать таймеры (по одной строчке на таймер)
5)прописать в прерывании каждого таймера код постоянного изменения состояния ножки к которой подключен светодиод.
Итак, давайте это рассмотрим более подробно. Первым делом запускам нашу программу CubeMX
и настраиваем наши 2 вывода PD12 и PD13 на вывод (ножки куда подключены светодиоды). Устанавливаем для них режим GPIO_Output, и режим Output Push_Pull.
Далее слева активируем наши базовые таймеры 6 и 7.

Теперь переходим в вкладку конфигурации. Как мы помним, мы не стали ничего менять в настройках частот для нашего процессора, поэтому у нас все шины тактируюся частотой -16МГц. Теперь исходя из этого, и исходя из того что нам нужно получить, давайте настроим наши значения предделителей и регистра автоперезагрузки.

Как мы помним, нам нужно чтобы один светодиод мигал с частотой 1Гц (период 1000мсек), а второй с частотой 2Гц (период 500 мсек) . Как нам это получить — да очень просто. Так как предделитель на СТМ32 можно ставить любой, то мы просто вычислим его значение
Итак частота у нас 16 000 000 тиков в секундку, а нужно 1000 тиков в секунду. Значит 16 000 000 \ 1 000 = 16 000. Это число минус 1 и вписываем в значение предделителя. То есть число у нас получается 15999.
Теперь наш таймер тикает с частотой 1000 раз в секунду. Далее, мы должны указать когда же нам нужно прерывание по переполнению. Для этого мы записываем нужное нам число в Counter Period (autoreload register).
То есть нам нужно получить одно прерывание в секунду, а как мы помним наш таймер тикает 1 раз в милисекунду. В одной секнуде — 1000 мсек — значит это значение и вписываем в регистр автоперезагрузки.
Для того, чтобы получить прерывание раз в пол секунды — записываем соответсвенно — 500.

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

HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);

Именно в нём и находятся наши обработчки прерывания для наших таймеров
Вот обработчик прерывания для таймера 7

void TIM7_IRQHandler(void)
{
/* USER CODE BEGIN TIM7_IRQn 0 */

/* USER CODE END TIM7_IRQn 0 */
HAL_TIM_IRQHandler(&htim7);
/* USER CODE BEGIN TIM7_IRQn 1 */

/* USER CODE END TIM7_IRQn 1 */
}

Вписываем в обработчик прерывания то что мы хотим делать — а мы хотим в каждом прерывании менять состояние наших ножек к которым подключены свтеодиоды.
Используем вот такую конструкцию — HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13) ;

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

Мы уже рассматривали таймер SysTick, который является частью ядра Cortex. Однако на этом всё не заканчивается. Таймеров в stm32 много, и они бывают разные. В зависимости от цели вам придется выбрать тот или иной таймер:

  • SysTick;
  • таймеры общего назначения (англ. general purpose timer) - TIM2, TIM3, TIM4, TIM15, TIM16, TIM17;
  • продвинутый таймер (англ. advanced timer) - TIM1;
  • сторожевой таймер (англ. watchdog timer).

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

Таймеры бывают разной битности: например, SysTick - 24-битный, а все таймеры, что имеются у нас в камне - 16-битные (т. е. могут считать до 2 16 = 65535), кроме WatchDog. Кроме того, каждый таймер имеет некоторое количество каналов, то есть, по сути, он может работать за двоих, троих и т. д. Эти таймеры умеют работать с инкрементными энкодерами, датчиками Холла, могут генерировать ШИМ (широтно-импульсная модуляция, англ. Pulse Width Modulation - о которой мы поговорим позже) и многое другое. Кроме того, они могут генерирувать прерывания или совершать запрос к другим модулям (например, к DMA - Direct Memory Access) по разным событиям:

  • переполнение (англ. overflow);
  • захват сигнала (англ. input capture);
  • сравнение (англ. output compere);
  • событие-триггер (англ. event trigger).

Если с переполнением таймера (точнее, достижением «0») нам всё понятно - мы рассматривали SysTick - то с другими возможными режимами работы мы еще не знакомы. Давайте рассмотрим их подробнее.

Захват сигнала

Этот режим хорошо подходит, чтобы измерять период следования импульсов (или их количество, скажем, за секунду). Когда на выход МК приходит импульс, таймер генерирует прерывание, с которого мы можем снять текущее значение счетчика (из регистра TIM_CCRx , где x - номер канала) и сохранить во внешнюю переменную. Затем придет следующий импульс, и мы простой операцией вычитания получим «время» между двумя импульсами. Отлавливать можно как передний, так и задний фронт импульса, или даже сразу оба. Зачем это нужно? Предположим, у вас есть магнит, который вы приклеили к диску на колесе, а датчик Холла - к вилке велосипеда. Тогда, вращая колесо, вы будете получать импульс каждый раз, когда магнит на колесе будет в той же плоскости, что и датчик Холла. Зная расстояние которое пролетает магнит за оборот и время можно вычислить скорость движения.

Также существует режим захвата ШИМ, однако это скорее особый способ настройки таймера, нежели отдельный режим работы: один канал ловит передние фронты, а второй - задние фронты. Тогда первый канал детектирует период, а второй - заполнение.

Режим сравнения

В таком режиме выбранный канал таймера подключается к соответствующему выводу, и как только таймер достигнет определенного значения, состояние вывода изменится в зависимости от настройки режима (это может быть «1» или «0», или состояние на выходе просто инвертируется).

Режим генерации ШИМ

Как понятно из названия, таймер в таком режиме генерирует широтно-импульсную модуляцию. Подробнее о таком режиме, а также о том, где его можно / стоит применять, мы поговорим в следующем занятии после рассмотрения захвата сигнала.

При помощи продвинутого таймера можно формировать трехфазный ШИМ, который очень пригодится для управления трехфазным двигателем.

Режим Dead-time

Эта функция есть у некоторых таймеров; она нужна для создания задержек на выходах, которые требуются, например, для исключения сквозных токов при управлении силовыми ключами.

В курсе мы будем использовать только «захват сигнала» и «генерацию ШИМ».