Мини обзор на плату расширения EBAZ4205

от автора

В этой статье я хочу сделать краткий обзор на плату расширения к китайской плате с ПЛИС. Данная плата хорошо дополняет функционал основной платы EBAZ. В конце статьи будет демонстрация ролика Bad Apple.

Имеется

Когда плата появилась на Алиэкспресс, она стоила 700-900 рублей, затем китайцы поняли, что плату в голом виде использовать не удобно и начали делать различные платы расширения. Я выбрал ту, что на фото выше, о ней и пойдет речь.

На данной плате имеется дисплей ST7789V, buzzer, HDMI, так же 5 кнопок и 3 светодиода. Китайцы не забыли и про USB‑UART.

Отличительной особенностью этой платы от многих других плат с ПЛИС является то, что на ней нанесены надписи к каким пинам ПЛИС подключается тот или иной модуль. Например, если присмотреться к кнопкам и светодиодам, возле каждого элемента имеется характерная надпись (Например LED1_E19 или K1_T19. E19 и T19 — есть пины ПЛИС, которые нужно указать при ее программировании).

Также они не забыли подписать 20-пиновую гребенку контактов на обратной стороне. Кроме подписанных пинов можно обратить внимание на место пайки шлейфа дисплея — первые два контакта спаяны вместе. Один из контактов является сигналом подсветки, а второй GND, таким образом подсветка всегда включена. Я на своей плате это соединение убрал и подсветкой управляю непосредственно с ПЛИС.

Недостатки/достоинства. Из недостатков хотел бы обратить внимание на два момента. Во‑первых, на плате нет кнопки питания, и в случае, если ее нужно перезагрузить либо сбросить прошивку, приходится дергать разъем USB. Во‑вторых, плата расширения перекрывает 6-пиновый разъем питания, но в целом без него все работает, поэтому у меня претензий нет, но может кому‑то пригодилось бы. Из достоинств так же есть два момента. Во‑первых — размер, есть аналоги которые имеют гораздо большую площадь, а функционал либо хуже либо такой же. Во‑вторых, питание и USB‑UART объединены в один интерфейс, а это уменьшает число проводов.

Кому интересно тут разные варианты плат расширения:

HDMI. Внимание так же привлекает HDMI порт. Никакой дополнительной микросхемы для этого порта не предусмотрено, а это значит, что все сигналы нужно полностью формировать в ПЛИС. Китайцы и тут позаботились о разработчиках и вместе документацией поставляют IP-ядро, которое конвертирует привычный нам VGA в HDMI. Как я понял, это IP-ядро было выдернуто с одной из отладочных плат Digilent.

IP-ядро достаточно простое само по себе.

IP-ядро достаточно простое само по себе.

Теперь самая интересная часть. Работа с дисплеем ST7789.

Наверняка некоторые читают статью ради того, чтобы посмотреть ролик BadApple, поэтому не будем тянуть дисплей за пиксель и приступим.)

Цветовые режимы. Дисплей представляет из себя экран на 240х240 пикселей, с различными цветовыми режимами работы RGB 4:4:4, RGB 5:6:5, RGB 6:6:6. Я реализовал два последних режима работы, они удобнее в реализации, а также режим 4:4:4 мне не интересен, так как имеет небольшой цветовой диапазон.

Частота тактирования. В документации сказано что минимальный период тактирующего сигнала (SCL) составляет 66 нс, что соответствует 15.151 МГц. На просторах интернета я натыкался на высказывания, что предел частоты данного дисплея 50 МГц, я ставил частоту порядка 100 МГц и дисплей справлялся. В итоге в своем проекте поставил частоту 50 МГц и, как будет видно ниже, все работает.

Область отрисовки. Дисплей предусматривает возможность рисовать в любой области дисплея. Так же можно переключаться на ходу. Это позволяет не только выводить картинку целиком на экран, но и выводить отдельно символы, так же можно добиться вывода только одного пикселя.

Примеры рисования на дисплее:
Демонстрация возможности вывода небольших областей. В данном случае сначала был выведен фон из 4х полос, затем поверху выведены небольшие квадратики с буквой А. Это было выполнено за счет изменения области отрисовки.

Демонстрация возможности вывода небольших областей. В данном случае сначала был выведен фон из 4х полос, затем поверху выведены небольшие квадратики с буквой А. Это было выполнено за счет изменения области отрисовки.
Вывод любой картинки на дисплей с ПК. Картинка передавалась по UART.

Вывод любой картинки на дисплей с ПК. Картинка передавалась по UART.
Простой градиент каналов RGB

Простой градиент каналов RGB

Модуль для работы с LCD дисплеем.

В модуль я заложил три этапа работы. Этап инициализации, этап установки области отрисовки, этап передачи данных (значения пикселей).

Обобщенная структурная схема модуля

Обобщенная структурная схема модуля

Этап инициализации запускается автоматически после сигнала сброса. В рамках инициализации происходит выставление сигнала сброса для дисплея (RES), затем выжидается время, за которое дисплей приходит в себя. В документации указаны различные тайминги после сигнала сброса, когда можно писать в регистры и когда можно включать дисплей, я поступил хитрым образом — просто после сигнала сброса выждал 500 мс и начал инициализацию внутренних регистров дисплея.

Вот список адресов, которые я инициализировал (список адресов указан в порядке их инициализации): MADCTL, COLMOD, PORCTRL, GCTRL, VCOMS, LCMCTRL, VDVVRHEN, VRHS, VDVS, FRCTRL, PWCTRL1, PVGAMCTRL, NVGAMCTRL, INVON, SLPOUT, DISPON.

Этот список адресов я брал из китайского примера. В ходе экспериментов я отключил PVGAMCTRL, NVGAMCTRL и PORCTRL. Наверняка можно отключить и еще большинство регистров, но раз работает, то трогать не стал.

Этап установки области отрисовки заключается в передаче двух координат, начала и конца прямоугольника. Они пишутся в регистры CASET и RASET. Обязательным условием является сброс указателя текущего пикселя, это регистр RAMWR. После этой команды дисплей ждет массив пикселей.

Этап передачи данных. Смысл этого этапа очевиден, после настройки дисплея и выставления области отрисовки, нужно передать значения пикселей в том формате, в котором выбрали. Для SPI это будут либо 2 байта, либо 3 байта, в зависимости от выбранного цветового диапазона. Для входного интерфейса модуля — это 3 байта(RGB) и сигнал валида, тут нужно учесть, что в зависимости от выбранного режима будет использованы разное количество бит из входных данных.

Вот код модуля на SystemVerilog:

Чтобы применить код, просто скопируйте его в отдельные файлы (1 модуль = 1 файл), импортируйте к себе в проект и смотрите результат. По сигналу ready_RGB можно начинать передавать данные на дисплей. Если нужно указать другую область вывода на дисплее, то нужно воспользоваться сигналами: set_size_img_en_i, set_new_XY_i, X1, X2, Y1, Y2. set_size_img_en_i – поднимается на 1 такт и говорит модулю чтобы тот запустил процесс изменения области отрисовки. set_new_XY_i — если нужно задать новые размеры области отрисовки, если оставить в нуле то размер не изменится, только сбросится указатель текущего пикселя к нулевым координатам. X1, X2, Y1, Y2 — используются для задания координат двух точек прямоугольника, начала и конца. Так же нужно учитывать что частота сигнала SCL всегда будет меньше в 2 раза чем частота работы модуля(если clk == 50МГц то SCL == 25МГц).

module top_LCD
`timescale 1ns / 1ps    module top_LCD #(     parameter FREQ_CLK = 50000000,      parameter PIXEL_MODE = 8'h06   // 'h03 - 4:4:4, 'h05 - 5:6:5, 'h06 - 6:6:6; )(     input clk,     input rst,          input set_size_img_en_i,     input set_new_XY_i,     input [15:0] X1,     input [15:0] X2,     input [15:0] Y1,     input [15:0] Y2,          input valid_RGB,     output ready_RGB,     input [7:0] R,     input [7:0] G,     input [7:0] B,          output reg BL = 1,  // display backlight     output DC,  // data strobe: command or data. "0" - cmd, "1" - data     output SCL, // clk SPI     output SDA, // data SPI     output nRES  // reset LCD. "0" - reset, "1" - normal work     );           logic done_init,done_set_img;      logic DC_init,SCL_init,SDA_init; logic [15:0] X1_reg = 0, X2_reg = 239, Y1_reg = 0, Y2_reg = 239; logic set_new_XY = 0, set_size_img_en = 0; logic DC_set_img,SCL_set_img,SDA_set_img;  logic ready_RGB_s,valid_RGB_s; logic DC_rgb,SCL_rgb,SDA_rgb;  logic [3:0] state_lcd = 0;  always_ff@(posedge clk) begin     if(rst) begin         state_lcd <= 'd0;         {X1_reg, X2_reg, Y1_reg, Y2_reg} <= {16'd0,16'd239,16'd0,16'd239};     end else begin         case(state_lcd)             0: begin //wait block init                 if(done_init) begin                     state_lcd <= 'd1;                     set_size_img_en <= 'd1;                 end             end             1: begin // wait block set size image                 if(set_size_img_en) begin                     set_size_img_en <= 'd0;                     set_new_XY <= 'd0;                 end else if (done_set_img) begin                     state_lcd <= 'd2;                 end             end             2: begin // normal work                 if(set_size_img_en_i) begin//waiting set new image size                      if(set_new_XY_i) begin                         {X1_reg, X2_reg, Y1_reg, Y2_reg} <= {X1,X2,Y1,Y2};                         set_new_XY <= 'd1;                      end                                           state_lcd <= 'd3;                 end             end             3: begin                 if(ready_RGB_s) begin                     set_size_img_en <= 'd1;                     state_lcd <= 'd1;                 end             end         endcase     end end          init_LCD #(     .FREQ_CLK(FREQ_CLK),     .PIXEL_MODE(PIXEL_MODE) ) init_LCD_inst  (     .clk (clk),     .rst (rst),     .start_init('d0),     .done(done_init),     .DC  (DC_init),     .SCL (SCL_init),     .SDA (SDA_init),     .nRES(nRES) );      set_img_size set_img_size (     .clk(clk),     .set_size_img_en (set_size_img_en),     .set_new_XY      (set_new_XY),     .X1     (X1_reg),     .X2     (X2_reg),     .Y1     (Y1_reg),     .Y2     (Y2_reg),     .done   (done_set_img),          .DC  (DC_set_img ),     .SCL (SCL_set_img),     .SDA (SDA_set_img) );   RGB_transmit #(     .PIXEL_MODE(PIXEL_MODE) // 'h03 - 4:4:4 (not supported here), 'h05 - 5:6:5, 'h06 - 6:6:6;  ) RGB_transmit_inst (     .clk(clk),     .valid_RGB(valid_RGB_s),     .ready    (ready_RGB_s),     .R(R),     .G(G),     .B(B),          .DC (DC_rgb),     .SCL(SCL_rgb),     .SDA(SDA_rgb) );  assign valid_RGB_s = (state_lcd == 'd2) ? valid_RGB : 'd0; assign ready_RGB   = (state_lcd == 'd2) ? ready_RGB_s : 'd0;   assign  DC    = (state_lcd == 'd0) ? DC_init :                  (state_lcd == 'd1) ? DC_set_img :                 (state_lcd == 'd2) ? DC_rgb :                 'd0;  assign  SCL   = (state_lcd == 'd0) ? SCL_init:                  (state_lcd == 'd1) ? SCL_set_img:                 (state_lcd == 'd2) ? SCL_rgb:                 'd1;                  assign  SDA   = (state_lcd == 'd0) ? SDA_init:                  (state_lcd == 'd1) ? SDA_set_img:                 (state_lcd == 'd2) ? SDA_rgb:                 'd0;  endmodule 

module init_LCD
`timescale 1ns / 1ps   module init_LCD #(     parameter FREQ_CLK = 50000000,      parameter PIXEL_MODE = 8'h06   // 'h03 - 4:4:4, 'h05 - 5:6:5, 'h06 - 6:6:6; ) (     input clk,     input rst,          input start_init,     output logic done = 0,          output DC,  // data strobe: command or data. "0" - cmd, "1" - data     output SCL, // clk SPI     output SDA, // data SPI     output reg nRES = 0  // reset LCD. "0" - reset, "1" - normal work     );  // display color correction. adress command 'hE0, 'hE1(PVGAMCTRL,NVGAMCTRL). "FALSE" - off setting state. localparam COLOR_CORRECT = "FALSE";  localparam SETTING_PORCTRL = "FALSE";//adress command 'hB2 //"FALSE" - off setting state.      logic [7:0] send_data = 0; logic send_valid = 0;     logic send_ready; logic send_cd = 0;  logic [7:0] state_init = 0;  logic [31:0] rst_LCD = 0; logic [3:0] ch_reg = 0; logic [3:0] ch_reg_1 = 0; logic [3:0] ch_reg_2 = 0;  logic [7:0] MADCTL [0:1]; logic [7:0] COLMOD [0:1]; logic [7:0] PORCTRL [0:5]; logic [7:0] GCTRL [0:1]; logic [7:0] VCOMS [0:1]; logic [7:0] LCMCTRL [0:1]; logic [7:0] VDVVRHEN [0:1]; logic [7:0] VRHS [0:1]; logic [7:0] VDVS [0:1]; logic [7:0] FRCTRL [0:1]; logic [7:0] PWCTRL1 [0:2]; logic [7:0] PVGAMCTRL [0:14]; logic [7:0] NVGAMCTRL [0:14]; logic [7:0] START_WORK [0:2];  initial begin  // cmd and params MADCTL MADCTL[0] = 'h36; MADCTL[1] = 'h00;  //RGB format  COLMOD[0] = 'h3A; COLMOD[1] = PIXEL_MODE; //interface pixel format(RGB) 'h03 - 4:4:4, 'h05 - 5:6:5, 'h06 - 6:6:6;  //setting porch PORCTRL[0] = 'hB2; PORCTRL[1] = 'h0C; PORCTRL[2] = 'h0C; PORCTRL[3] = 'h00; PORCTRL[4] = 'h33; PORCTRL[5] = 'h33;  //GCTRL. Gate Control GCTRL[0] = 'hB7; GCTRL[1] = 'h35;   //VCOMS. VCOM Setting VCOMS[0] = 'hBB; VCOMS[1] = 'h19;  //LCMCTRL. LCM Control LCMCTRL[0] = 'hC0; LCMCTRL[1] = 'h2C;  //VDVVRHEN. VDV and VRH Command Enable VDVVRHEN[0] = 'hC2; VDVVRHEN[1] = 'h01;  //VRHS. VRH Set VRHS[0] = 'hC3; VRHS[1] = 'h12;  //VDVS. VDV Set VDVS[0] = 'hC4; VDVS[1] = 'h20;  //FRCTRL. Frame Rate Control in Normal Mode FRCTRL[0] = 'hC6; FRCTRL[1] = 'h0F; // 60Hz  //PWCTRL1. Power Control 1 PWCTRL1[0] = 'hD0; PWCTRL1[1] = 'hA4; // const PWCTRL1[2] = 'hA1;  //PVGAMCTRL. Positive Voltage Gamma Control PVGAMCTRL[0] = 'hE0 ; PVGAMCTRL[1] = 'hD0 ; PVGAMCTRL[2] = 'h04 ; PVGAMCTRL[3] = 'h0D ; PVGAMCTRL[4] = 'h11 ; PVGAMCTRL[5] = 'h13 ; PVGAMCTRL[6] = 'h2B ; PVGAMCTRL[7] = 'h3F ; PVGAMCTRL[8] = 'h54 ; PVGAMCTRL[9] = 'h4C ; PVGAMCTRL[10] = 'h18 ; PVGAMCTRL[11] = 'h0D ; PVGAMCTRL[12] = 'h0B ; PVGAMCTRL[13] = 'h1F ; PVGAMCTRL[14] = 'h23 ;  //NVGAMCTRL. Negative Voltage Gamma Control NVGAMCTRL[0] = 'hE1 ; NVGAMCTRL[1] = 'hD0 ; NVGAMCTRL[2] = 'h04 ; NVGAMCTRL[3] = 'h0C ; NVGAMCTRL[4] = 'h11 ; NVGAMCTRL[5] = 'h13 ; NVGAMCTRL[6] = 'h2C ; NVGAMCTRL[7] = 'h3F ; NVGAMCTRL[8] = 'h44 ; NVGAMCTRL[9] = 'h51 ; NVGAMCTRL[10] = 'h2F ; NVGAMCTRL[11] = 'h1F ; NVGAMCTRL[12] = 'h1F ; NVGAMCTRL[13] = 'h20 ; NVGAMCTRL[14] = 'h23 ;  // last commands to work. START_WORK[0] = 'h21; // INVON, turns on inversion START_WORK[1] = 'h11; // SLPOUT, turn off sleep mode START_WORK[2] = 'h29; // DISPON, display on   end   always_ff@(posedge clk) begin     if(rst) begin         state_init <= 'd0;         nRES <= 'd0;         rst_LCD <= 'd0;         done <= 'd0;         ch_reg <= 'd0;         ch_reg_1 <= 'd0;         ch_reg_2 <= 'd0;     end else begin         case(state_init)             0: begin // reset LCD. iter 0                 nRES <= 'd1;                 if(rst_LCD >= FREQ_CLK) begin                     rst_LCD <= 'd0;                     state_init <= 1;                 end else rst_LCD <= rst_LCD + 1;             end             1: begin // reset LCD. iter 1                                                  if(rst_LCD == 'd0) begin// reset on                     nRES <= 'd0;                 end else if(rst_LCD >= (2*FREQ_CLK)) begin //wait delay                     state_init <= 2;                 end else if(rst_LCD >= FREQ_CLK) begin //reset off                     nRES <= 'd1;                 end                 rst_LCD <= rst_LCD + 1;             end             2: begin //send cmd MADCTL ('h36), data 8'h00; see datasheet page 212.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= MADCTL[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 3;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end                            end             3: begin //send cmd COLMOD ('h3A), data 8'h03/8'h05/8'h06; see datasheet page 221.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= COLMOD[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         if(SETTING_PORCTRL == "FALSE") state_init <= 5;                         else state_init <= 4;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end             4: begin //send cmd PORCTRL ('hB2), 5 bytes data; see datasheet page 260.                 if(SETTING_PORCTRL != "FALSE") begin                     send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                     if(send_ready & (!send_valid)) begin                         send_data <= PORCTRL[ch_reg];                         send_valid <= 'd1;                         if(ch_reg == 5) begin                             state_init <= 5;                             ch_reg <= 'd0;                         end else ch_reg <= ch_reg + 1;                     end else begin                         send_valid <= 'd0;                     end                  end else state_init <= 0;             end                5: begin //send cmd GCTRL ('hB7), data 'h35; see datasheet page 263.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= GCTRL[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 6;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end               6: begin //send cmd VCOMS ('hBB), data 'h19; see datasheet page 266.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= VCOMS[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 7;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end                7: begin //send cmd LCMCTRL ('hC0), data 'h2C; see datasheet page 268.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= LCMCTRL[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 8;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end              8: begin //send cmd VDVVRHEN ('hC2), data 'h01; see datasheet page 270.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= VDVVRHEN[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 9;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end              9: begin //send cmd VRHS ('hC3), data 'h12; see datasheet page 271.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= VRHS[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 10;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end                10: begin //send cmd VDVS ('hC4), data 'h20; see datasheet page 273.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= VDVS[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 11;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end               11: begin //send cmd FRCTRL ('hC6), data 'h0F; see datasheet page 277.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= FRCTRL[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 1) begin                         state_init <= 12;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end                 12: begin //send cmd PWCTRL1 ('hD0), 2 bytes data; see datasheet page 283.                 send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;                 if(send_ready & (!send_valid)) begin                     send_data <= PWCTRL1[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 2) begin                         if(COLOR_CORRECT == "FALSE") state_init <= 15;                         else state_init <= 13;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end               13: begin //send cmd PVGAMCTRL ('hE0), 14 bytes data; see datasheet page 287.                 if(COLOR_CORRECT != "FALSE") begin                     send_cd <= (ch_reg_1 == 'd0) ? 'd0 : 'd1;                     if(send_ready & (!send_valid)) begin                         send_data <= PVGAMCTRL[ch_reg_1];                         send_valid <= 'd1;                         if(ch_reg_1 == 14) begin                             state_init <= 14;                             ch_reg_1 <= 'd0;                         end else ch_reg_1 <= ch_reg_1 + 1;                     end else begin                         send_valid <= 'd0;                     end                   end else state_init <= 0;             end               14: begin //send cmd NVGAMCTRL ('hE1), 14 bytes data; see datasheet page 289.                 if(COLOR_CORRECT != "FALSE") begin                     send_cd <= (ch_reg_2 == 'd0) ? 'd0 : 'd1;                     if(send_ready & (!send_valid)) begin                         send_data <= NVGAMCTRL[ch_reg_2];                         send_valid <= 'd1;                         if(ch_reg_2 == 14) begin                             state_init <= 15;                             ch_reg_2 <= 'd0;                         end else ch_reg_2 <= ch_reg_2 + 1;                     end else begin                         send_valid <= 'd0;                     end                  end else state_init <= 0;             end                 15: begin //send cmd INVON ('h21), SLPOUT('h11), DISPON('h29). 0 bytes data; see datasheet page 187, 181, 193.                 send_cd <= 'd0;                  if(send_ready & (!send_valid)) begin                     send_data <= START_WORK[ch_reg];                     send_valid <= 'd1;                     if(ch_reg == 2) begin                         state_init <= 16;                         ch_reg <= 'd0;                     end else ch_reg <= ch_reg + 1;                 end else begin                     send_valid <= 'd0;                 end               end             16: begin//idle state. and waiting for start_init signal                 send_valid <= 'd0;                 if(start_init) begin                     state_init <= 0;                     done <= 'd0;                     rst_LCD <= 'd0;                 end else if(send_ready & (!send_valid)) begin                     done <= 'd1;                 end                                             end                                                                                                                 endcase     end end             send_byte send_byte_init (     .clk(clk),          .cmd_data (send_cd),     .data  (send_data ),     .valid (send_valid),     .ready (send_ready),          .DC  (DC ),       .SCL (SCL),      .SDA (SDA)  );     endmodule 

