Визуальная электроника

В третьей части проекта для нашей платы VE-XC6SLX9 мы создадим видеоигру. За основу возьмем проект VGA sprites, уже упоминавшегося Arlet`a. В основу этого дизайна заложена идея генерации меняющихся спрайтов, в зависимости от положения счетчиков VGA развертки по горизонтали и вертикали. Спрайт (англ. Sprite — фея; эльф) — графический объект в компьютерной графике. Чаще всего — растровое изображение, которое можно отобразить на экране. В состав проекта входят следующие файлы:

  • sprites_demo.v - модуль верхнего уровня, выполняет вывод и управление спрайтами
  • vga.v - генератор VGA сигналов
  • hardware_sprite.v - генератор спрайтов
  • tester.v - тесты

 Самым интересным файлом является hardware_sprite.v, рассмотрим его подробнее:

Verilog Code:
  1. /*
  2.  * Hardware Sprite and Supporting Modules
  3.  *
  4.  * by Brendan Doms, Sean McBride, Brian Shih, and Mikell Taylor
  5.  * (Olin College - Computer Architecture - Fall 2005)
  6.  *
  7.  */
  8.  
  9.  
  10. module hardware_sprite(Reset, R, G, B, A, clk, pclk, row, col, active, posx, posy, fliph, flipv, color);
  11.  
  12. input active, fliph, flipv, clk, pclk;
  13. input [9:0] col, posx;
  14. input [8:0] row, posy;
  15. input color, Reset;
  16. output R, G, B, A;
  17.  
  18. wire sR, sG, sB, sA;
  19. reg [3:0] xcoord, ycoord;
  20. reg [2:0] shape;
  21. reg [22:0] shapecounter;
  22. reg drawing;
  23.  
  24. parameter
  25. WIDTH = 10'd16,
  26. HEIGHT = 10'd16;
  27.  
  28. sprite_memory spritemem0 (Reset, sR, sG, sB, sA, clk, pclk, xcoord, ycoord, shape);
  29.  
  30. and a0 (R, sR, drawing);
  31. and a1 (G, sG, drawing);
  32. and a2 (B, sB, drawing);
  33. and a3 (A, sA, drawing);
  34.  
  35. // Initialize values
  36. always @(posedge clk) begin
  37. if (Reset) begin
  38. // Initialize things
  39. shapecounter = 0;
  40. if (color == 0)
  41. shape = 0;
  42. else if (color == 1)
  43. shape = 2;
  44. end
  45. else begin
  46. // Change the shape
  47. shapecounter = shapecounter + 1;
  48. if (shapecounter == 0) begin
  49. if (shape == 0)
  50. shape = 1;
  51. else if (shape == 1)
  52. shape = 0;
  53. else if (shape == 2)
  54. shape = 3;
  55. else if (shape == 3)
  56. shape = 2;
  57. end
  58. end
  59. end
  60.  
  61. always @(posedge pclk) begin
  62. drawing = (active && (col >= posx) && (col < posx + WIDTH) && (row >= posy) && (row < posy + HEIGHT));
  63. // Make sure NEXT pixel is being requested
  64. xcoord = col-posx+1;
  65. // Current row requested
  66. ycoord = row-posy;
  67. end
  68.  
  69. endmodule
  70.  
  71. // Video selector, essentially a 3 bit mux
  72. module video_selector(ROut, GOut, BOut, RIn, GIn, BIn, R, G, B, A);
  73.  
  74. output ROut, GOut, BOut;
  75. input RIn, GIn, BIn, R, G, B, A;
  76.  
  77. Mux2 mR (ROut, RIn, R, A);
  78. Mux2 mG (GOut, GIn, G, A);
  79. Mux2 mB (BOut, BIn, B, A);
  80.  
  81. endmodule
  82.  
  83. module Mux2 (OUT, A, B, S);
  84. // OUT=A if S=0, OUT=B if S=1
  85. output OUT;
  86. input A, B, S;
  87. wire notS, andA, andB;
  88.  
  89. not Inv0 (notS, S);
  90. and And0 (andA, A, notS);
  91. and And1 (andB, B, S);
  92. or Or1 (OUT, andA, andB);
  93. endmodule
  94.  
  95. // 16x16 sprite memory, has 6 shapes saved
  96. module sprite_memory(Reset, R, G, B, A, clk, pclk, xcoord, ycoord, shape);
  97.  
  98. input [3:0] xcoord, ycoord;
  99. input [2:0] shape;
  100. input clk, pclk, Reset;
  101. output R, G, B, A;
  102.  
  103. reg R, G, B, A;
  104. reg [63:0] shaperow;
  105.  
  106. // Sprite Shape 1 (Red Ghost 1)
  107. reg [63:0] shape1mem[15:0];
  108.  
  109. // Sprite Shape 2 (Red Ghost 2)
  110. reg [63:0] shape2mem[15:0];
  111.  
  112. // Sprite Shape 3 (Cyan Ghost 1)
  113. reg [63:0] shape3mem[15:0];
  114.  
  115. // Sprite Shape 4 (Cyan Ghost 2)
  116. reg [63:0] shape4mem[15:0];
  117.  
  118. // Initialize
  119. always @(posedge clk) begin
  120. if (Reset) begin
  121. // Initialize shape 1
  122. shape1mem[0] = 64'b0000000000000000100110011001100110011001100110010000000000000000;
  123. shape1mem[1] = 64'b0000000000001001100110011001100110011001100110011001000000000000;
  124. shape1mem[2] = 64'b0000000010011001100110011001100110011001100110011001100100000000;
  125. shape1mem[3] = 64'b0000100110011001111111111001100110011001111111111001100110010000;
  126. shape1mem[4] = 64'b1001100110011111111111111111100110011111111111111111100110011001;
  127. shape1mem[5] = 64'b1001100110011111001111111111100110011111001111111111100110011001;
  128. shape1mem[6] = 64'b1001100110011111001111111111100110011111001111111111100110011001;
  129. shape1mem[7] = 64'b1001100110011001001111111001100110011001001111111001100110011001;
  130. shape1mem[8] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  131. shape1mem[9] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  132. shape1mem[10] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  133. shape1mem[11] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  134. shape1mem[12] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  135. shape1mem[13] = 64'b1001100110010000100110011001100110010000100110011001100110010000;
  136. shape1mem[14] = 64'b1001100100000000000010011001100100000000000010011001100100000000;
  137. shape1mem[15] = 64'b1001000000000000000000001001000000000000000000001001000000000000;
  138. // Initialize shape 2
  139. shape2mem[0] = 64'b0000000000000000100110011001100110011001100110010000000000000000;
  140. shape2mem[1] = 64'b0000000000001001100110011001100110011001100110011001000000000000;
  141. shape2mem[2] = 64'b0000000010011001100110011001100110011001100110011001100100000000;
  142. shape2mem[3] = 64'b0000100110011001111111111001100110011001111111111001100110010000;
  143. shape2mem[4] = 64'b1001100110011111111111111111100110011111111111111111100110011001;
  144. shape2mem[5] = 64'b1001100110011111001111111111100110011111001111111111100110011001;
  145. shape2mem[6] = 64'b1001100110011111001111111111100110011111001111111111100110011001;
  146. shape2mem[7] = 64'b1001100110011001001111111001100110011001001111111001100110011001;
  147. shape2mem[8] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  148. shape2mem[9] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  149. shape2mem[10] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  150. shape2mem[11] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  151. shape2mem[12] = 64'b1001100110011001100110011001100110011001100110011001100110011001;
  152. shape2mem[13] = 64'b0000100110011001100110010000100110011001100110010000100110011001;
  153. shape2mem[14] = 64'b0000000010011001100100000000000010011001100100000000000010011001;
  154. shape2mem[15] = 64'b0000000000001001000000000000000000001001000000000000000000001001;
  155. // Initialize shape 3
  156. shape3mem[0] = 64'b0000000000000000011101110111011101110111011101110000000000000000;
  157. shape3mem[1] = 64'b0000000000000111011101110111011101110111011101110111000000000000;
  158. shape3mem[2] = 64'b0000000001110111011101110111011101110111011101110111011100000000;
  159. shape3mem[3] = 64'b0000011101110111111111110111011101110111111111110111011101110000;
  160. shape3mem[4] = 64'b0111011101111111111111111111011101111111111111111111011101110111;
  161. shape3mem[5] = 64'b0111011101111111001111111111011101111111001111111111011101110111;
  162. shape3mem[6] = 64'b0111011101111111001111111111011101111111001111111111011101110111;
  163. shape3mem[7] = 64'b0111011101110111001111110111011101110111001111110111011101110111;
  164. shape3mem[8] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  165. shape3mem[9] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  166. shape3mem[10] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  167. shape3mem[11] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  168. shape3mem[12] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  169. shape3mem[13] = 64'b0111011101110000011101110111011101110000011101110111011101110000;
  170. shape3mem[14] = 64'b0111011100000000000001110111011100000000000001110111011100000000;
  171. shape3mem[15] = 64'b0111000000000000000000000111000000000000000000000111000000000000;
  172. // Initialize shape 4
  173. shape4mem[0] = 64'b0000000000000000011101110111011101110111011101110000000000000000;
  174. shape4mem[1] = 64'b0000000000000111011101110111011101110111011101110111000000000000;
  175. shape4mem[2] = 64'b0000000001110111011101110111011101110111011101110111011100000000;
  176. shape4mem[3] = 64'b0000011101110111111111110111011101110111111111110111011101110000;
  177. shape4mem[4] = 64'b0111011101111111111111111111011101111111111111111111011101110111;
  178. shape4mem[5] = 64'b0111011101111111001111111111011101111111001111111111011101110111;
  179. shape4mem[6] = 64'b0111011101111111001111111111011101111111001111111111011101110111;
  180. shape4mem[7] = 64'b0111011101110111001111110111011101110111001111110111011101110111;
  181. shape4mem[8] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  182. shape4mem[9] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  183. shape4mem[10] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  184. shape4mem[11] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  185. shape4mem[12] = 64'b0111011101110111011101110111011101110111011101110111011101110111;
  186. shape4mem[13] = 64'b0000011101110111011101110000011101110111011101110000011101110111;
  187. shape4mem[14] = 64'b0000000001110111011100000000000001110111011100000000000001110111;
  188. shape4mem[15] = 64'b0000000000000111000000000000000000000111000000000000000000000111;
  189. end
  190. end
  191.  
  192. // Assign signals to proper outputs
  193. always @(posedge pclk) begin
  194. if (shape == 0)
  195. shaperow = shape1mem[ycoord];
  196. if (shape == 1)
  197. shaperow = shape2mem[ycoord];
  198. if (shape == 2)
  199. shaperow = shape3mem[ycoord];
  200. if (shape == 3)
  201. shaperow = shape4mem[ycoord];
  202. R = shaperow[(xcoord*4)+3];
  203. G = shaperow[(xcoord*4)+2];
  204. B = shaperow[(xcoord*4)+1];
  205. A = shaperow[xcoord*4];
  206. end
  207.  
  208. 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 на операционных системахWindowsLinux и 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:
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. #include <QDir>
  5. #include <QDebug>
  6. #include <QLibrary>
  7. #include <QKeyEvent>
  8.  
  9. #include <unistd.h>
  10.  
  11. #include "hidapi.h"
  12.  
  13. #define REPORT_LENGTH 255
  14.  
  15. //Объявление функций библиотеки
  16. typedef struct hid_device_info* (*HID_ENUMERATE)(unsigned short, unsigned short);
  17. typedef void (*HID_FREE_ENUMARATION)(struct hid_device_info*);
  18. typedef hid_device* (*HID_OPEN)(unsigned short, unsigned short, wchar_t*);
  19. typedef int (*HID_GET_MANUFACTURER_STRING)(hid_device *, wchar_t *, size_t);
  20. typedef int (*HID_GET_PRODUCT_STRING)(hid_device*, wchar_t*, size_t);
  21. typedef int (*HID_GET_SERIAL_NUMBER_STRING)(hid_device*, wchar_t*, size_t);
  22. typedef int (*HID_SET_NONBLOCKING)(hid_device*, int);
  23. typedef int (*HID_SEND_FEATURE_REPORT)(hid_device*, const unsigned char*, size_t);
  24. typedef int (*HID_GET_FEATURE_REPORT)(hid_device*, unsigned char*, size_t);
  25. typedef int (*HID_WRITE)(hid_device*, const unsigned char*, size_t);
  26.  
  27.  
  28. //Класс для работы с USB HID устройствами
  29. class hidControl
  30. {
  31. private:
  32. HID_OPEN hid_open;
  33. HID_SET_NONBLOCKING hid_set_nonblocking;
  34. HID_SEND_FEATURE_REPORT hid_send_feature_report;
  35. HID_GET_FEATURE_REPORT hid_get_feature_report;
  36. HID_GET_MANUFACTURER_STRING hid_get_manufacturer_string;
  37. HID_GET_PRODUCT_STRING hid_get_product_string;
  38. HID_ENUMERATE hid_enumerate;
  39. HID_FREE_ENUMARATION hid_free_enumeration;
  40. HID_WRITE hid_write;
  41.  
  42. public:
  43. hidControl()
  44. {}
  45.  
  46. ~hidControl()
  47. {}
  48.  
  49. unsigned char buf[REPORT_LENGTH];
  50. bool deviceFound;
  51. hid_device *handle;
  52. struct hid_device_info *devs, *cur_dev;
  53.  
  54. // Инициализация HID
  55. int init(unsigned int VID, unsigned int PID)
  56. {
  57. // Пробуем подключить библиотеку
  58. QString QString_str;
  59. QDir curdir;
  60. wchar_t wstr[255];
  61.  
  62. QLibrary lib(curdir.absolutePath() + "/hidapi");
  63.  
  64. lib.load();
  65.  
  66. if (!lib.isLoaded())
  67. return -1;
  68.  
  69. //Библиотека подключена можно вызывать функции библиотеки
  70. hid_open = (HID_OPEN)lib.resolve("hid_open");
  71. hid_set_nonblocking = (HID_SET_NONBLOCKING)lib.resolve("hid_set_nonblocking");
  72. hid_send_feature_report = (HID_SEND_FEATURE_REPORT)lib.resolve("hid_send_feature_report");
  73. hid_get_feature_report = (HID_GET_FEATURE_REPORT)lib.resolve("hid_get_feature_report");
  74. hid_get_manufacturer_string = (HID_GET_MANUFACTURER_STRING)lib.resolve("hid_get_manufacturer_string");
  75. hid_get_product_string = (HID_GET_PRODUCT_STRING)lib.resolve("hid_get_product_string");
  76. hid_enumerate = (HID_ENUMERATE)lib.resolve("hid_enumerate");
  77. hid_free_enumeration = (HID_FREE_ENUMARATION)lib.resolve("hid_free_enumeration");
  78. hid_write = (HID_WRITE)lib.resolve("hid_write");
  79.  
  80. //Переберем все USB HID устройства для отладки
  81. devs = hid_enumerate(0x00, 0x00);
  82. cur_dev = devs;
  83.  
  84. while (cur_dev)
  85. {
  86. qDebug() << "VID " << cur_dev->vendor_id << "PID " << cur_dev->product_id;
  87. cur_dev = cur_dev->next;
  88. }
  89. hid_free_enumeration(devs);
  90.  
  91. //Открываем нашу плату
  92. deviceFound = false;
  93. handle = hid_open(VID, PID, NULL);
  94.  
  95. if (handle)
  96. {
  97. //Если успешно открыли установим неблокирующий режим и выведем
  98. //строки производителя и устройства
  99. deviceFound = true;
  100. hid_set_nonblocking(handle, 1);
  101.  
  102. hid_get_manufacturer_string(handle, wstr, 255);
  103. QString_str = QString::fromStdWString(wstr);
  104. qDebug() << QString_str;
  105.  
  106. hid_get_product_string(handle, wstr, 255);
  107. QString_str = QString::fromStdWString(wstr);
  108. qDebug() << QString_str;
  109.  
  110. }
  111. else
  112. return -1;
  113.  
  114. return 0;
  115. }
  116.  
  117. int write()
  118. {
  119. if(deviceFound)
  120. {
  121. buf[0] = 1; // В первом байте находится номер репорта
  122. return hid_write(handle, buf, 65);
  123. }
  124. return -1;
  125. }
  126.  
  127. int read()
  128. {
  129. //Чтение нам пока не нужно
  130. return -1;
  131. }
  132.  
  133. };
  134.  
  135. hidControl *hid;
  136. unsigned char keys=0;
  137.  
  138. //Обработка нажатия клавиш------------------------------------------------------------
  139. void MainWindow::keyPressEvent(QKeyEvent *event)
  140. {
  141.  
  142. if(event->key()==Qt::Key_Up)
  143. {
  144. ui->label->setText("up");
  145. keys=keys | 0x08;
  146. }
  147. else if(event->key()==Qt::Key_Down)
  148. {
  149. ui->label->setText("down");
  150. keys=keys | 0x01;
  151. }
  152. else if(event->key()==Qt::Key_Left)
  153. {
  154. ui->label->setText("left");
  155. keys=keys | 0x02;
  156. }
  157. else if(event->key()==Qt::Key_Right)
  158. {
  159. ui->label->setText("right");
  160. keys=keys | 0x04;
  161. }
  162.  
  163. hid->buf[1]=keys;
  164.  
  165. qDebug() << hid->write();
  166.  
  167. QMainWindow::keyPressEvent( event );
  168. }
  169.  
  170. //Обработка отпускания клавиш------------------------------------------------------------
  171. void MainWindow::keyReleaseEvent(QKeyEvent *event)
  172. {
  173.  
  174. if(event->key()==Qt::Key_Up)
  175. {
  176. ui->label->setText("up");
  177. keys=keys & ~0x08;
  178. }
  179. else if(event->key()==Qt::Key_Down)
  180. {
  181. ui->label->setText("down");
  182. keys=keys & ~0x01;
  183. }
  184. else if(event->key()==Qt::Key_Left)
  185. {
  186. ui->label->setText("left");
  187. keys=keys & ~0x02;
  188. }
  189. else if(event->key()==Qt::Key_Right)
  190. {
  191. ui->label->setText("right");
  192. keys=keys & ~0x04;
  193. }
  194.  
  195. hid->buf[1]=keys;
  196.  
  197. qDebug() << hid->write();
  198.  
  199. QMainWindow::keyPressEvent( event );
  200. }
  201.  
  202. //Конструктор основного класса-----------------------------------------------------------
  203. MainWindow::MainWindow(QWidget *parent) :
  204. QMainWindow(parent),
  205. ui(new Ui::MainWindow)
  206. {
  207. ui->setupUi(this);
  208.  
  209. hid = new hidControl();
  210.  
  211.  
  212. if(hid->init(0x1FC9, 0x0003)<0)
  213. {
  214. ui->label->setText("No device");
  215. qDebug() << "No device";
  216. }
  217. else
  218. {
  219. ui->label->setText("Start");
  220. qDebug() << "Start";
  221. }
  222.  
  223. }
  224.  
  225. //Деструктор основного класса------------------------------------------------------------
  226. MainWindow::~MainWindow()
  227. {
  228. delete ui;
  229. }

Тут в принципе ничего сложного нет. Все делает библиотека HIDAPI. От нас только требуется открыть наше устройство с VID=0x1FC9 и PID=0x0003, и если устройство открылось успешно, то можно управлять выводами нашего контролера по нажатиям на клавиатуре. Ну и по традиции видео и исходники:

Проект вывод спрайтов для FPGA: sprites_demo.zip

Проект управление по USB для PC: usb_hid_qt.zip

Добавить комментарий