Найти в Дзене
ПЛИСовод.

FPGA. Бизнес-логика кухонного таймера.

Оглавление

Приветствую в своём блоге. Тут я реализовываю проекты на ПЛИС по нарастающей сложности. И сегодня я доделаю кухонный таймер на Cyclone IV, начатый в предыдущей статье.

Если модули понижения частоты, дисплейный и антидребезг - кажутся простыми и понятными, то бизнес-логика простой бывает редко. Даже для устройства с 4-мя 7-сегментными индикаторами и 4 кнопками.

Состояния кухонного таймера
Состояния кухонного таймера

Чтобы понять, что как и где должно действовать, я даже набросал диаграмму состояний, и могу даже рассказать о каждом из них:

"Ожидание" - это когда на таймере 00:00 и он доступен для настройки времени. Таймер из этого состояния нельзя перевести в состояние "Запущен". Единственная кнопка, которая реагирует - это кнопка "plus".

"Настройка" - в это состояние таймер попадает при нажатии на кнопку "plus". Отличается от состояния ожидания лишь в том, что функционировать начинают все кнопки: "plus" увеличивает время на 30 секунд, "minus" - уменьшает на 30, кнопка "reset" сбросит время в 0 (то есть вернёт в состояние "ожидания", а "start" запустит таймер

"Запущен". В этом состоянии таймер отсчитывает от выставленного времени до 0. Нажатие на "reset" приводит к сбросу таймера, нажатие на "start" - поставит таймер на паузу, а истечение времени приведёт к переводу таймера в режим ожидания с одновременным проигрыванием звукового сигнала.

"Остановлен" - в это состояние попадает таймер, если из запущенного режима нажать на кнопку "start". Так же как и в состоянии "start", кнопки "plus" и "minus" не работают.

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

Пищалка

Вот только я написал про проигрывание звукового сигнала, а модуля для этого пока не сделал. Исправляюсь, вот код

beeper.v

Данная пищалка выводит на динамик исходный сигнал тактирования, который у меня составляет 512гц. Получается в районе ноты ДО второй октавы, хотя попадание и не идеальное. Принцип работы можно увидеть ниже:

-2

Как и всегда, для построения диаграммы, я уменьшил значение делителя, и каждый сигнал на ней состоит из 4 тактов, а не 512, как в железе.

Запускается пищалка стробом signal. После которого идут 4 импульса длительностью 1 секунду с интервалами в 1 секунду.

Модуль бизнес-логики

И, разобравшись с последним недостающим модулем, а также расписав бизнес-логику, я реализовал таймер вот так:

timer.v

Изначальный код несколько отличался от текущего тем, что тактировался напрямую от кварца, с частотой 48 мгц.

И он не работал.

Нет, на диаграмме тестбенча всё было достаточно хорошо, как и сейчас:

Таймер ведёт свой отсчёт и выдаёт finish в конце.
Таймер ведёт свой отсчёт и выдаёт finish в конце.

Но при заливке на железо, нормально не работала даже кнопка "plus" из режима ожидания. При её нажатии время устанавливалось сразу в 99:30 - а это максимально возможное значение. Неработоспособность на железе + полная корректность в тестбенче подсказали мне повнимательнее рассмотреть лог Quartus-а. И да - TimeQuest Analyzer в этом выводе сиял новогодней ёлкой.

Critical Warning: Timing requirements not met

И это на каких-то 48 мгц. Да и сам код модуля оказался сложным. Его читать совершенно не просто. Десятки введённых мной проводов (wire) попросту запутываются в какое-то спагетти. Так что я получил что хотел - узнал как код писать не надо. Хотя поделиться я им всё же поделился. Затактировал от общих для всей схемы 512 гц, которые оно, разумеется, выдержало. После заливки на платку, всё отлично даже заработало.

Распутываю спагетти

Вспоминаю, что до того, как я начал реализовывать кнопки plus и minus, этот модуль выглядел довольно простым и понятным. А значит, что обратный отсчёт можно вывести в отдельный модуль. Да и сделать его максимально независимым от бизнес-логики и всех этих состояний и кнопок. Пусть управляется только 2 линиями: enabled и paused. Со сброшенной enabled модуль будет пропускать входные значения, а с установленной - считать. Итого получился вот такой модуль:

counter.v

Лапши стало на порядок меньше, всего 4 провода. Хотя и они будут значительно понижать максимально возможную частоту тактирования. Способы борьбы с этим есть, но для текущего проекта это станет лишь ненужным усложнением.

Главной бедой первой версии контроллера таймера было совмещение обратного отсчёта с настройкой времени таймера, а значит в новой версии они должны быть раздельны. Обратный отсчёт уже готов, осталось ввести отдельный модуль для настройки. Делаю его так:

setup.v

Отдельного внимания заслуживает вывод секунд. Раз по каждому нажатию кнопки прибавляется или отнимается 30 секунд времени, то ничего кроме 00 или 30 секунд оно вывести не сможет. Ну а раз так, то достаточно одного вывода на секунды. Работать модуль будет так:

-4

Новое поведение

Важным отличием нового контроллера от предыдущего является то, что в новом есть регистры как для хранения значения обратного отсчёта (внутри timer.v), так и для хранения настроенного времени (внутри setup.v).

А значит кнопку "reset" можно сделать чуть по-умнее: из режима таймера она может выбрасывать на исходно настроенное время. И лишь повторный "reset" должен обнулить часы. Получается вот такая диаграма:

Обновлённая диаграмма состояний
Обновлённая диаграмма состояний

Итого - вся логика по работе с циферьками разошлась на модули counter.v и setup.v - на долю timer.v осталась только логика работы с состояниями. А там лишь 2 регистра: run_mode, и paused. Первый разделяет "левые" от "правых состояний". То есть run_mode == 1 - это либо "Остановлен" либо "Запущен". А run_mode == 0 - это "Ожидание"/"Настройка". Разница между "Ожиданием" и "Настройкой" заключена в выбранном времени, ничего дополнительно не надо. А для различения "Остановлен" и "Запущен" вводится регистр paused. Ну и в целом, получается вот так:

timer.v

Большую часть кода занимает размещение и связывание уже написанных counter и setup. А логика модуля заключена в always блоке внизу страницы.

Последние приготовления и запуск.

Введенная в терминале команда

make flash

cкомпилировала всё мной написанное в Quartus, и загрузила на платку. Вот только идеальной работу назвать было сложно - дребезг контактов у кнопки "plus" явно превышал расчётные 6мс. Я психанул и переписал модуль debounce, добавив в него коротенький предделитель. Теперь debounce работает от каких-то 64 гц. А значит что теперь на дребезг выделяются сразу 30мс, и больше багов я не наблюдал.

Обновлённый debounce.v

Теперь точно всё, заливаю ещё раз, включаю, смотрю:

На видео я устанавливаю таймер на 1 минуту, запускаю, затем сбрасываю. Первое нажатие на сброс возвращает к изначально установленной 1 минуте, повторное - сбрасывает время в 0. Затем я снова устанавливаю таймер уже на 30 секунд и запускаю. Дойдя до "0", таймер начинает пищать. Ну - как и положенно кухонному таймеру.

Готово, в следующий раз буду делать "музыкальную шкатулку".