В качестве заключительного проекта для нашей платы VE-EP4CE10E я решил создать фоторамку с интерфейсом USB. Цифровая фоторамка (digital photo frame) — это электронное устройство, предназначенное для считывания и показа изображений, записанных в цифровом виде. Цифровые фоторамки предшествует планшетным компьютерам, которые могут служить той же цели в некоторых ситуациях; однако, цифровые фоторамки, как правило, разработаны специально для стационарного, эстетического отображения фотографий и поэтому обычно обеспечивают более красивую рамку и систему питания, предназначенную для непрерывного использования. В этом проекте мы пройдем весь путь передачи данных: от компьютера через USB в ARM. От ARM через SPI в FPGA. Отображение содержимое RAM на VGA мониторе средствами ПЛИС. Приступим к созданию нашей фоторамки:
Для начала создадим проект для FPGA. За основу возьмем Первый проект для VE-EP4CE10E часть 3. Вместо входа управления от процессора, мы заведем сигналы SPI: sck и mosi. Это не все сигналы интерфейса, но нам, для записи данных в FPGA хватит. Добавим еще два сигнала clr и blank. Сигнал clr идет от ARM в FPGA и обнуляет указатель записи видеопамяти. Второй сигнал blank идет от FPGA в ARM и останавливает передачу данных во время вывода памяти на дисплей. Запись возможна только во время так называемого обратного хода луча. На современных мониторах никакого луча нет, но время когда память не используется для чтения, осталось. Им мы и воспользуемся. Код на языке Verilog:
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, //external SRAM inout [7:0]sram_data, output reg[18:0]sram_adress, output reg CS, output reg WE, output reg OE, //SPI input sck, input mosi, input clr ); localparam state0 = 4'b0000, state1 = 4'b0001, state2 = 4'b0010, state3 = 4'b0011; reg r_w=0; reg [3:0]nBit = 0; reg [3:0]delay = 0; reg [7:0]spi_data; reg [2:0]spi_cnt = 0; reg [7:0]tmp_sram_data_wr = 0; reg [18:0]tmp_sram_adr_wr = 0; reg [18:0]sram_adr_cmp = 0; reg [18:0]sram_adr_wr = 0; reg [7:0]sram_data_wr = 0; reg [3:0]r_state = state0; reg [3:0]w_state = state0; assign sram_data = r_w ? 8'bzzzzzzzz : sram_data_wr; //шина с 3 состояниями //Прием данных SPI always @(posedge sck, negedge clr) begin if(clr == 0) //Сбрасываем адрес памяти и счетчик spi begin sram_adr_wr <= 0; spi_cnt <= 0; end else begin spi_data = {spi_data[6:0], mosi}; //сдвиговый регистр spi_cnt = spi_cnt+1; if(spi_cnt==0) //приняли 8 бит begin tmp_sram_data_wr <= spi_data; //данные для записи в озу tmp_sram_adr_wr <= sram_adr_wr; sram_adr_wr <= sram_adr_wr + 1; if(sram_adr_wr>393214) //Наш экран кончился begin sram_adr_wr <= 0; end end end end //Вывод изображения always @(posedge char_clock) begin CS <= 1'b0; if((blank==0) | (w_state !=state0)) begin r_w <= 0; OE <= 1'b1; //Время для записи памяти if(sram_adr_cmp!=tmp_sram_adr_wr) begin delay <= delay+1; if(delay>1) begin delay <= 0; if(w_state == state0) begin OE <= 1'b1; sram_adress <= tmp_sram_adr_wr; sram_data_wr <= tmp_sram_data_wr; WE <= 1'b0; w_state <= state1; end else if(w_state == state1) begin WE <= 1'b1; sram_adr_cmp <= tmp_sram_adr_wr; w_state <= state0; end end end //Изображение за кадром, выводим черный цвет blue_out <= 0; green_out <= 0; red_out <= 0; end if((blank==1) | (r_state !=state0)) begin //Время для чтения памяти r_w <= 1; WE <=1'b1; if(r_state == state0) begin sram_adress <= line_count*512 | char_count>>1; OE <= 1'b0; r_state <= state1; end else if(r_state == state1) begin //Формирование изображения blue_out <= (sram_data >> 4) & 8'b00001110; green_out <= (sram_data >> 1) & 8'b00001100; red_out <= (sram_data << 1) & 8'b00001110; OE <= 1'b1; r_state <= state0; end end end endmodule
Наш модуль вывода видеопамяти, дополнился модулем приема данных по SPI. После приема байта данных мы увеличиваем указатель записи видеопамяти на единицу, и во время обратного хода луча, пишем в видео память. Вид схемы FPGA:
Далее займемся созданием программы для ARM:
C++ Code:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI2_Init(); MX_USB_DEVICE_Init(); /* USER CODE BEGIN 2 */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //clr=1 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if (VCP_read(&rx_buff, 64) == 64) //пришел пакет по USB { if(reset==1) //Если начало передачи, сбросим fpga { reset=0; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); //clr=0 asm ("nop"); //nop HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //clr=1 } for(unsigned cnt1=0;cnt1<64;cnt1++) //передаем пакет в fpga { spi_transmit[0]=rx_buff[cnt1]; while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14)!=GPIO_PIN_RESET) //если мы за кадром { } HAL_SPI_Transmit(&hspi2, spi_transmit, 1, 10); //передаем 1 байт по spi } } else { reset=1; //картинка кончилась нужен сброс } } /* USER CODE END 3 */ }
По приходу USB пакета, мы сбрасываем сигнал clr который обнуляет указатель памяти в FPGA. Затем мы отправляем принятый пакет в SPI, если нет сигнала blank. Напомню если присутствует сигнал blank то FPGA занята выводом видео памяти. Ну и напоследок создадим программу на Qt, которая будет отправлять картинки в USB:
C++ Code:
#include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include QSerialPort *serial = new QSerialPort; QTimer *timer = new QTimer; QByteArray picture; QFileInfoList list; unsigned int pic_width=0, pic_height=0,list_ptr=0; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) { /*qDebug() << "Name : " << info.portName(); qDebug() << "Description : " << info.description(); qDebug() << "Manufacturer: " << info.manufacturer();*/ ui->comboBox->addItem(info.portName()); } //соединяем сигнал timout нашего таймера со слотом update connect(timer, SIGNAL(timeout()), this, SLOT(update())); } MainWindow::~MainWindow() { delete ui; } //Старт void MainWindow::on_pushButton_clicked() { //Список всех файлов *.bmp в директории files QDir dir(QApplication::applicationDirPath()+"/files"); dir.setFilter( QDir::Files | QDir::Readable | QDir::AllDirs | QDir::NoDotAndDotDot ); list = dir.entryInfoList(QDir::nameFiltersFromString("*.bmp")); /*for (int i=0; i<(list.count());i++) { qDebug() << list.at(i).fileName(); }*/ //Настройки ком порта serial->setPortName(ui->comboBox->currentText()); serial->setBaudRate(QSerialPort::Baud115200); serial->setDataBits(QSerialPort::Data8); serial->setParity(QSerialPort::NoParity); serial->setStopBits(QSerialPort::OneStop); serial->setFlowControl(QSerialPort::NoFlowControl); serial->open(QIODevice::ReadWrite); //Вызываем функцию таймера, что бы не дждать 10 секунд list_ptr=0; update(); timer->start(10000); //таймер раз в 10 секунд } //Стоп void MainWindow::on_pushButton_2_clicked() { serial->close(); //закрыли порт timer->stop(); //остановили таймер } //Таймер void MainWindow::update() { QByteArray msg; unsigned long pic_adr; unsigned char R,G,B,rgb; QGraphicsScene *scene = new QGraphicsScene; QString width,height; QString path; if(list.size()>0){//перебор списка с картинками path=QApplication::applicationDirPath()+"/files/"+list.at(list_ptr).fileName(); if(list_ptr<list.size()-1){ list_ptr++;} else{ list_ptr=0;} } 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); //выводим картинку на форму } if(pic_height*pic_width!=0) { //Перебираем пиксели bmp файла for(unsigned int dy=0;dy<pic_height;dy++) { for(unsigned int dx=0;dx<pic_width;dx++) { pic_adr=(pic_width*(pic_height-dy-1)+dx)*3+54; R=picture.data()[pic_adr]; G=picture.data()[pic_adr+1]; B=picture.data()[pic_adr+2]; rgb=R & 0b11100000 | G>>3 & 0b00011000 | B>>5 & 0b00000111; msg.append(rgb); } } //Пишем в виртуальный COM порт serial->write(msg, msg.length()); } }
Программа создает список всех com портов системы и выводит их в combobox. Далее по кнопке Start создает список всех картинок формата bmp в директории "files". Настраивает и открывает com порт, запускает таймер с интервалом 10 секунд. В слоте таймера void MainWindow::update() берется текущая картинка и преобразуется из 24 bit BMP в формат 3R2G3B. Далее полученный массив отправляется в USB com порт. Вид нашей программы:
В заключение видео работы и исходники:
Исходные файлы проекта BMP to COM: bmptocom_qt.zip
Проект фоторамки для FPGA: usb_to_vga.zip
Проект фоторамки для ARM: usb_to_spi.zip