Частотомер на микроконтроллере stm32





Статья описывает способ измерения какой-либо внешней частоты с помощью микроконтроллера stm32, а заодно является косвенным продолжением статей о таймерах.

Максимальная измеряемая частота зависит от того, на какой частоте работает сам микроконтроллер. Например, если использовать популярную плату BluePill работающую на 72МГц, то можно измерить частоту около 190МГц.

Стоит уточнить, что измеряемая частота должна быть постоянная.


Вначале небольшоё объяснение как всё работает: будут задействованы три таймера. Таймер №1 будет отмерять интервал времени равный одной секунде. Таймер №2 будет тактироваться от измеряемой частоты через ETR-пин, и тем самым его счётчик (CNT, в Кубе он называется Counter Period) будет выступать в роли счётчика импульсов. Поскольку таймеры у stm32 16-ти битные, то таймер №2 сможет насчитать не более 65535 импульсов, то есть можно измерить частоту не более 65.535 КГц. Чтобы преодолеть этот барьер нужно использовать таймер №3, который будет отсчитывать количество переполнений таймера №2. То есть, тамеры №2 и №3 будут работать каскадно, как один 32-х битный таймер.

Более внятные объяснения будут сделаны по ходу статьи. И да, кроме самой платы никакого стороннего обвеса не нужно.

Программа полностью написана с использованием CubeMX и библиотеки HAL, так что никаких регистров не будет



Переходим от слов к делу. Настраиваем систему тактирования на максимум…




Активируем пин RCC_MCO (Master Clock Output), он будет выдавать «эталонную» частоту для проверки нашего частотомера…


Ставим галочку Master Clock Output и указываем максимальную скорость (Maximum output speed) для пина PA8 (RCC_MCO).


Источником для выхода MCO указываем HSE…



Готово, в дальнейшем соединим этот выход со входом и поглядим что получилось.



Таймеры


Настраиваем таймер №1



Таймер будет переполняться раз в секунду и генерировать прерывание, включите его на вкладке NVIC Settings ⇨ TIM1 update interrupt.

Из особенностей здесь три пункта:

One Pulse Mode — таймер будет отрабатывать один раз (один цикл переполнения), генерировать прерывание и останавливаться, перезапускать его будем «вручную» в колбеке.

Trigger Event Selection ⇨ Enable(CNT_EN) — триггерный сигнал (HIGH), подаётся с момента старта таймера и удерживается до его остановки, после остановки таймера сигнал снимается (переключается в LOW).

При старте, таймер №1 будет аппаратно подавать этот сигнал на таймер №2 и тем самым запускать его. При остановке таймера №1 этот сигнал будет снят и таймер №2 остановится. То есть, мы «вручную» запустили таймер №1 (пошёл отсчёт секунды), а он в свою очередь «толкает» таймер №2, который начинает тактироваться от измеряемой частоты и складывать поступающие импульсы в свой счётчик. Таким образом у нас получается следующее: оба таймера, №1 (отмеряющий секундный интервал) и №2 (считающий импульсы) стартанут чётко одновременно, а когда таймер №1 отмерит секунду и остановится, то и таймер №2 тоже остановится (перестанет считать импульсы) — в счётчике таймера №2 будет лежать количество импульсов входящей частоты полученные за одну секунду (при условии что частота была не больше 65.535 КГц, если частота выше, то в дело вступит таймер №3, который будет подсчитывать количество переполнений таймера №2).

Master/Slave Mode (MSM bit) ⇨ Enable... — при активации этого пункта, таймер №1 перед тем как запустится, сделает небольшую паузу чтоб сигнал для запуска таймера №2 успел дойти. Короче говоря, это нужно чтоб оба таймера стартанули одновременно.



Настраиваем таймер №2



Slave Mode ⇨ Gate Mode — таймер будет работать пока подаётся триггерный сигнал от таймера №1.

Trigger Source ⇨ ITR0 — триггерный сигнал посылает таймер №1.

Подробно про триггеры я писал здесь.

