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

Ну, и так далее.
  • +3
  • 27 января 2012, 13:46
  • burjui

Комментарии (21)

RSS свернуть / развернуть
+
+1
Большое спасибо. Как раз закупил демо-плату с stm32, пока доставят, смогу её изучить работу по вашим статьям.
avatar

realist

  • 27 января 2012, 23:51
+
+1
Огромное спасибо, ваши уроки здорово помогают в освоении платы!=)
avatar

AndR

  • 28 января 2012, 10:46
+
+2
Огромное спасибо за старания. Подробного русскоязычного курса по этой платформе еще не было. Автор можно сказать благородным просветительским делом занят. Прикупил себе плату, буду по ходу дела изучать.
В целом очень много общего с AVR-ками. А возможностей гораздо больше.
avatar

Ozze

  • 30 января 2012, 18:13
+
0
Привет. сделал все как в статье, но обработчик переполнения не вызывается((
библиотеки все есть, среда — 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));
  }
}


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

crane

  • 9 марта 2012, 18:52
+
0
Странно, ничего подозрительного не вижу. Может, в IAR в startup-коде обработчик по-другому называется? У меня IAR'а нет, проверить не на чем. Выложите архив своего проекта на какой-нибудь файлообменник типа Народ.Диск.
avatar

burjui

  • 9 марта 2012, 19:14
+
0
Мой Project
avatar

crane

  • 9 марта 2012, 21:50
+
0
Пока качается 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 (:
avatar

burjui

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

crane

  • 9 марта 2012, 23:56
+
0
я уже понял глупость вопроса…
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));
  }
}

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

crane

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

burjui

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

Sergey_4i

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

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

burjui

  • 10 марта 2012, 00:49
+
0
для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).
avatar

crane

  • 11 марта 2012, 16:56
+
0
для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).
StLink Driver
avatar

crane

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

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

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

burjui

  • 11 марта 2012, 17:42
+
0
первый урок. который я нашел, был на IAR. да и кряк быстро нашелся)))
чистый еклипс позволяет работать с СТ?
так как перспектив в 32Кб кода не очень…
avatar

crane

  • 11 марта 2012, 17:53
+
0
Урок по настройке Eclipse и остального добра
robocraft.ru/blog/ARM/653.html

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

burjui

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

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

crane

  • 11 марта 2012, 20:28
+
0
Артем… что то я невнимательно отлаживал… короче, программа падает при
NVIC_EnableIRQ(TIM6_DAC_IRQn);

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

crane

  • 11 марта 2012, 23:10
+
0
внутренности так сказать)) именно на той функции
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
avatar

crane

  • 11 марта 2012, 23:21
+
0
еще скриншот
screencast.com/t/d8ZBZ1SyPqy
avatar

crane

  • 11 марта 2012, 23:38

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.