Хочу предложить Вашему вниманию свою схему частотомера с диапазоном измерения 1Гц-200МГц. Он построен на связке микроконтроллёр — ПЛИС. Идея данного устройства возникла у меня в качестве первого более-менее серьёзного (пробного) проекта на ПЛИС, после того как просто мигать светодиодом стало неинтересно и хотелось сделать что-то более интересное. В моём распоряжении была Altera MAXII на 240 ячеек в виде отладочной платы, максимальная частота около 230 МГц (что и ограничило верхний порог измерения частотомера) чего вполне достаточно для радиолюбительских целей.
Итак, задача поставлена, приступим к реализации проекта. Начнём с принципа работы частотомера: как известно — это устройство, которое считает количество импульсов за фиксированный промежуток времени. Исходя из этого разделим наш прибор на две основные части: первая — это быстрый счётчик импульсов на ПЛИС, вторая — устройство формирования точной задержки в одну секунду и вывода информации, этим будет заниматься микроконтроллер.
Начнём с быстродействующей части. Казалось бы, что может быть проще, просто берём двоичный счётчик достаточной разрядности (бита 24, а лучше 32), запускаем его ровно на одну секунду и получаем желаемый результат. Но! это при условии, что мы будем выводить результат с помощью 32 битного МК (например STM32, который может и 64 битные числа переваривать на ура), но если взять AVR, то возникнут проблемы (есть ограничение на логический сдвиг больше 16 бит). А мне хотелось сделать что-то универсальное. Потому я решил выводить информацию поразрядно (единицы, десятки, сотни герц и т.д.). Для этого я взял каскад десятичных счётчиков с функцией переноса (в моём случае — девять штук, чего достаточно для счёта до 999 миллионов импульсов). Каждый счётчик — четырёхбитный, а значит имеет 4 выхода (Q0-Q3), девять счётчиков — это 36 выводов, согласитесь, не каждый МК имеет такое количество портов. Значит — нужен мультиплексор, который будет коммутировать счётные регистры счётчиков. А к мультиплексору нужен свой отдельный счётчик, который будет им управлять. В итоге получилось что-то, вроде этого (см картинку):
Поясню логику устройства. Каждый десятичный счётчик имеет счётный вход (clk), который реагирует на возрастающий фронт импульса, разрешающий вход (EN), который разрешает счёт, при подаче на него лог нуля, сигнал сброса счётчика (res), который обнуляет счётчик по возрастающему фронту поданного на него импульса, выход переноса pr, который даёт сигнал переноса, когда счётчик досчитал до 10. Все счётчики соединены в виде каскада. Счётные импульсы (измеряемого сигнала) подаются на счётный вход clk первого счётчика (со входа count), на входы EN всех счётчиков (вывод cs_count) подаём лог ноль ровно на одну секунду. Первый счётчик начинает считать, досчитывает до девятого импульса, а на десятый — обнуляется и даёт импульс на вывод pr, который соединён с входом clk следующего счётчика. И так по цепочке до 9 разряда. Когда на входах EN появляется лог единица, счётчики прекращают счёт, но посчитанные импульсы сохраняются на их выходных регистрах.
Для вывода информации микроконтроллёр будет подавать тактовый сигнал на вход clk, который соединён со счётным входом счётчика мультиплексора и входом стробирования самого мультиплексора. Счётчик мультиплексора практически аналогичен входным счётчикам, кроме того, что он считает до 15 и у него нет выхода переноса (он там не нужен). При подаче тактового импульса на clk и разрешающего сигнала (лог ноль на вход cs_data и соответственно на вход EN счётчика) счётчик начинает считать и выводить результат на счётный вход С мультиплексора, а тот в зависимости от результата счёта коммутирует свои входные шины I1-I9 с выходной шиной О. Таким образом мы поочередно выводим значения каждого счётчика на выходную четырёхбитную шину data (изначально я пытался организовать вывод по последовательной шине по типу spi интерфейса, и при симуляции даже всё выглядело очень прилично, но в «железе» так и не заработало, выводились случайные числа, поэтому я отказался от данной идеи в пользу четырёхбитной шины данных).
Вот так это всё выглядит на языке описания аппаратуры «Verilog» (к слову, который мне удалось освоить, поверхностно, конечно, за неделю):
//Основной модуль
module main(count,cs_count,cs_data,reset,dat,clk,g_clk,out_0,out_2_16,led);
input count,cs_count,cs_data,reset,clk,g_clk;
output out_0,led;
output[3:0]dat,out_2_16;
wire pr1,pr2,pr3,pr4,pr5,pr6,pr7,pr8,rst;
wire [3:0]QI1,QI2,QI3,QI4,QI5,QI6,QI7,QI8,QI9;
wire [5:0]CQ;
cnt_10 my_cnt1(.clk(count),.EN(cs_count),.pr(pr1),.res(rst),.Q(QI1));
cnt_10 my_cnt2(.clk(pr1),.EN(cs_count),.pr(pr2),.res(rst),.Q(QI2));
cnt_10 my_cnt3(.clk(pr2),.EN(cs_count),.pr(pr3),.res(rst),.Q(QI3));
cnt_10 my_cnt4(.clk(pr3),.EN(cs_count),.pr(pr4),.res(rst),.Q(QI4));
cnt_10 my_cnt5(.clk(pr4),.EN(cs_count),.pr(pr5),.res(rst),.Q(QI5));
cnt_10 my_cnt6(.clk(pr5),.EN(cs_count),.pr(pr6),.res(rst),.Q(QI6));
cnt_10 my_cnt7(.clk(pr6),.EN(cs_count),.pr(pr7),.res(rst),.Q(QI7));
cnt_10 my_cnt8(.clk(pr7),.EN(cs_count),.pr(pr8),.res(rst),.Q(QI8));
cnt_10 my_cnt9(.clk(pr8),.EN(cs_count),.res(rst),.Q(QI9));
no my_no(.A(reset),.B(rst));
cnt_mux my_cmux(.clk(clk),.EN(cs_data),.res(rst),.Q(CQ));
cnt_16 my_16(.clk(g_clk),.Q(out_2_16));
mux31 my_mux (.I1(QI1),.I2(QI2),.I3(QI3),.I4(QI4),.I5(QI5),.I6(QI6),
.I7(QI7),.I8(QI8),.I9(QI9),.C(CQ),.EN(cs_data),.out(dat),.strobe(clk));
assign out_0 = g_clk;
assign led = ~cs_count;
endmodule
module cnt_10(clk,EN,Q,pr,res); //Десятичный счётчик
input clk,EN,res;
output reg[3:0]Q;
output reg pr;
always@(posedge clk or posedge res)
begin
if(res) begin pr <= 1’b0;
Q <= 4’b0000; end
else
begin
if(EN==0)
begin
Q <= Q+1’b1;
if(Q==4’b1001) begin pr <= 1’b1;
Q <= 4’d0; end
else begin pr <= 1’b0; end
end
end
end
endmodule
module no(A,B); //Логический элемент НЕ
input A;
output B;
assign B=~A;
endmodule
module cnt_mux(clk,EN,Q,res); //Счётчик мультиплексора
input clk,EN,res;
output reg[3:0]Q;
always@(negedge clk or posedge res)
begin
if(res) begin Q <= 4’b0000; end
else
begin
if(EN==0)
begin
Q <= Q+1’b1;
end
end
end
endmodule
module mux31(I1,I2,I3,I4,I5,I6,I7,I8,I9,C,strobe,out,EN);//Мультиплексор
input [3:0]I1,I2,I3,I4,I5,I6,I7,I8,I9;
input strobe,EN;
input [3:0]C;
output reg [3:0]out;
always@(posedge strobe)
begin
if(strobe)begin
if(EN==0) begin
case(C)
4’b0001: out <= I1;
4’b0010: out <= I2;
4’b0011: out <= I3;
4’b0100: out <= I4;
4’b0101: out <= I5;
4’b0110: out <= I6;
4’b0111: out <= I7;
4’b1000: out <= I8;
4’b1001: out <= I9;
default: out <= 4’d0;
endcase
end
end
end
endmodule
module cnt_16(clk,Q,res);//Дополнительный счётчик-делитель
input clk,res; //частоты для демонстрации
output reg[3:0]Q; //не связан с основным устройством
//делит частоту кварцевого генератора на 50 мГц
always@(posedge clk) //установленного на плате ПЛИС
begin
if(res) begin Q <= 4’b0000; end
else
begin Q <= Q+1’b1; end
end
endmodule
Вторая часть устройства, как я уже писал выше, реализована на микроконтроллере (у меня это atmega328, можно любой другой с достаточным количеством выводов и памятью от 2 килобайт). Его задачи — сформировать секундную задержку, подать в это время разрешающий сигнал на счёт импульсов и вывести полученный результат. Реализуем это всё на Си.
С первой задачей я «схалтурил», задержку сделал встроенной функцией «delay_ms» из CVAVR. Хотя, я честно пытался организовать задержку на таймере 1, но так и не понял как его однократно запустить на счёт по команде, а не постоянно выдавать прерывания (если кто знает как это сделать, напишите в комментариях). Кстати результат получился очень даже приемлемый показания частоты не плавают в ходе постоянного измерения (не более 1Гц).
Теперь о выводе результатов. Он у нас происходит по нисходящему фронту сигнала clk подаваемого при низком уровне разрешающего сигнала cs_data, просто даём импульс на clk и после считывает данные с выводов data_0 — data_3 девять раз, а данные записываем в массив. Вся эта красота отображается, в моём случае, на дисплее HD44780, подключенном через расширитель портов pcf8574T по последовательной шине i2c. Просто выводим по очереди каждый разряд, начиная со старшего, попутно проверяем, если старший разряд равен нулю, то просто ставим пробел (чтобы отсечь нули слева от числа и не получать значения типа 00000125 Герц) . При желании индикацию можно сделать любую (хоть на семисегментных индикаторах, хоть на LCD дисплее или вообще выводить в терминал через uart).
Код на Си выглядит так (весь проект будет приложен к статье):
unsigned char result[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void cpld_res(void){
res=0;
delay_us(50);
res=1;
}
void count(void){
cs_count = 0; //Запускает плисину на счёт
delay_ms(1000);
cs_count = 1;
delay_ms(1);
}
void processing(void){ //Пишем результат в массив
unsigned int j;
cs_data=0;
clk=0;
delay_us(5);
for(j=0;j<10;j++){
clk=1;
delay_us(10);
clk=0;
delay_us(5);
result[j]=((data_0|(data_1<<1)|(data_2<<2)|(data_3<<3))&0x0F);
delay_us(5);
}
cs_data=1;
delay_us(50);
cpld_res(); //Сброс счётчика плисины
lcd_gotoxy(4,0); //Выводим на индикатор
lcd_print(«Chastota»);
lcd_gotoxy(2,1);
if(result[9]>0){lcd_print_char(result[9]+0x30);
lcd_print_char(result[8]+0x30);
lcd_print_char(result[7]+0x30);
lcd_print_char(result[6]+0x30);
lcd_print_char(result[5]+0x30);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[8]>0){lcd_print(» «);
lcd_print_char(result[8]+0x30);
lcd_print_char(result[7]+0x30);
lcd_print_char(result[6]+0x30);
lcd_print_char(result[5]+0x30);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[7]>0){lcd_print(» «);
lcd_print_char(result[7]+0x30);
lcd_print_char(result[6]+0x30);
lcd_print_char(result[5]+0x30);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[6]>0){lcd_print(» «);
lcd_print_char(result[6]+0x30);
lcd_print_char(result[5]+0x30);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[5]>0){lcd_print(» «);
lcd_print_char(result[5]+0x30);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[4]>0){lcd_print(» «);
lcd_print_char(result[4]+0x30);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[3]>0){lcd_print(» «);
lcd_print_char(result[3]+0x30);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else if(result[2]>0){lcd_print(» «);
lcd_print_char(result[2]+0x30);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
else{lcd_print(» «);
lcd_print_char(result[1]+0x30);
lcd_print(» Hz»);}
for(j=0;j<10;j++){result[j] = 0;}
}
Фьюзы атмеги: LOW 0xFF, HIGH 0xDD, EXT 0x05;
Некоторые могли обратить внимание, что в массиве 10 байт, а разрядов у меня лишь девять. Это не моя ошибка. У меня постоянно получался ноль в первом разряде, а всё значение смещалось влево. Так я убрал этот лишний ноль. Я думаю, это ошибка в ПЛИС (т.е. моей схеме), по первому импульсу clk у меня считывается значение default с мультиплексора, а оно равно нулю.
Теперь схематически устройство в сборе:
Выводы ПЛИС на схеме не обозначены, потому что они назначаются произвольно, так же не обозначены конденсаторы кварца и резистор ножки reset с подтяжкой к плюсу атмеги.
И, конечно же фото готового устройства (собрано на демо-платах, соединённых проводами).
Результат со встроенного в плату ПЛИС генератора на 50 МГц (как видно есть погрешность в 35438 Гц, можно подправить секундную задержку, хотя и в точности кварцевого генератора я не сильно уверен):
После делителя на 2 (есть проекте плис на отдельном счётчике-делителе):
Делитель на 4:
Знаю, что найдутся люди, которые скажут, зачем такие сложности, можно было просто на МК или на рассыпухе типа cd4017. Я повторюсь, это моя первая проба на ПЛИС. Это не совсем готовое устройство, оно не имеет входного буфера, щупа. Это скорее демонстрация работы ПЛИС в связке с микроконтроллером.
На этом всё, спасибо за внимание, пишите свои замечания в комментариях (желательно по делу).
Список радиоэлементовОбозначение
Тип
Номинал
Количество
ПримечаниеМагазинМой блокнот
ПЛИС
МикросхемаEPM240T100C51
Можно любую ПЛИС от 80 ячеек
МК AVR 8-битATmega328P1
LCD-дисплейHD447801
ИС I2C интерфейсаPCF85741
Добавить все
Скачать список элементов (PDF)
Прикрепленные файлы:
- Исходник.rar (483 Кб)