Всех приветствую! Сегодня мы снова займемся работой с таймерами в 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, и настраиваем два из его каналов на работу в прямом и косвенном режимах:
Обратите внимание, CubeMx активировал ножку PA0 для работы в качестве входа для таймера:
Далее выставляем "традиционные" настройки таймера, а именно, предделитель частоты и период. В данном примере мы не будем использовать событие переполнения таймера, поэтому период, по большому счету, роли не играет. Но! Длительность периода должна быть больше, чем период измеряемого сигнала, иначе таймер обнулит свой счетчик автоматически еще до прихода фронта и измеренное значение окажется неверным:
На самом деле, и в данном случае можно обеспечить выполнение задачи. Надо просто в прерывании по переполнению таймера увеличивать счетчик переполнений и учитывать его значение при расчете параметров захваченного сигнала. Для примера же задаем так:
При таком значении предделителя частота таймера составит 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.