STM32: Урок 6.1 — Базовые таймеры


Всё-таки, меня всегда умиляло название «таймер» для этих сложных штук в микроконтроллерах. Шутка ли: кроме срабатывания в строго заданный интервал, они имеют ещё до десятка дополнительных функций типа генерации ШИМ и подсчёта входящих импульсов. В микроконтроллерах STM32 таймеры настолько круты, что я посчитал нужным разбить их описание на несколько статей.

Таймеров в STM32 много, и они имеют разный набор возможностей, который делит их на 3 группы — базовые (basic timers), общего назначения (general-purpose timers) и продвинутые (advanced-control timers). Последние являются самыми навороченными и включают в себя функции остальных, и вдобавок имеют дополнительные функции для управления всякими трёхфазными двигателями — например, настройку dead-time.

Эта статья будет посвящена базовым таймерам STM32. Они значительно скромнее, согласно руководству по линейке STM32F100xx (можно скачать и тут):

  • 16-битный счётчик с автоперезагрузкой.
  • 16-битный программируемый делитель частоты: с 1 по 65535.
  • Схема синхронизации для запуска гравиЦАП.
  • Генерация прерывания и/или запроса DMA по переполнению счётчика.

Таймер действительно совсем базовый — только счётчиком умеет пощёлкивать, но и с таким можно сделать кое-что полезное. Программируемый делитель можно выбрать любой из указанного диапазона: хоть 666, если захочется. Это сильно упрощает настройку частоты счёта таймера по сравнению с теми же AVR (у них всего несколько возможных значений делителя). Например, у нас есть STM32VLDiscovery, на ней камень STM32F100RBT6B, работающий на частоте 24 МГц, и нужно инкрементировать счётчик каждую миллисекунду. Нет проблем: выставляем делитель в 24000, и всего делов. Предделитель, кстати, можно менять прямо в ходе работы таймера.

Переполнение происходит по достижении счётчиком специального числа (периода), которое настраивается в пределах с 0 по 65535. То есть, можно настроить таймер так, чтобы переполнение происходило через каждые 500 тиков, например. Период по умолчанию тоже можно менять в ходе работы таймера, но можно эту функцию отключить программно (даже не спрашивайте, зачем).

У нашего МК базовых таймеров два — TIM6 и TIM7. Задействуем первый для мигания светодиодами:

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
/* В этом файле - всё для работы с таймерами */
#include <stm32f10x_tim.h>
/* В этом - для работы с NVIC */
#include <misc.h>

enum { BLUE_LED = GPIO_Pin_8, GREEN_LED = GPIO_Pin_9 };

void init_leds();
void init_timer();

int main()
{
  init_leds();
  GPIO_SetBits(GPIOC, BLUE_LED);
  GPIO_ResetBits(GPIOC, GREEN_LED);

  init_timer();

  do __NOP(); while (1);
}

void init_leds()
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

  GPIO_InitTypeDef gpio;
  GPIO_StructInit(&gpio);
  gpio.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio.GPIO_Pin = BLUE_LED | GREEN_LED;
  GPIO_Init(GPIOC, &gpio);
}

void init_timer()
{
  /* Не забываем затактировать таймер */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

  /* Инициализируем базовый таймер: делитель 24000, период 500 мс.
   * Другие параметры структуры TIM_TimeBaseInitTypeDef
   * не имеют смысла для базовых таймеров.
   */
  TIM_TimeBaseInitTypeDef base_timer;
  TIM_TimeBaseStructInit(&base_timer);
  /* Делитель учитывается как TIM_Prescaler + 1, поэтому отнимаем 1 */
  base_timer.TIM_Prescaler = 24000 - 1;
  base_timer.TIM_Period = 500;
  TIM_TimeBaseInit(TIM6, &base_timer);

  /* Разрешаем прерывание по обновлению (в данном случае -
   * по переполнению) счётчика таймера TIM6.
   */
  TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
  /* Включаем таймер */
  TIM_Cmd(TIM6, ENABLE);

  /* Разрешаем обработку прерывания по переполнению счётчика
   * таймера TIM6. Так получилось, что это же прерывание
   * отвечает и за опустошение ЦАП.
   */
  NVIC_EnableIRQ(TIM6_DAC_IRQn);
}

