STM32Cube. Таймер и прерывания.

Всем доброго времени суток!

Сегодня мы снова будем исследовать возможности STM32Cube. Вот предыдущие статьи мини-цикла )

Создание проекта в STM32Cube

Настройка GPIO в STM32Cube

Таймер в микроконтроллерах

Без лишних слов, перейдем сразу к делу 😉

В предыдущей статье мы разобрались как настраивать и использовать порты ввода-вывода в STM32Cube, а сегодня рассмотрим конфигурацию таймера и прерываний. Пусть будет такая задача — задействуем таймер таким образом, чтобы он генерировал прерывания, допустим, каждые 500 мс, и в прерывании помигаем диодиком для наглядной демонстрации работы. В принципе, ничего сложного, так что приступаем!

Выполняем привычную нам последовательность действий — создаем и настраиваем новый проект в STM32Cube. И для начала нам нужно активировать таймер. Пусть в нашем примере будет использоваться TIM3:

Настройка таймера

Для активации таймера в разделе TIM3 нам нужно в поле Clock Source выбрать источник тактирования. Кроме того, я тут настроил вывод PD12 на работу в режиме выхода, чтобы управлять светодиодом на плате для демонстрации работы программы. Вроде бы все понятно, но с таймером не все так просто. Его нужно как минимум еще и настроить ) Для этого переходим на вкладку Configuration:

Настройки таймера

Дважды тыкаем на TIM3 и открывается новое окно, в котором мы уже можем более гибко настроить параметры таймера:

STM32Cube

Таймер сидит на шине APB1, частота которой составляет 16 МГц (это настройка по умолчанию — как изменить тактовые частоты при помощи Cube я расскажу  в следующей статье =) ). Выбираем делитель частоты равным 16000. В итоге получаем (16 МГц / 16000) = 1000 Гц. То есть один тик таймера будет соответствовать 1 мс. Установив период 499 (это значение в Cube надо ставить на единицу меньше, чем целевое значение, аналогично и значение делителя) получим прерывания по переполнению таймера каждые 500 мс — то, что нам и надо. В этом же окне во вкладке NVIC Settings нужно, собственно, включить нужное нам прерывание, и на этом настройки заканчиваются. Генерируем код и получаем готовый проект для IAR.

Теперь осталось совсем немного — запустить наш таймер (STM32Cube занимается только инициализацией, все активные действия мы должны совершать сами). Кроме того, в прерывании по переполнению таймера мы будем менять состояние светодиода. Кстати, сам обработчик прерывания Cube создал за нас и найти его совсем несложно — для всех прерываний генерируется отдельный файл stm32f4xx_it.c.

Давайте все это поэтапно осуществим. Для начала идем в main() и запускаем таймер:

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_TIM3_Init();
 
  /* USER CODE BEGIN 2 */
 
  HAL_TIM_Base_Start_IT(&htim3);
 
  /* USER CODE END 2 */
 
  /* USER CODE BEGIN 3 */
  /* Infinite loop */
  while (1)
  {
 
  }
  /* USER CODE END 3 */
 
}

Поскольку мы будем использовать прерывания, то нам нужна функция HAL_TIM_Base_Start_IT().

Переходим к следующему этапу — а именно к изменению состояния светодиода в прерывании. Для этого даже есть специальная функция HAL_GPIO_TogglePin():

void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
 
  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */
 
  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
 
  /* USER CODE END TIM3_IRQn 1 */
}

Теперь мы можем скомпилировать код, собрать проект и прошить микроконтроллер. Сразу же увидим мигающий диод, причем мигает он каждые 500 мс 😉 Задача успешно реализована!

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

Понравилась статья? Поделись с друзьями!

STM32Cube. Таймер и прерывания.: 47 комментариев
  1. Спасибо за материал! Ответьте, пожалуйста, на вопрос! Можно ли при работе с АЦП устанавливать частоту дискретизации, т.е. количество выборок в секунду? Или нужно использовать прерывание по таймеру и делать однократное преобразование?

    • Ну АЦП у тебя будет обрабатывать сигнал с высокой частотой (1 мвыб/ с, например), а уж сколько раз считывать значение — твое дело. Можно спокойно включить АЦП в непрерывном режиме и считывать значение по таймеру, допустим, 5 раз в секунду.

      • Спасибо за ответ! А в каком регистре задается значение, указанное вами 1 мвыборка/с? Наверное, слишком бегло я просмотрел даташит и не увидел. Могли вы привести просто пару строчек кода с примером! Спасибо!

  2. STM32Cube вещь интересная.
    Но! Сделав простой пример, поразился размером прошивки даже с оптимизацией. На SPL в 3 раза меньше получается, уж не говоря про проект на чисто регистрах.
    И мало хелпа, я так и не смог запустить ADC.

    • С размером кода — это, конечно, минус HAL_Driver, но в то же время это вполне ожидаемый минус, то есть, в принципе, ничего неожиданного в этом нет =)

  3. В Версии иара 7.3 строка HAL_TIM_Base_Start_IT(&htim3); работает в таком виде только: HAL_TIM_Base_Start_IT(htim3); , на описанный вид ругается. И еще, чтобы сделать, например 10 мс период, необходимо например при 1 кГц предделителя ставить 9 (а не 10) в число периода.

  4. *извиняюсь: В Версии иара 7.3 строка HAL_TIM_Base_Start_IT(&htim3); работает в таком виде только: HAL_TIM_Base_Start_IT(&htim3);

  5. И насчет точности времени: приходит на APB1 Timer Clocks 2 МГц, выставляю такие параметры (используя таймер 12)
    htim12.Instance = TIM12;
    htim12.Init.Prescaler = 2000;
    htim12.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim12.Init.Period = 3;
    htim12.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim12);

    получаю на осцилле период в 8 мс (частотой 125 Гц), то есть таймер меняется через каждые 4 мс. То есть получается, что для получения нужного периода необходимо убавлять единицу, возможно это происходит, что счет начинается с 0, и по факту при занесении константы 3, у нас будет период 4 мс., верно ли я полагаю???

  6. Здравствуйте. Подскажите пожалуйста. Вот строка HAL_TIM_Base_Start_IT(&htim3); запускает таймер? То есть таймер начинает считать только после этой строки. Правильно ли я понял?
    И в связи с этим вопрос: а при настройке таймера в режим аппаратного ШИМ эта строка так же необходима?

  7. Друзья, недавно в stm. Сразу начал осваивать hal. Требуется помощь.
    Подскажите, пожалуйста, как правильно обрабатывать прерывания DMA? Я настроил прерывания от DMA. Настроил таймеры. Обработчик находится в _it.c. я так понимаю.
    У меня алгоритм следующий. 1) Таймер по захвату передает данные посредством DMA. DMA по окончанию приема должно выработать прерывание. Но так как перывания от DMA объединены, нам надо убедиться, что это «прерывание по завершению» прочитав соответствующий флаг.
    Верная ли последовательность?
    Как это сделать (прочитать флаг)?
    Нужно ли потом очищать флаги?
    2) Нужно определить по спаду или нарастанию на таймере-счетчике произошел запрос от DMA.
    Как прочитать регистр флагов прерывания?
    3) подскажите как пользоваться библиотекой, иногда получается (с выводами, с USART, а иногда нет).
    Вот например очистка флагов DMA. В dma.h есть такой define:

    #define __HAL_DMA_CLEAR_FLAG(__HANDLE__, __FLAG__) \
    (((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HIFCR = (__FLAG__)) :\
    ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LIFCR = (__FLAG__)) :\
    ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HIFCR = (__FLAG__)) : (DMA1->LIFCR = (__FLAG__)))

    #define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)\
    (((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA2_Stream3)? (DMA2->HISR & (__FLAG__)) :\
    ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream7)? (DMA2->LISR & (__FLAG__)) :\
    ((uint32_t)((__HANDLE__)->Instance) > (uint32_t)DMA1_Stream3)? (DMA1->HISR & (__FLAG__)) : (DMA1->LISR & (__FLAG__)))

    Как им воспользоваться, как с помощью него считывать?

  8. И еще один вопрос. После создания проекта в Cube в keil у меня на некоторых строках в файлах .h следующая надпись unknow type name uint32_t , в частности напротив этой строки #include «stm32f4xx_hal_def.h» . Это ненормально же? Как это исправить можно?

  9. Keil5. Сразу после генерации проекта в Cube, еще ничего не пишу, просто проект собираю и в .h файлах такая штука появляется

  10. TIM1 расширенный режим. Генерация комплементарных сигналов PWM с deadtime.
    Запуск HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
    Вопрос: сигнал генерится только на одном выходе TIM1_CH1N (PA7), на другом выходе TIM1_CH1 (PA8) фиксированный уровень. В чем может быть проблема?

  11. Если ВЫ уж используете HAL, то советую не в прерывании писать а использовать Callback, например HAL_TIM_PeriodElapsedCallback(прерывание по периоду).

  12. Здравствуйте.
    Подскажите пожалуйста как пользоваться Callback-ми для обработки данных? Вот допустим получил данные по SPI, мне необходимо обработать их. Если не писать обработку в обработчике прерываний, то как это сделать с помощью callback?

  13. Здравствуйте.
    Столкнулся с проблемой разного времени выхода на вектор прерывания. В cube установил высокий приоритет HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); у всех остальных HAL_NVIC_SetPriority(x, 0, 1);
    Вот код void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    counter_time ++;
    switch (counter_time)
    {
    case 1:
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
    break;

    case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); flag_1 = 1; break;

    case 3: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); break;

    case 4: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); counter_time =0;break;

    }
    }
    Смотрел осциллографом pin stm32f429. Все болтается такое ощущение что что-то выполняет какие то операции и не дает сразу перейти на вектор но что не понятно. Подскажите что делать

  14. Здравствуйте!
    Я начинающий, поэтому не судите строго, но данное занятие я проделать не смог.
    Не объясняется что , как и почему, почему именно эти значения в инфекциях и откуда взяли. Также куда вставили последний кусок
    В общем приходится додумывать самому

  15. в общем разобрался откуда ноги растут
    https://www.youtube.com/watch?v=GrF0Kto5c48
    вот не плохое видео. которое хорошо дополняет этот урок
    сам все делаю в воркбенхе код после куба автоматом разбит на файлы
    править нужно майн и stm32f1xx_it.c
    единственное пока не понял у меня 103 контроллер от кварца на 72 мегагерца. таймер 2 на 1APB частота 36М делил на 36к и потом умножал на 499 получалось 2 секунды. пока с определением времени вопрос открытый

    • Добрый день!

      На самом деле вот в этом абзаце полностью объясняется откуда берутся два числа, которые используются в примере:
      «Выбираем делитель частоты равным 16000. В итоге получаем (16 МГц / 16000) = 1000 Гц. То есть один тик таймера будет соответствовать 1 мс. Установив период 499 (это значение в Cube надо ставить на единицу меньше, чем целевое значение, аналогично и значение делителя) получим прерывания по переполнению таймера каждые 500 мс — то, что нам и надо.»

      Ну а последний кусок — это же прерывание, из названия функции это видно…

      По поводу частоты — видимо тактирование неверно настроено

      • Добрый день Aveal. Спасибо за такие классные материалы по HAL, всё очень просто и понятно.

        Есть у меня небольшой вопрос. Подразумевает ли Cube возможность изменение периода таймера во время работы контроллера? Я хочу изменять период таймера на работающем контроллере, без перепрошивки.

        Я предполагаю нужно в этой строке «htim3.Init.Period = 499» вместо числового значения поставить переменную «htim3.Init.Period = a», а потом перезапускать таймер после изменения значения этой переменной. Но боюсь Cube сотрёт эту переменную, т.к. она будет находится вне блока User Code.

        Может есть ещё способы?
        А может ткнёте носом в мануал\ссылку, где это всё расписано 🙂

        • Можно просто в регистр таймера напрямую писать значения — регистр TIMx->ARR, насколько я помню.

  16. Ув. Aveal насчет частот все понятно.
    вы не указали что надо поставить галочку NVIC Setting ну отсюда и естественно куда вкладывать код моргания диода (без галочки он не генерируется), так же нет объяснения откуда взялся параметр (&htim3) — по логике вещей понятно, но на видео по ссылке, что вкладывал автор все показывает и становится понятно, т.к. хотелось, что бы инструкция была относительно универсальна. (т.е. к разным МК и таймерам)
    для своего Мк выставил все верно — таймер2 частота 36МГЦ (по кубу и это максимальная по даташиту) при делении на 36к и умножении на 499 почему то получилось 2сек, в итоге сделал умножение на 249 и вроде стала 1сек., но почему не понял

  17. (c)»вы не указали что надо поставить галочку NVIC Setting»
    А я два часа голову себе ломал — что я не так делаю. Досадная неточность автора. Спасибо Ефим.

    • Не совсем понял…В тексте же написано — «В этом же окне во вкладке NVIC Settings нужно, собственно, включить нужное нам прерывание, и на этом настройки заканчиваются.»

    • Ну и, в общем-то, сказано, куда поместить код моргания диодом — «Кстати, сам обработчик прерывания Cube создал за нас и найти его совсем несложно — для всех прерываний генерируется отдельный файл stm32f4xx_it.c.»

  18. Спасибо. Разобрался, в общем. Есть вопрос — как посчитать количество импульсов за единицу времени. Как я понял — нужно считать «тики» между спадом импульса и фронтом следующего. Нет ли у Вас подобного примера? Может, будет желание статью написать?

    • Наверно удобнее всего будет внешние прерывания использовать — http://microtechnics.ru/stm32f3-ispolzovanie-vneshnix-preryvanij/ . Тоже через Cube можно легко настроить. Тогда при приходе переднего (к примеру) фронта будет срабатывать прерывание и в нем просто можно инкрементировать счетчик.

      • Метод надежен, но есть слабое место. При низкой частоте импульсов — очень большая погрешность. Я собираюсь считывать сигнал с импульсного водомера. Частота (средняя), пусть, 10 имп./сек. При опросе счетчика, по прерыванию таймера, например, каждую секунду — может быть полная лажа. 8-11…а это более 10%
        Я думал, что ловить нужно спад импульса, начинать считать «тики» и по фронту следующего — считывать счетчик. Частота «тиков» известна, все остальное — уже детали. Но вот именно этот кусок — мне непонятно, как реализовать. Спад — подсчет — остановка по фронту… Если подскажете примером — буду очень признателен 🙂

        • Ну я и имел ввиду не прерывание таймера, а прерывания внешней линии EXTI — ловим в прерывании передний или задний фронт импульса (или и то, и другое). Параллельно работает таймер (можно без прерывания). И попав в прерывание EXTI считываем значение счетчика таймера. Затем для другого импульса — и рассчитываем период.

          Другой вариант — без таймера отдельного — например, на SysTick (он по умолчанию включен) отсчитывать секунду. А через EXTI считать число импульсов за секунду.

          • Спасибо. Метод с обработкой значения счетчика в прерывании по входу — похоже, то, что надо. Но там, видимо, есть нюанс — с учетом переполнения таймера, оно может попасть в период между импульсами на входе. Возможно, таймер нужно сбрасывать в 0, в обработчике прерывания?
            Нет ли у Вас интереса на эту тему сделать урок? Например, урок в DISCOVERY — как считать количество нажатий на кнопку за 1…10 сек?

        • Ну да, таймер можем сбросить в прерывании.

          Вообще хорошая тема для статьи, но до Нового Года со временем тяжело, если только после получится =)

          • Здравствуйте!
            Вот, как раз ищу, как сбросить таймер, когда он еще не «досчитал» до сброса?
            Спасибо

    • Ну и соответственно количество за единицу времени если знаем, то рассчитываем период. Можно и не считать количество, а настроить внешнее прерывание на передний и задний фронт и по значению таймера рассчитать период.

      А вообще у STM32 в таймерах огромнейшее количество разных режимов — вполне возможно, что какое-то аппаратное решение и для такой задачи найдется, надо даташит поковырять.

  19. при вхождении в процедуру обработки прерывания надо сбрасывать флаг прерывания или он автоматически сбрасывается?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *