Приветствую в своём блоге. Тут я реализовываю проекты на ПЛИС по нарастающей сложности. И сегодня я доделаю кухонный таймер на Cyclone IV, начатый в предыдущей статье.
Если модули понижения частоты, дисплейный и антидребезг - кажутся простыми и понятными, то бизнес-логика простой бывает редко. Даже для устройства с 4-мя 7-сегментными индикаторами и 4 кнопками.
Чтобы понять, что как и где должно действовать, я даже набросал диаграмму состояний, и могу даже рассказать о каждом из них:
"Ожидание" - это когда на таймере 00:00 и он доступен для настройки времени. Таймер из этого состояния нельзя перевести в состояние "Запущен". Единственная кнопка, которая реагирует - это кнопка "plus".
"Настройка" - в это состояние таймер попадает при нажатии на кнопку "plus". Отличается от состояния ожидания лишь в том, что функционировать начинают все кнопки: "plus" увеличивает время на 30 секунд, "minus" - уменьшает на 30, кнопка "reset" сбросит время в 0 (то есть вернёт в состояние "ожидания", а "start" запустит таймер
"Запущен". В этом состоянии таймер отсчитывает от выставленного времени до 0. Нажатие на "reset" приводит к сбросу таймера, нажатие на "start" - поставит таймер на паузу, а истечение времени приведёт к переводу таймера в режим ожидания с одновременным проигрыванием звукового сигнала.
"Остановлен" - в это состояние попадает таймер, если из запущенного режима нажать на кнопку "start". Так же как и в состоянии "start", кнопки "plus" и "minus" не работают.
Да, я знаю, что всё это чертовски формализовано и детально расписано. Но без подобного, постоянно пришлось бы натыкаться на вопросы в духе: а если вот прямо сейчас будет нажата такая-то кнопка, что нужно сделать.
Пищалка
Вот только я написал про проигрывание звукового сигнала, а модуля для этого пока не сделал. Исправляюсь, вот код
Данная пищалка выводит на динамик исходный сигнал тактирования, который у меня составляет 512гц. Получается в районе ноты ДО второй октавы, хотя попадание и не идеальное. Принцип работы можно увидеть ниже:
Как и всегда, для построения диаграммы, я уменьшил значение делителя, и каждый сигнал на ней состоит из 4 тактов, а не 512, как в железе.
Запускается пищалка стробом signal. После которого идут 4 импульса длительностью 1 секунду с интервалами в 1 секунду.
Модуль бизнес-логики
И, разобравшись с последним недостающим модулем, а также расписав бизнес-логику, я реализовал таймер вот так:
Изначальный код несколько отличался от текущего тем, что тактировался напрямую от кварца, с частотой 48 мгц.
И он не работал.
Нет, на диаграмме тестбенча всё было достаточно хорошо, как и сейчас:
Но при заливке на железо, нормально не работала даже кнопка "plus" из режима ожидания. При её нажатии время устанавливалось сразу в 99:30 - а это максимально возможное значение. Неработоспособность на железе + полная корректность в тестбенче подсказали мне повнимательнее рассмотреть лог Quartus-а. И да - TimeQuest Analyzer в этом выводе сиял новогодней ёлкой.
Critical Warning: Timing requirements not met
И это на каких-то 48 мгц. Да и сам код модуля оказался сложным. Его читать совершенно не просто. Десятки введённых мной проводов (wire) попросту запутываются в какое-то спагетти. Так что я получил что хотел - узнал как код писать не надо. Хотя поделиться я им всё же поделился. Затактировал от общих для всей схемы 512 гц, которые оно, разумеется, выдержало. После заливки на платку, всё отлично даже заработало.
Распутываю спагетти
Вспоминаю, что до того, как я начал реализовывать кнопки plus и minus, этот модуль выглядел довольно простым и понятным. А значит, что обратный отсчёт можно вывести в отдельный модуль. Да и сделать его максимально независимым от бизнес-логики и всех этих состояний и кнопок. Пусть управляется только 2 линиями: enabled и paused. Со сброшенной enabled модуль будет пропускать входные значения, а с установленной - считать. Итого получился вот такой модуль:
Лапши стало на порядок меньше, всего 4 провода. Хотя и они будут значительно понижать максимально возможную частоту тактирования. Способы борьбы с этим есть, но для текущего проекта это станет лишь ненужным усложнением.
Главной бедой первой версии контроллера таймера было совмещение обратного отсчёта с настройкой времени таймера, а значит в новой версии они должны быть раздельны. Обратный отсчёт уже готов, осталось ввести отдельный модуль для настройки. Делаю его так:
Отдельного внимания заслуживает вывод секунд. Раз по каждому нажатию кнопки прибавляется или отнимается 30 секунд времени, то ничего кроме 00 или 30 секунд оно вывести не сможет. Ну а раз так, то достаточно одного вывода на секунды. Работать модуль будет так:
Новое поведение
Важным отличием нового контроллера от предыдущего является то, что в новом есть регистры как для хранения значения обратного отсчёта (внутри timer.v), так и для хранения настроенного времени (внутри setup.v).
А значит кнопку "reset" можно сделать чуть по-умнее: из режима таймера она может выбрасывать на исходно настроенное время. И лишь повторный "reset" должен обнулить часы. Получается вот такая диаграма:
Итого - вся логика по работе с циферьками разошлась на модули counter.v и setup.v - на долю timer.v осталась только логика работы с состояниями. А там лишь 2 регистра: run_mode, и paused. Первый разделяет "левые" от "правых состояний". То есть run_mode == 1 - это либо "Остановлен" либо "Запущен". А run_mode == 0 - это "Ожидание"/"Настройка". Разница между "Ожиданием" и "Настройкой" заключена в выбранном времени, ничего дополнительно не надо. А для различения "Остановлен" и "Запущен" вводится регистр paused. Ну и в целом, получается вот так:
Большую часть кода занимает размещение и связывание уже написанных counter и setup. А логика модуля заключена в always блоке внизу страницы.
Последние приготовления и запуск.
Введенная в терминале команда
make flash
cкомпилировала всё мной написанное в Quartus, и загрузила на платку. Вот только идеальной работу назвать было сложно - дребезг контактов у кнопки "plus" явно превышал расчётные 6мс. Я психанул и переписал модуль debounce, добавив в него коротенький предделитель. Теперь debounce работает от каких-то 64 гц. А значит что теперь на дребезг выделяются сразу 30мс, и больше багов я не наблюдал.
Теперь точно всё, заливаю ещё раз, включаю, смотрю:
На видео я устанавливаю таймер на 1 минуту, запускаю, затем сбрасываю. Первое нажатие на сброс возвращает к изначально установленной 1 минуте, повторное - сбрасывает время в 0. Затем я снова устанавливаю таймер уже на 30 секунд и запускаю. Дойдя до "0", таймер начинает пищать. Ну - как и положенно кухонному таймеру.
Готово, в следующий раз буду делать "музыкальную шкатулку".