Одному из наших заказчиков понадобилось измерить частоту сигнала в полевых условиях. Весь процесс происходил на Крайнем Севере, поэтому кроме мультиметра и индикаторной отвертки мы ничего не нашли. Под рукой была только наша плата VE-EP4CE10E и VGA монитор. Пришла идея сделать простенький частотомер с VGA выходом. Напомню, что частотоме́р это радиоизмерительный прибор для определения частоты периодического процесса или частот гармонических составляющих спектра сигнала.
Первая проблема с которой мы столкнулись, это способ записи изображения цифр в память FPGA. Блоки памяти можно инициализировать либо *.HEX либо *.MIF файлом. Изображение наших цифр мы решили хранить в 24 битном *.BMP файле. Естественно нам понадобилась программа конвертер из *.BMP в *.MIF. Поискав по просторам интернет ничего стоящего мы не нашли, и решили по быстрому соорудить свою на Qt5. Вот ее код:
C++ Code:
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QFileDialog> #include <QString> #include <QDebug> QByteArray picture; unsigned int pic_width=0, pic_height=0; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } //Открываем BMP файл void MainWindow::on_pushButton_clicked() { QGraphicsScene *scene = new QGraphicsScene; QString width,height; QString path; path = QFileDialog::getOpenFileName(0,"Open BMP", QApplication::applicationDirPath()+"/files", "*.bmp"); QFile fileIn(path); if(fileIn.open(QIODevice::ReadOnly)) { picture = fileIn.readAll(); //читаем файл fileIn.close(); //закрываем файл scene->addPixmap(QPixmap(path)); //добавляем картинку pic_height=scene->height(); //определяем размеры pic_width=scene->width(); height="Heigth="+QString::number(scene->height()); width="Width="+QString::number(scene->width()); ui->label->setText(height); ui->label_2->setText(width); ui->graphicsView->setGeometry(10,40,scene->width(),scene->height()); ui->graphicsView->setScene(scene); //выводим картинку на форму } } //Формируем MIF файл void MainWindow::on_pushButton_2_clicked() { QString mif,adr_str,rgb_str; unsigned long adr1,adr2,rgb; unsigned char R,G,B; if(pic_height*pic_width!=0) { mif.append("-- Copyright (C) 1991-2015 Altera Corporation. All rights reserved\n"); mif.append("-- Your use of Altera Corporation's design tools, logic functions\n"); mif.append("-- and other software and tools, and its AMPP partner logic\n"); mif.append("-- functions, and any output files from any of the foregoing\n"); mif.append("-- (including device programming or simulation files), and any\n"); mif.append("-- associated documentation or information are expressly subject\n"); mif.append("-- to the terms and conditions of the Altera Program License\n"); mif.append("-- Subscription Agreement, the Altera Quartus II License Agreement,\n"); mif.append("-- the Altera MegaCore Function License Agreement, or other\n"); mif.append("-- applicable license agreement, including, without limitation,\n"); mif.append("-- that your use is for the sole purpose of programming logic\n"); mif.append("-- devices manufactured by Altera and sold by Altera or its\n"); mif.append("-- authorized distributors. Please refer to the applicable\n"); mif.append("-- agreement for further details.\n"); mif.append(" \n"); mif.append("-- Quartus II generated Memory Initialization File (.mif)\n"); mif.append(" \n"); mif.append("WIDTH=24;\n"); mif.append("DEPTH="+QString::number(pic_height*pic_width)+";\n"); mif.append(" \n"); mif.append("ADDRESS_RADIX=UNS;\n"); mif.append("DATA_RADIX=UNS;\n"); mif.append(" \n"); mif.append("CONTENT BEGIN\n"); //Перебираем пиксели bmp файла for(unsigned int dy=0;dy<pic_height;dy++) { for(unsigned int dx=0;dx<pic_width;dx++) { adr1=pic_width*dy+dx; adr2=pic_width*(pic_height-dy-1)+dx; R=picture.data()[54+adr2*3]; G=picture.data()[54+adr2*3+1]; B=picture.data()[54+adr2*3+2]; rgb=R*65536 + G*256 + B; adr_str=QString::number(adr1); rgb_str=QString::number(rgb); mif.append(adr_str); mif.append(":"); mif.append(rgb_str); mif.append(";\n"); } } mif.append("END;\n"); //Сораняем файл QString path; path = QFileDialog::getSaveFileName(0,"Save MIF", QApplication::applicationDirPath()+"/files", "*.mif"); QFile fileOut(path); if(fileOut.open(QIODevice::WriteOnly)) { QTextStream writeStream(&fileOut); // Создаем объект класса QTextStream // и передаем ему адрес объекта fileOut writeStream << mif; // Посылаем строку в поток для записи fileOut.close(); // Закрываем файл } } }
В принципе тут все просто. Открываем файл *.BMP пробегаем по пикселям, преобразуем 3 байта RGB в 24 битное число, и формируем *.MIF файл. В этой версии преобразуется изображение с глубиной цвета 24 бита. В принципе не сложно доработать для любого изображения. Вот так выглядит наша программа:
Далее создадим проект в Quartus II:
Генератор развертки VGA сигнала мы уже использовали в своих проектах. Модуль контроля формирует один раз в секунду импульсы окончания счета, и записи полученного значения в регистр вывода на экран. Код BCD счетчика достаточно банальный. Поэтому рассмотрим код преобразование десятичного счетчика в изображение.
Verilog Code:
module pic_gen( //vga input [11:0]char_count, input [11:0]line_count, input wire blank, input wire char_clock, output reg [3:0]red_out, output reg [3:0]green_out, output reg [3:0]blue_out, //built-in memory input [23:0]bmp_data, output reg[13:0]bmp_adress, //counter input input [31:0]bcd_cnt, input wire strobe ); reg [31:0]loc_cnt; reg [11:0]dx; reg [11:0]dy; //по стробу защелкиваем счетчик always @(posedge strobe) begin loc_cnt = bcd_cnt; end //Вывод изображения always @(posedge char_clock) begin //Ширина изображения одной цифры не превышает 32 пикселей dx<=char_count & 5'b11111; //Выбор чифры в знакоместе, в зависимости от значения счетчика if(char_count<32) begin bmp_adress <= dx+line_count*320+32*loc_cnt[31:28]; end else if(char_count>=32 && char_count<64) begin bmp_adress <= dx+line_count*320+32*loc_cnt[27:24]; end else if(char_count>=64 && char_count<96) begin bmp_adress <= dx+line_count*320+32*loc_cnt[23:20]; end else if(char_count>=96 && char_count<128) begin bmp_adress <= dx+line_count*320+32*loc_cnt[19:16]; end else if(char_count>=128 && char_count<160) begin bmp_adress <= dx+line_count*320+32*loc_cnt[15:12]; end else if(char_count>=160 && char_count<192) begin bmp_adress <= dx+line_count*320+32*loc_cnt[11:8]; end else if(char_count>=192 && char_count<224) begin bmp_adress <= dx+line_count*320+32*loc_cnt[7:4]; end else if(char_count>=224 && char_count<256) begin bmp_adress <= dx+line_count*320+32*loc_cnt[3:0]; end //Формирование изображения //Изображение за кадром, выводим черный цвет if(blank==0) begin blue_out <= 0; green_out <= 0; red_out <= 0; end //В окне 0,256,0,32 выводим нашу частоту else begin if(char_count<256 && line_count<32)<br /> begin<br /> blue_out <= (bmp_data >> 20) & 8'b00001111;<br /> green_out <= (bmp_data >> 12) & 8'b00001111; red_out <= (bmp_data >> 4) & 8'b00001111; end //Остальное закрашиваем белым else begin blue_out <= 4'b1111; green_out <= 4'b1111; red_out <= 4'b1111; end end end endmodule
Логика программы в принципе понятна из комментариев. Напомню модуль встроенной памяти хранит изображение цифр от 0 до 9. Ну и в конце видео работы и исходные файлы:
Для начала мы измерили тактовую частоты ПЛИС (50 МГц) затем частоту кадровой развертки VGA (60 Гц) и наконец частоту строчной развертки (48.3 КГц). Как видно из видео, измерения довольно точные.
Исходные файлы проекта BMP to MIF: bmptomif_qt.zip
Файл проекта частотомера: freq_count.zip