STM32: Урок 6.2 - Таймеры общего назначения и продвинутые

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

Умеют они всё то же, что и базовые таймеры, но у них есть дополнительные возможности:
  • До 4-х каналов для:
    • Захвата сигнала (input capture).
    • Сравнения вывода (output compare).
    • Генерации сигнала ШИМ (выровненного по границе или по центру).
    • Генерации одиночных импульсов.
  • Схемы синхронизации для управления таймерами при помощи внешних сигналов и для соединения нескольких таймеров друг с другом.
  • Комплементарные выходы с программируемым dead-time.
  • Счётчик повторений.
  • Вход BRK для сброса выходов таймера или выставления их в известное состояние.
  • Поддерживают инкрементальные (квадратурные) энкодеры и датчики Холла.
  • Генерация прерывания или запроса DMA по следующим событиям:
    • Обновление: переполнение счётчика.
    • Событие-триггер: старт, остановка, инициализация счётчика или его обновление внутренним или внешним триггером.
    • Захват сигнала.
    • Сравнение (output compare).
    • Включение BRK.
Неплохо, да? Но в этой бочке мёда таки есть маленькая ложечка дёгтя: эти возможности малость неравномерно распределены между таймерами, поэтому придётся поглядывать в разделы (их три) General-purpose timers (...) в руководства по STM32F100xx (ещё ссылка), прежде чем пытаться воспользоваться какой-либо из них.

Вот как вы думаете, если у таймеров общего назначения так много функций, чем тогда продвинутые (advanced-control) таймеры отличаются от них? o_O
Правильный ответ — почти ничем, это по факту просто таймеры общего назначения, которые не имеют никаких ограничений: в них напихано по 4 канала (с комплементарными) и есть все возможности сразу, без какого-то ни было разброса. Так что остальная часть статьи будет относиться ко всем таймерам выше базовых, а продвинутые таймеры я отдельно упоминать не буду.

В даташите на STM32F100xx (ещё ссылка) есть сводная таблица возможностей таймеров, в которую тоже удобно поглядывать для справки:



Кстати, обращайте внимание на сноски. Например, там написано, что у МК семейства Low density Value line нет таймера TIM4.

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

В этом режиме с выбранного канала захватываются импульсы, на каждый из них текущее значение счётчика таймера кладётся в регистр TIM_CCRx, где x — номер канала. Таким образом, период следования импульсов равен разнице между текущим значением TIM_CCRx и предыдущим. Ну а для того, чтобы получить период в каких-то внятных единицах измерения, нужно настроить предделитель через функции базового таймера.

При этом можно настроить генерацию прерывания и запроса DMA на приход очередного импульса, и если в это время предыдущее значение TIM_CCRx не было считано, будет сгенерировано так называемое прерывание over-capture, т.е. сигнал о том, что предыдущее значение потерялось.

Ловить можно фронты, спады или и то, и другое вместе. Есть настройка так называемого фильтра — числа выборок, после которого переход уровня будет считаться состоявшимся (полезно для устранения дребезга). Значение фильтра может принимать значения от 0 (фильтр выключен) до 15 (0xF). Также настраивается делитель входной частоты — 2, 4 или 8: будет ловиться каждый 2й, 4й или 8й импульс соответственно.

Примера ради подёргаем вывод PB15 и замерим таймером TIM3 период, подключив PB15 к его каналу 1 (PA6):

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

void init_gpio(void);
void init_timer(void);
uint16_t uint16_time_diff(uint16_t now, uint16_t before);

volatile uint16_t systick_ms = 0;
volatile uint16_t capture1 = 0, capture2 = 0;
volatile uint8_t capture_is_first = 1, capture_is_ready = 0;

int main(void)
{
  init_gpio();
  init_timer();

  SysTick_Config(SystemCoreClock / 1000);

  while (1)
  {
    /* Каждые 73 миллисекунды меняем уровень на ножке на противоположный,
       так что период импульсов будет равен 146 мс. */
    static uint32_t toggle_ms = 0;

    if (uint16_time_diff(systick_ms, toggle_ms) >= 73)
    {
      toggle_ms = systick_ms;
      GPIO_Write(GPIOB, GPIO_ReadOutputData(GPIOB) ^ GPIO_Pin_15);
    }

    if (capture_is_ready)
    {
      NVIC_DisableIRQ(TIM3_IRQn);
      capture_is_ready = 0;

      /* Обрабатываем захваченный период, который должен быть равен 146 */
      const uint16_t period = uint16_time_diff(capture2, capture1);
      // ...

      NVIC_EnableIRQ(TIM3_IRQn);
    }
  }
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio_cfg;
  GPIO_StructInit(&gpio_cfg);

  /* Вывод тестового сигнала на PB15 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio_cfg.GPIO_Pin = GPIO_Pin_15;
  GPIO_Init(GPIOB, &gpio_cfg);

  /* Таймер TIM3, канал 1 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  gpio_cfg.GPIO_Pin = GPIO_Pin_6;
  GPIO_Init(GPIOA, &gpio_cfg);
}

void init_timer(void)
{
  /* Подаём такты на TIM3 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  /* Настраиваем предделитель так, чтобы таймер считал миллисекунды.
     На бо́льших частотах следите, чтобы предделитель не превысил
     максимальное значение uint16_t - 0xFFFF (65535) */
  TIM_TimeBaseInitTypeDef timer_base;
  TIM_TimeBaseStructInit(&timer_base);
  timer_base.TIM_Prescaler = 24000 - 1;
  TIM_TimeBaseInit(TIM3, &timer_base);

  /* Настраиваем захват сигнала:
   - канал: 1
   - счёт: по нарастанию
   - источник: напрямую со входа
   - делитель: отключен
   - фильтр: отключен */
  TIM_ICInitTypeDef timer_ic;
  timer_ic.TIM_Channel = TIM_Channel_1;
  timer_ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
  timer_ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
  timer_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  timer_ic.TIM_ICFilter = 0;
  TIM_ICInit(TIM3, &timer_ic);

  /* Разрешаем таймеру генерировать прерывание по захвату */
  TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
  /* Включаем таймер */
  TIM_Cmd(TIM3, ENABLE);
  /* Разрешаем прерывания таймера TIM3 */
  NVIC_EnableIRQ(TIM3_IRQn);
}

