В третьей части проекта для нашей платы VE-XC6SLX9 мы создадим видеоигру. За основу возьмем проект VGA sprites, уже упоминавшегося Arlet`a. В основу этого дизайна заложена идея генерации меняющихся спрайтов, в зависимости от положения счетчиков VGA развертки по горизонтали и вертикали. Спрайт (англ. Sprite — фея; эльф) — графический объект в компьютерной графике. Чаще всего — растровое изображение, которое можно отобразить на экране. В состав проекта входят следующие файлы:
- sprites_demo.v - модуль верхнего уровня, выполняет вывод и управление спрайтами
- vga.v - генератор VGA сигналов
- hardware_sprite.v - генератор спрайтов
- tester.v - тесты
Самым интересным файлом является hardware_sprite.v, рассмотрим его подробнее:
Verilog Code:
/* * Hardware Sprite and Supporting Modules * * by Brendan Doms, Sean McBride, Brian Shih, and Mikell Taylor * (Olin College - Computer Architecture - Fall 2005) * */ module hardware_sprite(Reset, R, G, B, A, clk, pclk, row, col, active, posx, posy, fliph, flipv, color); input active, fliph, flipv, clk, pclk; input [9:0] col, posx; input [8:0] row, posy; input color, Reset; output R, G, B, A; wire sR, sG, sB, sA; reg [3:0] xcoord, ycoord; reg [2:0] shape; reg [22:0] shapecounter; reg drawing; parameter WIDTH = 10'd16, HEIGHT = 10'd16; sprite_memory spritemem0 (Reset, sR, sG, sB, sA, clk, pclk, xcoord, ycoord, shape); and a0 (R, sR, drawing); and a1 (G, sG, drawing); and a2 (B, sB, drawing); and a3 (A, sA, drawing); // Initialize values always @(posedge clk) begin if (Reset) begin // Initialize things shapecounter = 0; if (color == 0) shape = 0; else if (color == 1) shape = 2; end else begin // Change the shape shapecounter = shapecounter + 1; if (shapecounter == 0) begin if (shape == 0) shape = 1; else if (shape == 1) shape = 0; else if (shape == 2) shape = 3; else if (shape == 3) shape = 2; end end end always @(posedge pclk) begin drawing = (active && (col >= posx) && (col < posx + WIDTH) && (row >= posy) && (row < posy + HEIGHT)); // Make sure NEXT pixel is being requested xcoord = col-posx+1; // Current row requested ycoord = row-posy; end endmodule // Video selector, essentially a 3 bit mux module video_selector(ROut, GOut, BOut, RIn, GIn, BIn, R, G, B, A); output ROut, GOut, BOut; input RIn, GIn, BIn, R, G, B, A; Mux2 mR (ROut, RIn, R, A); Mux2 mG (GOut, GIn, G, A); Mux2 mB (BOut, BIn, B, A); endmodule module Mux2 (OUT, A, B, S); // OUT=A if S=0, OUT=B if S=1 output OUT; input A, B, S; wire notS, andA, andB; not Inv0 (notS, S); and And0 (andA, A, notS); and And1 (andB, B, S); or Or1 (OUT, andA, andB); endmodule // 16x16 sprite memory, has 6 shapes saved module sprite_memory(Reset, R, G, B, A, clk, pclk, xcoord, ycoord, shape); input [3:0] xcoord, ycoord; input [2:0] shape; input clk, pclk, Reset; output R, G, B, A; reg R, G, B, A; reg [63:0] shaperow; // Sprite Shape 1 (Red Ghost 1) reg [63:0] shape1mem[15:0]; // Sprite Shape 2 (Red Ghost 2) reg [63:0] shape2mem[15:0]; // Sprite Shape 3 (Cyan Ghost 1) reg [63:0] shape3mem[15:0]; // Sprite Shape 4 (Cyan Ghost 2) reg [63:0] shape4mem[15:0]; // Initialize always @(posedge clk) begin if (Reset) begin // Initialize shape 1 shape1mem[0] = 64'b0000000000000000100110011001100110011001100110010000000000000000; shape1mem[1] = 64'b0000000000001001100110011001100110011001100110011001000000000000; shape1mem[2] = 64'b0000000010011001100110011001100110011001100110011001100100000000; shape1mem[3] = 64'b0000100110011001111111111001100110011001111111111001100110010000; shape1mem[4] = 64'b1001100110011111111111111111100110011111111111111111100110011001; shape1mem[5] = 64'b1001100110011111001111111111100110011111001111111111100110011001; shape1mem[6] = 64'b1001100110011111001111111111100110011111001111111111100110011001; shape1mem[7] = 64'b1001100110011001001111111001100110011001001111111001100110011001; shape1mem[8] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape1mem[9] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape1mem[10] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape1mem[11] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape1mem[12] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape1mem[13] = 64'b1001100110010000100110011001100110010000100110011001100110010000; shape1mem[14] = 64'b1001100100000000000010011001100100000000000010011001100100000000; shape1mem[15] = 64'b1001000000000000000000001001000000000000000000001001000000000000; // Initialize shape 2 shape2mem[0] = 64'b0000000000000000100110011001100110011001100110010000000000000000; shape2mem[1] = 64'b0000000000001001100110011001100110011001100110011001000000000000; shape2mem[2] = 64'b0000000010011001100110011001100110011001100110011001100100000000; shape2mem[3] = 64'b0000100110011001111111111001100110011001111111111001100110010000; shape2mem[4] = 64'b1001100110011111111111111111100110011111111111111111100110011001; shape2mem[5] = 64'b1001100110011111001111111111100110011111001111111111100110011001; shape2mem[6] = 64'b1001100110011111001111111111100110011111001111111111100110011001; shape2mem[7] = 64'b1001100110011001001111111001100110011001001111111001100110011001; shape2mem[8] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape2mem[9] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape2mem[10] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape2mem[11] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape2mem[12] = 64'b1001100110011001100110011001100110011001100110011001100110011001; shape2mem[13] = 64'b0000100110011001100110010000100110011001100110010000100110011001; shape2mem[14] = 64'b0000000010011001100100000000000010011001100100000000000010011001; shape2mem[15] = 64'b0000000000001001000000000000000000001001000000000000000000001001; // Initialize shape 3 shape3mem[0] = 64'b0000000000000000011101110111011101110111011101110000000000000000; shape3mem[1] = 64'b0000000000000111011101110111011101110111011101110111000000000000; shape3mem[2] = 64'b0000000001110111011101110111011101110111011101110111011100000000; shape3mem[3] = 64'b0000011101110111111111110111011101110111111111110111011101110000; shape3mem[4] = 64'b0111011101111111111111111111011101111111111111111111011101110111; shape3mem[5] = 64'b0111011101111111001111111111011101111111001111111111011101110111; shape3mem[6] = 64'b0111011101111111001111111111011101111111001111111111011101110111; shape3mem[7] = 64'b0111011101110111001111110111011101110111001111110111011101110111; shape3mem[8] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape3mem[9] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape3mem[10] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape3mem[11] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape3mem[12] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape3mem[13] = 64'b0111011101110000011101110111011101110000011101110111011101110000; shape3mem[14] = 64'b0111011100000000000001110111011100000000000001110111011100000000; shape3mem[15] = 64'b0111000000000000000000000111000000000000000000000111000000000000; // Initialize shape 4 shape4mem[0] = 64'b0000000000000000011101110111011101110111011101110000000000000000; shape4mem[1] = 64'b0000000000000111011101110111011101110111011101110111000000000000; shape4mem[2] = 64'b0000000001110111011101110111011101110111011101110111011100000000; shape4mem[3] = 64'b0000011101110111111111110111011101110111111111110111011101110000; shape4mem[4] = 64'b0111011101111111111111111111011101111111111111111111011101110111; shape4mem[5] = 64'b0111011101111111001111111111011101111111001111111111011101110111; shape4mem[6] = 64'b0111011101111111001111111111011101111111001111111111011101110111; shape4mem[7] = 64'b0111011101110111001111110111011101110111001111110111011101110111; shape4mem[8] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape4mem[9] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape4mem[10] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape4mem[11] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape4mem[12] = 64'b0111011101110111011101110111011101110111011101110111011101110111; shape4mem[13] = 64'b0000011101110111011101110000011101110111011101110000011101110111; shape4mem[14] = 64'b0000000001110111011100000000000001110111011100000000000001110111; shape4mem[15] = 64'b0000000000000111000000000000000000000111000000000000000000000111; end end // Assign signals to proper outputs always @(posedge pclk) begin if (shape == 0) shaperow = shape1mem[ycoord]; if (shape == 1) shaperow = shape2mem[ycoord]; if (shape == 2) shaperow = shape3mem[ycoord]; if (shape == 3) shaperow = shape4mem[ycoord]; R = shaperow[(xcoord*4)+3]; G = shaperow[(xcoord*4)+2]; B = shaperow[(xcoord*4)+1]; A = shaperow[xcoord*4]; end endmodule
Основным модулем здесь является module sprite_memory(Reset, R, G, B, A, clk, pclk, xcoord, ycoord, shape). Его основной задачей является формирование сигналов цветов R, G и B в зависимости от координат X и Y а также типа спрайта (shape). Сам спрайт формируется с помощью модуля sprite_memory(Reset, R, G, B, A, clk, pclk, xcoord, ycoord, shape). Далее для наглядности сформируем символ из модуля sprites_demo, разместим его на схему и заодно инвертируем сигнал Reset:
На следующем шаге сопоставим сигналы проекта с реальными выводами FPGA:
После компиляции проекта и заливки в нашу отладочную плату, можно уже увидеть изображение на мониторе. Остался последний шаг, прикрутить к нашей FPGA управление от контроллера. В первой части проекта мы создали USB HID устройство, которым можно управлять с компьютера. Честно говоря фирменная программа от NXP нас не устроила (хотя бы по тому что она без исходников). Самым лучшим выход нам показалось создать программу в QT5 с использованием библиотеки HIDAPI.
HIDAPI это многоплатформенная библиотека, которая позволяет писать приложения (ПО хоста USB) с доступом к интерфейсам устройств USB и Bluetooth HID-Class на операционных системахWindows, Linux и Mac OS X. В то время как эта библиотека может использоваться обмена данными со стандартными устройствами HID наподобие клавиатур, мышей и джойстиков, особенно полезное применение библиотеки - работа с пользовательскими (определенными производителем, Vendor-Defined) устройствами HID. Множество устройств работают так, что не требуется писать специальный драйвер для каждой платформы. HIDAPI просто интегрируется с клиентским приложением (ПО хоста), потому что нужно всего лишь подключить в проект файл исходного кода. На платформе Windows, HIDAPI также можно опционально использовать как функции в DLL.
Программы, которые используют HIDAPI, не нуждаются в драйверах. Это означает, что не требуется применять пользовательский драйвер для каждого устройства на каждой платформе.
HIDAPI предоставляет ясный и непротиворечивый программный интерфейс для каждой платформы (операционной системы), что делает простым написание приложений, которые обмениваются данными с устройствами USB HID, при этом нет необходимости вдаваться в детали библиотек HID и интерфейсов операционной системы на каждой платформе.
Исходный код HIDAPI также предоставляет тестовое GUI-приложение, которое может производить энумерацию и обмен данными с любым устройством HID, подключенным к операционной системе. Тестовое GUI компилируется и работает на всех платформах, поддерживаемых HIDAPI.
Как видите, практически идеальное решение ))) Приступим к созданию проекта. По большому счету все действия описываются в одном файле mainwindow.cpp:
C++ Code:
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QDir> #include <QDebug> #include <QLibrary> #include <QKeyEvent> #include <unistd.h> #include "hidapi.h" #define REPORT_LENGTH 255 //Объявление функций библиотеки typedef struct hid_device_info* (*HID_ENUMERATE)(unsigned short, unsigned short); typedef void (*HID_FREE_ENUMARATION)(struct hid_device_info*); typedef hid_device* (*HID_OPEN)(unsigned short, unsigned short, wchar_t*); typedef int (*HID_GET_MANUFACTURER_STRING)(hid_device *, wchar_t *, size_t); typedef int (*HID_GET_PRODUCT_STRING)(hid_device*, wchar_t*, size_t); typedef int (*HID_GET_SERIAL_NUMBER_STRING)(hid_device*, wchar_t*, size_t); typedef int (*HID_SET_NONBLOCKING)(hid_device*, int); typedef int (*HID_SEND_FEATURE_REPORT)(hid_device*, const unsigned char*, size_t); typedef int (*HID_GET_FEATURE_REPORT)(hid_device*, unsigned char*, size_t); typedef int (*HID_WRITE)(hid_device*, const unsigned char*, size_t); //Класс для работы с USB HID устройствами class hidControl { private: HID_OPEN hid_open; HID_SET_NONBLOCKING hid_set_nonblocking; HID_SEND_FEATURE_REPORT hid_send_feature_report; HID_GET_FEATURE_REPORT hid_get_feature_report; HID_GET_MANUFACTURER_STRING hid_get_manufacturer_string; HID_GET_PRODUCT_STRING hid_get_product_string; HID_ENUMERATE hid_enumerate; HID_FREE_ENUMARATION hid_free_enumeration; HID_WRITE hid_write; public: hidControl() {} ~hidControl() {} unsigned char buf[REPORT_LENGTH]; bool deviceFound; hid_device *handle; struct hid_device_info *devs, *cur_dev; // Инициализация HID int init(unsigned int VID, unsigned int PID) { // Пробуем подключить библиотеку QString QString_str; QDir curdir; wchar_t wstr[255]; QLibrary lib(curdir.absolutePath() + "/hidapi"); lib.load(); if (!lib.isLoaded()) return -1; //Библиотека подключена можно вызывать функции библиотеки hid_open = (HID_OPEN)lib.resolve("hid_open"); hid_set_nonblocking = (HID_SET_NONBLOCKING)lib.resolve("hid_set_nonblocking"); hid_send_feature_report = (HID_SEND_FEATURE_REPORT)lib.resolve("hid_send_feature_report"); hid_get_feature_report = (HID_GET_FEATURE_REPORT)lib.resolve("hid_get_feature_report"); hid_get_manufacturer_string = (HID_GET_MANUFACTURER_STRING)lib.resolve("hid_get_manufacturer_string"); hid_get_product_string = (HID_GET_PRODUCT_STRING)lib.resolve("hid_get_product_string"); hid_enumerate = (HID_ENUMERATE)lib.resolve("hid_enumerate"); hid_free_enumeration = (HID_FREE_ENUMARATION)lib.resolve("hid_free_enumeration"); hid_write = (HID_WRITE)lib.resolve("hid_write"); //Переберем все USB HID устройства для отладки devs = hid_enumerate(0x00, 0x00); cur_dev = devs; while (cur_dev) { qDebug() << "VID " << cur_dev->vendor_id << "PID " << cur_dev->product_id; cur_dev = cur_dev->next; } hid_free_enumeration(devs); //Открываем нашу плату deviceFound = false; handle = hid_open(VID, PID, NULL); if (handle) { //Если успешно открыли установим неблокирующий режим и выведем //строки производителя и устройства deviceFound = true; hid_set_nonblocking(handle, 1); hid_get_manufacturer_string(handle, wstr, 255); QString_str = QString::fromStdWString(wstr); qDebug() << QString_str; hid_get_product_string(handle, wstr, 255); QString_str = QString::fromStdWString(wstr); qDebug() << QString_str; } else return -1; return 0; } int write() { if(deviceFound) { buf[0] = 1; // В первом байте находится номер репорта return hid_write(handle, buf, 65); } return -1; } int read() { //Чтение нам пока не нужно return -1; } }; hidControl *hid; unsigned char keys=0; //Обработка нажатия клавиш------------------------------------------------------------ void MainWindow::keyPressEvent(QKeyEvent *event) { if(event->key()==Qt::Key_Up) { ui->label->setText("up"); keys=keys | 0x08; } else if(event->key()==Qt::Key_Down) { ui->label->setText("down"); keys=keys | 0x01; } else if(event->key()==Qt::Key_Left) { ui->label->setText("left"); keys=keys | 0x02; } else if(event->key()==Qt::Key_Right) { ui->label->setText("right"); keys=keys | 0x04; } hid->buf[1]=keys; qDebug() << hid->write(); QMainWindow::keyPressEvent( event ); } //Обработка отпускания клавиш------------------------------------------------------------ void MainWindow::keyReleaseEvent(QKeyEvent *event) { if(event->key()==Qt::Key_Up) { ui->label->setText("up"); keys=keys & ~0x08; } else if(event->key()==Qt::Key_Down) { ui->label->setText("down"); keys=keys & ~0x01; } else if(event->key()==Qt::Key_Left) { ui->label->setText("left"); keys=keys & ~0x02; } else if(event->key()==Qt::Key_Right) { ui->label->setText("right"); keys=keys & ~0x04; } hid->buf[1]=keys; qDebug() << hid->write(); QMainWindow::keyPressEvent( event ); } //Конструктор основного класса----------------------------------------------------------- MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); hid = new hidControl(); if(hid->init(0x1FC9, 0x0003)<0) { ui->label->setText("No device"); qDebug() << "No device"; } else { ui->label->setText("Start"); qDebug() << "Start"; } } //Деструктор основного класса------------------------------------------------------------ MainWindow::~MainWindow() { delete ui; }
Тут в принципе ничего сложного нет. Все делает библиотека HIDAPI. От нас только требуется открыть наше устройство с VID=0x1FC9 и PID=0x0003, и если устройство открылось успешно, то можно управлять выводами нашего контролера по нажатиям на клавиатуре. Ну и по традиции видео и исходники:
Проект вывод спрайтов для FPGA: sprites_demo.zip
Проект управление по USB для PC: usb_hid_qt.zip