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

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


Значит, что мы имеем:
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.
Про хостинг — мне в личку посоветовали, перелью туда.
Это мой первый топик, до этого опыта публикаций не было, картинки залил на первый вспомнившийся хостинг.
Я только сегодня оказался в родном городе и с нормальным интернетом. Завтра перепилю с учетом пожеланий!
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 17

    +1
    Отличная работа! Не так много на Хабре топиков по программированию ПЛИС, но благодаря таким, как вы, ситуация меняется к лучшему.
      –3
      Уйдите сради кала! Мои бедные глазки тошнит от этой кучи рекламы :(
        0
        Да!!! Мультиплексер с гамбургерами — это сильно!
        0
        А на сколько ваши часы будут убегать в сутки? В статье вы указали «нам нужен точный источник», но далее никак не раскрыли, что за кварц используется на плате.
          –2
          большинство (ширпотребных) кварцевых резонаторов имеют разброс около ±100ppm (peak-per-minut).

          число тактов (peaks) в минуту кварца 50МГц= 50 000 000 х 60 = 3 000 000 000

          значит если 3 000 000 100 тактов за минуту, а за сутки 4 320 000 144 000 ( в сутках 1440 минут)

          коэффициент деления у нас постоянный. сначала на 50 в PLL и потом 1 000 000 в счетчике.

          получаем что сутки максимум секунд будет = 86400,00288 что означает что примерно 2.88 милисекунды (!) в сутки.

          через год часы уйдут где-то на 1,0512 секунды.

          выше рассмотрен случай если +100ppm.

          А эта нестабильность может меняться от температуры напряжения питания и множества других факторов.

            +2
            PPM — это не «peak-per-minut», а parts per million — частей на миллион. То есть, если точность кварца 100ppm, значит частота гуляет от заявленной на 1/10000, то есть на 0,01%
              +2
              Таким образом, если в сутках 24*3600=86400 секунд, часы будут убегать (или отставать) на 86400/10000=8,64 секунды.
              А вообще для кварца ±100ppm — это много, обычно постоянное отклонение ±10...20ppm (и от него можно избавиться один раз подстройкой генератора), плюс температурное 1...5ppm/°C.
                0
                Вы правы, кто то меня давно еще не верно информировал.
          +1
          Вот только собственно Verilog'a то тут совсем чуть-чуть и настолько примитивного, что дальше собственно то и некуда…
            0
            Хорошо, переименуем
            0
            Предлагаю дальнейшее направление для деятельности: научить часы устанавливать текущее время и создать будильник ;)
              0
              > научить часы устанавливать текущее время
              Так они умеют устанавливать, кнопочками. Или вы предлагаете NTP прикрутить?
                0
                Вообще говоря, у меня лежит дисплей от автомагнитолы Сони. Хороший дисплей, вполне пойдет, чтобы выводить инфу с ПК. Загрузка процессора, текущий трек в плеере и все такое.
                Вот хочу его как-нить прикрутить ;-)
                  0
                  Для этого ПЛИС не особо и нужна. Был бы рад увидеть статью про какую-нибудь задачу, где ПЛИС уместна: обработку видеосигнала, например, или что-нибудь со скоростными интерфейсами.
                    0
                    Ну да, я понимаю. Просто у меня будет, куда плату девать, когда она не задействована ))))
                    Хотя переделаю на МК. Но мне надо просто раскурить, как точно с ним общаться.
                0
                Прикольно, год назад писал практически такую же статью сюда :)
                habrahabr.ru/blogs/DIY/80056/
                  0
                  Когда-то читал. Хороший топик — все расписано подробно.
                  У меня как было — сначала сделал часики, потом подумал, а почему бы не заделать статейку куда.
                  Думал вот о том, что у меня может получиться дубль вашей статьи. Но, например, у вас с использованием VHDL, я вот предпочел Verilog. Он мне куда больше нравится, хотя вот VHDL приходится изучать, так как без него никак.
                  Хотя у меня все равно большая часть на стандартных мегафункциях.
                  Про сами часы давно читал в какой-то книге по электронике… Старая книжка, там еще все на дискретной логике. Вот додумал сам установку и все, что из башки вылетело. Не спорю, возможно, не самый лучший вариант применил. Зато свой )))
                  А про перевод из bin в bcd… Нашел вот вариант мгновенного преобразования, в комбинационной схеме. Видел ваш вариант, но он мне не понравился… Наверно, за счет деления в коде. Надо будет посмотреть, во что квартус этот вариант синтезирует. Деление — дорогая операция, ее не так просто сделать. Не думаю, что квартус так лихо оптимизирует данный код.

                Only users with full accounts can post comments. Log in, please.