void TIM3_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
  {
    /* Даём знать, что обработали прерывание */
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);

    /* Запоминаем предыдущее измерение и считываем текущее */
    capture1 = capture2;
    capture2 = TIM_GetCapture1(TIM3);

    /* Для корректной обработки нужно минимум два измерения */
    if (!capture_is_first)
      capture_is_ready = 1;

    capture_is_first = 0;

    /* Тут как-нибудь обрабатываем событие over-capture, если провороним */
    if (TIM_GetFlagStatus(TIM3, TIM_FLAG_CC1OF) != RESET)
    {
      TIM_ClearFlag(TIM3, TIM_FLAG_CC1OF);
      // ...
    }
  }
}

/* Тут просто считаем миллисекунды */
void SysTick_Handler(void)
{
  ++systick_ms;
}

/* Вычисляет разность во времени с учётом переполнения счётчика таймера */
uint16_t uint16_time_diff(uint16_t now, uint16_t before)
{
  return (now >= before) ? (now - before) : (UINT16_MAX - before + now);
}

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



Изменим предыдущий пример, используя захват ШИМ (прокомментированы только изменения):

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

void init_gpio(void);
void init_timer(void);
uint16_t uint16_time_diff(uint16_t now, uint16_t before);

volatile uint16_t systick_ms = 0;
volatile uint16_t period_capture = 0, duty_cycle_capture = 0;
volatile uint8_t capture_is_first = 1, capture_is_ready = 0;

int main(void)
{
  init_gpio();
  init_timer();

  SysTick_Config(SystemCoreClock / 1000);

  while (1)
  {
    static uint32_t toggle_ms = 0;

    if (uint16_time_diff(systick_ms, toggle_ms) >= 73)
    {
      toggle_ms = systick_ms;
      GPIO_Write(GPIOB, GPIO_ReadOutputData(GPIOB) ^ GPIO_Pin_15);
    }

    if (capture_is_ready)
    {
      /* Опять же, копируем результаты, пока нет прерываний,
         но не задерживаем прерывания надолго. */
      NVIC_DisableIRQ(TIM3_IRQn);
      capture_is_ready = 0;
      const uint16_t period = period_capture, duty_cycle = duty_cycle_capture;
      NVIC_EnableIRQ(TIM3_IRQn);

      /* Обрабатываем параметры ШИМ (переменные period и duty_cycle).
         Ничего рассчитывать на сей раз не придётся (:
         ... */
    }
  }
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio_cfg;
  GPIO_StructInit(&gpio_cfg);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio_cfg.GPIO_Pin = GPIO_Pin_15;
  GPIO_Init(GPIOB, &gpio_cfg);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  gpio_cfg.GPIO_Pin = GPIO_Pin_6;
  GPIO_Init(GPIOA, &gpio_cfg);
}

void init_timer(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  TIM_TimeBaseInitTypeDef timer_base;
  TIM_TimeBaseStructInit(&timer_base);
  timer_base.TIM_Prescaler = 24000 - 1;
  TIM_TimeBaseInit(TIM3, &timer_base);

  TIM_ICInitTypeDef timer_ic;
  timer_ic.TIM_Channel = TIM_Channel_1;
  timer_ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
  timer_ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
  timer_ic.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  timer_ic.TIM_ICFilter = 0;

  /* Эта функция настроит канал 1 для захвата периода,
     а канал 2 - для захвата заполнения. */
  TIM_PWMIConfig(TIM3, &timer_ic);
  /* Выбираем источник для триггера: вход 1 (PA6) */
  TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
  /* По событию от триггера счётчик будет сбрасываться. */
  TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
  /* Включаем события от триггера */
  TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);

  TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
  TIM_Cmd(TIM3, ENABLE);
  NVIC_EnableIRQ(TIM3_IRQn);
}

void TIM3_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
    NVIC_DisableIRQ(TIM3_IRQn);

    /* А вот и параметры ШИМ, захваченные с каналов 1 и 2 */
    period_capture = TIM_GetCapture1(TIM3);
    duty_cycle_capture = TIM_GetCapture2(TIM3);

    NVIC_EnableIRQ(TIM3_IRQn);

    if (!capture_is_first)
      capture_is_ready = 1;

    capture_is_first = 0;

    if (TIM_GetFlagStatus(TIM3, TIM_FLAG_CC1OF) != RESET)
    {
      TIM_ClearFlag(TIM3, TIM_FLAG_CC1OF);
      // ...
    }
  }
}

void SysTick_Handler(void)
{
  ++systick_ms;
}

uint16_t uint16_time_diff(uint16_t now, uint16_t before)
{
  return (now >= before) ? (now - before) : (UINT16_MAX - before + now);
}

Режим чтения энкодера

Работу с энкодером я уже как-то описывал, и тогда я считывал и декодировал данные с энкодера программно, здесь же таймер сделает работу за нас (не всю, конечно же). Боковые выводы энкодера надо подключить к двум каналам таймера, а средний вывод — к GND. Таймер в этом режиме сам обрабатывает поступающие с энкодера импульсы, а также увеличивает/уменьшает свой счётчик на 4 при каждом щелчке энкодера, и запоминает направление вращения.

Так как мне захотелось ещё и прерывание заиметь, я сделал период равным 4 и разрешил счёт в обе стороны, так что теперь прерывание будет возникать при каждом щелчке энкодера. Использовал я каналы 1 и 2 таймера TIM3 (PA6 и PA7):

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

