Всех приветствую! Сегодня мы снова займемся работой с таймерами в 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.
Результат в микросекундах?
В "тиках" таймера, в этом примере, да, в микросекундах.
Спасибо вам за ваш труд!
Вы очень помогли мне(уверен, что не только мне) в освоении HAL!
Мне начинает нравиться STM!!!)))))
Большое спасибо за отличный отзыв! =))
Разрешите маленькое примечание:
Фактически нижнюю границу измеряемой частоты можно вычислять, как частота тактирования/counter period.
К примеру для 8000000/65535=122Гц
Если нужно меньше то ставим Prescaler например 4.
После этого частота тактирования станет равна 2000000.
Соответственно нижний порог станет 2000000/65535=30Гц
Да,все верно!
Небольшая ремарка: TIM1->CNT = 0; лучше делать ДО чтения CCR регистров. Т.к. наткнулся на то, что поздний сброс в этой call-back функции "забирает" тики у таймера, вероятно из-за того, что в момент нахождения в этой функции запрещены прерывания.
Хм, это интересно... Изменил в статье, спасибо!
Скорее так: конечно запрет прерываний на тики не влиял. У меня просто медлительная отсылка в uart добавляла лишним тики, если не сбрасывать их в начале функции.
Подскажите а как сделать так чтобы можно было измерять длительность одиночных импульсов а не периодических? я так понял что ваша программа выдает значения по переднему фронту импульса и работает только с периодическим сигналом например с ШИМ а мне нужно чтобы прерывание сработало по переднему фронту а по заднему выдало значение, только вот не пойму как это сделать)))
Добрый день! Можно по переднему фронту обнулять счетный регистр таймера, а по заднему - считывать это значение, либо регистр CCR2. А какой фронт проверяем условием:
Здравствуй у вас в проекте используется два канала для измерения переода и положительного импультся, а можно ли сделать что чтобы с помощью одного канала узнать только положительный импульс
Добрый день, зависит от контроллера - у некоторых есть возможность захвата и переднего и заднего фронтов, у некоторых - нет. Можно реализовать через прерывания GPIO EXTI - по приходу импульсов сохранять значения таймера и из них рассчитывать.
Спасибо за ваш труд ! Благодарю! Удач вам во всех делах!
Благодарю за отзыв!
Хорошая статья. Можно использовать ее, в том числе, для Manchester II. Может получиться декодер адаптированный к тактовой частоте ...
Доброго) А как все-таки добавить функцию для прерывания по переполнению?
Доброго! Заводим счетчик и инкрементируем при переполнении:
И при расчете периода и всего остального добавляем к текущему значению:
Немного не то имел ввиду, опишу пожалуй всю суть проблемы). Вход таймера у меня настроен как вход датчика холла. XOR ON / Hall Sensor mode. Мне нужно чтобы прерывания приходили и по переполнению и по захвату. Я сделал так:
Беда в том, что в HAL_TIM_PeriodElapsedCallback приходят и прерывания по захвату и по переполнению. Я не понимаю почему так. Другой таймер у меня настроен просто на захват (input capture) + переполнение в этих же коллбэках и все работает адекватно - переполнение приходит в HAL_TIM_PeriodElapsedCallback а захват в HAL_TIM_IC_CaptureCallback.
Меня бы устроил и тот вариант, что сейчас имею, если бы при вызове
когда прерывание приходит по переполнению, а не по захвату, эта функция бы мне возвращала 0. Но она возвращает последнее принятое значение по захвату.
проблему не решает.
Можно конечно расставить в колбэках флаги, но мне кажется это костылем. Может подскажете чего?
А можешь проект скинуть?
Скинул Вам на почту.
День добрый. Сделал как описано, но для таймера TIM22, не работает. Прерывания от таймера нет, хотя в NVIC галочку поставил
С сигналом на входе не может быть проблемы?
Нет, проверил осциллографом, все присутствует. Сейчас пытаюсь высвободить таймер 2, что бы проверить, может что-то в архитектуре таймеров отличается.
Прошу прощения-мой косяк, пытался использовать функцию для обработки прерывания от таймера HAL_TIM_PeriodElapsedCallback вместо HAL_TIM_IC_CaptureCallback.
Бывает, главное, что обнаружилось быстро.