【FPGA experiment】VGA display

【FPGA experiment】VGA display

1. Introduction to VGA

​ The full name of VGA is Video Graphics Array, which is a video graphics array that uses analog signals for video transmission.

lose standard. Early CRT monitors can only receive analog signal input due to design and manufacturing reasons, so the computer

The internal graphics card is responsible for digital-to-analog conversion, and the VGA interface is the interface on the graphics card that outputs analog signals. LCD monitors are now

Although it can directly receive digital signals, but in order to be compatible with the VGA interface on the graphics card, most of them also support the VGA standard.

insert image description here

The definition of VGA interface and the function description of each pin are shown in Figure 18.1.2. We generally only use 1 (RED) and 2

(GREEN), 3 (BLUE), 13 (HSYNC), 14 (VSYNC) signals. Pins 1, 2, and 3 respectively output red, green, and blue

Color analog signal, the voltage range is 0~0.714V, 0V means no color, 0.714V means full color; pin 13, 14 output TTL

Level standard line/field sync signal.
insert image description here

The synchronization timing in the program is divided into row timing and field timing:

insert image description here

VGA timing parameter
insert image description hereclock frequency of different resolutions = row frame length × column frame length * refresh rate, 640 × 480 60HZ corresponds to clock frequency = 800 × 525 × 60 = 25.2M, so it needs to be done with frequency division clock or manually set two crossover.

Color display:

The bit width of the color data output by the FPGA pin is 16 bits, and the data format is RGB565, that is, the upper 5 bits of the data represent red, the middle 6 bits represent green, and the lower 5 bits represent blue. The data in RGB565 format can represent a total of 65536 colors. In addition, the commonly used color data format is RGB888. The larger the data bit width, the richer the color types that can be represented.

VGA interface:

insert image description here

Pin assignment:

insert image description here

2. VGA display color stripes

Resolution control part:


```c
`define vga_640_480
`define vga_1920_1080
`define vga_1024_768

`ifdef  vga_640_480
    //执行操作A
    `define H_Right_Border 8
    `define H_Front_Porch  8
    `define H_Sync_Time    96
    `define H_Back_Porch   40
    `define H_Left_Border  8
    `define H_Data_Time    640
    `define H_Total_Time   800

    `define V_Bottom_Border 8
    `define V_Front_Porch   2
    `define V_Sync_Time     2
    `define V_Back_Porch    25
    `define V_Top_Border    8
    `define V_Data_Time     480
    `define V_Total_Time    525

`elsif  vga_1920_1080
    //执行操作B
    `define H_Right_Border 0
    `define H_Front_Porch  88
    `define H_Sync_Time    44
    `define H_Back_Porch   148
    `define H_Left_Border  0
    `define H_Data_Time    1920
    `define H_Total_Time   2200

    `define V_Bottom_Border 0
    `define V_Front_Porch   4
    `define V_Sync_Time     5
    `define V_Back_Porch    36
    `define V_Top_Border    0
    `define V_Data_Time     1080
    `define V_Total_Time    1125

`elsif vga_1024_768 
    `define H_Right_Border 0
    `define H_Front_Porch  24
    `define H_Sync_Time    136
    `define H_Back_Porch   160
    `define H_Left_Border  0
    `define H_Data_Time    1024
    `define H_Total_Time   1344

    `define V_Bottom_Border 0 
    `define V_Front_Porch   3 
    `define V_Sync_Time     6 
    `define V_Back_Porch    29
    `define V_Top_Border    0 
    `define V_Data_Time     768 
    `define V_Total_Time    806

`else

`endif


```c
VAG驱动
`define vga_640_480

`include "vga_para.v"

module vga_ctrl(
    input                 clk         ,//时钟信号 //25.2MHZ
    input                 rst_n       ,//复位信号
    input         [23:0]  data_disp   ,

    output  reg   [10:0]  h_addr      ,//数据有效显示区域行地址
    output  reg   [10:0]  v_addr      ,//数据有效显示区域场地址

    output  reg           vsync       ,
    output  reg           hsync       ,

    output  reg   [7 :0]  vga_r       ,
    output  reg   [7 :0]  vga_b       ,
    output  reg   [7 :0]  vga_g       ,
    output  wire          vga_blk     ,
    output  wire          vga_sync    ,
    output  reg           vga_clk      //25.2MHZ
);

