Некоторое время назад, хаброжитель DmitrySpb79 написал статьи о создании электронных часов. В них он рассмотрел источники точного времени, а так-же элементную базу для создания электронных часов. Были упомянуты Arduino, STM, Raspberry PI, ESP8266, но совсем забыли про ПЛИС.
Давайте заполним этот небольшой пробел. Узнаем, на сколько просто сделать часы на ПЛИС и какие аппаратные ресурсы для этого потребуются. К тому же, мне подарили микросхему ПЛИС очень малого объема — 64 макроячейки. Это ПЛИС LC4064v фирмы Lattice с которыми я до этого никогда не работал. Я думаю, будет интересно!
Цели:
- попытаться уместить логику работы часов в ПЛИС малого размера (64 макроячейки);
- освоить статическую либо динамическую светодиодную индикацию на ПЛИС для вывода времени;
- собрать кучу граблей, связанных с самостоятельной разработкой схем и получить новый опыт;
- освоить новую среду разработки и программирования для ПЛИС фирмы Lattice, оценить сложность перехода
Меня ожидает несколько очень приятных вечеров, посвященных разработке на ПЛИС!
Производитель ПЛИС Lattice?
Lattice Semiconductor Corporation — американский производитель микросхем. Компания располагается в Орегоне и выпускает высокопроизводительные ПЛИС. Есть информация о том, что микросхемы популярны в промышленных разработках из за открытости документации и протоколов. Широкий перечень изделий и их назначений говорит о том, что они делают серии ПЛИС для автоматизации, автомобилестроения, создания компактных, портативных, экономичных устройств. Так же есть серии ПЛИС больших объемов (до 100k и более макроячеек).
Где найти ПЛИС Lattice?
К сожалению, я пока не смог найти магазин, где можно было бы купить ПЛИС объемом в 1 — 4 тысячи макроячеек и ценой в одну-две тысячи рублей. Однако, плату под свои требования я нашел — Programmable Logic IC Development Tools MachXO3LF StarterKit. Количество ячеек — 6900, на плате уже есть программатор (FTDI), 8 светодиодов. Цена 25$, однако, стоимость доставки, при оформлении покупки в Lattice Store, оказалась 45$. Чтож, будут деньги — буду искать на digikey или mouser и заодно их осваивать. Однако, еще нашелся вариант покупки на Родине, в магазине Элитан, по цене 2992 рубля. На Ebay/Aliexpress выбор пока не большой, однако удалось найти чип без платы на 5 тысяч ячеек — LFXP2-5E-5QN208C.
При разборке старой электроники, периодически попадаются CPLD небольших размеров (32, 64 ячейки). Одну такую микросхему мне и подарил мой товарищ. Микросхема ПЛИС LC4064V (pdf) — представитель семейства ispMACH 4000V/B/C/Z Family. Мой вариант работает от напряжения 3.3В, на частотах до 400 МГц! В корпусе TQFP имеется целых 32 линии ввода/вывода, из них две линии используются под тактовые входы. Линии толерантны к 5 В в режимах LVCMOS3.3, LVTTL и PCI. Все выводы могут быть "притянуты" pull-up, pull-down либо могут висеть в воздухе. Так же, можно работать в режиме open-drain.
Среда разработки для Lattice
Для небольших ПЛИС, к которым относится LC4064v нужен: ispLEVER Classic.
Для прошивки нужно установить: Diamond Programmer.
Для более крупных ПЛИС: Lattice Diamond.
Для активации программ, нужно зарегистрироваться на сайте, указать свой MAC адрес и скачать ключи лицензий.
Для создании общего представления о том, как разрабатывать проекты в среде Lattice Diamond, рекомендую ознакомиться с этим видео.
Чем «прошивать» Lattice?
Программаторы LPT уже практически ушли в прошлое. Да и собирать программатор нет времени и желания(есть желание делать проект). Как и в случае с Altera, я решил найти и купить готовый китайский клон программатора, на площадках Ebay или Aliexpress. Однако, если программатор для Altera стоит в пределах 300 рублей, то для Lattice цены уже порядка 2000 рублей. За, казалось бы, одинаковые изделия, отличающиеся только наклейкой. Но если хорошенько поискать, то можно найти дешевле — 1300 рублей! Беру сразу два: себе и товарищу. По приезду, один программатор отказался работать. Начал было общаться с продавцом, тот уверил, что они все проверяют. В итоге, я его сам и починил, пропаяв землю на разъеме USB.
Сравнил с программатором для Altera, чтобы убедиться, что они всетаки разные, а не "отличаются только ID USB".
Еще немного фото: 1, 2, 3. Микросхем действительно больше, так что вполне очевидно, что программатор получается дороже.
Для проверки микросхемы на скорую руку делаю плату со светодиодами и кнопкой. Саму микросхему припаиваю на плату-переходник.
В качестве генератора не нашел ничего проще, чем ПЛИС Altera Cyclone :) Поделил частоту тактового генератора 50 МГц до 2 Гц и вывел обратно. Этот сигнал подал на тактовый вход LC4064v. Сделал простейший 4х битный счетчик, а его биты вывел наружу и подключил к светодиодам.
Прошиваю — работает!
Теперь, когда стало ясно, что ПЛИС работает и поддается программированию, можно придумать себе проблему задачу и героически ее решить!
Какую задачу решить?
На самом деле, идея сделать часы пришла не сразу. Количество ячеек, равное 64, заставляет задуматься над задачей, которую можно им поручить. Вспоминаются старые времена, когда в наличии имелся строго определенный МК, который я умел программировать, и я пытался уместить в него решаемую задачу. Программирование ПЛИС предполагает обратное: решение задачи и выбор ПЛИС нужного размера по результатам синтеза. Но тут мы переворачиваем всё с ног на голову, и ограничиваем себя размером. Это заставит нас потрудиться, как в выборе задачи, так и в ее реализации. Эдакое экстремальное программирование в ограниченных условиях.
Что сделать же сделать? Электронный синтезатор? Маловато ячеек: только приемник MIDI команд занимает не одну сотню. Нужно что-то простое! Например декодер звукового I2S потока и вывод его через ЦАП на резисторах? Как то очень специфично, просто и не очень зрелищно. Линий много — можно повесить семисегментные индикаторы (7х4 = 28 даже статически!). А что изображать? Можно сделать либо частотомер (отличный вариант, ведь микросхема работает аж до 400 МГц) либо часы. Решено выбрать часы.
Детали для сборки часов
Для часов нам потребуются семисегментный индикатор на четыре цифры. Сначала я хотел приобрести новый индикатор в магазине, а потом вспомнил, что у меня уже есть часовой индикатор, который лежит в ящике уже, наверное, лет десять. Мне его когда-то очень давно подарил regenerator с форума РадиоКот. Схема включения моего индикатора имеет следующий страшный вид:
В рамках избавления ящиков от залежавшихся компонентов, а так же издевательства над ПЛИС(и собой), было решено использовать именно этот, на первый взгляд, непонятный индикатор. На самом же деле все достаточно просто: сверху мы подаем управляющее напряжение на индикаторы. Как правило, один провод идет сразу на два сегмента. А какой из двух сегментов будет работать, определяется шиной, которую мы подключили к общему проводу в данный момент. Схема, в целом, соответствует классической схеме динамической индикации с той разницей, что индикаторов у нас получается всего два. Для проверки работы всех индикаторов, вывел логическую единицу на все выходы, а сигнал на транзисторы вывел по очереди.
По результатам теста, выяснилось, что мы, в принципе, можем выводить цифру на любой индикатор. Однако, в самом левом индикаторе отсутствует один сегмент. Это не позволит выводить на него все возможные цифры, но самое важное — цифру "ноль". Этот факт подтверждается и вот этой картинкой. Поэтому ноль в десятках часов будем гасить.
Еще нам потребуется "часовой генератор" — это генератор на частоту 32768 Гц. Почему такая частота? Потому что, если взять 15 битный двоичный счетчик и подать на него эту частоту, то на выходе мы получим 1 Гц (2^15 = 32768). То есть интервал в одну секунду. Хочу отметить еще один важный момент в выборе частоты генератора. Для того, чтобы поделить частоту на 32768 потребуется 15 битный двоичный счетчик, а это как минимум займет 15 ячеек в ПЛИС, что сразу отнимет почти четверть ресурсов. Счетчик для генератора в 50 МГц потребует уже 30 бит для деления до секундного интервала. При таких затратах, ячеек на логику работы часов может не хватить.
Пока часового кварца нет, в качестве генератора частоты опять же будет выступать Altera Cyclone, в которой я целочисленно поделил частоту 50 МГц до 32768 Гц.
Для деления частоты применяю свой модуль на Verilog. Работать с ним очень просто: на clk подаем исходную частоту, из s_out получаем результат. Коэффициент деления задаем параметром DIV. В моем случае 50000000 Гц это входная тактовая частота, а 32768 Гц — частота, которую нужно получить.
frqdivmod #(.DIV(50000000/32768)) divider(.clk(clk50M), .s_out(clk32768));
module frqdivmod(clk, s_out);
parameter DIV=2;
// calculated parameters
parameter WITH = ($clog2(DIV)>0) ? $clog2(DIV) : 1;
input wire clk;
output wire s_out;
reg clk_n;
reg [(WITH-1):0] pos_cnt;
reg [(WITH-1):0] neg_cnt;
wire [(WITH-1):0] div_value = DIV[(WITH-1):0];
initial begin
clk_n <= 1'b0;
pos_cnt <= {(WITH){1'b0}};
neg_cnt <= {(WITH){1'b0}};
end
assign s_out = (DIV==1) ? clk : clk_n;
always @ (posedge clk) begin
pos_cnt <= (pos_cnt + 1'b1) % div_value;
end
always @ (negedge clk) begin
neg_cnt <= (neg_cnt + 1'b1) % div_value;
end
always @(clk, pos_cnt, neg_cnt) begin //pos_cnt in sens list addd for resolve warning "variable is read inside the Always Construct but isn't in the Always Construct's Event Control"
if ((DIV%2) == 1'b0) begin
clk_n <= ( pos_cnt >= (DIV/2)) ? 1'b1 : 1'b0;
end else begin
clk_n <= (( pos_cnt > (DIV/2)) || ( neg_cnt > (DIV/2))) ? 1'b1 : 1'b0;
end
end
endmodule
Задаем аппаратную логику
Первая итерация по созданию часов — это создание схемы двоичного счетчика с асинхронным сбросом (клик по картинке откроет схему на сайте http://www.falstad.com/ где вы сможете ее корректировать по собственному желанию)
При достижении счетчиком значения "10" (b1010)- мы должны его сбросить. Этот же сигнал будет использоваться в качестве тактового для счетчика следующего разряда.
Цепочка RC используется для того, чтобы увеличить период сигнала достижения "10". Если этот сигнал будет слишком коротким, то некоторые триггеры могут не успеть сброситься, или поменять свое значение. Но применение асинхронного сброса в ПЛИС может привести к ряду проблем. Для того, чтобы увеличить период сигнала, мы применяем аналоговую схемотехнику, хотя разрабатываем цифровую схему. Если мы захотим применить такой подход в ПЛИС, то нам потребуется вывести этот сигнал наружу микросхемы — на внешний вывод, установить туда цепочку, и потом подать это обратно — уже на другой вход ПЛИС.
// counter
module cntr #(parameter width=4) (clk, res, out);
input wire clk, res;
output reg [width-1:0] out; initial out <= {width{1'b0}};
always @(posedge clk or posedge res) begin
if (res) begin
out <= 0;
end else begin
out <= out + 1'b1;
end
end
endmodule
В качестве эксперимента, я все-таки сделал часы на счетчиках с асинхронным сбросом. При этом никаких RC цепочек я не выводил и не использовал. Моей целью было выяснить, действительно ли не стоит использовать асинхронную логику, или "да ладно и так сойдет". До какого-то этапа часы исправно отсчитывали время
Но в тот момент, когда я решил добавить в часы мигание секунд, всё внезапно поломалось.
При разработке на ПЛИС настоятельно рекомендуется использовать синхронную логику. В случае с часами и счетчиком, текущее значение счетчика должно храниться в регистре, а следующее значение регистра мы должны вычислять комбинационной схемой. Следующее значение однозначно будет вычислено(мы надеемся на это) до наступления следующего такта, но загружено в регистр будет по фронту тактового импульса. То есть никаких "иголок" сброса проскакивать не будет. Исходя из текущих значений регистров, мы всегда однозначно определяем значение на следующем такте.
reg [3:0] s;// 0..9 - 4bit --регистр секунд
// флаг переполнения значения единиц секунд
wire s_cy = (s == 4'd9);
//вычисление следующего значения для регистра:
wire [3:0] next_s = (s_cy) ? 4'd0 : (s + 1'b1); //либо это текущее+1, либо 0, когда достигли 9
//меняем значение по положительному фронту тактового сигнала
always @(posedge clk1Hz) begin
s <= next_s;
end
Для вывода значения регистра на семисегментный индикатор, нам потребуется модуль преобразователя из двоичного в семисегментный код:
//bin to 7seg
module bcd2seg0_9(sin, sout);
input wire [3:0] sin;
output wire [6:0] sout;
reg [6:0] SEG_buf;
always @ (sin)
begin
case(sin)
4'h0: SEG_buf <= 7'b0111111;
4'h1: SEG_buf <= 7'b0000110;
4'h2: SEG_buf <= 7'b1011011;
4'h3: SEG_buf <= 7'b1001111;
4'h4: SEG_buf <= 7'b1100110;
4'h5: SEG_buf <= 7'b1101101;
4'h6: SEG_buf <= 7'b1111101;
4'h7: SEG_buf <= 7'b0000111;
4'h8: SEG_buf <= 7'b1111111;
4'h9: SEG_buf <= 7'b1101111;
default: SEG_buf <= 7'b0000000;
endcase
end
assign sout = SEG_buf;
endmodule
Для экономии ресурсов, описываем варианты только для чисел от 0 до 9. Для эксперимента, пробуем альтернативный модуль преобразователя:
//bin to 7seg
module bcd2seg(sin, sout);
input wire [3:0] sin;
output wire [6:0] sout;
assign sout = (sin==4'h0) ? 7'b0111111 :
(sin==4'h1) ? 7'b0000110 :
(sin==4'h2) ? 7'b1011011 :
(sin==4'h3) ? 7'b1001111 :
(sin==4'h4) ? 7'b1100110 :
(sin==4'h5) ? 7'b1101101 :
(sin==4'h6) ? 7'b1111101 :
(sin==4'h7) ? 7'b0000111 :
(sin==4'h8) ? 7'b1111111 :
(sin==4'h9) ? 7'b1101111 : 7'b0000000;
endmodule
Но объем затраченных ресурсов не изменился. Радуемся за умный синтезатор.
Дальше просто создаем регистры для секунд(единицы, десятки), минут(единицы, десятки), часов(единицы, десятки). Для каждого задаем расчет следующего значения. Но изменение значения секунд мы делаем при каждом секундном импульсе, а вот изменение значения десятков секунд — только при переполнении единиц. Соответственно, единицы минут меняются только когда переполнились и десятки и единицы секунд. И так далее.
При этом, синтез показал, что все отлично, но ячеек устройства уже не хватает. Упс. Для того, чтобы исправить ситуацию, решено секундный регистр упростить. Он все равно не выводится, поэтому два двоично-десятичных счетчика меняем на один двоичный от 0 до 59. А забегая немного вперед, я сделал его на один бит больше, зато сократил счетчик общего делителя частоты часового кварца. Поэтому счет идет от 0 до 119, но с частотой 2 Гц (см ниже, часть про установку времени).
//clk - general clock 32768
reg [13:0] clk_div; initial clk_div <= 14'd0; //?? не синтезируется в Lattice
always @(posedge clk) clk_div <= clk_div + 1'b1;
//регистры секунд, минут, часов
reg [6:0] sec;// 0..119
reg [3:0] m ;// 0..9
reg [3:0] mm ;// 0..5
reg [3:0] h ;// 0..9
reg [3:0] hh ;// 0..2
//следующие значения и переполнения
wire sec_cy = (sec == 7'd119);
wire [6:0] next_sec = (sec_cy) ? 7'd0 : (sec + 1'b1);
wire m_cy = (m == 4'd9);// && sec_cy;
wire [3:0] next_m = (m_cy) ? 4'd0 : (m + 1'b1);//(m_cy) ? 4'd0 : (m + ss_cy);
wire mm_cy = ((mm == 3'd5) &&(m == 4'd9));
wire [2:0] next_mm = (mm_cy) ? 3'd0 : (mm + 1'b1);
wire h_cy = (h == 4'd9)||((hh == 4'd2) && (h == 4'd3));
wire [3:0] next_h = (h_cy) ? 4'd0 : (h + 1'b1);
wire hh_cy = ((hh == 2'd2) && (h == 4'd3));
wire [1:0] next_hh = (hh_cy) ? 2'd0 : (hh + 1'b1);
//заносим новые значения в регистры
wire timer_clk = clk_div[13];
always @(posedge timer_clk) begin
sec <= next_sec;
m <= ( sec_cy) ? next_m : m;
mm <= ( m_cy&&sec_cy) ? next_mm : mm;
h <= ( mm_cy&&m_cy&&sec_cy) ? next_h : h;
hh <= (h_cy&mm_cy&&m_cy&&sec_cy) ? next_hh : hh;
end
Вывод на семисегментные индикаторы
Каждый регистр ЧЧ: ММ мы выводим на семисегментные индикаторы. Но перед этим преобразовываем в семисегментный код.
//семирязрядныли линии для вывода на семисегментники
wire [6:0] s_m;
wire [6:0] s_mm;
wire [6:0] s_h;
wire [6:0] s_hh;
//модули преобразования из двоичного в семисегментный код
bcd2seg0_9 sseg_m( .sin(m), .sout(s_m));
bcd2seg0_5 sseg_mm(.sin(mm), .sout(s_mm));
bcd2seg0_9 sseg_h( .sin(h), .sout(s_h));
bcd2seg0_2 sseg_hh(.sin(hh), .sout(s_hh));
Но у нас динамическая индикация. Причем, при выводе переключаются не отдельные семисегментные блоки, а отдельные элементы индикаторов. Поэтому, спустимся до уровня каждого элемента индикатора:
//отдельные элементы каждого индикатора
wire a1,b1,c1,d1,e1,f1,g1;
wire a2,b2,c2,d2,e2,f2,g2;
wire a3,b3,c3,d3,e3,f3,g3;
wire a4,b4,c4,d4,e4,f4,g4;
//разложим биты линий по отельным элементам
assign {g4, f4, e4, d4, c4, b4, a4} = s_m;
assign {g3, f3, e3, d3, c3, b3, a3} = s_mm;
assign {g2, f2, e2, d2, c2, b2, a2} = s_h;
assign {g1, f1, e1, d1, c1, b1, a1} = s_hh;
Дальше, исходя из схемы индикатора, понимаем, что там две линии по минусу. Будем их переключать по очереди:
wire led_line1 = (clk_div[7]); //8-й бит делителя частоты 32768 выдает нам частоту
wire led_line2 = (~led_gnd1);//динамического переключения сегментов 256 Гц
Присваиваем совмещенным линиям сегментов, соответствующие им значения, в зависимости от того, какая сейчас линия активна:
wire h_show = !(hh==0); //гасим ноль в десятках часов
assign led6 = (led_line1&&(b1&&h_show)) || (led_line2&&(b1&&h_show)); // b1
assign led7 = (led_line1&&(a1&&h_show)) || (led_line2&&(g1&&h_show)); // a1/g1
assign led8 = (led_line1&&(d1&&h_show)) || (led_line2&&(e1&&h_show)); // d1/e1
assign led9 = (led_line1&&e2) || (led_line2&&(c1&&h_show)); // e2/c1
assign led10 = (led_line1&&g2) || (led_line2&&b2); // g2/b2
assign led12 = (led_line1&&d2) || (led_line2&&c2); // d2/c2
assign led13 = (led_line1&&f2) || (led_line2&&a2); // f2/a2
assign led15 = (led_line1&&a3) || (led_line2&&f3); // a3/f3
assign led16 = (led_line1&&b3) || (led_line2&&g3); // b3/g3
assign led17 = (led_line1&&c3) || (led_line2&&d3); // c3/d3
assign led18 = (led_line1&&e4) || ((led_line2)&&e3); // e3/e4 !!
assign led19 = (led_line1&&g4) || ((led_line2)&&b4); // g4/b4
assign led20 = (led_line1&&d4) || ((led_line2)&&c4); // d4/c4
assign led21 = (led_line1&&f4) || ((led_line2)&&a4); // f4/a4
Борьба за ресурсы
И тут выясняется, что ресурсов снова не хватает! В процессе борьбы за ресурсы были предприняты следующие шаги:
Переделал енкодер из bcd в семисегментный код. Обычный енкодер преобразует числа от 0 до 15 в шестнадцатиричный формат. В часах используется десятичная система для отображения, поэтому в енкодере были оставлены только цифры от 0 до 9, то есть вход — 4 бита и выход 10 вариантов семибитных слов. Но для десятков минут (от 0 до 5) достаточно 3 битного енкодера. То есть 6 вариантов. Похожая картина и для десятков часов. Только там вообще 3 цифры — от 0 до 2. И это уже 2 бита для входа енкодера и всего 3 варианта на выход. Все это наверняка должно было снизить сложность синтеза. Так и случилось! Количество использованных ячеек снизилось с 63 до 59.
Уменьшил разрядность регистров десятков минут и часов. Это снизит количество ячеек по количеству бит, плюс упростит схемы сумматоров.
reg [6:0] sec;// 0..119 - 7bit
reg [3:0] m ;// 0..9 - 4bit
reg [2:0] mm ;// 0..5 - 3bit
reg [3:0] h ;// 0..9 - 4bit
reg [1:0] hh ;// 0..2 - 2bit
В общей сложности, на момент начала оптимизаций было занято 63 из 64 ячеек, что ставило под вопрос возможность реализации не только возможных дополнительных функций, но и основных (таких, как установка времени). Теперь занято стало 56 макроячеек и 8 свободны. В процентном соотношении было 98%, стало 87%. Для такого малого объема это очень хорошо: около 10%!
Установка времени
Теперь, когда с ресурсами стало легче, займемся установкой времени. Решено сделать 3 кнопки: "часы", "минуты", "секунды". При нажатии и удерживании кнопки "часы", их значение должно увеличиваться. Те же условия и для кнопки "минуты". Сделать это просто: мы просто разрешаем записать в регистр новое значение при каждом такте, если нажата кнопка (а не только при переполнении младшего разряда).
При нажатии кнопки "секунды" — другая логика: секундный счетчик должен быть сброшен и счет секунд должен быть остановлен.
Для того, чтобы установка времени была более комфортной, скорость увеличения значения пришлось увеличить, а для этого частоту итогового тактового генератора выбрать не 1 Гц, а 2 Гц (об этом я писал выше). Это так-же сэкономило одну ячейку на делителе частоты, которому потребовалось на один разряд меньше.
input wire btn_HH, btn_MM, btn_SS; //кнопки часов, минут, секунд
wire timer_clk = clk_div[13];
always @(posedge timer_clk) begin
sec <= (btn_SS) ? 7'd0 : next_sec; //reset seconds
m <= ( sec_cy)||(btn_MM) ? next_m : m;
mm <= ( m_cy&&sec_cy)||(btn_MM&&m_cy) ? next_mm : mm;
h <= ( mm_cy&&m_cy&&sec_cy)||(btn_HH) ? next_h : h;
hh <= (h_cy&mm_cy&&m_cy&&sec_cy)||(btn_HH&&h_cy) ? next_hh : hh;
end
Как видим, два раза в секунду у нас проверяется сигнал переполнения или признак нажатия кнопки. В обоих случаях, часы или минуты увеличиваются. А значению секунд присваивается ноль, пока удерживается кнопка "секунды".
Секундный индикатор
Для секундного индикатора нам уже не подходит какой-либо бит из регистра делителя частоты 32768. Потому что минимальная частота там — 2Гц, а мигать надо раз в секунду. Но два раза в секунду у нас увеличивается двоичный счетчик секунд. Поэтому просто возьем нулевой бит из этого счетчика:
assign led_second_tick = sec[0];
Потребление энергии и энергосбережение
Высвобождение ячеек позволило добавить функционала. Одна из идей — сделать источник питания с резервной батареей. При наличии основного питания, часы могут работать в полноценном режиме, а в случае перехода на резервное питание — уходить в экономный режим: отключать индикацию и заниматься только подсчетом времени.
Для того, чтобы определить целесообразность этого функционала, нужно определить ток в штатном режиме и ток, который будет потреблять устройство в режиме энергосбережения. Можно ориентировочно посчитать, что при токе примерно в 10 мА на сегмент и не больше 12-ти одновременно работающих сегментов, мы получим только от индикации — 120 мА. Плюс сама ПЛИС потребляет энергию. Согласно документации — это должно быть порядка 10 мА.
Измерения показывают, что всё устройство в целом потребляет 125 мА. Это близко к расчетам. Добавляю в логику дополнительные условия и еще один сигнал на вход. Это сигнал перевода устройства в экономный режим. Управляющий сигнал будем брать с источника питания, с той части схемы, которая получает энергию из сети. Чтобы пользователь видел, что устройство работает, находясь в экономном режиме, добавим мигание секундным светодиодом. Частота будет та же, но заполнение не 50%, как в штатном режиме, а сделаем очень короткую вспышку. Для этого возьмем часть битов из счетчика-делителя частоты. На реализацию этой логики ушла всего одна ячейка.
input wire btn_SAFE; //входная линия с БП
wire SAFE_MODE = ~btn_SAFE; //когда идет 1 с БП, значит нормальный режим, инвертируем
//гасим линии вывода
wire led_line1 = (clk_div[7])&&(SAFE_MODE==0);
wire led_line2 = (~led_gnd1) &&(SAFE_MODE==0);
//корректируем вывод на секундный индикатор
assign led_second_tick = (sec[0]&&(!SAFE_MODE))||(sec[0]&&(clk_div[13:6]==10'd0)&&(SAFE_MODE));
Ток в режиме энергосбережения составил 18,5 мА. В принципе, это не так и мало. Однако, если найти примененную ПЛИС на сайте производителя, то она там классифицируется, как "architectures to offer a SuperFAST™ CPLD solution with low power". Супербыстрые — это до 400 МГц. У меня подозрения, что такой ток ради быстроты. Но в линейке есть более медленные и при этом экономные варианты: ispMACH 4064ZE, Operating Power Supply Current Vcc = 1.8V при этом ток 80 микроампер.
Доделываем генератор
Для создания генератора, изначально решил попробовать использовать логические элементы ПЛИС. Это было бы весьма оригинально и экономно. Однако, "что-то пошло не так" и такой вариант работать не стал. Причем даже на Cyclone, причем даже в случае создания железных буферов. Я думаю, конечно, можно заставить работать и такую схему, поиграв с параметрами обратной связи. В общем, нужно делать внешний генератор. Поискал схемы. Выбранная схема на транзисторах заработала, но параметры получаемого сигнала не позволили использовать ее в качестве генератора. В итоге решил собрать проверенную схему генератора — на ТТЛ микросхеме. Но на КР1533ЛА3 вот эта схема не заработала. В итоге запустился генератор по мотивам вот этой схемы:
Осталась одна проблема: микросхема КР1533ЛА3 должна работать при напряжении 5В +- 10%, то есть от 4.5 до 5.5, а ПЛИС работает от 3.3 вольт. Однако, работа микросхемы происходит по сути в аналоговом режиме. Рискнул, запустил от 3.3 вольт — работает! Поставил часы походить.
Эксперимент по выбегу ротора турбин частоты генератора показал, что часы "бегут". За три часа набирается уже секунд 20. При тестировании на начальном этапе, в качестве генератора использовался Cyclone с делением частоты генератора 50 МГц. При этом какого-то ощутимого отклонения в ходе часов не наблюдалось. А тут за 6 часов все "убегает" на 2-3 минуты! Подводить часы каждый день неприемлемо. Поэтому я вернулся к схеме
Анализ схем такого рода навел меня на мысль о том, что тип микросхемы имеет какое-то значение. Во всех таких схемах применяется КМОП, а в моем случае была микросхема ТТЛ. Чтобы проверить эту гипотезу, я зашел в магазин и приобрел несколько микросхем 561-й серии. Среди них оказалась микросхема К561ЛА9. Изучение документации показало, что работать эта микросхема может от напряжений в диапазоне 3..18 вольт, что снимает вопрос о правильности её питания. Схема запустилась сразу, только не на 32768 Гц, а на какой-то более высокой гармонике. Резистор 100К пришлось увеличить до 300К. Поставил часы походить. За два дня часы убежали вперед примерно на 30 секунд. Это тоже не мало, но уже лучше, чем +3 минуты за 6 часов. Теперь можно подобрать конденсаторы в схему генератора для более установки более точного хода. И продумать способ построения частотомера, способного измерить такое отклонение частоты. Иначе настройка точности хода может затянуться. С помощью ПЛИС Cyclone и генератора на 50 МГц я набросал частотомер со временем измерения 1 и 10 секунд, данные читал с помощью SignalTap. В генератор поставил конденсаторы по питанию, подобрал конденсатор и подстроечный конденсатор частоты хода. Частоту вогнал в 32768 плюс минус точность измерений. За сутки хода, видимого на глаз отклонения не обнаружено. На этом с генератором все.
Чтение МРБ 1178 показало, что с часовыми кварцами можно достичь точности хода до 2 секунд в месяц. Считаю, что такое значение точности вполне достаточно для простых часов.
Блок питания
Согласно расчетам и измерениям, устройство потребляет не мало: 120 мА. Питать устройство изначально планировалось от электросети 220 вольт. Поэтому высокие значения КПД от преобразователя 220 -> 3.3 вольта — не требовались. При таких токах, линейный стабилизатор приведет к увеличению потребления, но при этом даст отличную надежность, в силу своей простоты. Вообще, я согласен, что надо беречь энергию и даже подобрал подходящие компоненты. Но у нас простые часы и я готов оплатить перерасход электроэнергии.
Сигнал о том, что напряжение в сети есть, будем брать с выхода стабилизатора. Если его там нету — гасим индикаторы!
Корпус
Корпус выбрал маленький. Пришлось постараться, чтобы в него все поместилось. Некоторые стойки для монтажа накруткой пришлось обкусить. Блок питания придется использовать внешний. На корпус выведены кнопки установки часов, минут и сброса секунд.
Список собранных граблей
- Правильное питание. Сначала ПЛИС программировалась, но после прошивки и подключения платы со светодиодами, начались проблемы. Программатор перестал "видеть" и определять микросхему. Проблема решилась установкой фильтрующих конденсаторов по каждой линии питания микросхемы.
- Асинхронная логика. Счетчики с асинхронным сбросом — это типичное решение для часов на дискретных счетчиках. Применение асинхронной логики на ПЛИС настоятельно не рекомендуется из-за непредсказуемости работы. Надежные решения на ПЛИС — это синхронная логика.
- Отсутствие автоматического сброса сразу после прошивки ПЛИС. После программирования микросхема на какое-то время перестает работать, а потом начинает работать заново. Однако, при этом, видимо, не сбрасываются текущие значения триггеров. При этом часы начинают отображать на экране какую-то ерунду. В каких то случаях может показаться, что все нормально. Это может ввести в заблуждение и заставить думать, что что-то не так запрограммировано. Нет, просто нужно выключить и включить питание микросхемы, чтобы все регистры обнулились. На JTAG входах этой микросхемы ПЛИС отсутствует вход сброса, поэтому автоматический сброс после программирования не происходит.
- Отсутствие предустановки значений регистров. При программировании микросхем Lattice нужно учитывать, что конструкции Initial для регистров не будут синтезированы. Следовательно, если предполагается изначальное значение регистров отличное от ноля, то initial тут не поможет. Нужно либо реализовывать аналоговую цепь для сброса и заводить ее на вход микросхемы. Либо сделать модуль со счетчиком, который будет считать от ноля до N, выдавая до этого времени единицу. В нашем случае, инициализация не потребовалось, потому что начинать с нулевых значений всех регистров (значение по умолчанию) для часов — это норма.
- Не гаснущие сегменты индикаторов. Такой проблемы сначала не было, но постепенно, в процессе разработки логики, стали появляться индикаторы, которые гаснут не полностью. С чем это было связано — было трудно понять. Возможно рос ток потребления из за увеличения схемы. Возможно увеличивались задержки в комбинационных схемах по мере роста объема логики. Потому что одно дело просто вывести единичку, другое дело преобразовать в код. Но это тоже далеко от реальности, т.к. частота микросхемы 400 МГц. Поэтому все пути привели к схеме переключения земли индикаторов — на транзисторах КТ315. Очевидно, какой-то транзистор не отключал свою землю и через светодиоды продолжал протекать небольшой ток. А может тому виной режим выхода pull-up, что задается по умолчанию? Тестовые медленные переключения индикаторов показали, какой из двух транзисторов дает проблему. Его замена не помогла. Переключение режима выводов в pull-down не помогла. Самостоятельное "притягивание" базы к земле тоже не помогло. Замена транзистора не помогла. А помогла… очистка печатной платы от флюса ТТ!!!
- Точность часов. Генератор на часовом кварце + ТТЛ микросхеме — стабильно убегает за сутки на 3 минуты. Более точный генератор удалось сделать на КМОП микросхеме. В целом, вопрос проектирования кварцевых генераторов весьма обширен, и выходит за рамки статьи. См. [6, 7, 8]
Выводы
В результате проделанной работы я пришел к следующим положительным выводам:
- ПЛИС фирмы Lattice и их среда разработки, вполне пригодны для работы;
- использование универсальных HDL-языков, таких, как Verilog, позволяет без особых проблем сменить производителя ПЛИС;
- большая часть проблем, с которыми я столкнулся при разработке проекта, не были связаны с тем, что основа проекта — это ПЛИС (за исключением моментов со сбросом после прошивки и инициализации регистров);
- ПЛИС малых объемов, порядка 64...500 ячеек, могут быть применены не только, как замена горстки микросхем вспомогательной мелкой логики, но и для разработки простых самодостаточных цифровых устройств, типа часов или частотомеров;
- По объемам: ресурсы ПЛИС порядка 64 ячеек достаточны для создания часов. Но 32 ячеек на такие часы уже не хватит. Возможно, удастся сделать таймер без индикации. Для добавления функционала будильника ресурсов в 64 ячейки будет мало. Подключение RTC — часов реального времени DS1302, DS1307, DS3231, потребует еще больших ресурсов. Но, к примеру, для чтения времени с GPS приемника и вывода его на LCD индикатор, будет достаточно ~240 ячеек[9].
Я отлично провел время, изучая новую для себя ПЛИС, узнал много нового, пристроил подаренную микросхему, светодиодный экран и собрал часы для дачи. Желаю вам интересных проектов и отличного настроения!
Исходные коды проекта на GitHub:
https://github.com/UA3MQJ/fpga_simple_clock
Ссылки
- Часы на ПЛИС с применением Quartus II и немного Verilog
- Часы на ПЛИС
- Lattice products
- we.easyelectronics.ru — Флюс-гель ТТ (Осторожно, говно!)
- МРБ 1178.Электронные часы на МОП интегральных микросхемах. Справ. пособие. — М.: Радио и связь, 1993. — 48 с.: ил.-(Массовая радиобиблиотека; Вып. 1178).
- Особенности кварцевой стабилизации частоты генераторов
- Кварцевые генераторы
- КВАРЦЕВЫЕ РЕЗОНАТОРЫ
- Часики. GPS + LCD-дисплей WH0802
Автор: UA3MQJ