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 режим захвата сигнала.

Обратите внимание, Cube активировал ножку 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
11 комментариев
старее
новее большинство голосов
Inline Feedbacks
View all comments
Павел
Павел
1 год назад

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

Сергей
Сергей
11 месяцев назад

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

Сергей
Сергей
Reply to  Aveal
11 месяцев назад

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

Last edited 11 месяцев назад by Сергей
Владимир
Владимир
9 месяцев назад

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

Владимир
Владимир
Reply to  Aveal
9 месяцев назад

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

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

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

11
0
Would love your thoughts, please comment.x
()
x