//参数定义
    parameter   H_SYNC_START = 1,
                H_SYNC_STOP  = `H_Sync_Time ,
                H_DATA_START = `H_Sync_Time + `H_Back_Porch + `H_Left_Border,
                H_DATA_STOP  = `H_Sync_Time + `H_Back_Porch + `H_Left_Border + `H_Data_Time,

                V_SYNC_START = 1,
                V_SYNC_STOP  = `V_Sync_Time,
                V_DATA_START = `V_Sync_Time + `V_Back_Porch + `V_Top_Border,
                V_DATA_STOP  = `V_Sync_Time + `V_Back_Porch + `V_Top_Border + `V_Data_Time;

//信号定义
    reg     [11:0]  cnt_h_addr  ;//行地址计数器
    wire            add_h_addr  ;
    wire            end_h_addr  ;

    reg     [11:0]  cnt_v_addr  ;//长地址计数器
    wire            add_v_addr  ;
    wire            end_v_addr  ;

    assign vga_sync = 1'b0;

    assign vga_blk = ~((cnt_h_addr<`H_Front_Porch + `H_Sync_Time + `H_Back_Porch)||(cnt_v_addr<`V_Front_Porch + `V_Sync_Time + `V_Back_Porch)); 

    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_h_addr <= 12'd0;
        end
        else if(add_h_addr)begin
            if(end_h_addr)begin
                cnt_h_addr <= 12'd0;
            end
            else begin
                cnt_h_addr <= cnt_h_addr + 12'd1;
            end
        end
        else begin
            cnt_h_addr <= 12'd0;
        end
    end

    assign add_h_addr = 1'b1;
    assign end_h_addr = add_h_addr && cnt_h_addr == `H_Total_Time - 1;

    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_v_addr <= 12'd0;
        end
        else if(add_v_addr)begin
            if(end_v_addr)begin
                cnt_v_addr <= 12'd0;
            end
            else begin
                cnt_v_addr <= cnt_v_addr + 12'd1;
            end
        end
        else begin
            cnt_v_addr <= cnt_v_addr;
        end
    end

    assign add_v_addr = end_h_addr;
    assign end_v_addr = add_v_addr && cnt_v_addr == `V_Total_Time - 1;

    //行场同步信号
    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            hsync <= 1'b1;
        end
        else if(cnt_h_addr == H_SYNC_START - 1)begin
            hsync <= 1'b0;
        end
        else if(cnt_h_addr == H_SYNC_STOP - 1)begin
            hsync <= 1'b1;
        end
        else begin
            hsync <= hsync;
        end
    end

    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            vsync <= 1'b1;
        end
        else if(cnt_v_addr == V_SYNC_START - 1)begin
            vsync <= 1'b0;
        end
        else if(cnt_v_addr == V_SYNC_STOP - 1)begin
            vsync <= 1'b1;
        end
        else begin
            vsync <= vsync;
        end
    end

    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            vga_clk =0;
        end
        else begin
            vga_clk = ~vga_clk;
        end
    end 

    //数据有效显示区域定义
    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            h_addr <= 11'd0;
        end
        else if((cnt_h_addr >= H_DATA_START - 1) &&( cnt_h_addr <= H_DATA_STOP - 1))begin
            h_addr <= cnt_h_addr - H_DATA_START - 1;
        end
        else begin
            h_addr <= 11'd0;
        end
    end

    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            v_addr <= 11'd0;
        end
        else if((cnt_v_addr >= V_DATA_START - 1) && (cnt_v_addr <= V_DATA_STOP - 1))begin
            v_addr <= cnt_v_addr - V_DATA_START -1;
        end
        else begin
            v_addr <= 11'd0;
        end
    end

    //显示数据
    always@(posedge vga_clk or negedge rst_n)begin
        if(!rst_n)begin
            vga_r <= 8'b0;
            vga_g <= 8'b0;
            vga_b <= 8'b0;
        end
        else if((cnt_h_addr >= H_DATA_START - 1) &&( cnt_h_addr <= H_DATA_STOP - 1) 
                && (cnt_v_addr >= V_DATA_START - 1) && (cnt_v_addr <= V_DATA_STOP - 1))begin
            vga_r <= data_disp[23:16];
            vga_g <= data_disp[15: 8];
            vga_b <= data_disp[7 : 0];
        end
        else begin
            vga_r <= 8'b0;
            vga_g <= 8'b0;
            vga_b <= 8'b0;
        end
    end

endmodule

Color display:

module data_gen(
    input                   clk     ,//时钟信号
    input                   rst_n   ,//复位信号

    input       [10:0]      h_addr  ,//数据有效显示区域地址
    input       [10:0]      v_addr  ,//数据有效显示区域地址
    
    output  reg [23:0]      data_disp        
);
//参数定义
    parameter   BLACK       = 24'h000000,
                RED         = 24'hFF0000,
                GREEN       = 24'h00FF00,
                BLUE        = 24'h0000FF,
                YELLOW      = 24'hFFFF00,
                SKY_BULE    = 24'h00FFFF,
                PURPLE      = 24'hFF00FF,
                GREY        = 24'hC0C0C0,
                WIGHT       = 24'hFFFFFF;

    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            data_disp <= BLACK;
        end
        else begin
            case(h_addr)
                0  : data_disp <= RED;
                80 : data_disp <= GREEN;
                160: data_disp <= BLUE;
                240: data_disp <= YELLOW;
                320: data_disp <= SKY_BULE;
                400: data_disp <= PURPLE;
                480: data_disp <= GREY;
                560: data_disp <= WIGHT;
                default:data_disp <= data_disp;
            endcase
        end
    end


endmodule

Top-level module:

module vga_top(
    input                  clk         ,//时钟信号
    input                  rst_n       ,//复位信号
  
    output  wire           vsync       ,
    output  wire           hsync       ,
    output  wire   [7 :0]  vga_r       ,
    output  wire   [7 :0]  vga_b       ,
    output  wire   [7 :0]  vga_g       ,
    output                 vga_blk     ,
    output  wire           vga_sync    ,
    output                 vga_clk                  
);
    wire     [23:0]      data_disp   ;

    wire     [10:0]      h_addr      ;
    wire     [10:0]      v_addr      ;


data_gen u_data_gen(
    .clk        (vga_clk    ),//时钟信号
    .rst_n      (rst_n      ),//复位信号
    
    .h_addr     (h_addr     ),//数据有效显示区域地址
    .v_addr     (v_addr     ),//数据有效显示区域地址
        
    .data_disp  (data_disp  )        
);

vga_ctrl u_vga_ctrl(
    .clk         (clk       ),//时钟信号 25.2MHZ
    .rst_n       (rst_n     ),//复位信号
    .data_disp   (data_disp ),

    .h_addr      (h_addr    ),//数据有效显示区域行地址
    .v_addr      (v_addr    ),//数据有效显示区域场地址

    .vsync       (vsync     ),
    .hsync       (hsync     ),

    .vga_r       (vga_r     ),
    .vga_b       (vga_b     ),
    .vga_g       (vga_g     ),
    .vga_blk     (vga_blk   ),
    .vga_sync    (vga_sync  ),
    .vga_clk     (vga_clk   )
);

endmodule

The results show that:

insert image description here

three character display

Characters need to be displayed here, and a Chinese character dot matrix tool is needed. Here we use the modulo software "PCtoLCD2002" to obtain

Take the font of the Chinese character "punctual atom"

Name:

insert image description here

Save it as a bmp file, then click the file to open the bpm file and enter the image mode

Generate glyphs:

{0020002000202020FC501020105013FE},
{108882221124422412124A207C100BFC},
{11FC1284100412881008E24810882250},
{1C502220E02024504010248800100906}

finally got

0020002000200000000000000000000000000000000000000000000000000000
7E20002020200000000000000000000000000000000000000000000000000000
0220FC5010200000000000000000000000000000000000000000000000000000
0420105013FE000007F00FE000800FE007E01FFC07E007F007E007F000801FFC
0820108882220000081830180780301818183008181808181818081807803008
08A8112442240000100038180180300C381C2010381C1000381C100001802010
0AA412124A200000300000180180700C300C0020300C3000300C300001800020
0CA47C100BFCFFFF37F000600180301C300C0040300C37F0300C37F001800040
392211FC12840000380C01F00180382C300C0080300C380C300C380C01800080
C922100412880000300C001801800FCC300C0180300C300C300C300C01800180
0A221008E2480000300C000C0180001C300C0300300C300C300C300C01800300
0820108822500000300C380C01800018381803003818300C3818300C01800300
08201C502220000018183018018038301C1003801C1018181C10181801800380
0820E0202450000007E00FE00FF80FC007E0030007E007E007E007E00FF80300
28A0401024880000000000000000000000000000000000000000000000000000
1040001009060000000000000000000000000000000000000000000000000000

code part:

module vga_driver(
OSC_50,     //原CLK2_50时钟信号
VGA_CLK,    //VGA自时钟
VGA_HS,     //行同步信号
VGA_VS,     //场同步信号
VGA_BLANK,  //复合空白信号控制信号  当BLANK为低电平时模拟视频输出消隐电平,此时从R9~R0,G9~G0,B9~B0输入的所有数据被忽略
VGA_SYNC,   //符合同步控制信号      行时序和场时序都要产生同步脉冲
VGA_R,      //VGA绿色
VGA_B,      //VGA蓝色
VGA_G);     //VGA绿色
 input OSC_50;     //外部时钟信号CLK2_50
 output VGA_CLK,VGA_HS,VGA_VS,VGA_BLANK,VGA_SYNC;
 output [7:0] VGA_R,VGA_B,VGA_G;
 parameter H_FRONT = 16;     //行同步前沿信号周期长
 parameter H_SYNC = 96;      //行同步信号周期长
 parameter H_BACK = 48;      //行同步后沿信号周期长
 parameter H_ACT = 640;      //行显示周期长
 parameter H_BLANK = H_FRONT+H_SYNC+H_BACK;        //行空白信号总周期长
 parameter H_TOTAL = H_FRONT+H_SYNC+H_BACK+H_ACT;  //行总周期长耗时
 parameter V_FRONT = 11;     //场同步前沿信号周期长
 parameter V_SYNC = 2;       //场同步信号周期长
 parameter V_BACK = 31;      //场同步后沿信号周期长
 parameter V_ACT = 480;      //场显示周期长
 parameter V_BLANK = V_FRONT+V_SYNC+V_BACK;        //场空白信号总周期长
 parameter V_TOTAL = V_FRONT+V_SYNC+V_BACK+V_ACT;  //场总周期长耗时
 reg [10:0] H_Cont;        //行周期计数器
 reg [10:0] V_Cont;        //场周期计数器
 wire [7:0] VGA_R;         //VGA红色控制线
 wire [7:0] VGA_G;         //VGA绿色控制线
 wire [7:0] VGA_B;         //VGA蓝色控制线
 reg VGA_HS;
 reg VGA_VS;
 reg [10:0] X;             //当前行第几个像素点
 reg [10:0] Y;             //当前场第几行
 reg CLK_25;
 always@(posedge OSC_50)
    begin 
      CLK_25=~CLK_25;         //时钟
    end 
    assign VGA_SYNC = 1'b0;   //同步信号低电平
    assign VGA_BLANK = ~((H_Cont<H_BLANK)||(V_Cont<V_BLANK));  //当行计数器小于行空白总长或场计数器小于场空白总长时,空白信号低电平
    assign VGA_CLK = ~CLK_to_DAC;  //VGA时钟等于CLK_25取反
    assign CLK_to_DAC = CLK_25;
 always@(posedge CLK_to_DAC)
    begin
        if(H_Cont<H_TOTAL)           //如果行计数器小于行总时长
            H_Cont<=H_Cont+1'b1;      //行计数器+1
        else H_Cont<=0;              //否则行计数器清零
        if(H_Cont==H_FRONT-1)        //如果行计数器等于行前沿空白时间-1
            VGA_HS<=1'b0;             //行同步信号置0
        if(H_Cont==H_FRONT+H_SYNC-1) //如果行计数器等于行前沿+行同步-1
            VGA_HS<=1'b1;             //行同步信号置1
        if(H_Cont>=H_BLANK)          //如果行计数器大于等于行空白总时长
            X<=H_Cont-H_BLANK;        //X等于行计数器-行空白总时长   (X为当前行第几个像素点)
        else X<=0;                   //否则X为0
    end
 always@(posedge VGA_HS)
    begin
        if(V_Cont<V_TOTAL)           //如果场计数器小于行总时长
            V_Cont<=V_Cont+1'b1;      //场计数器+1
        else V_Cont<=0;              //否则场计数器清零
        if(V_Cont==V_FRONT-1)       //如果场计数器等于场前沿空白时间-1
            VGA_VS<=1'b0;             //场同步信号置0
        if(V_Cont==V_FRONT+V_SYNC-1) //如果场计数器等于行前沿+场同步-1
            VGA_VS<=1'b1;             //场同步信号置1
        if(V_Cont>=V_BLANK)          //如果场计数器大于等于场空白总时长
            Y<=V_Cont-V_BLANK;        //Y等于场计数器-场空白总时长    (Y为当前场第几行)  
        else Y<=0;                   //否则Y为0
    end
    reg valid_yr;
    
 always@(posedge CLK_to_DAC)
    if(V_Cont == 10'd32)         //场计数器=32时
        valid_yr<=1'b1;           //行输入激活
    else if(V_Cont==10'd512)     //场计数器=512时
        valid_yr<=1'b0;           //行输入冻结
    wire valid_y=valid_yr;       //连线   
    reg valid_r;            
 always@(posedge CLK_to_DAC)   
    if((H_Cont == 10'd32)&&valid_y)     //行计数器=32时
        valid_r<=1'b1;                   //像素输入激活
    else if((H_Cont==10'd512)&&valid_y) //行计数器=512时 
        valid_r<=1'b0;                   //像素输入冻结
    wire valid = valid_r;               //连线
    wire[10:0] x_dis;     //像素显示控制信号
    wire[10:0] y_dis;     //行显示控制信号
    assign x_dis=X;       //连线X
    assign y_dis=Y;       //连线Y
        parameter

    char_line00=240'h0020002000200000000000000000000000000000000000000000000000000000,
    char_line01=240'h7E20002020200000000000000000000000000000000000000000000000000000,
    char_line02=240'h0220FC5010200000000000000000000000000000000000000000000000000000,
    char_line03=240'h0420105013FE000007F00FE000800FE007E01FFC07E007F007E007F000801FFC,
    char_line04=240'h0820108882220000081830180780301818183008181808181818081807803008,
    char_line05=240'h08A8112442240000100038180180300C381C2010381C1000381C100001802010,
    char_line06=240'h0AA412124A200000300000180180700C300C0020300C3000300C300001800020,
    char_line07=240'h0CA47C100BFCFFFF37F000600180301C300C0040300C37F0300C37F001800040,
    char_line08=240'h392211FC12840000380C01F00180382C300C0080300C380C300C380C01800080,
    char_line09=240'hC922100412880000300C001801800FCC300C0180300C300C300C300C01800180,
    char_line0a=240'h0A221008E2480000300C000C0180001C300C0300300C300C300C300C01800300,
    char_line0b=240'h0820108822500000300C380C01800018381803003818300C3818300C01800300,
    char_line0c=240'h08201C502220000018183018018038301C1003801C1018181C10181801800380,
    char_line0d=240'h0820E0202450000007E00FE00FF80FC007E0030007E007E007E007E00FF80300,
    char_line0e=240'h28A0401024880000000000000000000000000000000000000000000000000000,
    char_line0f=240'h1040001009060000000000000000000000000000000000000000000000000000;

    reg[7:0] char_bit;
    always@(posedge CLK_to_DAC)
        if(X==10'd144)char_bit<=9'd240;   //当显示到144像素时准备开始输出图像数据
        else if(X>10'd144&&X<10'd384)     //左边距屏幕144像素到416像素时    416=144+272(图像宽度)
            char_bit<=char_bit-1'b1;       //倒着输出图像信息
            
    reg[29:0] vga_rgb;                //定义颜色缓存
    always@(posedge CLK_to_DAC) 
        if(X>10'd144&&X<10'd384)    //X控制图像的横向显示边界:左边距屏幕左边144像素  右边界距屏幕左边界416像素
            begin case(Y)            //Y控制图像的纵向显示边界:从距离屏幕顶部160像素开始显示第一行数据
                10'd160:
                if(char_line00[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;  //如果该行有数据 则颜色为红色
                else vga_rgb<=30'b0000000000_0000000000_0000000000;                      //否则为黑色
                10'd162:
                if(char_line01[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd163:
                if(char_line02[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd164:
                if(char_line03[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd165:
                if(char_line04[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd166:
                if(char_line05[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd167:
                if(char_line06[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd168:
                if(char_line07[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd169:
                if(char_line08[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd170:
                if(char_line09[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd171:
                if(char_line0a[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd172:
                if(char_line0b[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd173:
                if(char_line0c[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd174:
                if(char_line0d[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd175:
                if(char_line0e[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd176:
                if(char_line0f[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                default:vga_rgb<=30'h0000000000;   //默认颜色黑色
            endcase 
        end
    else  vga_rgb<=30'h000000000;             //否则黑色
    assign VGA_R=vga_rgb[23:16];
    assign VGA_G=vga_rgb[15:8];
    assign VGA_B=vga_rgb[7:0];
endmodule



The result is as follows:

Please add a picture description

4. Picture display

Since the size of a 640×480 24-bit picture exceeds the memory of the chip, the picture cannot be saved, so a 128*78 picture is used for display.
Original image:

insert image description here

insert image description here

Convert to HEX file

insert image description here

Call the rom core to complete
insert image description here

Set the bit width to 16 bits and the size to the picture size 128×78 = 9984

insert image description here

Uncheck:

insert image description here

Find the data1.hex file just generated

insert image description hereinsert image description here

result:

`Please add a picture description

Guess you like

Origin blog.csdn.net/qq_52445967/article/details/125169879