void TIM6_DAC_IRQHandler()
{
  /* Так как этот обработчик вызывается и для ЦАП, нужно проверять,
   * произошло ли прерывание по переполнению счётчика таймера TIM6.
   */
  if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
  {
    /* Очищаем бит обрабатываемого прерывания */
    TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
    /* Инвертируем состояние светодиодов */
    GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (BLUE_LED | GREEN_LED));
  }
}

Возможно, у вас возник вопрос: а где узнать конкретные константы для прерываний и имена обработчиков? Отвечу: константы смотреть нужно в stm32f10x.h, с учётом категории МК. Наш относится к Meduim density Value line (STM32F10X_MD_VL). Название обработчика обычно состоит из названия константы (без n) и слова Handler:

TIM6_DAC_IRQn → TIM6_DAC_IRQHandler
TIM7_IRQn → TIM7_IRQHandler

Ну, и так далее.


21 комментарий на «“STM32: Урок 6.1 — Базовые таймеры”»

  1. Огромное спасибо за старания. Подробного русскоязычного курса по этой платформе еще не было. Автор можно сказать благородным просветительским делом занят. Прикупил себе плату, буду по ходу дела изучать.
    В целом очень много общего с AVR-ками. А возможностей гораздо больше.

  2. Привет. сделал все как в статье, но обработчик переполнения не вызывается((
    библиотеки все есть, среда — IAR 6.21(c++ project), плата — STM32 Discovery
    пробовал и на TIM6, и на TIM7 — не работает.
    Код для Таймера7

    void init_timer()
    {
      TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
      NVIC_InitTypeDef NVIC_InitStructure;
      
      RCC_APB1PeriphClockCmd ( RCC_APB1Periph_TIM7, ENABLE );
      
      NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructure);
    
      TIM_TimeBaseStructure.TIM_Period = 500;
      TIM_TimeBaseStructure.TIM_Prescaler = 24000 - 1;
      TIM_TimeBaseStructure.TIM_ClockDivision = 0;
      TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;	
      TIM_TimeBaseInit ( TIM7, &TIM_TimeBaseStructure );
      
      TIM_ITConfig ( TIM7, TIM_IT_Update, ENABLE );
      TIM_Cmd ( TIM7, ENABLE );
    }
    void TIM7_IRQHandler ( void )
    {
      if ( TIM_GetITStatus ( TIM7, TIM_IT_Update ) != RESET ) {
        TIM_ClearITPendingBit ( TIM7, TIM_IT_Update );
        GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (BLUE_LED | GREEN_LED));
      }
    }
    

    вроде все по инструкции… плиз, хелп ми.

    • Странно, ничего подозрительного не вижу. Может, в IAR в startup-коде обработчик по-другому называется? У меня IAR’а нет, проверить не на чем. Выложите архив своего проекта на какой-нибудь файлообменник типа Народ.Диск.

    • Пока качается IAR, я заглянул в ваши сорцы. Что ж вы сразу не сказали, что на C++ кодите? Компиляторы C++ всегда добавляют к именам функций разные символы для обозначения возвращаемого типа и типов аргументов, т.к. в C++ может быть несколько функций с одинаковым именем, но с разными типами и числом аргументов (т.н. «перегрузка» функций). А то, что делает компилятор C++ с именами функций называется name mangling.

      Я заглянул в объектный файл main.o, сгенерированный из исходника main.cpp, и увидел то, что и ожидал: ваш обработчик называется в нём "_Z15TIM7_IRQHandlerv". По имени видно, что 15 — это длина оригинального имени «TIM7_IRQHandler», «v» обозначает, очевидно, void (который вместо аргументов), а _Z — void, который возвращает функция. Чтобы указать компилятору, что функция не C++’ная, а C’шная, нужно писать:

      extern "C" void TIM7_IRQHandler(void)

      Но ещё лучше не маятся фигнёй и писать на чистом C, попутно изменив расширение имени файла на .c вместо .cpp (:

    • я сразу написал что IAR 6.21(c++ project)…
      а можно сделать екстерн для всех функцый ST, чтоб каждую не екстернить?
      в том то й дело, что мне надо с++(ради класов).

    • А, блин, не заметил сразу — не привык, что кто-то под МК на плюсах пишет, увидел «с project» на автомате (:
      Во всех заголовочных файлах SPL и так extern «C» стоит, а TIM7_IRQHandler — это ж ваша функция, вот её и нужно экстернить. Если хотите, можно сразу несколько функций в блок засунуть — extern «C» {… }

      Другое дело, что не работает… Тут я даже не знаю, в чём дело. Завтра посмотрю в IAR.

    • для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).

    • для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).
      StLink Driver

    • Заменил DLLку, всё равно не пашет:
      «Fatal error: ST-Link Connection error»

      Я ж говорю — ну его нафиг. Кривая IDE + C++ — это взрывная смесь. Я лично устал с этим ковыряться, ковыряйтесь сами.

      Вы лучше скажите, что вас побудило пользоваться IAR, когда есть Eclipse и куча IDE на его базе? Интересно, чем эта среда может быть лучше других хотя бы теоретически.

    • первый урок. который я нашел, был на IAR. да и кряк быстро нашелся)))
      чистый еклипс позволяет работать с СТ?
      так как перспектив в 32Кб кода не очень…

    • Урок по настройке Eclipse и остального добра
      robocraft.ru/blog/ARM/653.html

      В конце написано про уже собранную IDE, но там старая версия утилиты stlink, может медленно заливать на МК.

    • спасибо. еще IAR поковыряю, если будет глухо, перейду на еклисп… статья большая, а я ленивый к таким настройкам))))
      но если будет глухо, то от судьбы не уйдешь)))

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

    • я уже понял глупость вопроса…

      extern "C" void TIM7_IRQHandler ( void )
      {
        if ( TIM_GetITStatus ( TIM7, TIM_IT_Update ) != RESET )
        {
          TIM_ClearITPendingBit ( TIM7, TIM_IT_Update );
          GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (BLUE_LED | GREEN_LED));
        }
      }

      и так не работает

    • Установил я IAR 6.21… Мдааа, такой печальки я не ожидал, конечно. Прямо привет из 90-х какой-то. Я так и не заставил эту шнягу работать с ST-Link, и даже в режиме GDBServer эта безмозглая IDE не смогла залить прошивку. Все настройки облазил, несколько раз перенастроил проект для STM32VLDISCOVERY — всё без толку. Мой вам совет — выкиньте это дерьмо мамонта и поставьте себе Eclipse. С такой IDE, как IAR, я ничем помочь не могу, увы — слишком много гемора.

    • В комментариях по настройке IDE спрашивал спрашивал, как настроить Eclipse, что бы работала функция sprintf(txt, "%f", float fl). После вызова данной функции контроллер зависает по неопределённому прерыванию в бесконечном цикле, при этом функция для чисел int работает. Кроме этого, после объявления числа как double, контроллер вообще не грузится. Не говоря уже необходимых «заглушках», что бы подключить stdlib. Настроить IAR понадобилось 2 часа, и всё работает и LINK и print(). Если кто объяснит как побороть грабли с float и double, останусь на современном Eclipse, а не на «прошлом веке».

  3. Артем… что то я невнимательно отлаживал… короче, программа падает при

    NVIC_EnableIRQ(TIM6_DAC_IRQn);

    еще не доходя к хендлеру(что пока радует)… есть идеи почему так?
    и как поймать ошибку в логи?
    возможно массив NVIC->ISER не существует, или сам NVIC…

    • внутренности так сказать)) именно на той функции

      NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
      NVIC_EnableIRQ:
      _Z14NVIC_EnableIRQ4IRQn:
          0x800023c: 0xb240         SXTB      R0, R0
          0x800023e: 0x0941         LSRS      R1, R0, #5
          0x8000240: 0x4a31         LDR.N     R2, ??DataTable6        ; SETENA0
          0x8000242: 0x2301         MOVS      R3, #1
          0x8000244: 0xf010 0x001f  ANDS.W    R0, R0, #31             ; 0x1f
          0x8000248: 0xfa13 0xf000  LSLS.W    R0, R3, R0
          0x800024c: 0xf842 0x0021  STR.W     R0, [R2, R1, LSL #2]
      }
          0x8000250: 0x4770         BX        LR

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

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение