Top.Mail.Ru

STM32 и Timer Input Capture. Режим захвата сигнала.

Всех приветствую! Сегодня мы снова займемся работой с таймерами в STM32 и рассмотрим один из многочисленных режимов работы, о котором раньше, по какой-то причине, у нас на сайте статей не было. Речь идет о режиме захвата сигнала (Input Capture) - крайне полезном и очень часто используемом режиме. Сначала рассмотрим, в чем, в принципе, заключается работа таймера при захвате сигнала, а затем создадим программу, которая все это использует. Причем просто отслеживать сигнал на входе не так интересно, поэтому чуть усложним задачу - будем измерять параметры подаваемого ШИМ сигнала.

Итак, идея режима Input Capture заключается в следующем. На порт микроконтроллера, который настроен в качестве входа таймера для захвата, подается сигнал. При изменении уровня сигнала в регистр таймера CCRx записывается текущее значение счетного регистра этого таймера. Таким образом, можно отследить момент прихода импульса, либо определить время между двумя последовательными импульсами, либо же, чем мы сейчас и займемся, измерить период и длительность импульса (время включения) ШИМ-сигнала.

У каждого из каналов таймера в STM32 есть свой собственный регистр CCR, в котором и будет сохраняться значение счетчика в момент прихода импульса:

  • Канал 1 - регистр CCR1
  • Канал 2 - регистр CCR2
  • Канал 3 - регистр CCR3
  • Канал 4 - регистр CCR4

Кроме того, два канала таймера можно настроить так, чтобы они оба использовали сигнал с одной и той же ножки контроллера. То есть сигнал у нас один, заходит он на один вывод контроллера, а дальше уже к нему имеют доступ два канала таймера. В этом случае один из каналов настраивается на работу в прямом режиме (direct), а второй - в косвенном (indirect).

Собственно, именно совместная работа каналов таймера нам и потребуется для измерения периода и длительности импульса ШИМ-сигнала:

ШИМ-сигнал.

Алгоритм будет такой:

  • по приходу заднего фронта импульса, 2-ой канал таймера захватывает значение счетчика и сохраняет его в регистр CCR2.
  • по приходу переднего фронта аналогичные действия производит 1-ый канал таймера.
  • и самое главное - нам нужно по приходу переднего фронта считать значения из регистров CCR1 и CCR2 и обнулить счетный регистр таймера CNT.

Таким образом, значение регистра CCR1 будет соответствовать периоду сигнала, а значение CCR2 - длительности импульса:

Измерение периода и длительности импульса ШИМ-сигнала.

Вот и все, приступаем к реализации. Для начала выбираем в STM32CubeMx таймер, который будет использоваться, например, Timer 2, и настраиваем два из его каналов на работу в прямом и косвенном режимах:

STM32CubeMx режим захвата сигнала.

Обратите внимание, CubeMx активировал ножку PA0 для работы в качестве входа для таймера:

STM32CubeMx, вход для захвата сигнала.

Далее выставляем "традиционные" настройки таймера, а именно, предделитель частоты и период. В данном примере мы не будем использовать событие переполнения таймера, поэтому период, по большому счету, роли не играет. Но! Длительность периода должна быть больше, чем период измеряемого сигнала, иначе таймер обнулит свой счетчик автоматически еще до прихода фронта и измеренное значение окажется неверным:

Переполнение таймера.

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

Настройки таймера в STM32CubeMx.

При таком значении предделителя частота таймера составит 1 МГц. Тогда один "тик" таймера соответствует 1 мкс. При величине периода 30000 получаем период, равный 30 мс. А значит период подаваемого сигнала для этих конкретных значений не должен превышать 30 мс.

А теперь переходим к специфичным настройкам таймера, которые используются исключительно для режима Input Capture:

Настройки режима захвата сигнала.

Нас тут интересует, в первую очередь, полярность сигнала - передний фронт для канала 1 и задний фронт для канала 2. Кроме того, можно настроить:

  • Prescaler Division Ratio - дополнительный предделитель входного сигнала. Если указать, к примеру, значение 4, то захват будет производиться по каждому 4-му фронту. В данном случае нам необходимо захватывать все импульсы без исключения.
  • Input Filter - позволяет настроить фильтрацию входного сигнала. Работает этот фильтр по стандартной схеме - подтверждает наличие сигнала на входе только по прошествии некоторого времени (некоторого количества циклов таймера). То есть, при изменении сигнала на входе фильтр в течение N циклов наблюдает за новым уровнем сигнала. И, если, в течении этих N отсчетов сигнал сохраняет свой новый уровень, то фильтр передает информацию об изменении уровня дальше. Это позволяет убрать случайные всплески и возможную нестабильность сигнала на входе.

Так, теперь включаем глобальное прерывание таймера и на этом заканчиваем настройку периферии:

Прерывания таймера.

Переходим к сгенерированному проекту. В общем-то, мы обсудили на словах все необходимые действия, теперь просто реализуем в программе. Первый делом включаем выбранные каналы таймера на работу в режиме Input Capture:

  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);

И добавляем callback для прерывания по захвату сигнала:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Instance == TIM2)
	{
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		{
			TIM2->CNT = 0;
		
			period = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
			pulseWidth = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);
		}
	}
}

В соответствии с алгоритмом нас интересует только момент захвата переднего фронта, а это канал 1. Поэтому сразу же проверяем, чем вызвано прерывание и реагируем только на то, что нам нужно:

if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)

Значение в регистре CCR2 второго канала сохраняется аппаратно по приходу заднего фронта. Так что считываем значения CCR1 и CCR2 и получаем период (period) и длительность (pulseWidth) ШИМ-сигнала:

period = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
pulseWidth = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);

И, конечно, же обнуляем счетный регистр таймера:

TIM2->CNT = 0;

Результаты, соответственно, в отсчетах таймера, что в данном примере составляет 1 мкс. Собираем проект и запускаем отладку. В соответствующих переменных будут отображаться измеренные значения 👍

Ссылка на проект - MT_Timer_IC_Example.

Подписаться
Уведомить о
guest

26 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Павел
Павел
4 лет назад

Результат в микросекундах?

Сергей
Сергей
4 лет назад

Спасибо вам за ваш труд!
Вы очень помогли мне(уверен, что не только мне) в освоении HAL!
Мне начинает нравиться STM!!!)))))

Сергей
Сергей
Ответ на комментарий  Aveal
4 лет назад

Разрешите маленькое примечание:
Фактически нижнюю границу измеряемой частоты можно вычислять, как частота тактирования/counter period.
К примеру для 8000000/65535=122Гц
Если нужно меньше то ставим Prescaler например 4.
После этого частота тактирования станет равна 2000000.
Соответственно нижний порог станет 2000000/65535=30Гц

Владимир
Владимир
3 лет назад

Небольшая ремарка: TIM1->CNT = 0; лучше делать ДО чтения CCR регистров. Т.к. наткнулся на то, что поздний сброс в этой call-back функции "забирает" тики у таймера, вероятно из-за того, что в момент нахождения в этой функции запрещены прерывания.

Владимир
Владимир
Ответ на комментарий  Aveal
3 лет назад

Скорее так: конечно запрет прерываний на тики не влиял. У меня просто медлительная отсылка в uart добавляла лишним тики, если не сбрасывать их в начале функции.

Александр
Александр
3 лет назад

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

Sabr
Sabr
2 лет назад

Здравствуй у вас в проекте используется два канала для измерения переода и положительного импультся, а можно ли сделать что чтобы с помощью одного канала узнать только положительный импульс

Asa
Asa
1 год назад

Спасибо за ваш труд ! Благодарю! Удач вам во всех делах!

Serg
Serg
10 месяцев назад

Хорошая статья. Можно использовать ее, в том числе, для Manchester II. Может получиться декодер адаптированный к тактовой частоте ...

Алекс
Алекс
8 месяцев назад

Доброго) А как все-таки добавить функцию для прерывания по переполнению?

Алекс
Алекс
Ответ на комментарий  Aveal
8 месяцев назад

Немного не то имел ввиду, опишу пожалуй всю суть проблемы). Вход таймера у меня настроен как вход датчика холла. XOR ON / Hall Sensor mode. Мне нужно чтобы прерывания приходили и по переполнению и по захвату. Я сделал так:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
      {
	HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_7);
//	tim_period = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
	tim_period  = __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1);
      }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM2)
    {
      HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_11);
	tim_period = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
    }
}

Беда в том, что в HAL_TIM_PeriodElapsedCallback приходят и прерывания по захвату и по переполнению. Я не понимаю почему так. Другой таймер у меня настроен просто на захват (input capture) + переполнение в этих же коллбэках и все работает адекватно - переполнение приходит в HAL_TIM_PeriodElapsedCallback а захват в HAL_TIM_IC_CaptureCallback.

Меня бы устроил и тот вариант, что сейчас имею, если бы при вызове

 HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);

когда прерывание приходит по переполнению, а не по захвату, эта функция бы мне возвращала 0. Но она возвращает последнее принятое значение по захвату.

TIM2->CNT = 0;

проблему не решает.

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

Алекс
Алекс
Ответ на комментарий  Aveal
8 месяцев назад

Скинул Вам на почту.

Алес
Алес
11 дней назад

День добрый. Сделал как описано, но для таймера TIM22, не работает. Прерывания от таймера нет, хотя в NVIC галочку поставил

Алес
Алес
Ответ на комментарий  Aveal
11 дней назад

Нет, проверил осциллографом, все присутствует. Сейчас пытаюсь высвободить таймер 2, что бы проверить, может что-то в архитектуре таймеров отличается.

Алес
Алес
Ответ на комментарий  Aveal
11 дней назад

Прошу прощения-мой косяк, пытался использовать функцию для обработки прерывания от таймера HAL_TIM_PeriodElapsedCallback вместо HAL_TIM_IC_CaptureCallback. 

26
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x