打印
[AT32F423]

【AT-START-F423测评】+NES模拟器

[复制链接]
1165|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
夜声|  楼主 | 2023-11-21 23:44 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 夜声 于 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]);
        }
}
显示效果如下:



使用特权

评论回复
沙发
qintian0303| | 2023-11-24 17:20 | 只看该作者
这个挺有趣,刷屏效果也不错,没考虑用FSMC刷屏吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

26

主题

85

帖子

2

粉丝