module set_img_size
`timescale 1ns / 1ps   module set_img_size(     input clk,          input set_size_img_en,     input set_new_XY,     input [15:0] X1,     input [15:0] X2,     input [15:0] Y1,     input [15:0] Y2,          output reg done = 1,               output DC,  // data strobe: command or data. "0" - cmd, "1" - data     output SCL, // clk SPI     output SDA // data SPI      );  logic [7:0] send_data = 0; logic send_valid = 0;     logic send_ready; logic send_cd = 0;    logic [3:0] ch_reg = 0;   logic [3:0] state_set_img = 0;  logic [7:0] CASET[0:4]; logic [7:0] RASET[0:4];  initial begin  //CASET CASET[0] = 'h2A; CASET[1] = 'h00; //H // X1 == 0 CASET[2] = 'h00; //L CASET[3] = 'h00; //H // X2 == 239 CASET[4] = 'hEF; //L  //RASET RASET[0] = 'h2B; RASET[1] = 'h00; RASET[2] = 'h00; RASET[3] = 'h00; RASET[4] = 'hEF; end  always_ff@(posedge clk) begin         case(state_set_img)         0: begin //wait set_size_img_en             if(set_size_img_en) begin                 done <= 'd0;                 state_set_img <= 'd1;                 if(set_new_XY) begin                     CASET[1] <= X1[15:8]; //H                      CASET[2] <= X1[7:0]; //L                     CASET[3] <= X2[15:8]; //H                      CASET[4] <= X2[7:0]; //L                                          RASET[1] <= Y1[15:8]; //H                      RASET[2] <= Y1[7:0]; //L                       RASET[3] <= Y2[15:8]; //H                      RASET[4] <= Y2[7:0]; //L                                       end             end         end         1: begin //send cmd CASET ('h2A), 4 bytes data; see datasheet page 195.             send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;             if(send_ready & (!send_valid)) begin                 send_data <= CASET[ch_reg];                 send_valid <= 'd1;                 if(ch_reg == 4) begin                     state_set_img <= 2;                     ch_reg <= 'd0;                 end else ch_reg <= ch_reg + 1;             end else begin                 send_valid <= 'd0;             end                        end         2: begin //send cmd RASET ('h2B), 4 bytes data; see datasheet page 197.             send_cd <= (ch_reg == 'd0) ? 'd0 : 'd1;             if(send_ready & (!send_valid)) begin                 send_data <= RASET[ch_reg];                 send_valid <= 'd1;                 if(ch_reg == 4) begin                     state_set_img <= 3;                     ch_reg <= 'd0;                 end else ch_reg <= ch_reg + 1;             end else begin                 send_valid <= 'd0;             end                        end           3: begin //send cmd RAMWR ('h2C). see datasheet page 199. for set cursot position to (0,0)             send_cd <= 'd0; //(ch_reg == 'd0) ? 'd0 : 'd1;             if(send_ready & (!send_valid)) begin                 send_data <= 'h2C;                 send_valid <= 'd1;                 state_set_img <= 4;             end else begin                 send_valid <= 'd0;             end          end           4: begin//wait end transmit spi             send_valid <= 'd0;             if(send_ready & (!send_valid)) begin                 done <= 'd1;                             state_set_img <= 0;             end         end     endcase    end            send_byte send_byte_set_img (     .clk(clk),          .cmd_data (send_cd),     .data  (send_data ),     .valid (send_valid),     .ready (send_ready),          .DC  (DC ),       .SCL (SCL),      .SDA (SDA)  );       endmodule 

module RGB_transmit
`timescale 1ns / 1ps   module RGB_transmit #(     parameter PIXEL_MODE = 'h06 // 'h03 - 4:4:4 (not supported here), 'h05 - 5:6:5, 'h06 - 6:6:6;  )(     input clk,          input valid_RGB,     output reg ready = 0,     input [7:0] R,     input [7:0] G,     input [7:0] B,          output DC,  // data strobe: command or data. "0" - cmd, "1" - data     output SCL, // clk SPI     output SDA // data SPI      );      logic [7:0] send_data = 0; logic send_valid = 0;     logic send_ready; logic send_cd = 1;    logic [7:0] bytes_send [0:2];  logic [3:0] state_RGB = 0;  always_ff@(posedge clk) begin     case(state_RGB)         0: begin //wait valid data             if(valid_RGB & ready) begin                 state_RGB <= 'd1;                 if(PIXEL_MODE == 'h05) begin                     bytes_send[0] <= {R[4:0],G[5:3]};                     bytes_send[1] <= {G[2:0],B[4:0]};                 end else if(PIXEL_MODE == 'h06) begin                     bytes_send[0] <= {R,2'd0};                     bytes_send[1] <= {G,2'd0};                     bytes_send[2] <= {B,2'd0};                 end                 ready <= 'd0;             end else begin                 ready <= 'd1;             end             send_valid <= 'd0;         end         1: begin             if(send_ready & (!send_valid)) begin                 send_data <= bytes_send[0];                 send_valid <= 'd1;                 state_RGB <= 2;             end else begin                 send_valid <= 'd0;             end          end         2: begin              if(send_ready & (!send_valid)) begin                 send_data <= bytes_send[1];                 send_valid <= 'd1;                 if(PIXEL_MODE == 'h05) begin                     state_RGB <= 0;                     ready <= 'd1;                 end else if (PIXEL_MODE == 'h06) state_RGB <= 3;             end else begin                 send_valid <= 'd0;             end          end           3: begin             if(send_ready & (!send_valid)) begin                 send_data <= bytes_send[2];                 send_valid <= 'd1;                 state_RGB <= 0;                 ready <= 'd1;             end else begin                 send_valid <= 'd0;             end          end            endcase end           send_byte send_byte_RGB (     .clk(clk),          .cmd_data (send_cd),     .data  (send_data ),     .valid (send_valid),     .ready (send_ready),          .DC  (DC ),       .SCL (SCL),      .SDA (SDA)  );         endmodule 

module send_byte
`timescale 1ns / 1ps  module send_byte(     input clk,     input cmd_data, // cmd - '0', data - '1'     input [7:0] data,     input valid,     output reg ready = 1,          output reg DC  = 0, // data strobe: command or data. "0" - cmd, "1" - data     output reg SCL = 1, // clk SPI     output reg SDA = 0  // data SPI     );          logic [7:0] buf_data_send = 0;    logic [2:0] state = 0; logic [3:0] cnt_bits = 0;        always_ff@(posedge clk) begin     case(state)     0: begin // wait byte to send         if(valid & ready) begin             buf_data_send <= data;             DC <= cmd_data;             ready <= 'd0;             state <= 'd1;                     end else begin             ready <= 'd1;         end             end     1: begin // send byte         if(SCL == 1) begin //set SCL=0, need set data to SDA             SDA <= buf_data_send[7];             buf_data_send <= {buf_data_send,1'b0};             SCL <= 'd0;         end else begin             SCL <= 'd1;             if(cnt_bits == 'd7) begin                 state <= 'd0;                 cnt_bits <= 'd0;             end else cnt_bits <= cnt_bits + 1;         end     end     endcase     end          endmodule 

Ролик Bad Apple

Основной проблемой запуска ролика был его размер. Нужно было сохранить 189_273_600 пикселей, ситуацию упрощало (но не полностью), что ролик черно-белый, даже двухцветный. Этот массив пикселей я обработал и упаковал в одномерный массив, который подцепил затем в SDK при программировании Zynq. Файл получился тяжелым, порядка 43 Мбайт (не забывайте о форматировании массива, чтобы синтаксис языка пройти).

Также на помощь пришла ОЗУ процессора и DMA. Zynq занимался тем, что говорил DMA отправлять в модуль данные из ОЗУ, а так же Zynq предоставил возможность легко поднять интерфейс DDR. Грузил я эту всю прошивку через программатор, прямиком в DDR, это заняло 5–6 минут чистого времени.

Давайте посмотрим, что получилось:

Комментарии к ролику. Я посчитал что будет скучно смотреть на один только дисплей, поэтому добавил музыкальное сопровождение. Ну и парой строк кода сделал мини эквалайзер из светодиодов, они загораются в зависимости от частоты звучания мелодии. Также выполнена плохая синхронизация звука и мелодии, особенно это заметно в конце когда музыка закончилась, а ролик воспроизводится дальше.

Используемые источники
  1. https://github.com/MasterPlayer/lcd-st7789-sv/tree/main  — еще один вариант реализации

  2. https://pdf1.alldatasheet.com/datasheet-pdf/download/1132511/SITRONIX/ST7789V.html — даташит на дисплей

  3. https://github.com/swindlesmccoop/bad-apple-arduino/blob/master/badapple/melody.h — тут я взял мелодию BadAplle, товарищ использовал 5 каналов для воспроизведения, я выбрал самую подходящую и импортировал к себе. Кратное пояснение: в переменных d1,d2,d3… хранятся значения времени в миллисекундах от начала запуска, это время показывает когда нужно переключиться на следующую частоту звучания. В переменных m1,m2,m3… хранятся частоты в герцах, которые нужно сгенерировать на buzzer.

  4. https://wx.mail.qq.com/ftn/download?func=3&k=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f20825d116c2&key=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f20825d116c2&code=1c2f7289&from=  — ссылка на скачивание архива документов что поставляют китайцы.

  5. https://www.youtube.com/@mihas6705 — хочу порекомендовать этот канал, там товарищ взял аналогичную плату за 700 рублей с авито.


ссылка на оригинал статьи https://habr.com/ru/articles/828530/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *