Mini review of the EBAZ4205 expansion board

In this article I want to make a brief review of the expansion board for the Chinese board with FPGA. This board complements the functionality of the main EBAZ board well. At the end of the article there will be a demonstration of the Bad Apple video.

Available

When the board appeared on Aliexpress, it cost 700-900 rubles, then the Chinese realized that the board in its bare form is not convenient to use and began to make various expansion boards. I chose the one in the photo above, and this is what we will talk about.

This board has a ST7789V display, buzzer, HDMI, as well as 5 buttons and 3 LEDs. The Chinese did not forget about USB-UART.

The distinctive feature of this board from many other boards with FPGA is that it has inscriptions on it indicating which FPGA pins a particular module is connected to. For example, if you look closely at the buttons and LEDs, there is a characteristic inscription next to each element (For example, LED1_E19 or K1_T19. E19 and T19 are FPGA pins that need to be specified when programming it).

They also didn't forget to sign the 20-pin contact comb on the back side. In addition to the signed pins, you can pay attention to the soldering place of the display cable – the first two contacts are soldered together. One of the contacts is the backlight signal, and the second is GND, so the backlight is always on. I removed this connection on my board and control the backlight directly from the FPGA.

Disadvantages/advantages. From disadvantages I would like to draw attention to two points. Firstly, there is no power button on the board, and if you need to reboot it or reset the firmware, you have to pull the USB connector. Secondly, the expansion board covers the 6-pin power connector, but in general everything works without it, so I have no complaints, but maybe someone would find it useful. From advantages There are also two points. Firstly, the size, there are analogs that have a much larger area, and the functionality is either worse or the same. Secondly, the power supply and USB-UART are combined into one interface, and this reduces the number of wires.

For those interested, here are different types of expansion boards:

HDMI. The HDMI port also attracts attention. No additional microcircuit is provided for this port, which means that all signals must be completely formed in the FPGA. The Chinese have taken care of the developers here too and together with the documentation supply an IP core that converts the VGA we are used to into HDMI. As I understand it, this IP core was pulled out from one of the Digilent debug boards.

The IP core itself is quite simple.

The IP core itself is quite simple.

Now comes the most interesting part. Working with the ST7789 display.

Surely some people are reading this article just to watch the BadApple video, so let's not pull the display by the pixel and get started.)

Color modes. The display is a 240×240 pixel screen with different color modes of operation RGB 4:4:4, RGB 5:6:5, RGB 6:6:6. I have implemented the last two modes of operation, they are more convenient to implement, and the 4:4:4 mode is not interesting to me, since it has a small color range.

Clock frequency. The documentation says that the minimum period of the clock signal (SCL) is 66 ns, which corresponds to 15.151 MHz. On the Internet, I came across statements that the frequency limit of this display is 50 MHz, I set the frequency to about 100 MHz and the display coped. As a result, in my project I set the frequency to 50 MHz and, as will be seen below, everything works.

Rendering area. The display provides the ability to draw in any area of ​​the display. You can also switch on the go. This allows you to not only display the entire image on the screen, but also display characters separately, and you can also achieve the output of only one pixel.

Examples of drawing on the display:
Demonstration of the ability to output small areas. In this case, the background of 4 stripes was first output, then small squares with the letter A were output on top. This was done by changing the rendering area.

Demonstration of the ability to output small areas. In this case, the background of 4 stripes was first output, then small squares with the letter A were output on top. This was done by changing the rendering area.

Output of any image to the display from the PC. The image was transmitted via UART.

Output of any image to the display from the PC. The image was transmitted via UART.

Simple RGB Channel Gradient

Simple RGB Channel Gradient

Module for working with LCD display.

I included three stages of work in the module. The initialization stage, the stage of setting the rendering area, the stage of data transfer (pixel values).

Generalized structural diagram of the module

Generalized structural diagram of the module

Initialization stage starts automatically after the reset signal. As part of the initialization, a reset signal is set for the display (RES), then the time is waited for the display to come to its senses. The documentation specifies different timings after the reset signal, when you can write to the registers and when you can turn on the display, I did it in a clever way – I simply waited 500 ms after the reset signal and began initializing the internal registers of the display.

Here is the list of addresses I initialized (the list of addresses is in the order they were initialized): MADCTL, COLMOD, PORCTRL, GCTRL, VCOMS, LCMCTRL, VDVVRHEN, VRHS, VDVS, FRCTRL, PWCTRL1, PVGAMCTRL, NVGAMCTRL, INVON, SLPOUT, DISPON.

I took this list of addresses from the Chinese example. During the experiments, I disabled PVGAMCTRL, NVGAMCTRL and PORCTRL. I'm sure you can disable most of the registers, but since it works, I didn't touch it.

Stage of setting the rendering area consists of transmitting two coordinates, the beginning and end of the rectangle. They are written to the CASET and RASET registers. A mandatory condition is the reset of the current pixel pointer, this is the RAMWR register. After this command, the display waits for an array of pixels.

Data transfer stage. The meaning of this stage is obvious, after setting up the display and setting the rendering area, you need to transfer the pixel values ​​in the format you selected. For SPI, these will be either 2 bytes or 3 bytes, depending on the selected color range. For the module's input interface, these are 3 bytes (RGB) and a valid signal, here you need to take into account that depending on the selected mode, a different number of bits from the input data will be used.

Here is the module code in SystemVerilog:

To apply the code, simply copy it into separate files (1 module = 1 file), import it into your project and see the result. The ready_RGB signal can be used to start transmitting data to the display. If you need to specify a different display output area, you need to use the signals: set_size_img_en_i, set_new_XY_i, X1, X2, Y1, Y2. set_size_img_en_i – rises by 1 clock cycle and tells the module to start the process of changing the rendering area. set_new_XY_i – if you need to set new dimensions of the rendering area, if you leave it at zero, the size will not change, only the current pixel pointer will be reset to zero coordinates. X1, X2, Y1, Y2 – are used to set the coordinates of two points of the rectangle, the beginning and the end. It is also necessary to take into account that the frequency of the SCL signal will always be 2 times less than the frequency of the module (if clk == 50 MHz, then SCL == 25 MHz).

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 video.

The main problem with launching the video was its size. It was necessary to save 189_273_600 pixels, the situation was simplified (but not completely) by the fact that the video was black and white, even two-color. I processed this array of pixels and packed it into a one-dimensional array, which I then hooked into the SDK when programming Zynq. The file turned out to be heavy, about 43 MB (don't forget about formatting the array so that the language syntax can pass).

The processor RAM and DMA also came to the rescue. Zynq was busy telling DMA to send data from RAM to the module, and Zynq also provided the ability to easily raise the DDR interface. I loaded this entire firmware through a programmer, directly into DDR, it took 5-6 minutes of pure time.

Let's see what happened:

Comments to the video. I thought it would be boring to look at just the display, so I added some music. And with a couple of lines of code I made a mini equalizer from LEDs, they light up depending on the frequency of the melody. Also, the sound and melody are poorly synchronized, this is especially noticeable at the end when the music is over and the video continues to play.

Sources used.

  1. https://github.com/MasterPlayer/lcd-st7789-sv/tree/main – another implementation option

  2. https://pdf1.alldatasheet.com/datasheet-pdf/download/1132511/SITRONIX/ST7789V.html – datasheet for the display

  3. https://github.com/swindlesmccoop/bad-apple-arduino/blob/master/badapple/melody.h – here I took the BadAplle melody, a friend used 5 channels for playback, I chose the most suitable one and imported it to myself. Brief explanation: the variables d1, d2, d3… store time values ​​in milliseconds from the start of the launch, this time shows when to switch to the next frequency of the sound. The variables m1, m2, m3… store frequencies in hertz, which need to be generated on the buzzer.

  4. https://wx.mail.qq.com/ftn/download?func=3&k=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f2 0825d116c2&key=ce9c50661c249426fcef1c6635323839015f006737323839144c4a120104590d05551f0706530c1403570a521a505d5d064e0752550b590f550102550f5129397421733c0300080c6e075312561c4a5843775b8ed2ba8d0dcad41eda98efd476f20825d116c2&code=1c2f7289&from= – link to download the archive of documents supplied by the Chinese.

  5. https://www.youtube.com/@mihas6705 – I want to recommend this channel, a friend there took a similar payment for 700 rubles from Avito.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *