Шауэрман Александр А. shamrel@yandex.ru
В учебном стенде LESO2 для ввода данных используются кнопка и тумблеры. Если тумблеры, как правило, используются для задания статического уровня, то кнопка используется в качестве генератора импульсов синхронизации. В этом случае схема должна реагировать не на статический уровень, а на факт нажатия, то есть на переход сигнала из одного логического состояния на ножке ПЛИС в другое. При использовании механических коммутаторов (кнопки, тумблеры и прочие ключи) возникает такое неприятное явление, как дребезг контактов. В лабораторных работах по цифровой схемотехнике, при изучении триггеров, счетчиков, мы рекомендовали использовать простейшую схему подавления нежелательных импульсов (Подавление дребезга контактов). Схема выполнена в редакторе Block Diagram/Schematic и проста для повторения, однако обладает рядом недостатков, не позволяющих применять ее в проектах, серьезнее лабораторной работы.
Во-первых, нажатие кнопки происходит в произвольный момент времени, и потому, относительно остальной схемы внутри ПЛИС, является событием асинхронным. Смена логического состояния на входной ножке ПЛИС может совпасть с моментом переключения принимающего триггера, и есть вероятность, что триггер окажется в неопределенном ("нецифровом") метастабильном состоянии. Это может привести к непредсказуемым результатам. Во-вторых, в зависимости от продолжительности удержания кнопки, генерируется несколько событий, что не всегда удобно. Желательно однозначно фиксировать факт нажатия – генерировать только один импульс.
Создадим универсальный модуль на Verilog для обработки сигнала с механических переключателей. В иностранной литературе аналогичное устройство или блок называется Debouncer. Определимся с портами. Очевидно, что для ввода сигнала непосредственно с ножки ПЛИС нужен входной порт, назовем его sw_i
(в статье Пишем "демку" для LESO2 на Verilog я приводил некоторые рассуждения по именованию входных/выходных портов, в этой и последующих работах я буду придерживаться такого стиля). Нужен вход для системных тактовых импульсов, с которыми синхронно работает вся остальная схема – clk_i
. Добавим вход для асинхронного сброса всей схемы – rst_i
. Сделаем универсальный модуль: предусмотрим два раздельных выхода для события "нажал кнопку" и события "отпустил кнопку" – sw_down_o
и sw_up_o
. Для того, чтобы не потерять информацию о длительности нажатия (а вдруг пригодится), создадим выход sw_state_o
, на котором будет отражаться состояние кнопки, естественно, после устранения эффекта дребезга. Ниже показан листинг "заготовки" для нашего модуля.
module button_debouncer
(
input clk_i,
input rst_i,
input sw_i,
output reg sw_state_o,
output reg sw_down_o,
output reg sw_up_o
);
endmodule
Избежать проблему метастабильных состояний можно классическим методом переноса сигнала из одного частотного домена в другой – с помощью двух последовательных D-триггеров.
reg [1:0] sw_r;
always @ (posedge rst_i or posedge clk_i)
sw_r <= {sw_r[0], ~sw_i};
Два триггера входят в состав регистра sw_r
. С каждым тактом на линии clk_i
уровень с линии sw_i
защелкивается в триггер sw_r[0]
(с инверсией), а предыдущее содержимое sw_r[0]
попадает в sw_r[1]
. Выход триггера sw_r[1]
можно считать синхронным относительно clk_i
и использовать в схеме. Ниже на рисунке показана временная диаграмма работы цепочки триггеров.
Теперь разберемся, как бороться с дребезгом и ложными срабатываниями. Будем считать состояние на линии устоявшимся, если в течении некоторого времени оно неизменно. Алгоритмически это можно описать так: как только состояние на линии sw_i
сменилось (вернее, теперь корректнее анализировать не sw_i
, а выход регистра sw_r[1]
) запускаем таймер, если в течении определенного времени состояние не изменилось, то такое состояние считаем устойчивым. Введем триггер sw_state_r
, в нем будем хранить последнее стабильное состояние кнопки. Флаг sw_change_f
устанавливается в единицу, когда текущее стабильное состояние отличается от того, что установлено на входе sw_i
:
wire sw_change_f = (sw_state_o != sw_r[1]);
Флаг sw_change_f может равняться единице в двух случаях: либо это нажатие кнопки, либо ложное срабатывание. Для того, чтобы выяснить с чем мы имеем дело, запускаем счетчик, и если за время работы счетчика (успеет досчитать до своего максимального значения) состояние вновь не смениться, то текущее состояние признаем стабильным: запишем его в sw_state_r. Иначе сбросим счетчик и оставим sw_state_r без изменений.
always @(posedge clk_i) // Каждый положительный фрон сигнала clk_i
if(sw_change_f) // проверяем, состояние на входе sw_i
begin // и если оно по прежнему отличается от предыдущего стабильного,
sw_count <= sw_count + 'd1; // то счетчик инкрементируется.
if(sw_cnt_max) // Счетчик достиг максимального значения.
sw_state_o <= ~sw_state_o; // Фиксируем смену состояний.
end
else // А вот если, состояние опять равно зафиксированному стабильному,
sw_count <= 0; // то обнуляем счет. Было ложное срабатывание.
В общем случае максимальное значение счета можно задать по разному, один из самых простых методов, прекратить счет, когда в регистре sw_count будут все единицы:
wire sw_cnt_max = (sw_count == 16'hFFFF);
Или записать тоже самое более универсальным способом, не зависимым от разрядности счетчика:
wire sw_cnt_max = &sw_count;
Ниже приведена диаграмма работы, при действительной смене логического уровня. После того, как счетчик закончил счет, sw_state_r
сменил свое значение:
А на этой диаграмме ложное срабатывание. Значение sw_state_o
осталось первоначальным:
Осталось добавить в блоки always
асинхронный сброс для всех элементов с памятью и защелкнуть сигналы на выходные триггеры:
always @(posedge clk_i)
begin
sw_down_o <= sw_change_f & sw_cnt_max & ~sw_state_o;
sw_up_o <= sw_change_f & sw_cnt_max & sw_state_o;
end
Временная диаграмма работы модуля:
Проверим работоспособность нового модуля. За основу возьмем демонстрационный проект для leso2.4 из статьи "Пишем "демку" для LESO2 на Verilog". Скачиваем архив проекта и распаковываем в любое удобное место на локальном диске. В корневом каталоге проекта создаем текстовый файл для модуля button_debouncer
и копируем туда исходный код. Файл должен иметь расширение .v, я его назвал debouncer.v Запускаем Quartus II, открываем проект. Для того, чтобы можно использовать модуль button_debouncer
, мы должны включить файл debouncer.v в проект. Для этого заходим Assigment -> Settings … На вкладке Files добавляем файл с модулем (указываем путь, жмем "add", затем "Apply", только потом "OK" ).
Из всей периферии стенда нам понадобится тактовая кнопка и сегментные индикаторы. Для проверки модуля будем подсчитывать количество импульсов, генерируемых при нажатии кнопки. Создадим простейший 8-ми битный счетчик, пусть он будет срабатывать по заднему фронту (negedge sw_i
) сигнала с кнопки (мы же помним, что кнопка работает с инверсией: в нажатом состоянии генерируется логический ноль), результат счета выведем на семисегментные индикаторы (используем модуль seven):
Компилируем, загружаем. При однократном нажатии кнопки, счетчик не всегда увеличивает свое значение на единицу, часто перескакивает через несколько значений сразу. Это говорит о том, что при нажатии кнопки генерируется более одного импульса. И чем дольше стенд в процессе эксплуатации и больший износ у кнопки, тем в большей степени будет проявляться этот эффект. Особо сильно эффект дребезга будет выражен, если вместо кнопки использовать один из тумблеров.
Отлично. Обработаем сигнал с кнопки с помощью модуля:
Компилируем, прошиваем. Теперь однократное нажатие кнопки приводит к увеличению значения счетчика строго на единицу.
В текущем примере не важно, сработает счетчик при нажатии или отпускании кнопки, потому вместо вывода sw_down_o
(событие – "кнопка нажата"), может быть использованы выхода sw_state_o
(статическое состояние кнопки), sw_up_o
(событие – "кнопка отпущена"). При объявлении экземпляра модуля, для сокращения записи, неиспользуемые выводы можно опустить:
button_debouncer debouncer
(.clk_i(clk_50MHz_i), .sw_i(sw_i), .sw_down_o(sw_down));
Ниже на листинге исходный код модуля верхнего уровня: