В этой статье я хочу сделать краткий обзор на плату расширения к китайской плате с ПЛИС. Данная плата хорошо дополняет функционал основной платы 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.
Теперь самая интересная часть. Работа с дисплеем 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 МГц и, как будет видно ниже, все работает.
Область отрисовки. Дисплей предусматривает возможность рисовать в любой области дисплея. Так же можно переключаться на ходу. Это позволяет не только выводить картинку целиком на экран, но и выводить отдельно символы, так же можно добиться вывода только одного пикселя.
Примеры рисования на дисплее:
Модуль для работы с 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 минут чистого времени.
Давайте посмотрим, что получилось:
Комментарии к ролику. Я посчитал что будет скучно смотреть на один только дисплей, поэтому добавил музыкальное сопровождение. Ну и парой строк кода сделал мини эквалайзер из светодиодов, они загораются в зависимости от частоты звучания мелодии. Также выполнена плохая синхронизация звука и мелодии, особенно это заметно в конце когда музыка закончилась, а ролик воспроизводится дальше.
Используемые источники
-
https://github.com/MasterPlayer/lcd-st7789-sv/tree/main — еще один вариант реализации
-
https://pdf1.alldatasheet.com/datasheet-pdf/download/1132511/SITRONIX/ST7789V.html — даташит на дисплей
-
https://github.com/swindlesmccoop/bad-apple-arduino/blob/master/badapple/melody.h — тут я взял мелодию BadAplle, товарищ использовал 5 каналов для воспроизведения, я выбрал самую подходящую и импортировал к себе. Кратное пояснение: в переменных d1,d2,d3… хранятся значения времени в миллисекундах от начала запуска, это время показывает когда нужно переключиться на следующую частоту звучания. В переменных m1,m2,m3… хранятся частоты в герцах, которые нужно сгенерировать на buzzer.
-
https://wx.mail.qq.com/ftn/download?func=3&k=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f20825d116c2&key=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f20825d116c2&code=1c2f7289&from= — ссылка на скачивание архива документов что поставляют китайцы.
-
https://www.youtube.com/@mihas6705 — хочу порекомендовать этот канал, там товарищ взял аналогичную плату за 700 рублей с авито.
ссылка на оригинал статьи https://habr.com/ru/articles/828530/
Добавить комментарий