Pull to refresh

Часы на ПЛИС с применением Quartus II и немного Verilog

Reading time 6 min
Views 63K
В данном топике хочу рассказать о том, как на ПЛИС можно реализовать часы. Кому-то покажется это странным, ненужным — но надо же с чего-то начинать, поэтому, этот топик будет полезным для начинающих, которые светодиодами помигали и хотят что-нить поинтереснее.


Значит, что мы имеем:
1) Отладочную плату Terasic DE0-Nano с Cyclone IV на борту.
image
2) Макетку с распаянным на ней семисегментным индикатором, на который мы будем выводить часы и минуты/
По этим двум пунктам полная свобода выбора практически любая ПЛИС подойдет, да и макетку с индикатором каждый сделает так, как сделает.

Теперь о проекте.
Будем идти по порядку. Перво-наперво зададимся вопросом: что такое часы? Часы — это прибор, который отсчитывает время. Для того, чтобы его отсчитывать, нам нужен точный источник. На нашей плате есть генератор тактового сигнала 50 МГц. Многовато, поэтому нам нужно его разделить на 50 000 000, чтобы получить частоту 1 Гц(один «тик» в секунду). Как поделить? Я сделал это в два этапа: используя PLL, поделил на 50 и используя счетчик, поделил на миллион.
Что такое PLL? Грубо говоря, это такая штука, которая может изменять тактовый сигнал: умножать, делить, сдвигать по фазе, менять скважность. В Cyclone IV их 4 штуки, так почему бы не воспользоваться одной…

Для этого открываем MegaWizard Plugin Manager и используем ALT_PLL из раздела IO:
1) Устанавливаем входную частоту 50 МГц:

2) Отключаем ненужные входы:

3)Три раза жмем Next и попадаем в окно выбора параметров сигнала, где задаем коэффициент деления 50:

Дальше можно смело нажимать Finish, сохранять и вставлять в проект.
Вот такую красоту мы получили:


Слева вход 50 МГц, справа 1 МГц.
Теперь нам нужно поделить еще на миллион — для этого используем счетчик по модулю 1000000. Модуль счета — это количество возможных состояний счетчика.
Создаем его с помощью того же MegaWizard, используя LPM_Counter.
Задаем разрядность — 20 бит, и выбираем modulus, with a count modulus of 1000000.
Получаем вот что:


Теперь у нас аккуратненькие 1 Гц, которые можно получить с старшего (19) разряда.

Дальше нам необходимо реализовать каскад из 3-х счетчиков: два по модулю 60 (минуты и секунды), и один по модулю 24(очевидно, часы).
Секундный счетчик — с асинхронным сбросом, который происходит при нажатии любой кнопки(установка часов/минут):

Как видно, использован выход Carry Out (перенос при переполнении в следующий счетчик), а секунды выводятся на светодиоды.

Следующие 2 счетчика строятся аналогично, за одним исключением: для установки времени необходимо подавать тактовый сигнал побыстрее 1 Гц, для того, чтобы не уснуть при настройке. :)
Для этого используем мультиплексоры, управляемые кнопками:


Верхний мультиплексор используется для выбора, какую частоту подавать на вход счетчика, а нижний — выбирает, что подавать на вход cin: перенос из cout секундного счетчика, или просто уровень лог. «1».
Суть входа cin в том, что счетчик считает только при наличии на этом входе уровня логической единицы. То есть, когда счетчик секунд переполняется, он поднимает cout в единицу. Эта «1» поступает на вход cin следующего счетчика и он отчитывает одну минуту, и так далее.
Таким образом, при отпущенной кнопке key[0] минутный счетчик считает 1 раз в минуту(что логично), а при нажатой — где-то 4 раза в секунду.

Аналогично для часового счетчика:

Верхний мультиплексор заведует переносом, а нижний частотой, за исключением одного момента: при нажатой кнопке key[0] тактовые сигналы на вод счетчика не поступают(это сделано для того, чтобы он часы не сбивались при настройке минут). Это сделано за счет элемента AND, на вход которого подается тактовый сигнал и key[0]. При отпущенной кнопке на key[0] высокий уровень и q[19] проходит через него, при нажатой — уровень «0» и на выходе тоже «0». В принципе, можно было бы включить у счетчика вход «count enable» и отключать счетчик через него — в данной задаче это непринципиально.

Теперь нам надо выводить данные с счетчиков. Для этого нам необходимо сделать преобразователь из двоичного кода в двоично-десятичный. Это такое представление десятичного числа в двоичном коде, когда каждая десятичная цифра задается отдельным 4мя двоичными разрядами.
Пример:
738 = 0111 0011 1000.

Для этого преобразования воспользуемся алгоритмом double-dabble.
Суть алгоритма:
1) Сдвинуть двоичное число влево на один разряд.
2) Если 4 сдвига — число BCD в колонках «десятки» и «единицы». Конец алгоритма
3) Если в любой колонке число больше 4 — прибавить 3.
Перейти к п1.

Вот иллюстрация:
image

А вот то, что нам нужно реализовать, чтобы это преобразование выполнялось без задержек:
s16.radikal.ru/i191/1107/a4/59618e3101ca.png
Каждый модуль С прибавляет к входным данным 3, если на входе число, большее, чем 4.

Вот он сам и таблица истинности его работы:
image
Для реализации всего этого хозяйства пишем вот два таких модуля на verilog:

module add3(in,out);
input [3:0] in;
output [3:0] out;
reg [3:0] out;

always @ (in)
case (in)
4'b0000: out <= 4'b0000;
4'b0001: out <= 4'b0001;
4'b0010: out <= 4'b0010;
4'b0011: out <= 4'b0011;
4'b0100: out <= 4'b0100;
4'b0101: out <= 4'b1000;
4'b0110: out <= 4'b1001;
4'b0111: out <= 4'b1010;
4'b1000: out <= 4'b1011;
4'b1001: out <= 4'b1100;
default: out <= 4'b0000;
endcase
endmodule

module binary_to_BCD(A,ONES,TENS);
input [5:0] A;
output [3:0] ONES, TENS;

wire [3:0] c1,c2,c3;
wire [3:0] d1,d2,d3;

assign d1 = {1'b0,A[5:3]};
assign d2 = {c1[2:0],A[2]};
assign d3 = {c2[2:0],A[1]};
add3 m1(d1,c1);
add3 m2(d2,c2);
add3 m3(d3,c3);
assign ONES = {c3[2:0],A[0]};
assign TENS = {1'b0,c1[3],c2[3],c3[3]};
endmodule

Сохраняем и вставляем в проект в количестве двух штук:
image
Один будет для часов, другой — для минут.

Теперь необходимо сделать лирическое отступление касательно нашего индикатора.
Я использовал Lite-On ltc-4727js.
Согласно даташиту, у него сегменты с общим катодом для каждой цифры + входы анодов сегментов каждой цифры объединены. Это значит, что мы сможем одновременно зажигать только 1 цифру. Не беда, будем зажигать их по-очереди, только переключать будем очень быстро. Так быстро, что даже Чак Норрис не заметит мерцания ;-)
Самые интересные части из даташита:
image
и

Как я это все подключил?
А вот используя такие пины:

С сегментами, думаю, все ясно, а вот что такое загадочное digit[4..0]… Всё просто — digit[4..1] —
это наши цифры, а digit[0] — это вспомогательные сегменты(двоеточие между 2 и 3 цифрами). Скажу вам, самое трудное — это правильно подключить все и правильно поставить соответствие между выходами ПЛИС и нашими пинами!

Разберем теперь механизм вывода на индикатор.
Занимается этим вот такая монструозная конструкция:

Слева у нас конверторы bin-to-BCD. В центре внимания — мультиплексор на 5 входов шириной 4 бита. Первые 4 входа — цифры, пятый вход — для зажигания точек(которые мигают раз в секунду). Внизу виднеется схемка, которая на этот секундный вход подает 1111 или 1110. О том, почему именно так — немного дальше. В зависимости от того, какая комбинация приходит на управляющий вход мультиплексора, он выводит нужный разряд на дешифратор(устройство которого рассмотрим чуть позже). Выше него — девайс, который выбирает, на какой катод подавать «0», чтобы загорался нужный сегмент. Вот его внутренности:

module segment_select (in,out,sel);
input in;
output reg [4:0] out;
output reg [2:0] sel;

always @ (posedge in)
if (sel == 4)
sel = 0;
else sel = sel + 1;

always @(*)
case (sel)
0: out <= 5'b0zzzz;
1: out <= 5'bz0zzz;
2: out <= 5'bzz0zz;
3: out <= 5'bzzz0z;
4: out <= 5'bzzzz0;
default: out <= 5'bzzzzz;
endcase
endmodule

Обратите внимание на запись вида 5'b0zzzz. Здесь 5'b означает, что мы задаем 5 бит в двоичном виде, 0 — уровень нуля, z — высокоомное состояние(ток в пин не течет). А почему именно 0? Да потому, что у нас общий катод на цифре, ток течет от анода к катоду, или от 1 к 0. Вот и задаем на катоде 0, а на аноде 1.

Теперь устройство дешифратора:

module decoder_7seg (BCD, segA, segB, segC, segD, segE, segF, segG, segDP);

input [3:0] BCD;
output segA, segB, segC, segD, segE, segF, segG, segDP;
reg [7:0] SevenSeg;
always @(BCD)
case(BCD)
4'h0: SevenSeg = 8'b11111100;
4'h1: SevenSeg = 8'b01100000;
4'h2: SevenSeg = 8'b11011010;
4'h3: SevenSeg = 8'b11110010;
4'h4: SevenSeg = 8'b01100110;
4'h5: SevenSeg = 8'b10110110;
4'h6: SevenSeg = 8'b10111110;
4'h7: SevenSeg = 8'b11100000;
4'h8: SevenSeg = 8'b11111110;
4'h9: SevenSeg = 8'b11110110;
4'b1111: SevenSeg = 8'b11000000;
default: SevenSeg = 8'b00000000;
endcase


assign {segA, segB, segC, segD, segE, segF, segG, segDP} = SevenSeg;
endmodule
Тут в зависимости от того, какая цифра нам нужна, выбирается, какие сегменты зажигать.
Интересуют два момента — default и число 1111. Когда у нас BCD принимает значение, отличное от заданных(1110, о котором говорилось ранее) — то все сегменты выключены. Когда у нас приходит 1111 — то зажигаются сегменты A и B. Как можно видеть из даташита, они же подключены к точкам L1 и L2.

Вот, собственно, и все.
Собираем все в одну кучу один проект, назначаем пины, компилируем(ага, используется менее 1% нашего могучего камушка) и заливаем в ПЛИС.

Вот проект:
ifolder.ru/24865983
Необходимо выбрать вашу ПЛИС, и переназначить пины. Правда, я использовал PLL… Поэтому при переносе проекта на другую ПЛИС необходимо будет вашу входную частоту(не факт, что у вас там будет 50 МГЦ) поделить самостоятельно.
Вот видео:

www.youtube.com/watch?v=iUoiOO8wYls
Источники:
1) Даташит на индикатор:
pdf.eicom.ru/datasheets/lite-on_pdfs/ltc-4727js/ltc-4727js.pdf
2) Перевод из BIN в BCD:
www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html
www.ece.msstate.edu/courses/ece4743/fall2007/Shift_add_3.pdf
3) Вывод на сегменты:
www2.engr.arizona.edu/~rlysecky/courses/ece274-07f/labs/lab4.pdf
we.easyelectronics.ru/Shematech/dinamicheskaya-indikaciya_2.html
www.fpga4fun.com/Opto4.html
4)Дешифратор:
www.fpga4fun.com/Opto3.html

Спасибо за внимание, надеюсь, кому-нибудь пригодится!
UPD
Спасибо за комментарии всем!
Я не знаю, как оно оказалось в этом блоге. Я хотел перепилить, залить картинки норм, переделать кое-чего… Но я сегодня вообще на Хабр не заходил!
Есть идея о том, чтобы сделать получение времени извне, например по NTP.
Про хостинг — мне в личку посоветовали, перелью туда.
Это мой первый топик, до этого опыта публикаций не было, картинки залил на первый вспомнившийся хостинг.
Я только сегодня оказался в родном городе и с нормальным интернетом. Завтра перепилю с учетом пожеланий!
Tags:
Hubs:
+27
Comments 17
Comments Comments 17

Articles