typedef enum { FORWARD, BACKWARD } Direction;

volatile uint8_t capture_is_first = 1, capture_is_ready = 0;
volatile Direction captured_direction = FORWARD;

void init_gpio(void);
void init_timer(void);

int main(void)
{
  init_gpio();
  init_timer();

  while (1)
  {
    if (capture_is_ready)
    {
      NVIC_DisableIRQ(TIM3_IRQn);
      capture_is_ready = 0;
      const Direction direction = captured_direction;
      NVIC_EnableIRQ(TIM3_IRQn);

      /* Обрабатываем direction ... */
    }
  }
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio_cfg;
  GPIO_StructInit(&gpio_cfg);

  /* Каналы 1 и 2 таймера TIM3 - на вход, подтянуть к питанию */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  gpio_cfg.GPIO_Mode = GPIO_Mode_IPU;
  gpio_cfg.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_Init(GPIOA, &gpio_cfg);
}

void init_timer(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  /* Разрешаем счёт в обе стороны, период ставим 4 */
  TIM_TimeBaseInitTypeDef timer_base;
  TIM_TimeBaseStructInit(&timer_base);
  timer_base.TIM_Period = 4;
  timer_base.TIM_CounterMode = TIM_CounterMode_Down | TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM3, &timer_base);

  /* Считать будем все переходы лог. уровня с обоих каналов */
  TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
      TIM_ICPolarity_BothEdge, TIM_ICPolarity_BothEdge);
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
  TIM_Cmd(TIM3, ENABLE);

  NVIC_EnableIRQ(TIM3_IRQn);
}

void TIM3_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

    if (!capture_is_first)
      capture_is_ready = 1;

    capture_is_first = 0;
    /* В бите TIM_CR1_DIR регистра TIM3_CR1 хранится
       направление вращения энкодера, запоминаем его. */
    captured_direction = (TIM3->CR1 & TIM_CR1_DIR ? FORWARD : BACKWARD);
  }
}

Сравнение вывода (output compare)

В этом режиме выбранный канал таймера будет подключен к соответствующему выводу и будет изменять его (вывода) состояние каждый раз, когда счётчик таймера досчитает до значения регистра TIM_CCRx. Состояние вывода, в зависимости от настройки, будет меняться на ноль, на единицу или на противоположное текущему. У многих таймеров у каналов есть комплементарные выводы, которые по умолчанию являются инверсными: на такой выход подаётся тот же сигнал, что и на обычный, но с противоположным уровнем.

Смотрим в сводную таблицу по таймерам в даташите и видим, что комплементарных выводов у TIM3 нет, но вот у единственного канала таймера TIM16 есть такой вывод — этот таймер я и использую для примера. Вообще, комплементарные выводы есть и у нескольких других таймеров, но вот TIM15 — особенный: у него есть два канала, но комплементарный вывод имеет только 1й канал. Будьте бдительны!

В таблице пинаутов находим выводы канала 1 таймера TIM16 — PB8 (основной) и PB6 (комплементарный). Для иллюстрации работы таймера подключим эти выводы к светодиодам на плате STM32VLDiscovery — PC8 и PC9, которые в коде мы отключим от греха подальше. Таким образом, выводы канала таймера будут напрямую мигать светодиодами:

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

void init_gpio(void);
void init_timer(void);

int main(void)
{

  init_gpio();
  init_timer();

  do __NOP(); while (1);
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio;
  GPIO_StructInit(&gpio);

  /* TIM16, канал 1  */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_8 | // TIM16_CH1 - основной вывод
                  GPIO_Pin_6;  // TIM16_CH1N - комплементарный
  GPIO_Init(GPIOB, &gpio);
}

void init_timer(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, ENABLE);

  /* Делитель выставляем так, чтобы счётчик тикал каждую миллисекунду,
     и выставляем период в 2 секунды. */
  TIM_TimeBaseInitTypeDef base_timer;
  TIM_TimeBaseStructInit(&base_timer);
  base_timer.TIM_Prescaler = 24000 - 1;
  base_timer.TIM_Period = 1000;
  TIM_TimeBaseInit(TIM16, &base_timer);

  /* Теперь настраиваем 1й канал таймера */
  TIM_OCInitTypeDef timer_oc;
  TIM_OCStructInit(&timer_oc);
  timer_oc.TIM_Pulse = 500;
  timer_oc.TIM_OCMode = TIM_OCMode_Toggle;
  /* Включаем основной и комплементарный выводы */
  timer_oc.TIM_OutputState = TIM_OutputState_Enable;
  timer_oc.TIM_OutputNState = TIM_OutputNState_Enable;
  /* Активируем каналы */
  TIM_OC1Init(TIM16, &timer_oc);

  /* Включать AOE нужно для всех таймеров, имеющих break input,
     т.к. по умолчанию вывод на все каналы выключен. */
  TIM_BDTRInitTypeDef timer_bdtr;
  TIM_BDTRStructInit(&timer_bdtr);
  timer_bdtr.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
  TIM_BDTRConfig(TIM16, &timer_bdtr);

  TIM_Cmd(TIM16, ENABLE);
}

В это примере я выбрал режим переключения вывода в противоположное состояние (TIM_OCMode_Toggle), а остальные настройки оставил по умолчанию. Кстати, не забывайте вызывать функции типа TIM_OCStructInit() для инициализации соответствующих структур, даже если заполняете все поля структур вручную: copy&paste-ориентированное программирование никто не отменял, но при нём легко забыть заполнить какое-нибудь поле и ловить потом баги.

Для обоих выводов канала можно настроить (поля TIM_OCPolarity и TIM_OCNPolarity структуры) так называемую «полярность» — состояние вывода в промежуток времени от начала отсчёта и до TIM_Pulse. По умолчанию для выводов выставляются значения TIM_OCPolarity_High и TIM_OCNPolarity_High, но комплементарный вывод является инверсным — поэтому, если на нём нужен обычный (не инверсный) сигнал, нужно ему выставить TIM_OCNPolarity_Low.

