Пьезоэлектрический излучатель. Синхронизация таймеров

Мы уже научились генерировать мелодию, но сделали это весьма топорно: после запуска функции buzzer_play() нет никакой возможности заставить устройство замолчать не отключая питания. Все остальные операции, кроме прерываний, на устройстве так же будут не возможны. Что же делать?

Можно добавить флаг, ориентируясь на который продолжать воспроизведение или выходить из функции. Но это плохое решение. Другой, довольно очевидный вариант настроить другой таймер и в прерывании по его переполнению переключаться с паузы на ноту, попутно изменяя параметры ШИМа и отмеряемого времени. Реализация не самая элегантная, но рабочая.

Можно немного уменьшить количество кода, убрать проверку что именно сейчас должно «звучать» — нота или пауза, ведь в STM32 таймеры можно синхронизировать — в зависимости от настроек они могут работать как в независимом (англ. master, то что мы делали раньше), так и в зависимом (англ. slave) режиме.

Ведущий таймер может сбрасывать, запускать, останавливать или тактировать счётчик ведомого таймера.

Связать таймеры мы можем только по одному событию-триггеру. Подумаем по какому.

Пусть таймер генерирующий звук, TIM2, будет настроен как подчинённый, тогда другой, допустим TIM1 будет ведущим. Так как нас интересуем ШИМ в ШИМ, то TIM1 следует так же настроить на его генерацию, но без вывода оного на ножку МК (Output Compare, No output, PWM Mode 1). Полный период можно легко найти — это сумма .note_duration и .pause_duration, а заполнение ШИМ, как не сложно догадаться это просто .note_duration. У ШИМ, есть всего два события: output compare (т.е. достижение счётчиком значения из регистра CCER3) и переполнение. Пусть по переполнению срабатывает прерывание, в котором мы меняем параметры обоих таймеров, а вот Output Compare будет отправляться сигнал подчинённому таймеру на сброс.

pwm sync

Но почему мы выбрали именно TIM1? Вы не можете связать любой таймер с любым — чисто на физическом уровне это сложная пространственная задача: внутри чипа тоже есть дорожки. По этой причине в микроконтроллерах ST вы можете связать любой с группой других таймеров по слотам. Ниже приведена таблица из Reference Manual.

Reference Manual, Table 86. TIMx Internal trigger connection

TIM8 нет в нашем МК, а TIM3 и TIM4 мы используем для обработки поворота энкодера. К слову и TIM1 мы уже успели задействовать, при длительном удержании кнопки, но так как с таймерами у нас дефицит, придётся их использовать по второму разу.

Это непременно приведёт багу в прошивке. Что если во время проигрывания мелодии кто-то нажмёт на кнопку? Оставим обработку данной ситуации на вашей совести.

За настройку таймера в режим мастера отвечают биты MMS регистра CR2; так как мы будем использовать первый канал TIM1 для генерации ШИМ, то в MMS следует записать 110 . Обратитесь к документации. Запись 1 в бит MSM регистра SMCR обеспечивает точную синхронизацию между входом и выходом триггера. Для удобства, таймер TIM1 можно настроить на режим одиночного импульса (One Pulse Mode) — если мы дошли до последней ноты, нам не придётся его отключать.

За настройку подчинённого режима отвечает уже известная нам (энкодер) группа бит SMS из регистра SMCR. Записав 100 по приходу сигнала на TRGI таймер будет сбрасываться. Указать источник можно через группу бит TS. Так как нам нужен ITR0, в TS следует записать 000 (см. таблицу выше).

Преступим к реализации и для начала перепишем функцию инициализации.

Так как мелодий может быть несколько, допустим банально мы хотим добавить «бип», то надо как-то давать знать функции buzzer_play(), что именно мы хотим проиграть. Создадим под это дело структуру.

В поле .non хранится количество нот в мелодии, а в .cn текущая проигрываемая нота. Осталось создать нужные мелодии.

Для воспроизведения потребуется отдельная переменная, в которой мы будем хранить текущую, выбранную мелодию, а в функции воспроизведения мы просто будем её запоминать.

Как только мы запустим таймеры, они сразу же начнут воспроизводить звук, согласно своим настройкам. Поэтому, чтобы избежать чего-то неожиданного, придётся записать значения в регистры ARR и CCRx значения из первой ноты. А так как в ней появились значения локальных (для модуля) переменных, то сделать её встроенной мы больше не можем, поэтому перенесём её в файл исходного кода.

Добавим обработчик прерывания: в нём, если ноты ещё остались, то мы настраиваем ШИМ-ы на нужные параметры и перезапускаем таймер. В противном случае вызываем функцию buzzer_timer_off().

Переменную melody, по-хорошему следовало бы обернуть в функции get()/set(), но для ускорения работы лучше дать прямой доступ к ней. Это не очень хорошо, но в данном случае ничего страшного не произойдёт.

Заметьте, мы не добавили регулировку громкости, сделайте это сами.

Осталось вызвать функцию воспроизведения.

Код на можно найти на GitHub: CMSIS, SPL.


Назад | Оглавление | Дальше