В этой небольшой статье хочу рассказать вам о различных методах измерения частоты прямоугольного сигнала с помощью микроконтроллера STM32.  В процессе работы над одной из железок возникла необходимость организовать несколько выводов, которые бы измеряли частоту входного сигнала. Опробовав несколько разных вариантов, я решил, что негоже примерам пылиться на задворках диска D и стоит ими поделиться с сообществом. Надеюсь кому-то, находящемся в похожей ситуации, этот материал будет полезен. Материал в первую очередь рассчитан на новичков. Начальные условия: частота входного сигнала от 0 до 10 кГц. Микроконтроллер STM32F103C8T6, всем известная плата bluepill. Библиотека HAL. Источником сигнала, частота которого будет измеряться, для проверки работоспособности будет служить двухканальный генератор частоты JDS6600. Я буду использовать модуль CH340G (Преобразователь USB - UART) для передачи данных в терминал (terminal v1.9b) для наглядности. В него я буду посылать полученное после обработки значение.  Рассмотрим первый способ – измерение частоты с помощью таймера. Подключение: Генератор частоты напрямую подключен к выводам микроконтроллера PA0 и PA2. Выводы земли объединены. Настройка проекта: перейдем в STM32IDE и создаем проект под наш контроллер. После настройки источника тактирования включаем UART для отправки значений: Частоту тактирования настроил на 64 МГц. В примере я буду использовать Timer 2. Настроим таймер на обработку сразу двух сигналов: Канал 1 — это основной (direct) канал, а канал 2 это косвенный (indirect). Косвенный канал не имеет отдельного вывода. Аналогично для канала 3 и 4. Захват частоты происходит следующим образом – первый канал реагирует на передний фронт, и мы фиксируем время начала нового периода. После второй канал фиксирует задний фронт, и мы фиксируем время окончания импульса. Затем первый канал фиксирует начало нового периода / окончание предыдущего. Имея эти три временные точки, можно посчитать длительность периода и скважность. Значение делителя и периода Timer 2:  Значение предделителя определяет шаг, с которым будет считать таймер. Чем меньше шаг, тем точнее результат, но тогда можно столкнуться с тем, что в длительность одного периода таймера не будет помещаться захватываемый сигнал. При частоте тактирования таймера 64 МГц и предделителе = 64 (предделитель в программе выставляется на 1 меньше) получаем, что длительность одного тика таймера равна микросекунде. Период (Counter periode) определяет, до какого значения будет считать таймер до переполнения. В данной задаче лучше его оставить в максимальном значение. Фильтр входного сигнала (Input Filter) используется для устранения помех и шумов, которые могут возникать на входе таймера. Он позволяет фильтровать входной сигнал и принимать во внимание только стабильные и длительные изменения сигнала, игнорируя кратковременные нежелательные помехи. Если входной сигнал стабилен и не подвержен сильным помехам, можно установить значение фильтра на минимальное или даже отключить его. Однако, при работе с шумными или нестабильными входными сигналами, может потребоваться увеличение значения фильтра для надежной и стабильной работы таймера. Я выставляю Input Filter в максимум. Включим прерывание таймера: Пишем код: в начале заведем глобальные переменные для хранения длительностей: где в переменной start будет храниться значение начала периода, end_imp будет храниться значение конца импульса, end_ per будет храниться значение конца периода. 1 и 2 соответственно CH1+CH2 и CH3+CH4. Тут будем хранить вычисленные переменные и будем выставлять флаг о том, что пришел сигнал. Далее включим все каналы нашего таймера и заведем массив для отправки по UART: После срабатывания прерывания по захвату программа будет попадать в callback (эта функция, которая вызывается после определенного события/прерывания) функцию, которая описана ниже: В ней выставляется флаг «flag_IC», по которому будем определять приход новых данных. В отдельной функции будем вычислять значение периода и коэффициента заполнения: Условие «if (end_per1 > start1)» нужно для того, чтобы не обсчитывать импульсы, которые попадают на переполнение таймера, когда начало периода было в одном цикле таймера, а конец в другом. Но обсчёт такого случая тоже можно реализовать. В основном цикле отслеживаем значение флага, как только он стал равен единице, вычисляем значение частоты и коэффициента заполнения, после отправляем данные по UART в терминал и сбрасываем значение флага: Я запускаю генератор частоты с вот такими настройками:  Оправляем два прямоугольных сигнала с частотой 1кГц и 850 Гц. Теперь посмотрим, что нам пришло на терминал: В терминале выставляем скорость 115200 и получаем результат. Теперь проверим этот же код, но на динамический сигнал. Я покрутил значение частоты на первом канале от 1 кГц до 8 кГц и обратно с шагом в 1 кГц и вот что получил:  В принципе работает, но в реальных системах частота может менять сильно быстрее, чем я кручу ручку, так что тест с динамическим сигналом правильно считать упрощенным.  Достоинства и недостатки: + - Есть возможность измерить скважность импульса. Необходимы таймеры, по одному таймеру на два входа. Маленького контроллера под большое количество сигналов может просто не хватить, так как многие другие задачи тоже требуют свободных таймеров. В отличие от других методов, может за 1 период посчитать частоту сигнала (при отключенном фильтре). Ограничение диапазона захвата частоты. В данном примере таймер считает до 65 мс, и если период сигнала больше этого значения,   например 100 мс (10 Гц), то таймер не сможет его захватить без дополнительных   программных ухищрений.   Если входной сигнал пропал – значения частоты не обнулится само по себе. Можно проверять, меняется ли значение частоты, и если   нет, то обнулять значение частоты, или же выставлять какой-нибудь флаг при   попадании в callback. Теперь рассмотрим вариант измерения значения частоты с использованием вывода EXTI. Выводы EXTI (External Interrupt) у микроконтроллеров STM32 предназначены для обработки внешних прерываний от различных источников. EXTI позволяет реагировать на изменения состояния внешних сигналов и генерировать прерывания для обработки этих событий. Логика работы довольно проста – каждый раз, когда приходит импульс, мы по прерыванию будем инкрементировать значение импульсов, а раз в секунду смотреть, сколько импульсов пришло. Так и получим необходимую нам частоту. Подключение: генератор частоты подключен к выводам PA0 и PA1. Настройка проекта: В настройках контроллера включаем выводы PA0 и PA1 как выводы EXTI. Также включаем прерывание у них. Прерывание будет срабатывать на восходящий фронт сигнала. Также надо включить таймер, который будет отсчитывать одну секунду, в прерывании которого мы будем фиксировать нашу входную частоту:  Пишем код:   Заводим переменные. Первые две будут инкрементироваться каждый раз, когда приходит импульс. Каждую секунду по прерыванию таймера будет вывешиваться флаг, при изменении которого мы будем фиксировать значение частоты и сбрасывать счетчики импульсов. Вот так будет выглядеть callback по EXTI выводам: И вот так по таймеру: Значение количества импульсов за период сбрасываем каждую секунду. Теперь отправляем значение частоты по UART: Подадим 1 кГц на первый канал и 5 кГц на втором: Вот результат захвата динамического сигнала (канал ch2 статичный): Отлично, все работает. Теперь о достоинствах и недостатках: + - Довольно простой метод Данные приходят довольно редко – в данном случае раз в   секунду Нужен всего один таймер   По умолчанию сразу получаем усредненный результат И последний рассмотренный мною метод – захват с помощью ETR2. При настройке источника тактирования таймера можно выбрать вывод ETR2 (External Trigger Input 2), и тогда таймер в качестве источника тактирования будет использовать внешний источник. На этот вывод будет подаваться замеряемый сигнал.  Реализуем частотомер второго рода. Основной принцип работы частотомера второго рода заключается в сравнении частоты входящего сигнала с частотой, генерируемой внутри прибора, в нашем случае длительность периода внешнего сигнала будет измеряться в кол-ве тиков другого таймера. Заводим timer 2, который просто считает импульсы с максимально возможной частотой. По приходу первого импульса на ETR2 запускается timer 4. Когда на timer 2 придет, допустим, сотый импульс, мы фиксируем, сколько импульсов насчитал timer 4. Делим это значение на 100 и получаем период захватываемого сигнала. Картинка для чуть лучшего понимания: Настройка проекта: настроим таймера 2, на который будет подаваться входной сигнал. Считает до 100го импульса, а потом счет сбрасывается и начинается новый период. Именно в этот момент мы будем фиксировать кол-во отсчитанного времени на 100 периодов входного сигнала. Настройка таймера 4 для отсчета времени:    У обоих таймеров необходимо включить прерывание. Переходим к коду: создаем глобальные переменные: Создаем callback для обработки данных. Суть в том, что таймер 4 считает с частотой больше частоты входного сигнала и будет много раз переполняться. Кол-во переполнений таймера 4 как раз и будем хранить в переменной num_per_tim, поэтому в callback таймера 4 необходимо каждый раз инкрементировать счетчик периодов таймера. А в callback таймера 2 программа будет попадать, когда пришли все 100 импульсов и будет фиксироваться затраченное количество импульсов таймера 4.  Теперь, отслеживая состояние flag_data можно считать частоту: В начале вычисляем суммарное потраченное время, а затем делим его на 100 и вуаля, значение частоты готово. Подадим на вывод PA0 сигнал с частотой 10 кГц: Теоретически, этот метод должен быть с самым большим диапазоном частоты входного сигнала. Попробуем 100 кГц:  Ну и 1 МГц: Последний результат уже с большой погрешностью, но, тем не менее, работает.  Достоинства и недостатки: + - Больший диапазон относительно других Нужен целый таймер с выводом ETR на каждый отдельный таймер для счета Конечно, тут описаны не все возможные способы измерения частоты входного сигнала, а только несколько, с которыми я столкнулся.  Некоторые из них были проверены только на макете и в реальных задачах могут вести себя отлично от того, что получилось у меня.  Для себя я выбрал вариант с выводами EXTI, так как в устройстве не требуется быстродействие и большая точность, но требуется много каналов захвата. Первый метод мне понравился меньше всех остальных, потому что в остальных вариантах мы получаем сразу некое усредненное значение, что мне подходило намного лучше. Ссылка на проекты  Микроконтроллеры, электроника, 3д печать