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

В качестве заключительного проекта для нашей платы 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:
  1. module pic_gen(
  2. //vga
  3. input [11:0]char_count,
  4. input [11:0]line_count,
  5. input wire blank,
  6. input wire char_clock,
  7.  
  8. output reg [3:0]red_out,
  9. output reg [3:0]green_out,
  10. output reg [3:0]blue_out,
  11.  
  12. //external SRAM
  13. inout [7:0]sram_data,
  14. output reg[18:0]sram_adress,
  15. output reg CS,
  16. output reg WE,
  17. output reg OE,
  18.  
  19. //SPI
  20. input sck,
  21. input mosi,
  22. input clr
  23.  
  24. );
  25.  
  26. localparam
  27. state0 = 4'b0000,
  28. state1 = 4'b0001,
  29. state2 = 4'b0010,
  30. state3 = 4'b0011;
  31.  
  32. reg r_w=0;
  33. reg [3:0]nBit = 0;
  34. reg [3:0]delay = 0;
  35. reg [7:0]spi_data;
  36. reg [2:0]spi_cnt = 0;
  37. reg [7:0]tmp_sram_data_wr = 0;
  38. reg [18:0]tmp_sram_adr_wr = 0;
  39. reg [18:0]sram_adr_cmp = 0;
  40. reg [18:0]sram_adr_wr = 0;
  41. reg [7:0]sram_data_wr = 0;
  42. reg [3:0]r_state = state0;
  43. reg [3:0]w_state = state0;
  44.  
  45. assign sram_data = r_w ? 8'bzzzzzzzz : sram_data_wr; //шина с 3 состояниями
  46.  
  47. //Прием данных SPI
  48. always @(posedge sck, negedge clr)
  49. begin
  50.  
  51. if(clr == 0) //Сбрасываем адрес памяти и счетчик spi
  52. begin
  53. sram_adr_wr <= 0;
  54. spi_cnt <= 0;
  55. end
  56. else
  57. begin
  58. spi_data = {spi_data[6:0], mosi}; //сдвиговый регистр
  59. spi_cnt = spi_cnt+1;
  60. if(spi_cnt==0) //приняли 8 бит
  61. begin
  62. tmp_sram_data_wr <= spi_data; //данные для записи в озу
  63. tmp_sram_adr_wr <= sram_adr_wr;
  64. sram_adr_wr <= sram_adr_wr + 1;
  65. if(sram_adr_wr>393214) //Наш экран кончился
  66. begin
  67. sram_adr_wr <= 0;
  68. end
  69. end
  70. end
  71. end
  72.  
  73. //Вывод изображения
  74. always @(posedge char_clock)
  75. begin
  76. CS <= 1'b0;
  77. if((blank==0) | (w_state !=state0))
  78. begin
  79. r_w <= 0;
  80. OE <= 1'b1;
  81. //Время для записи памяти
  82. if(sram_adr_cmp!=tmp_sram_adr_wr)
  83. begin
  84. delay <= delay+1;
  85. if(delay>1)
  86. begin
  87. delay <= 0;
  88. if(w_state == state0)
  89. begin
  90. OE <= 1'b1;
  91. sram_adress <= tmp_sram_adr_wr;
  92. sram_data_wr <= tmp_sram_data_wr;
  93. WE <= 1'b0;
  94. w_state <= state1;
  95. end
  96. else if(w_state == state1)
  97. begin
  98. WE <= 1'b1;
  99. sram_adr_cmp <= tmp_sram_adr_wr;
  100. w_state <= state0;
  101. end
  102. end
  103. end
  104. //Изображение за кадром, выводим черный цвет
  105. blue_out <= 0;
  106. green_out <= 0;
  107. red_out <= 0;
  108. end
  109. if((blank==1) | (r_state !=state0))
  110. begin
  111. //Время для чтения памяти
  112. r_w <= 1;
  113. WE <=1'b1;
  114. if(r_state == state0)
  115. begin
  116. sram_adress <= line_count*512 | char_count>>1;
  117. OE <= 1'b0;
  118. r_state <= state1;
  119. end
  120. else if(r_state == state1)
  121. begin
  122. //Формирование изображения
  123. blue_out <= (sram_data >> 4) & 8'b00001110;
  124. green_out <= (sram_data >> 1) & 8'b00001100;
  125. red_out <= (sram_data << 1) & 8'b00001110;
  126. OE <= 1'b1;
  127. r_state <= state0;
  128. end
  129. end
  130. end
  131.  
  132. endmodule

Наш модуль вывода видеопамяти, дополнился модулем приема данных по SPI. После приема байта данных мы увеличиваем указатель записи видеопамяти  на единицу, и во время обратного хода луча, пишем в видео память. Вид схемы FPGA:

Далее займемся созданием программы для ARM:

C++ Code:
  1. int main(void)
  2. {
  3.  
  4. /* USER CODE BEGIN 1 */
  5.  
  6. /* USER CODE END 1 */
  7.  
  8. /* MCU Configuration----------------------------------------------------------*/
  9.  
  10. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  11. HAL_Init();
  12.  
  13. /* Configure the system clock */
  14. SystemClock_Config();
  15.  
  16. /* Initialize all configured peripherals */
  17. MX_GPIO_Init();
  18. MX_SPI2_Init();
  19. MX_USB_DEVICE_Init();
  20.  
  21. /* USER CODE BEGIN 2 */
  22. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //clr=1
  23. /* USER CODE END 2 */
  24.  
  25. /* Infinite loop */
  26. /* USER CODE BEGIN WHILE */
  27. while (1)
  28. {
  29. /* USER CODE END WHILE */
  30.  
  31. /* USER CODE BEGIN 3 */
  32. if (VCP_read(&rx_buff, 64) == 64) //пришел пакет по USB
  33. {
  34. if(reset==1) //Если начало передачи, сбросим fpga
  35. {
  36. reset=0;
  37. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); //clr=0
  38. asm ("nop"); //nop
  39. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); //clr=1
  40. }
  41.  
  42. for(unsigned cnt1=0;cnt1<64;cnt1++) //передаем пакет в fpga
  43. {
  44. spi_transmit[0]=rx_buff[cnt1];
  45. while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14)!=GPIO_PIN_RESET) //если мы за кадром
  46. {
  47. }
  48. HAL_SPI_Transmit(&hspi2, spi_transmit, 1, 10); //передаем 1 байт по spi
  49. }
  50. }
  51. else
  52. {
  53. reset=1; //картинка кончилась нужен сброс
  54. }
  55. }
  56. /* USER CODE END 3 */
  57.  
  58. }

По приходу USB пакета, мы сбрасываем сигнал clr который обнуляет указатель памяти в FPGA. Затем мы отправляем принятый пакет в SPI, если нет сигнала blank. Напомню если присутствует сигнал blank то FPGA занята выводом видео памяти. Ну и напоследок создадим программу на Qt, которая будет отправлять картинки в USB:

C++ Code:
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3.  
  4. #include
  5. #include
  6. #include
  7. #include
  8. #include
  9. #include
  10. #include
  11.  
  12. QSerialPort *serial = new QSerialPort;
  13. QTimer *timer = new QTimer;
  14. QByteArray picture;
  15. QFileInfoList list;
  16.  
  17. unsigned int pic_width=0, pic_height=0,list_ptr=0;
  18.  
  19. MainWindow::MainWindow(QWidget *parent) :
  20. QMainWindow(parent),
  21. ui(new Ui::MainWindow)
  22. {
  23. ui->setupUi(this);
  24. foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
  25. /*qDebug() << "Name : " << info.portName();
  26.   qDebug() << "Description : " << info.description();
  27.   qDebug() << "Manufacturer: " << info.manufacturer();*/
  28. ui->comboBox->addItem(info.portName());
  29. }
  30. //соединяем сигнал timout нашего таймера со слотом update
  31. connect(timer, SIGNAL(timeout()), this, SLOT(update()));
  32.  
  33. }
  34.  
  35. MainWindow::~MainWindow()
  36. {
  37. delete ui;
  38. }
  39.  
  40. //Старт
  41. void MainWindow::on_pushButton_clicked()
  42. {
  43. //Список всех файлов *.bmp в директории files
  44. QDir dir(QApplication::applicationDirPath()+"/files");
  45. dir.setFilter( QDir::Files | QDir::Readable | QDir::AllDirs | QDir::NoDotAndDotDot );
  46. list = dir.entryInfoList(QDir::nameFiltersFromString("*.bmp"));
  47. /*for (int i=0; i<(list.count());i++)
  48.   {
  49.   qDebug() << list.at(i).fileName();
  50.   }*/
  51.  
  52. //Настройки ком порта
  53. serial->setPortName(ui->comboBox->currentText());
  54. serial->setBaudRate(QSerialPort::Baud115200);
  55. serial->setDataBits(QSerialPort::Data8);
  56. serial->setParity(QSerialPort::NoParity);
  57. serial->setStopBits(QSerialPort::OneStop);
  58. serial->setFlowControl(QSerialPort::NoFlowControl);
  59. serial->open(QIODevice::ReadWrite);
  60.  
  61. //Вызываем функцию таймера, что бы не дждать 10 секунд
  62. list_ptr=0;
  63. update();
  64. timer->start(10000); //таймер раз в 10 секунд
  65. }
  66.  
  67. //Стоп
  68. void MainWindow::on_pushButton_2_clicked()
  69. {
  70. serial->close(); //закрыли порт
  71. timer->stop(); //остановили таймер
  72. }
  73.  
  74. //Таймер
  75. void MainWindow::update()
  76. {
  77. QByteArray msg;
  78. unsigned long pic_adr;
  79. unsigned char R,G,B,rgb;
  80.  
  81. QGraphicsScene *scene = new QGraphicsScene;
  82. QString width,height;
  83. QString path;
  84. if(list.size()>0){//перебор списка с картинками
  85. path=QApplication::applicationDirPath()+"/files/"+list.at(list_ptr).fileName();
  86.  
  87. if(list_ptr<list.size()-1){
  88. list_ptr++;}
  89. else{
  90. list_ptr=0;}
  91. }
  92.  
  93. QFile fileIn(path);
  94.  
  95. if(fileIn.open(QIODevice::ReadOnly))
  96. {
  97. picture = fileIn.readAll(); //читаем файл
  98. fileIn.close(); //закрываем файл
  99. scene->addPixmap(QPixmap(path)); //добавляем картинку
  100. pic_height=scene->height(); //определяем размеры
  101. pic_width=scene->width();
  102. height="Heigth="+QString::number(scene->height());
  103. width="Width="+QString::number(scene->width());
  104. ui->label->setText(height);
  105. ui->label_2->setText(width);
  106. ui->graphicsView->setGeometry(10,40,scene->width(),scene->height());
  107. ui->graphicsView->setScene(scene); //выводим картинку на форму
  108. }
  109.  
  110. if(pic_height*pic_width!=0)
  111. {
  112. //Перебираем пиксели bmp файла
  113. for(unsigned int dy=0;dy<pic_height;dy++)
  114. {
  115. for(unsigned int dx=0;dx<pic_width;dx++)
  116. {
  117. pic_adr=(pic_width*(pic_height-dy-1)+dx)*3+54;
  118. R=picture.data()[pic_adr];
  119. G=picture.data()[pic_adr+1];
  120. B=picture.data()[pic_adr+2];
  121. rgb=R & 0b11100000 | G>>3 & 0b00011000 | B>>5 & 0b00000111;
  122. msg.append(rgb);
  123. }
  124. }
  125. //Пишем в виртуальный COM порт
  126. serial->write(msg, msg.length());
  127. }
  128. }

Программа создает список всех 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

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