本帖最后由 夜声 于 2023-11-21 23:46 编辑
一、前言
很幸运的参与本次AT32F423开发板的测评,计划首先按自己的风格新建个工程,实现基本外设SPI,UART,LED等外设,然后基于SPI实现2.8寸的LCDTFT,LCD正常显示后实现NES模拟器。
二、NES简介
NES是“Nintendo Entertainment System”的缩写,它是任天堂公司在1980年代推出的一款家用游戏机。在日本,它被称为Famicom(Family Computer)。NES于1983年在日本首次推出,随后于1985年在北美地区发行,成为当时家庭游戏市场的主导力量。
这款游戏机因其丰富的游戏库、经典的游戏系列以及在家庭游戏市场的影响力而备受推崇。它引入了许多经典游戏,如《马里奥兄弟》(Super Mario Bros.)、《塞尔达传说》(The Legend of Zelda)、《魂斗罗》(Contra)和《魔界村》(Castlevania)等,这些游戏至今仍然被认为是游戏史上的经典之作。
本次就基于AT32F423VCT6实现这部分代码。大概说一下这个芯片的资源。
AT32F423系列超值型ARM®Cortex®-M4F微控制器,高达150MHz的CPU运算速度与内建的单精度浮点运算单元(FPU)、数字信号处理器(DSP),多达256KB闪存存储器(Flash)及48KB随机存取存储器(SRAM),而系统存储器(20KB)除可作启动加载程序(Bootloader)外,也可一次性配置成一般用户程序和数据区,达到256+20KB的最大空间使用。片上丰富的外设资源,用以加强连接性,集成XMC接口(拓展PSRAM,NOR存储器,或8080/6800模式并行LCD)、1个OTG控制器(设备模式支持无晶振Xtal-less)、2组CAN总线、8个UART、3个SPI/I²S(可组合全双工模式)、3个I²C、1个16位高级定时器、8个16位通用定时器、1个32位通用定时器、2个16位基本定时器。1个采样率高达5.33Msps的12位24通道高速ADC与2个12位DAC,为支持混合信号控制提供更高的性价比。
三、移植准备
3.1新建工程
首先下载相关资料,函数库,以及一些示例,板级支持包
新建自己的工程目录,将函数库,M4头文件,板级文件拷贝过来
打开keil,新建工程,添加源码以及头文件路径
3.2 SPI驱动
这里的SPI只用于LCD的驱动,配置为主机模式,实现8位数据发送即可。
void spi1_init(void)
{
gpio_init_typegpio_initstructure;
spi_init_typespi_init_struct;
crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
gpio_default_para_init(&gpio_initstructure);
/* spi1 sck pin */
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pull = GPIO_PULL_UP;
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;
gpio_initstructure.gpio_pins = GPIO_PINS_5;
gpio_init(GPIOA, &gpio_initstructure);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE5, GPIO_MUX_5);
/* spi1 mosi pin */
gpio_initstructure.gpio_pull = GPIO_PULL_UP;
gpio_initstructure.gpio_pins = GPIO_PINS_7;
gpio_init(GPIOA, &gpio_initstructure);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE7, GPIO_MUX_5);
/* RST= PA2,DC=PA3 */
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_mode = GPIO_MODE_OUTPUT;
gpio_initstructure.gpio_pins = GPIO_PINS_2 | GPIO_PINS_3;
gpio_initstructure.gpio_pull = GPIO_PULL_UP;
gpio_init(GPIOA, &gpio_initstructure);
spi_default_para_init(&spi_init_struct);
spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_4;
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH;
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_1EDGE;
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
spi_init(SPI1, &spi_init_struct);
spi_enable(SPI1, TRUE);
}
void spi_writebyte(uint8_t TxData)
{
while(spi_i2s_flag_get(SPI1, SPI_I2S_TDBE_FLAG) == RESET);
spi_i2s_data_transmit(SPI1, TxData);
}
3.3 LCD显示实现
上面已经实现了SPI 的驱动,以及8位发送,移植LCD的驱动代码,替换显示发送函数,以及IO定义。
写数据函数:
<blockquote>void LCD_WR_DATA(u8 data)
写命令函数:
void LCD_WR_REG(u8 data)
{
LCD_CS_CLR;
LCD_RS_CLR;
spi_writebyte(data);
LCD_CS_SET;
}
写16位数据函数:
void Lcd_WriteData_16Bit(u16 Data)
{
LCD_CS_CLR;
LCD_RS_SET;
spi_writebyte(Data>>8);
spi_writebyte(Data);
LCD_CS_SET;
}
LCD所用IO定义:
#define LCD_CS_PIN GPIO_PINS_1
#define LCD_RST_PIN GPIO_PINS_2
#define LCD_DC_PIN GPIO_PINS_3
#define gpio_x GPIOA
//GPIOÖÃ루À¸ß£©
#define LCD_CS_SET gpio_x->scr = LCD_CS_PIN //Ƭѡ¶Ë¿Ú
#define LCD_RS_SET gpio_x->scr = LCD_DC_PIN //Êý¾Ý/ÃüÁî
#define LCD_RST_SET gpio_x->scr = LCD_RST_PIN //¸´Î»
//GPIO¸´Î»£¨ÀµÍ£©
#define LCD_CS_CLR gpio_x->clr = LCD_CS_PIN //Ƭѡ¶Ë¿Ú
#define LCD_RS_CLR gpio_x->clr = LCD_DC_PIN //Êý¾Ý/ÃüÁî
#define LCD_RST_CLR gpio_x->clr = LCD_RST_PIN //¸´Î»
四、移植过程
添加NES源码:
添加源码路径:
nes_main主函数:
void nes_main(void)
{
NesHeader *neshreader = (NesHeader *) rom_file;
init6502mem( 0, /*exp_rom*/
0, /*sram ÓÉ¿¨ÀàÐ;ö¶¨, Ôݲ»Ö§³Ö*/
(&rom_file[0x10]), /*prg_rombank, ´æ´¢Æ÷´óС ÓÉ¿¨ÀàÐ;ö¶¨*/
neshreader->romnum
); //³õʼ»¯6502´æ´¢Æ÷¾µÏñ
reset6502();
PPU_Init((&rom_file[0x10] + (neshreader->romnum * 0x4000)),(neshreader->romfeature & 0x01)); /*PPU_³õʼ»¯*/
//NES_JoyPadInit();
NesFrameCycle();
}
PPU显示每一行:
void NES_RenderLine(int y_axes)
{
// static u8 flag=0;
int i, render_spr_num, spr_size, dy_axes;
PPU_Reg.R2 &= ~R2_LOST_SPR; //ÉèÖÃPPU״̬¼Ä´æÆ÷R2 SPR LOSTµÄ±ê־λ
if(PPU_Reg.R1 & (R1_BG_VISIBLE | R1_SPR_VISIBLE)) //ÈôΪ¼Ù£¬¹Ø±ÕÏÔʾ£¬Ìî0ºÚ
{
/*Çå¿ÕÏÔʾ»º´æ£¬ÔÚ´ËÉèÖõױ³¾°É«£¨´ýÈ·¶¨£©*/
for(i=0; i<(8 + 256 + 8) ; i++) //ÏÔʾÇø 7 ~ 263 0~7 263~270 Ϊ·ÀÖ¹Òç³öÇø
{
Buffer_scanline[i] = NES_Color_Palette[PPU_Mem.image_palette[0]];
}
spr_size = PPU_Reg.R0 & R0_SPR_SIZE ? 0x0F : 0x07; //spr_size 8£º0~7£¬16: 0~15
/* ɨÃè±³¾°sprite²¢×ª»»³ÉÏÔʾÊý¾ÝдÈëµ½»º´æ,ÿһÐÐ×î¶àÖ»ÄÜÏÔʾ8¸öSprite*/
if(PPU_Reg.R1 & R1_SPR_VISIBLE) //Èô¿ªÆôspriteÏÔʾ
{
render_spr_num=0; //ÇåÁãÏÔʾ¼ÆÊýÆ÷
for(i=63; i>=0; i--) //ÈôÖصþsprites 0 ¾ßÓÐÏÔʾ×î¸ßÓÅÏȼ¶£¬ÆäÓàÓÅÏȼ¶Ë³Ðò´ÎÖ®£¬ËùÒÔ×îÏÈÏÔʾ×îµÍÓÅÏȼ¶
{
/*ÅжÏÏÔʾ²ã£¨·Ç£© ±³¾°*/
if(!(sprite[i].attr & SPR_BG_PRIO))
{
continue; //(0=Sprite In front of BG, 1=Sprite Behind BG)
}
/*ÅжÏÏÔʾλÖÃ*/
dy_axes = y_axes - (uint8)(sprite[i].y + 1); //ÅжÏspriteÊÇ·ñÔÚµ±Ç°ÐÐÏÔʾ·¶Î§ÄÚ,sprite y (FF,00,01,...EE)(0~239)
if(dy_axes != (dy_axes & spr_size))
{
continue; //Èô²»ÔÚÔò·µ»Ø¼ÌÐøÑ»·²éÕÒÏÂÒ»¸ö
}
/*Èô´æÔÚspriteÔÚµ±Ç°ÏÔʾÐÐ,ÔòתÈëÏÂÃæÏÔʾ½×¶Î*/
render_spr_num++; //ÒÑÏÔʾµÄspriteµÄÊýÄ¿+1
if(render_spr_num > 8 ) //Ò»Ðг¬¹ý8¸öspreite£¬Ìø³öÑ»·
{
PPU_Reg.R2 |= R2_LOST_SPR; //ÉèÖÃPPU״̬¼Ä´æÆ÷R2µÄ±ê־λ
break;
}
if(PPU_Reg.R0 & R0_SPR_SIZE) //ÈôΪÕ棬spriteµÄ´óС8*16
{
NES_RenderSprite16(&sprite[i], dy_axes);
}
else //ÈôΪ¼Ù£¬spriteµÄ´óС8*8
{
NES_RenderSprite88(&sprite[i], dy_axes);
}
}
}
/* ɨÃè±³¾° background*/
if(PPU_Reg.R1 & R1_BG_VISIBLE)
{
NES_RenderBGLine(y_axes); //ɨÃè²¢ÉèÖÃSprite #0Åöײ±êÖ¾
}
/* ɨÃèÇ°¾°sprite²¢×ª»»³ÉÏÔʾÊý¾ÝдÈëµ½»º´æ,ÿһÐÐ×î¶àÖ»ÄÜÏÔʾ8¸öSprite*/
if(PPU_Reg.R1 & R1_SPR_VISIBLE) //Èô¿ªÆôspriteÏÔʾ
{
render_spr_num=0; //ÇåÁãÏÔʾ¼ÆÊýÆ÷
/* ÈôÖصþsprites 0 ¾ßÓÐÏÔʾ×î¸ßÓÅÏȼ¶£¬ÆäÓàÓÅÏȼ¶Ë³Ðò´ÎÖ®£¬ËùÒÔ×îÏÈÏÔʾ×îµÍÓÅÏȼ¶
* ±¸×¢£ºÈôÇ°¾°sprites ÓÅÏȼ¶µÍÓÚ±³¾°ÓÅÏȼ¶£¬ÖصþµÄÑÕÉ«£¬Ç°¾°ÓÅÏȼ¶µÍÓÚ±³¾°ÓÅÏȼ¶µÄ»°£¬Ç°¾°½«²»»áÏÔʾ(ÔÝδ´¦Àí)*/
for(i=63; i>=0; i--)
{
/*ÅжÏÏÔʾ²ã Ç°¾°*/
if(sprite[i].attr & SPR_BG_PRIO)
{
continue; //(0=Sprite In front of BG, 1=Sprite Behind BG)
}
/*ÅжÏÏÔʾλÖÃ*/
dy_axes = y_axes - ((int)sprite[i].y + 1); //ÅжÏspriteÊÇ·ñÔÚµ±Ç°ÐÐÏÔʾ·¶Î§ÄÚ,sprite y (FF,00,01,...EE)(0~239)
if(dy_axes != (dy_axes & spr_size))
{
continue; //Èô²»ÔÚÔò·µ»Ø¼ÌÐøÑ»·²éÕÒÏÂÒ»¸ö
}
/*Èô´æÔÚspriteÔÚµ±Ç°ÏÔʾÐÐ,ÔòתÈëÏÂÃæÏÔʾ½×¶Î*/
render_spr_num++; //ÒÑÏÔʾµÄspriteµÄÊýÄ¿+1
if(render_spr_num > 8 ) //Ò»Ðг¬¹ý8¸öspreite£¬Ìø³öÑ»·
{
PPU_Reg.R2 |= R2_LOST_SPR; //ÉèÖÃPPU״̬¼Ä´æÆ÷R2µÄ±ê־λ
break;
}
if(PPU_Reg.R0 & R0_SPR_SIZE) //ÈôΪÕ棬spriteµÄ´óС8*16
{
NES_RenderSprite16(&sprite[i], dy_axes);
}
else //ÈôΪ¼Ù£¬spriteµÄ´óС8*8
{
NES_RenderSprite88(&sprite[i], dy_axes);
}
}
}
}
else
{
for(i=0; i<(8 + 256 + 8); i++)
{
Buffer_scanline[i] = BLACK;//Çå¿ÕÏÔʾ»º´æ,ºÚÆÁ
}
}
/*Íê³ÉɨÃ裬½«ÐÐÏÔʾ»º´æдÈëLCD*/
NES_LCD_DisplayLine(y_axes, Buffer_scanline);//Æô¶¯LCDÏÔʾһÐУ¬²éѯ»òDMA´«ËÍ
}
将行缓存写入LCD:
void NES_LCD_DisplayLine(int y_axes, uint16 *Disaplyline_buffer)
{
u16 index;
LCD_SetWindows(0,200,240,y_axes);
LCD_WriteRAM_Prepare();
for(index = 16; index < 256; index++)
{
Lcd_WriteData_16Bit( Buffer_scanline[index]);
}
}
显示效果如下:
|