В режиме сравнения генерируется то же самое прерывание, что и в режиме захвата сигнала — TIM_IT_CCx, но здесь оно было не нужно, поэтому я его не разрешал.

Генерация ШИМ

Вот это куда более интересная и практичная штука. Принципы ШИМ уже неоднократно были описаны — как на нашем сайте, так и у Di Halt'a (уж там разжёвано всё до мелочей), а я сосредоточусь на особенностях реализации в STM32.

Настройка этого режима не слишком отличается от настройки output compare: вместо режима TIM_OCMode_Toggle нужно выбрать один из режимов ШИМ, тогда TIM_Period будет трактоваться как период ШИМ, а поле TIM_Pulse — как заполнение (duty cycle). Режимов ШИМ имеется два — выровненный по границе и по центру (edge-aligned и center-aligned). У микроконтроллеров AVR они называются Fast PWM и Phase Correct PWM, соответственно.

Отличной иллюстрацией крутизны таймеров STM32 для генерации ШИМ будет типичная прикладная задача — управление сервомашинкой: Как известно, сервы управляются импульсами переменной ширины, которые шлются с частотой примерно 50 Гц (каждые 20 мс). У сервы, которая оказалась под рукой (Robbe 4.3 g), ширина управляющего импульса от 500 мкс (0°) до 2250 мкс (175°), судя по замерам — то есть, по 10 мкс на каждый градус поворота:

ΔT = T₂ — T₁ = 2250 — 500 = 1750 мкс
∠A = 175°
ΔT/A = 10 мкс/°

То есть, для максимально прозрачного управления сервой нужно:
  1. Установить таймеру такой делитель частоты, чтобы отсчёт вёлся каждые 10 мкс.
  2. Задать период ШИМ в 20 мс, то есть 2000 отрезков времени по 10 мкс.
  3. Класть в регистр сравнения число, равное 50 (500 мкс / 10 мкс) + задаваемый угол.
  4. Регистр сравнения лучше обновлять строго в момент окончания периода во избежание дёргания сервы.
Не такая уж сложная задача:

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

enum
{
  SERVO_SHORTEST_PULSE = 50,
  SERVO_MAX_ANGLE = 175
};

void init_gpio(void);
void init_timer(void);

int main(void)
{
  init_gpio();
  init_timer();

  /* Даём одну секунду на полный поворот сервы */
  SysTick_Config(SystemCoreClock / SERVO_MAX_ANGLE);

  do __NOP(); while (1);
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio;
  GPIO_StructInit(&gpio);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_8;
  GPIO_Init(GPIOB, &gpio);
}

void init_timer(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, ENABLE);

  /* Частота счёта - 10 мкс, период - 20 мс */
  TIM_TimeBaseInitTypeDef base_timer;
  TIM_TimeBaseStructInit(&base_timer);
  base_timer.TIM_Prescaler = SystemCoreClock / 100000 - 1;
  base_timer.TIM_Period = 2000;
  TIM_TimeBaseInit(TIM16, &base_timer);

  /* Конфигурируем канал:
   - начальное заполнение: 50 тиков (500 мкс)
   - режим: edge-aligned PWM */
  TIM_OCInitTypeDef timer_oc;
  TIM_OCStructInit(&timer_oc);
  timer_oc.TIM_Pulse = SERVO_SHORTEST_PULSE;
  timer_oc.TIM_OCMode = TIM_OCMode_PWM1;
  timer_oc.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OC1Init(TIM16, &timer_oc);

  TIM_BDTRInitTypeDef bdtr;
  TIM_BDTRStructInit(&bdtr);
  bdtr.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
  TIM_BDTRConfig(TIM16, &bdtr);

  /* Включаем прерывание переполнения счётчика */
  TIM_ITConfig(TIM16, TIM_IT_Update, ENABLE);
  TIM_Cmd(TIM16, ENABLE);

  NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);
}

volatile uint8_t servo_angle = 0;

void TIM1_UP_TIM16_IRQHandler(void)
{
  /* Если счётчик переполнился, можно смело закидывать
     в регистр сравнения новое значение. */
  if (TIM_GetITStatus(TIM16, TIM_IT_Update) != RESET)
  {
    TIM_ClearITPendingBit(TIM16, TIM_IT_Update);
    TIM_SetCompare1(TIM16, SERVO_SHORTEST_PULSE + servo_angle);
  }
}

void SysTick_Handler(void)
{
  /* Крутим серву от 0° до 180° и обратно */

  static int direction = 1;

  int new_angle = (int)servo_angle + direction;

  if (new_angle >= SERVO_MAX_ANGLE)
  {
    direction = -direction;
    new_angle = SERVO_MAX_ANGLE;
  }
  else if (new_angle < 0)
  {
    direction = -direction;
    new_angle = 0;
  }

  servo_angle = new_angle;
}

Dead-time

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

Настраивается этот самый dead-time в поле TIM_DeadTime структуры TIM_BDTRInitTypeDef и имеет диапазон значений с 0 по 255 (0xFF). Но смысл этого числа не так уж прямолинеен:



Ага, вот так оно и рассчитывается. Здесь Tdts — это длительность такта генератора dead-time (DTG), зависящая от Tdts — текущей частоты тактирования таймера. Обычно таймеры тактируются системной частотой, и TIM_Prescaler на это никак не влияет, а влияет поле TIM_ClockDivision структуры TIM_TimeBaseInitTypeDef — делитель частоты таймера.

Для примера положим, что таймер затактирован без деления частоты (делитель равен 1, TIM_CKD_DIV1), системная частота F равна 24 МГц, а значение DTG = 150. Тогда:

