1. Project Introduction
Based on the Lichuang Liangshanpai GD32F4 high-performance development board, make your own game console. This game console has transplanted NES emulator. Currently, general NES games can be played. There are hundreds of games available for download. In this project, we can learn the transplantation of LCD screen, the use of SD card file system, the production of simple UI interface, the function of music output, etc. In this project, we can recall our childhood.
2. Onboard functional resources

(Figure 1 PCB front)

(Figure 2 PCB back)
3. Key learning content:
1. File system, Fats read and write operation (SD card and Flash file system).
2. EXMC LCD driver (8080LCD interface)
3. Transplant NES display 240*240 to 480*480
4. LCD touch control and play games
5. DAC music output
6. Text library use
7. SDRAM memory management
8. Bmp picture file reading, storage and display
4. Circuit schematic introduction:
1. Power management part:

(Figure 3) TYPE-C charging interface, diode can prevent reverse input.

(Figure 4) Power management module
Figure 4 is the charging and boost management part. U10 is a charging IC with a maximum charging current of 1A, and U9 is a boost IC (ME2159), a relatively excellent lithium battery boost IC with a 2.5V-4.2V wide voltage conversion efficiency of up to 90%,
as shown in Figure 5 (official manual test parameters):

(Figure 5) ME2159 performance diagram
CHG1 and CHG2 in Figure 4 are the charging and charging completion status signals of the charging IC. The R27 resistor stops the boost after the charging power supply is inserted. The program needs to be delayed after the charging power supply is turned off, otherwise the screen will be blank. The battery voltage sampling current of R16 and R19 on the right side is calculated by the formula:
faValue = adcValue*(3.368/4096)*fadcoffset;
A. favalue: battery voltage
B. adcValue: value read by ADC
C. fadcoffset: correction value
2. Voice amplifier circuit:

(Figure 6) DAC output signal amplifier
circuit In Figure 6, changing the ratio of R10 and R8 can change the multiple of audio amplification, which can make the speaker sound louder.
3. Game control joystick (Figure 7) Physical display

of the circuit compatible with two joysticks : Figure 8 Model: RKJXV1220001 Lichuang Mall Purchase: RKJXV1220001_(ALPSALPINE (Alps Alpine)) RKJXV1220001 Chinese information_price_PDF manual-Lichuang Electronic Mall (szlcsc.com) Figure 9 Model: No Taobao purchase: https://item.taobao.com/item.htm?s pm=a1z09.2.0.0.38cf2e8dqWGxGP&id=642782754225&_u=jak0o1p43a3V. System Framework Diagram10. System Diagram1 . Display Screen Selection: Purchased Lichuang Liangshan School-Screen Expansion Board 10-piece Set (For details, please click below) Lichuang Liangshan School-Screen Expansion Board_(Lichuang Development Board) Lichuang Liangshan School-Screen Expansion Board Chinese Information_Price_PDF Manual-Lichuang Electronic Mall (szlcsc.com) 2. Liangshan Development Board: Lichuang Liangshan GD32F470ZGT6 Development Board Five-piece Set (Click below for details) Lichuang Liangshan GD32F470ZGT6 Development Board_(Lichuang Development Board) Lichuang Liangshan GD32F470ZGT6 Development Board Chinese Data_Price_PDF Manual-Lichuang Electronic Mall (szlcsc.com) 3. After assembly (after connection): This is what it looks like after it is fully connected. This is what it looks like after it is turned on. This is the game interface 6. Program Key Description 1. EXMC drives TFT-LCD and SDRAM to share: During the initial debugging, the EXMC LCD driver found that the SDRAM storage was abnormal, the game failed to run, and the LCD displayed normally. If the LCD driver is blocked, the SDRAM will run the game normally, because the game file needs to be loaded into the SDRAM to run. The problem is that when SDRAM and TFT-LCD are shared, the program will run abnormally. Finally, the problem was solved and the timing delay of SDRAM was optimized. The code is as follows: ErrStatus exmc_synchronous_dynamic_ram_init(uint32_t sdram_device){ exmc_sdram_parameter_struct sdram_init_struct; exmc_sdram_timing_parameter_struct sdram_timing_init_struct; exmc_sdram_command_parameter_struct sdram_command_init_struct; uint32_t command_content = 0, bank_select; uint32_t timeout = SDRAM_TIMEOUT; /* enable EXMC clock*/ rcu_periph_clock_enable(RCU_EXMC); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_GPIOF); rcu_periph_clock_enable(RCU_GPIOG); rcu_periph_clock_enable(RCU_GPIOH);





/* common GPIO configuration */ /* SDNWE(PC0),SDNE0(PC2),SDCKE0(PC3) pin configuration */ gpio_af_set(GPIOC, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3);
/* D2(PD0),D3(PD1),D13(PD8),D14(PD9),D15(PD10),D0(PD14),D1(PD15) pin configuration */ gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15); tions_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1
| GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | ,D8(PE11),D9(PE12),D10(PE13),D11(PE14),D12(PE15) pin configuration */ gpio_af_set(GPIOE, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GP IO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
/* A0(PF0),A1(PF1),A2(PF2),A3(PF3),A4(PF4),A5(PF5),NRAS(PF11),A6(PF12),A7(PF13),A8(PF14 ),A9(PF15) pin configuration */ gpio_af_set(GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); (GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 |GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_ PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
/* A10(PG0),A11(PG1),A12(PG2),A14(PG4),A15(PG5),SDCLK(PG8),NCAS(PG15) pin configuration */ gpio_af_set(GPIOG, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); gpio_output_options_set( GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15);
/* specify which SDRAM to read and write */ if(EXMC_SDRAM_DEVICE0 == sdram_device){ bank_select = EXMC_SDRAM_DEVICE0_SELECT; }else{ bank_select = EXMC_SDRAM_DEVICE1_SELECT; }
/* EXMC SDRAM device initialization sequence --------------------------------*/ /* Step 1: configure SDRAM timing registers -- ----------------------------------*/ /* LMRD: 2 clock cycles */ sdram_timing_init_struct.load_mode_register_delay = 2; /* XSRD : min = 75ns */ sdram_timing_init_struct.exit_selfrefresh_delay = 9; /* RASD: min=44ns, max=120k (ns) */ sdram_timing_init_struct.row_address_select_delay = 5; /* ARFD: min=66ns */ sdram_timing_init_struct.auto_refresh_delay = 8; /* WRD: min=1 Clock cycles +7.5ns */ sdram_timing_init_struct.write_recovery_delay = 3; /* RPD: min=20ns */ sdram_timing_init_struct.row_precharge_delay = 3; /* RCD: min=20ns */ sdram_timing_init_struct.row_to_column_delay = 3;
/* step 2: configure SDRAM control registers ----------------------------------*/ sdram_init_struct.sdram_device = sdram_device; sdram_init_struct.column_address_width = EXMC_SDRAM_COW_ADDRESS_9; sdram_init_struct.row_address_width = EXMC_SDRAM_ROW_ADDRESS_13; sdram_init_struct.data_width = EXMC_SDRAM_DATABUS_WIDTH_16B; sdram_init_struct.internal_bank_number = EXMC_SDRAM_4_INTER_BANK; sdram_init_struct.cas_latency = EXMC_CAS_LATENCY_3_SDCLK; sdram_init_struct.write_protection = DISABLE; sdram_init_struct.sdclock_config = EXMC_SDCLK_PERIODS_2_HCLK; sdram_init_struct.brust_read_switch = ENABLE; sdram_init_struct.pipeline_read_delay = EXMC_PIPELINE_DELAY_2_HCLK; sdram_init_struct.timing = &sdram_timing_init_struct; /* EXMC SDRAM bank initialization */ exmc_sdram_init(&sd ram_init_struct);
/* step 3: configure CKE high command-------- ----------------------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_CLOCK_ENABLE; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_2_SDCLK ; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)){ timeout--; } if(0 == timeout) { return ERROR; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct);
/* step 4: insert 10ms delay------------------------- ---------------------*/ delay_1ms(10);
/* step 5: configure precharge all command------------- ---------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_PRECHARGE_ALL; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)){ timeout- -; } if(0 == timeout){ return ERROR; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct);
/* step 6: configure Auto-Refresh command------------- -----------------------*/ sdram_command_init_struct.command = EXMC_SDRAM_AUTO_REFRESH; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_8_SDCLK; sdram_command_init_struct.mode_register_content = 0; /* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get (sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)){ timeout--; } if(0 == timeout){ return ERROR; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct);
/* step 7: configure load mode register command--------------------------*/ /* program mode register */ command_content = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | SDRAM_MODEREG_CAS_LATENCY_3 | SDRAM_MODEREG_OPERATING_MODE_STANDARD | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
sdram_command_init_struct.command = EXMC_SDRAM _LOAD_MODE_REGISTER; sdram_command_init_struct.bank_select = bank_select; sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_2_SDCLK; sdram_command_init_struct.mode_register_content = command_content;
/* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)){ timeout--; } if(0 == timeout){ return ERROR; } /* send the command */ exmc_sdram_command_config(&sdram_command_init_struct);
/* step 8: set the auto-refresh rate counter----------------------------- -------*/ /* 64ms, 8192-cycle refresh, 64ms/8192=7.81us */ /* SDCLK_Freq = SYS_Freq/2 */ /* (7.81 us * SDCLK_Freq) - 20 */ exmc_sdram_refresh_count_set(761 );
/* wait until the SDRAM controller is ready */ timeout = SDRAM_TIMEOUT; while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0)){ timeout--; } if(0 == timeout){ return ERROR; } return SUCCESS ; }
After modifying the code, the program runs the game normally.
2. TFT-LCD display picture modification:
The original code is as follows:
void LCD_ShowPicture1(uint16_t x,uint16_t y,uint16_t length,uint16_t width,const uint8_t *pic)
{
uint16_t i,j;
uint32_t k=0;
LCD_Address_Set(x, y,x+length-1,y+width-1);
LCD_DC_Set();//write data
LCD_CS_Clr();
for(i=0;i
{
for(j=0;j
{
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI4, *pic++);
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI4, *pic++);
}
}
while(RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE));
while(SET == spi_i2s_flag_get(SPI4, SPI_STAT_TRANS));
LCD_CS_Set();
while(spi_i2s_flag_get(SPI4, SPI_FLAG_RBNE)) i =spi_i2s_data_receive(SPI4);
}
The source code shows that the picture is written to the display screen directly using SPI, and there is no direct method to call the LCD driver. If you are not careful, the program will hang here when it is started.
The 8080 display driver is modified to:
void LCD_ShowPicture1( uint16_t x,uint16_t y,uint16_t length,uint16_t width,const uint8_t *pic)
{
uint16_t i,j;
uint32_t k=0;
uint16_t pdat;
LCD_Address_Set(x,y,x+length-1,y+width-1);
for(i=0;i
{
for(j=0;j
{
pdat = pic[k*2];
pdat = pdat<<8|pic[k*2+1];
LCD_WR_DATAs(pdat);
k++;
}
}
}
Font update: (required font files: UNIGBK.BIN, GBK12.BIN, GBK16.BIN, GBK24.BIN) I found the font files in Zhengdian Atom and used them, and found the following display problems:

Found the problem: text.c ---> Show_Font The function is as follows:
//************************************************************************
void Show_Font(u16 x,u16 y,u8 *font,uint16_t fc,uint16_t bc,u8 size,u8 mode)
{
u8 temp,t,t1;
u16 y0=y;
u8 dzk[72];
u8 csize=(size/8+((size%8)?1:0))*(size);//Get the number of bytes occupied by the dot matrix set corresponding to a character in the fontif
(size!=12&&size!=16&&size!=24)return;//Unsupported size
Get_HzMat(font,dzk,size);//Get the dot matrix data of the corresponding sizefor
(t=0;t
{
temp=dzk[t]; //Get the dot matrix datafor
(t1=0;t1<8;t1++)
{
if(temp&0x80) LCD_DrawPoint(x,y,fc);
else if(mode==0) LCD_DrawPoint(x,y,bc);
temp<<=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
//***********************************************************************
Just change the X and Y in the function, the changes are as follows:
//**********************************************************
void Show_Font(u16 x,u16 y,u8 *font,uint16_t fc,uint16_t bc,u8 size,u8 mode)
{
u8 temp,t,t1;
// u16 y0=y;
u16 x0=x;
u8 dzk[72];
u8 csize=(size/8+((size%8)?1:0))*(size);//Get the number of bytes occupied by a character in the font
if(size!=12&&size!=16&&size!=24)return;//Unsupported size
Get_HzMat(font,dzk,size);//Get the dot matrix data of corresponding sizefor
(t=0;t
{
temp=dzk[t];//Get the dot matrix datafor
(t1=0;t1<8;t1++)
{
if(temp&0x80) LCD_DrawPoint(x,y,fc);
else if(mode==0) LCD_DrawPoint(x,y,bc);
temp<<=1;
x++;
if((x-x0)==size)
{
x=x0;
y++;
break;
} }
}
}
//
******************************************************
Secondly, the display driver of the game: the resolution of 240*240 is changed to 480*480, without increasing the video memory:
nes_ppu.c ----> void scanline_draw(int LineNo) function
//***************************************************************************************
extern uint8_t nes_xoff; //Display the offset in the x-axis direction (actual display width = 256-2*nes_xoff)
void scanline_draw(int LineNo)
{
uint16 i;
uint16_t sx,ex;
do_scanline_and_draw(ppu->dummy_buffer);
#if 0
sx=nes_xoff+8;
ex=256+8-nes_xoff;
//printf("LineNo:%d sx:%d ex:%d
",LineNo,sx,ex) ;
LCD_Address_Set(LineNo,sx,LineNo,ex);//Set the display range across the screen
for(i=sx;i
//LCD_DrawPoint(LineNo,i,NES_Palette[ppu->dummy_buffer[i]] );
//printf("i:%d color:%d
",i,NES_Palette[ppu->dummy_buffer[i]]);
LCD_WR_DATA(NES_Palette[ppu->dummy_buffer[i]]);
}
#else
sx = 17;
ex = 240+17;
LCD_Address_Set(0,(LineNo*2)+40,480,(LineNo*2)+40);//Set the display range to display horizontally
for(i=sx;i
LCD_WR_DATAs(NES_Palette[ppu->dummy_buffer [i]]);
LCD_WR_DATAs(NES_Palette[ppu->dummy_buffer[i]]);
}
LCD_Address_Set(0,(LineNo*2+1)+40,480,(LineNo*2+1)+40);//Set display Range horizontal screen display
//LCD_DC_Set(); //Write data
// LCD_CS_Clr();
for(i=sx;i
LCD_WR_DATAs(NES_Palette[ppu->dummy_buffer[i]]);
LCD_WR_DATAs(NES_Palette[ppu->dummy_buffer[i]]);
}
}
The red part in the function: The general principle is to write the x coordinate data twice, and write two lines for one line of the Y coordinate, which is equivalent to magnifying a point by 1 times, 240* The 240 memory display is enlarged to 480*480 to save RAM video memory.
The game display part is ready.
3. Talk about Bmp file display:
The bmp function code is copied from the source code of the textbook,
important files: file_opera.c, bmp_opera.c
The modifications blocked most of the LCD driver codes in these two programs, leaving only the bmp file processing and parsing content.
FATs reads BMP files, the main format structure of BMP file information (bmp_opera.h):
//BMP file main information structure
typedef struct
{
//1. File information header
uint16 tbfType; //The first 2 bytes, file type , must be 0x4D42
uint32 tbfSize; //File size, in bytes
uint32_tbfOffset; //Data start offset address, in bytes
//2. Bitmap information header
uint32 tbiSize; //Number of bytes in the information header, Usually it is 40
uint32 tbiWidth; //BMP image width, pixel
uint32 tbiHeight; //BMP image height, pixel
uint16 tbiBitCount; //Number of bits per pixel
} BMPFileInfoDef;
bmp file is stored in SD card, file information data is arranged in order Arrange according to this structure diagram:
//Call the following function to read BMP file information, The file information is saved to the variable pointed to by the pointer BmpFileInfo
uint8_t ReadBmpFileInfo(uint8_t *Filename, BMPFileInfoDef *BmpFileInfo)
{
FIL file;
FRESULT res=f_open(&file, (char *)Filename, FA_READ); //fats read bmp file information
if( res != FR_OK)
{
f_close(&file);
return 0; //Return 0 if failed
}
ReadBmpHeader(&file, BmpFileInfo); //Read file header information
f_close(&file); //Close and clear file handle after success
return 1; //Successful return 1
}
Then, you can use void ShowBmpFileInfo(const BMPFileInfoDef *BmpFileInfo) to print out the information of the bmp file through printf.
Then, the bmp file image data is displayed on the LCD. The image is 24-bit RGB888, 3 characters per pixel. Section, sorting order BGR, BMP pictures are stored line by line from bottom to top, The image is flipped upside down on the computer, so
it needs to be read directly in sequence and converted into pixel data represented by RGB565 before it can be drawn on the LCD.
The function of reading and displaying at the same time is:
void DrawBmp(const uint16_t PosX, const uint16_t PosY,
FIL *file, BMPFileInfoDef *fileInfo)
Draw the entire BMP image on the LCD, and the return value of 0 indicates that the drawing is successful uint8_t DrawBmpFile(const uint16_t PosX, const uint16_t PosY, uint8_t *Filename)
The above is the Bmp image directly displayed on the LCD screen. Later, I changed it to directly read the data and save it in SDRAM
uint32_t BmpRead(uint16_t *buf, FIL *file, BMPFileInfoDef *fileInfo)
//Read the BMP image and write the data to the buffer. The return value is 0, indicating that the drawing is successful uint8_t ReadBmpFile(uint16_t *buf, uint8_t *Filename,BMPFileInfoDef *fileInfo)
Later, call the program and use it (in NES_GUI.c):
First allocate image memory:

Then read the bmp data into RAM:

These functions read the image display content into RAM, and then display

LCD_DrawBmp Because the LCD screen is designed to display from top to bottom, and the content of the bmp file is from bottom to top, this function is modified.