Clock Source ⇨ ETR2 — источником тактирования будет частота поступающая на пин TIM2_ETR (PA0). Это та самая частота, которую мы хотим измерить.


Counter Period — значение переполнение устанавливаем максимальное — 65535.


Trigger Event Selection ⇨ Update Event — настраиваем исходящий триггерный сигнал, который будет посылаться таймеру №3 в момент переполнения. То есть, таймер №2 переполнился, обнулился, послал импульс таймеру №3 и продолжил считать. Таким образом в счётчике таймера №3 будет лежать количество переполнений таймера №2.

Тут есть ещё один важный для нас пункт — Clock Prescaler, но к нему вернёмся позже.



Настраиваем таймер №3



Slave Mode ⇨ External Clock Mode 1 — здесь мы указываем что таймер будет тактироваться какими-то внешними (по отношению к таймеру) сигналами.

Trigger Source ⇨ ITR1 — а здесь говорим что эти тактирующие сигналы будут поступать от таймера №2.

Counter Period — переполнение опять же устанавливаем максимальное — 65535.

Больше нам здесь ничего не нужно и неинтересно.

Для второго и третьего таймеров никаких прерываний включать не нужно.


В итоге у нас получается следующая схема работы: стартуем таймер №1, он в свою очередь тут же запускает таймер №2, который начинает складывать в свой счётчик импульсы измеряемой частоты. Как только таймер №2 переполняется (при условии что поступающая частота выше 65.535 КГц, если ниже то всё уместится в один цикл), он посылает сигнал таймеру №3, таймер №3 делает один «тик» и записывает в свой счётчик единичку. После переполнения таймер №2 продолжает считать, вновь переполняется и вновь посылает сигнал таймеру №3, таймер №3 делает ещё один «тик» и записывает в свой счётчик ещё одну единичку. Так будет продолжаться до тех пор, пока не истечёт секунда и таймер №1 не остановится сгенерировав прерывание. При остановке таймера №1 автоматически остановится таймер №2.

Если предположить что мы измеряем частоту 200000 Гц (200КГц), то в идеале получится так: за одну секунду таймер №2 успеет переполнится три раза — 65535 * 3 = 196605, а после третьего переполнения в его счётчик запишется ещё 3395. По истечении секунды таймер №1 сгенерирует прерывание, в колбеке которого нам остаётся взять значение которое лежит в счётчике таймера №2 и сложить его со значением в счётчике таймера №3 умноженное на значение переполнения, то есть так — 3395 + (3 * 65535) = 200000 Гц.



Теперь напишем код и проверим работоспособность.

Перед бесконечным циклом запускаем таймер №1 в режиме прерывания, и активируем таймеры №2 и №3…

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_Base_Start(&htim2);
HAL_TIM_Base_Start(&htim3);
/* USER CODE END 2 */


Добавляем колбек первого таймера…

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim1)
	{
		uint16_t count_main = __HAL_TIM_GET_COUNTER(&htim2); // значение в счётчике таймера №2
		uint16_t count_secondary = __HAL_TIM_GET_COUNTER(&htim3); // значение в счётчике таймера №3
		uint16_t arr = __HAL_TIM_GET_AUTORELOAD(&htim2); // значение переполнения таймера №2 (65535)
		uint32_t freq = count_main + (count_secondary * arr) + count_secondary; // вычисляем

		///////////////////////// вывод инфы ///////////////////////////////
		char str[96] = {0,};

		snprintf(str, 96, "FREQUENCY: %.3f MHz | %.3f KHz | %lu Hz\n--------------------\n", (float)freq / 1000000.0, (float)freq / 1000.0, freq);
		HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), 1000);

		//////////////// обнуляем счётчики и рестартуем таймер №1 /////////////////
		__HAL_TIM_SET_COUNTER(&htim2, 0x0000);
		__HAL_TIM_SET_COUNTER(&htim3, 0x0000);
		HAL_TIM_Base_Start_IT(&htim1);
	}
}

Вот и вся программа

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



В моём примере данные выводятся в USART, но вы можете выводить их куда угодно. Здесь я делаю это прямо в колбеке, но в рабочем проекте лучше вынести это в бесконечный цикл, а в колбеке просто поднимать флажок. Прошивайте плату, соединяйте TIM2_ETR с RCC_MCO и смотрите результат…




Получилось всё очень круто, точность просто идеальная, однако здесь есть нюанс. Дело в том, что сейчас микроконтроллер измеряет частоту, которую он сам и выдаёт, то есть таймеры занимающиеся подсчётом, синхронизированы с этой частотой (в микроконтроллере всё работает синхронно, и ядро, и таймеры, и другая периферия, тактируясь от какого-то одного источника). В нашем случае микроконтроллер тактируется от кварцевого резонатора, и соответственно таймер отмеряющий секундный интервал основывается на частоте этого кварца, а в том что этот кварц точный, нет никакой уверенности.

В этом легко убедиться — вот такой результат получается если подать такой же сигнал с выхода MCO другой платы…


Подаётся 8 МГц.



Подаётся 16 МГц.



Подаётся 24 МГц.


Можно быть уверенным что врут кварцы на обоих платах, однако видно что погрешность увеличивается на приблизительно одинаковую величину около 50 единиц на каждые 8 МГц. Следовательно, чтобы частотомер получился точным, нужно подать сигнал с какого-то эталонного генератора частоты и сделать программную коррекцию. Идеально точный прибор конечно не получится, но тем не менее, погрешность в пару единиц на такой частоте — это очень хороший результат.


Однако не спешите ликовать, это ещё не всё. Дело в том, что при данных настройках мы не сможем измерить частоту, большую чем треть от системной. Причины этого ограничения описаны здесь (ETR2 и цифровой фильтр, пункт про Clock Prescaler).

Поэтому прежде чем измерять высокую частоту, нужно использовать специальный асинхронный делитель — Clock Prescaler. Допустим наш микроконтроллер тактируется на 72 МГц, а измерить мы хотим частоту тоже 72 МГц. Тогда идём в настройки таймера №2 и устанавливаем Clock Prescaler равным 4…


Если вы заглянули по ссылке выше, то понимаете о чём идёт речь.


Прошиваем плату, подаём 72 МГц и смотрим результат…



Вуаля, мы видим входящую частоту поделённую на четыре.


Ради эксперимента попробуем использовать делитель не 4, а 2…


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

Из теоремы Котельникова следует что для успешной дискретизации сигнала, системная частота должна более чем в два раза превышать измеряемую, а в мануале на микроконтроллер говориться что системная частота должна быть выше в три раза. Опираясь на мануал делаем следующий вывод: если микроконтроллер тактируется от 72 МГц, тогда максимальная частота которую мы сможем измерить будет в районе 190 МГц (190 / 8 = 24). 8 — это делитель, а 24 — это треть от системной.


Ну и в довершение любопытный момент. Сейчас я подавал сигнал с другой платы, а теперь подам частоту равную половине системной, с самой платы.

Настраиваем MCO…



Отключаем делитель, прошиваем плату и дивимся…


Свою собственную частоту, микроконтроллер может определить, так как она синхронизирована. Такие дела.


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

Плюс, ещё один пример, который будет более точно измерять низкую частоту от 1 до ~100 герц, а так же для измерения частоты меньше одного герца, то есть дробных частей. Например пол герца выглядит так…



С помощью первого канала таймера №4 в режиме захвата, измеряется время между двумя импульсами (сигнал подаётся на пин TIM4_CH1 — PB6), и путём нехитрых математических вычислений получается частота. Так же как и в предыдущем примере, здесь используется таймер №3, только тут он подсчитывает переполнения таймера №4.

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

Эти два примера можно совместить, собственно нужно всего лишь одну функцию перенести, и настроить таймер №4. Единственный нюанс — придётся у таймера №3 переключать Trigger Source (для первого проекта — ITR1, для этого — ITR3). Делать это можно «налету», прицепив ещё одну кнопку.


Вот теперь всё.



Форум

Телеграм-чат STM32


  • 0
  • 3391
Поддержать автора




Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.