Tdts = 1/F = 1/24 мкс
DTG = 150 = 100101102 ⇒ DTG[7:5] = 1002
Tdtg = 2⋅Tdts = 1/12 мкс
DT = (64 + DTG[5:0])Tdtg = (64 + 6)/12 = 5.8(3) мкс

«Just like that» ☯ ChosunNinja

Я тут для примера набросал код с dead-time попроще для расчёта: DTG=96 ⇒ DT=96.

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

void init_gpio(void);
void init_timer(void);

int main(void)
{
  init_gpio();
  init_timer();

  do __NOP(); while (1);
}

void init_gpio(void)
{
  GPIO_InitTypeDef gpio;
  GPIO_StructInit(&gpio);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_6;
  GPIO_Init(GPIOB, &gpio);
}

void init_timer(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM16, ENABLE);

  /* Частота счёта - 1 мкс, период - 20 мкс */
  TIM_TimeBaseInitTypeDef base_timer;
  TIM_TimeBaseStructInit(&base_timer);
  base_timer.TIM_Prescaler = 24 - 1;
  base_timer.TIM_Period = 20;
  TIM_TimeBaseInit(TIM16, &base_timer);

  /* Заполнение ШИМ 10 мкс */
  TIM_OCInitTypeDef timer_oc;
  TIM_OCStructInit(&timer_oc);
  timer_oc.TIM_Pulse = 10;
  timer_oc.TIM_OCMode = TIM_OCMode_PWM1;
  timer_oc.TIM_OutputState = TIM_OutputState_Enable;
  timer_oc.TIM_OutputNState = TIM_OutputNState_Enable;
  TIM_OC1Init(TIM16, &timer_oc);

  /* Выставляем dead-time в 4 мкс при 24 МГц */
  TIM_BDTRInitTypeDef bdtr;
  TIM_BDTRStructInit(&bdtr);
  bdtr.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
  bdtr.TIM_DeadTime = 24 * 4;
  TIM_BDTRConfig(TIM16, &bdtr);

  TIM_Cmd(TIM16, ENABLE);
}

Для того, чтобы узреть этот самый dead-time на одноканальном осциллографе, нужно подключить PB8 и PB6 через резисторы 1 кОм к его щупу:



Т.к. на эти выводы идут взаимно инверсные сигналы, на экране будут прекрасно видны места, где во время dead-time уровень на обоих входах одинаков из-за задержки фронтов:



Ну, и напоследок — имейте ввиду, что если длительность dead-time превышает длительность импульса на выводе, то соответствующий импульс не будет сгенерирован вообще.

Счётчик повторений

Этот счётчик имеется у нескольких таймеров (TIM15, TIM16 и TIM17) и выполняет он очень простую функцию: генерировать событие (прерывание или запрос DMA) update не на каждое переполнение счётчика, а на каждые N переполнений. То есть, вы задаёте счётчик повторений, таймер его копирует в скрытый регистр и при каждом переполнении уменьшает значение копии на 1. Когда значение достигает нуля, генерируется событие update, таймер снова копирует счётчик повторений и т.д. На самом деле, перечисленные таймеры и так задействуют этот счётчик, просто по умолчанию его значение равно нулю, и событие генерируется на каждое переполнение.

Счётчик может принимать значения от 0 до 255 (0xFF). Описывать тут особо нечего, потому что для использования этой функции достаточно при инициализации таймера написать что-то вроде:

base_timer.TIM_RepetitionCounter = 7;

и всё. В этом случае событие update будет генерироваться каждые 8 переполнений (7 повторений).

Вход BRK

Если вам вдруг понадобится резко перевести выводы каналов таймера в заранее определённое состояние (например, выключить), то эта функция — то, что нужно. Включить её проще пареной репы — нужно сконфигурировать пин TIMx_BKIN на вход, и при инициализации BDTR включить вход BRK:

/* Для примера возьмём таймер TIM16 */
...
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOB, &gpio);
...
bdtr.TIM_Break = TIM_Break_Enable;
TIM_BDTRConfig(TIM16, &bdtr);
...

По умолчанию для активации функции break нужно на вход BRK подать логический ноль, но это можно настроить в поле TIM_BreakPolarity. Как только break активирован, все выводы каналов переходят в состояние, которое задаётся при их инициализации полями TIM_OCIdleState и TIM_OCNIdleState в структуре TIM_OCInitTypeDef (по умолчанию на выводах будет низкий уровень). Dead-time при этом учитывается.

Синхронизация таймеров

Таймеры можно соединять друг с другом для синхронизации или образования цепочек таймеров. В первом случае появляется возможность синхронного старта нескольких таймеров по внешнему триггеру, а во втором — возможность одному таймеру управлять счётчиком другого — запускать, разрешать, сбрасывать.

Второй случай (цепочка таймеров) больше подойдёт для иллюстрации, ибо интереснее он. Сделаем-ка для примера 32-битный таймер из двух обычных 16-битных. Для примера я возьму таймеры TIM2 и TIM3. Задача состоит в том, таймер TIM3 тактировал таймер TIM2 по переполнению своего счётчика: то есть, счётчик таймера TIM2 будет увеличиваться при переполнении счётчика TIM3 — получаем 32-битный счётчик, «состоящий» из TIM2_CNT (старшие биты) и TIM3_CNT (младшие).

Для этого нужно настроить выходной триггер таймера TIM3 на переполнение (update), а входной триггер таймера TIM2 — на вход с триггера TIM3. Смотрим в таблицу соединения триггеров для таймеров TIM2-TIM4 (таких таблиц несколько — для разных групп таймеров):



Здесь мы видим, что TIM3 соединён с входом ITR2 таймера TIM2. И тут выясняется, что в Reference manual рассматриваемый случай описан в разделе «Using one timer as prescaler for another», но там допущена ошибка: вместо ITR2 там указан ITR1. Я джва года час искал ошибку в коде!

Ну что ж, переходим от слов к делу:
#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>

void init_timer(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

  /* Выбираем вход триггера от TIM3 (ITR2) */
  TIM_SelectInputTrigger(TIM2, TIM_TS_ITR2);
  /* Включаем тактирование от внешнего источника.
     Теперь TIM2 тактируется по ITR2. */
  TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable);
  TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_External1);

  /* Выходной триггер будет срабатывать по переполнению */
  TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);

  /* Обнуляем оба счётчика ( или один 32-битный - кому как :) */
  TIM_SetCounter(TIM2, 0);
  TIM_SetCounter(TIM3, 0);

  /* Поехали! */
  TIM_Cmd(TIM2, ENABLE);
  TIM_Cmd(TIM3, ENABLE);
}

int main(void)
{
  init_timer();

  while (1)
  {
    //  ...  //

    /* Где-то здесь нам вдруг захотелось прочитать значение нашего
       импровизированного 32-битного счётчика: для этого "сшиваем"
       два 16-битных счётчика вместе. Т.к. TIM2 тактируется от TIM3,
       его счётчик будет старшей половиной 32-битного значения. */
    const uint16_t high = TIM_GetCounter(TIM2), low = TIM_GetCounter(TIM3);
    const uint32_t counter = (uint32_t)(((uint32_t)high << 16) | low);

    /* Теперь у нас в counter - значение 32-битного счётчика. PROFIT! */

    //  ...  //
  }
}


Фиксация параметров

Есть возможность зафиксировать параметры таймера до следующего сброса (reset) МК. За это отвечает поле TIM_LOCKLevel структуры TIM_BDTRInitTypeDef. Фиксация возможна лишь единожды — изменить параметры фиксации нельзя, пока не произойдёт сброс. Функция имеет три уровня, которым соответствуют следующие наборы фиксируемых параметров:
  • TIM_LOCKLevel_1
    Фиксируются настройки dead-time, break enable, break polarity, AOE и состояний выводов таймера в режиме break.
  • TIM_LOCKLevel_2
    TIM_LOCKLevel_1 + фиксируются output polarity для каналов таймера, а также настройки таймера в режиме break (вывод вкл/выкл).
  • TIM_LOCKLevel_3
    TIM_LOCKLevel_2 + фиксируются настройки сравнения вывода и ШИМ.
  • +5
  • 18 февраля 2012, 11:27
  • burjui

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

RSS свернуть / развернуть
+
+1
Отличные статьи пишешь! Все по полочкам, долго искал в инете, почти нигде такого нет. Поменьше бы паузы между публикациями, и тогда вообще было бы замечательно!(:
avatar

kondnn

  • 20 февраля 2012, 17:55
+
0
Спасибо, рад, что статьи вам полезны (:

Паузы — это да. Всё ещё не умею писать в режиме «поток сознания»: прежде чем написать хоть строчку, начинаю на автомате продумывать все варианты, даже ненужные — дурацкая программерская привычка, которая тут не к месту |:
avatar

burjui

  • 21 февраля 2012, 08:42
+
0
Какую дальше тему планируешь разбирать?
avatar

kondnn

  • 21 февраля 2012, 09:38
+
0
АЦП, а затем — ЦАП.
avatar

burjui

  • 21 февраля 2012, 22:54
+
0
Подскажите, а захват (input capture) по обоим фронтам есть в МК серии F1xx? Я нашел в реф мане, что в F4xx это есть, но никак не могу найти это для сотой серии.
avatar

phantomlord

  • 22 февраля 2012, 09:20
+
0
Есть он и у STM32F100xx, просто в поле TIM_ICPolarity структуры TIM_ICInitTypeDef нужно внести значение TIM_ICPolarity_BothEdge.

А что вы смотрели для STM32F100xx? Я в каждой статье ссылаюсь на STM32F100xx Reference manual (RM0041), там всё расписано.
avatar

burjui

  • 22 февраля 2012, 15:17
+
0
Именно реф ман я и смотрел. Для f2 и f4 нашел сразу, а для f1 не вижу что-то. А можете страницу в реф мане указать?
avatar

phantomlord

  • 23 февраля 2012, 09:20
+
+1
Например, в RM0041 (Doc ID 16188 rev 4) на странице 371:

Bit 1 CC1P: Capture/Compare 1 output Polarity.
  ...
  CC1 channel configured as input:
  ...
  11: noninverted/both edges
  Circuit is sensitive to both TI1FP1 rising and
  falling edges (capture mode), TI1FP1 is not inverted.
avatar

burjui

  • 23 февраля 2012, 10:54
+
0
Приветствую. Только начал осваивать STM32, два дня сидел… не смог настроить таймер на необходимые функции, а время, блин, как всегда поджимает…
Подскажите, а если можно, то примерчик накидайте, как настроить таймер следующим образом: тактируется со входа ETR, по событию на CH1 захватывает значение, сбрасывает счет таймера и пересылает значение (по DMA) в какую-то ячейку памяти?
Пока только смог затактировать таймер внешним сигналом, и вызывать прерывание по захвату… в котором сбрасываю счет. И как-то не уверен, что все правильно настроил.
Пользуюсь coocox-ом. Огромное спасибо!
avatar

step

  • 16 апреля 2012, 06:20
+
0
DMA я сам ещё не трогал, так что тут помочь не могу.
avatar

burjui

  • 16 апреля 2012, 11:36
+
0
Продолжения серии не будет? :-(
avatar

knkd

  • 2 мая 2012, 15:39
+
0
Будет, но в другом формате. Курсов по периферии STM32 наплодилось — мама не горюй, а у меня, как назло, нехватка времени и творческий и трудовой тупик. Есть у меня идея писать про работу с SD-карточками, начиная с самого мяса (общения по SPI, усё руками) и заканчивая периферией SDIO, которая сама уже с извилинами (протокол, прерывания, DMA).

В целом, сконцентрироваться хочу на девайсах, а не на STM32. В конце концов, эти МК — просто инструмент, который надо изучать в деле, а не только в теории.

Сожалею, что заставляю ждать, но обстоятельства неумолимы.
avatar

burjui

  • 3 мая 2012, 22:24
+
0
То есть скоро можно будет увидеть новый урок? А то что затишье…
avatar

Dr_Semyon

  • 14 мая 2012, 20:51
+
0
Это будет скорее поучительное исследование. И не скоро, а недели через полторы-две. Про SD-карты, файловые системы и снова о модели драйверов, как в посте по библиотеку для HD44780.
avatar

burjui

  • 15 мая 2012, 03:18
+
0
откуда мы знаем что канал 1 это порт А — 6пин.
И как сделать тоже для кнопки stm32f4discovery? Хочу попробовать с дребезгом разобраться
avatar

lamazavr

  • 6 мая 2012, 19:54
+
0
В даташите, вестимо. STM32: Урок 3 — Документация, второй абзац, ключевое слово — распиновка.
avatar

burjui

  • 7 мая 2012, 02:17
+
0
lamazavr
AVRщик?

avatar

burjui

  • 7 мая 2012, 02:25
+
0
А нету кода по работе с ШИМ и TIM1? Не могу запустить. Точнее запустил, но работает как-то через раз. На TIM2 все идет без проблем.
avatar

Apik

  • 15 мая 2012, 05:58
+
0
Вопрос отпал, настроил.
avatar

Apik

  • 15 мая 2012, 09:41
+
0
Добрый день! У меня stm32l-discovery (STM32L152RBT6), пытаюсь определить частоту импульсов на входе PA01… использую для этого TIM2. TIM2 тактируется (TIM_GetCounter выдает изменяющиеся значения) даже возникает прерывание если в TIM_ICInit указываю TIM_Channel_2, но когда пытаюсь в прерывании считать TIM_GetCapture1 — там упорно 0. При попытке сконфигурировать TIM_PWMIConfig с последующим TIM_SelectInputTrigger TIM_SelectSlaveMode… прерывание пропадает… Такое впечатление что PA01 не подключено к входу счетчика, и не совсем ясно от чего срабатывает в такой ситуации прерывание. Не могли бы вы подсказать как найти в чем дело?
avatar

tarasii

  • 13 сентября 2012, 11:40
+
0
Сегодня мои телепатические способности дремлют, поэтому шлите код в личку — посмотрю.
avatar

burjui

  • 13 сентября 2012, 13:31
+
0
для L152 не хватало GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2);…
avatar

tarasii

  • 17 сентября 2012, 13:48
+
0
Повольте задать вопрос:

из первого примера, такой коменнтарий

/* Настраиваем захват сигнала:
— канал: 1
— счёт: по нарастанию
— источник: напрямую со входа
— делитель: отключен
— фильтр: отключен */

Я так понял, что пин B15 генерирует «сигнал» с периодом 146мс, а пин А6 принимает этот «сигнал».
И что бы все работало нужно пины(B15 и А6) соеденить перемычкой?

avatar

DeamonMV

  • 17 декабря 2012, 18:10
+
0
Мое предположение подтвердилось, порты замыкаем на себя.

У меня другой вопрос:
Почему значение переменных постоянно скачут до заполнения 16бит; capture1 и capture2 только в первую итерацию верные(с brakepoint на строчке const uint16_t period = uint16_time_diff(capture2, capture1); т.е. 73 и 219 соответственно, а уже в следующею итерацию они имеют вид 219 и 9934 соответственно… их значения каждый раз разные, закономерности не могу увидеть.

Почему так происходит. здесь речь я ввиду о первом примере.
для debug использую coocox CoIDE 1.6.0 и GNU tools ARM 4.6.2012q4
avatar

DeamonMV

  • 19 декабря 2012, 23:28
+
0
Так там же дальше написано в обработчике прерывания:

void TIM3_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
  {
    ...

    /* Запоминаем предыдущее измерение и считываем текущее */
    capture1 = capture2;
    capture2 = TIM_GetCapture1(TIM3);

    /* Для корректной обработки нужно минимум два измерения */
    if (!capture_is_first)
      capture_is_ready = 1;

Короче говоря, значение периода сигнала, идущего на вход таймера — это разница между текущим и предыдущим значениями счётчика таймера. А что он вам насчитает — миллисекунды или что ещё, уже зависит от настройки предделителя таймера.
avatar

burjui

  • 19 декабря 2012, 23:40
+
0
Спасибо за ответ.

почитал про TIM_Prescaler TIM_Period выставил параметры так:

timer_base.TIM_Prescaler = 24 - 1;
timer_base.TIM_Period = 1000 - 1;


Здесь частота выходит 1КГц — с этой частотой будет опрашиваться порт и каждый опрос
будет записываться в переменную таймера -если я правильно понял.

период сигнала который генерируется 146мс == 6.849 Гц. частота таймера 1КГц т.е. в этот период(146мс) должно влезть 146 «опросов».

Включаю debug ставлю brakepoint на строку::
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);

И получаю такие цифры. Стоит сразу отметить что systick имеет увиличение значения с каждым прирываем на 146мс. ::

интерация  1  2    3   4   5
systic:   73  219  365 511 657
capture1:  0  0    34  628 526
capture2:  0  34   628 526 681


Посмотрел еще раз значение константы period установив brakepoint в строчке::
const uint16_t period = uint16_time_diff(capture2, capture1);
вот такие там значеня:: 2048 2 681 54 64889 (тоже пять итераций)

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

avatar

DeamonMV

  • 20 декабря 2012, 19:17
+
0
Спасибо большое за статьи, с удовольствием буду читать! Извините за тупой неумный вопрос: а где можно почитать про API функции? Почему-то не могу найти на st.com соответствующего документа.
avatar

Elcarnado

  • 1 марта 2013, 14:41
+
0
Вполне возможно, что его и нет. Я не искал, а вместо этого просто смотрел в заголовочные файлы — там достаточно подробные комментарии перед определениями функций и прочего.
avatar

burjui

  • 1 марта 2013, 15:29
+
0
Функция
uint16_t uint16_time_diff(uint16_t now, uint16_t before)
{
  return (now >= before) ? (now - before) : (UINT16_MAX - before + now);
}

работает не правильно!
При значении before=65535 и now=0 результат будет 0, а должен быть 1.
Правильно будет так:
uint16_t uint16_time_diff(uint16_t now, uint16_t before)
{
  return (now >= before) ? (now - before) : ((UINT16_MAX +1) - before + now);
}

avatar

Masslakoff

  • 15 августа 2013, 14:30
+
0
Решил подключить энкодер к stm, решил воспользоваться вашим примером, но возникли вопрос (с языком с++ напряжно.)Keil ругается на присвоение константы
const Direction direction = captured_direction; 

Что выполняет данная строчка? присваивает переменой direction одно из двух значений FORWARD или BACKWARD. Правильно ли я понял?
avatar

496385290

  • 5 марта 2014, 18:03
+
0
Правильно. Так странно, на первый взгляд, сделано по следующей причине: переменная captured_direction изменяется в прерывании (поэтому она объявлена как volatile) — это значит, что если мы станем использовать captured_direction в расчётах, может получиться такая картина:

// *** В данный момент captured_direction = FORWARD
if (captured_direction == BACKWARD)
{
   // что-нибудь делаем
}

// *** Здесь произошло прерывание таймера
// *** Сейчас уже captured_direction = BACKWARD,
// *** потому что переменная была перезаписана в прерывании.
// *** С этого момента любые расчёты, которые учитывают captured_direction,
// *** будут учитывать новое значение BACKWARD.

Вот поэтому сначала константа direction инициализируется значением captured_direction, а уже потом рассчёты предполагается вести с direction, которая в прерывании не меняется. Короче, мы работаем с копией данных, которые могут измениться в прерывании.
avatar

burjui

  • 5 марта 2014, 19:51
+
0
Немного поправил ваш пример, чтобы Keil не матерился.
При такой записи, выдает:
main.c(32): error: #268: declaration may not appear after executable statement in block
int main(void)
{
  init_gpio();
  init_timer();

  while (1)
  {
    if (capture_is_ready)
    {
      NVIC_DisableIRQ(TIM3_IRQn);
      capture_is_ready = 0;
      const Direction direction = captured_direction; // <---------
      NVIC_EnableIRQ(TIM3_IRQn);

      /* Обрабатываем direction ... */
    }
  }
}


Но если все записать так же, но указать константу перед выключением прерывания, то Keil радуется что все ОК!
int main(void)
{
  init_gpio();
  init_timer();

  while (1)
  {
    if (capture_is_ready)
    {
      const Direction direction = captured_direction;  // <---------
      NVIC_DisableIRQ(TIM3_IRQn);
      capture_is_ready = 0;
      NVIC_EnableIRQ(TIM3_IRQn);

      /* Обрабатываем direction ... */
    }
  }
}

avatar

496385290

  • 5 марта 2014, 21:16
+
0
Всем привет. А кто-нибудь пробовал соединять последовательно таймеры в режиме Encoder. Чтобы повысить разрядность до 32 bit. Если соединять как написано здесь, то один работает нормально как энкодер, т.е. младший регистр и up, и down; а вот старший только up при каждом проходе через 0. Или возможно ли как-нибудь сделать, чтобы Slave Timer был двунаправленным. При Overflow — был up, а при Underflow — down?
avatar

Oleg

  • 24 февраля 2015, 08:26
+
0
Объясните пожалуйста, предложенный режим захвата параметров ШИМ возможен только для каналов 1 и 2? если возможность измерять длительность импульсов ( пусть последовательно во времени) на разных ножках (соответственно разных каналов) но одним и тем же таймером (TIM4, например)? Ведь у него целых 4 канала.

Цитата:«Также существует режим захвата ШИМ. На самом деле, это не отдельный режим, а просто особое сочетание настроек с таким эффектом. Таймер настраивается так, чтобы один канал ловил фронты и сбрасывал счётчик таймера, а второй ловил спады — тогда первый будет захватывать период ШИМ, а второй — заполнение. При этом оба канала подключаются к одному и тому же физическому входу.»
avatar

mseserge

  • 27 февраля 2016, 16:39
+
0
Прошу помощи у тех кто съел зубы на STM32F103C8T6 — задача запустить счетный вход, таймера ТМх а второй ТМх — отчет 1 сек. нужно замерять импульсы от 3кГц до 10кГц большой точности не надо (шаг 50Гц) _|-|______|-|____ (форма сигнала). Готовые на зх таймерах (а у меня только 1 — 4) но 4й занят уже.
Можно вход это прерывание,(не важно фронт или спад) кол-во тиков за это время
/ на число. Делаю контроль вращения вентилятора. 5000 об/мин (2 металических болта) = 10000 импульсов/мин.
avatar

pshonia

  • 29 августа 2016, 22:39
+
0
Добрый день. Я только начал знакомиться с ШИМ. Поставлена задача — реализовать 5 каналов для генерации частоты. Решил я для этого использовать 2 таймера — TIM1 и TIM3. Так вот, например, у таймера TIM3 есть 4 канала для генерации ШИМ. Подскажите, пожалуйста, как настроить конкретный канал? Просто я в вашем примере этого не нашел
avatar

Auratos

  • 25 мая 2018, 14:36
+
0
Или я неправильно понял концепцию таймеров для этой цели? Просто мне нужно генерировать 5 разных частот. Один таймер генерирует частоту на свои 4 канала одинаковую или есть настройка для каждого канала? Подскажите, пожалуйста
avatar

Auratos

  • 25 мая 2018, 14:46

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