Close
0%
0%

NES Emulator (2) for the PIC32MZ-EF

A simple NES emulator for the PIC32MZ-EF microcontroller, complete with sound and controls.

Similar projects worth following
NES emulator for the PIC32MZ2048EFH064 using customized Chipkit platform.

General specifications:
1.) ILI9341 SPI as display.
2.) Sound is generated using PWM (24kHz sampling).
3.) NES classic controller (I2C).

This is a basic NES emulator for Chipkit platform with PIC32MZ-EF microcontroller.

A very limited amount of games are supported - please test this using Mapper 0-based games first.

The NES emulator core is written by the guys in OpenEDV forum: http://www.openedv.com/thread-266483-1-1.html and NES sound driver is by the author named "beaglescout007":https://os.mbed.com/users/beaglescout007/code/Nucleo_Ex06_EMU/

The (2) is there because this is the 2nd try in porting an NES emulator - the 1st try was unsucessful (InfoNES), so the name got stuck there.

How to put a game inside?

As usual, the game are not included. Get your own game in *.NES file format, and then convert it to a C file using the HxD editor. Then copy it to "ROM.c".

Required Hardware:

1.) ILI9341 SPI display.

2.) a breadboard

3.) NES Classic Controller (I2C)

4.) some jumper cables

5.) PIC32MZ-EF microcontroller with at least 64-pins (PIC32MZ2048EFH064).

6.) speaker and a small amplifier (PAM8403)

7.) 1st order low pass filter (24kHz cutoff - trial and error)

Issues:

1.) The game runs at a relatively lower frame-rate than the actual NES. That is for sure. It would be a different story if the PIC32MZ runs more than 200MHz though. And yes - it is slower because the screen is using SPI instead of a parallel one.

2.) The screen sometimes get garbled between times but it recovers very quickly. I believe it is due to the long delays in setting up the drawing window in the ILI9341.

3.) Not all games are supported. Try games that are limited in only one screen first (no scrolling). And in certain games, the tiles in the screen are garbled. I do not have enough knowledge to check what is faulty inside the emulator - this is only a proof of concept testing for the PIC32MZ microcontrollers. Other visual problems include:

  • No screen shakes (some games have that)
  • Some sprites are not showing properly
  • Pseudo-random colors at the left or right end of the screen

4.) I am trying to tackle the sound issue - a higher order low pass filter is mandatory if you want don't want to hear squealing from the speakers.

5.) The NES classic controller is being polled manually - if the connection isn't too good, the entire game (and the emulator) would stall and crash.

  • 1 × PIC32MZ2048EFH064 microcontroller (preferably a board with that one)
  • 1 × PAM8403 mini amplifier
  • 1 × ILI9341 SPI screen can be found in many eBay or Taobao sellers!
  • 1 × NES Classic Controller (I2C)
  • 1 × Simple RC low pass filter (24kHz cutoff)

View all 8 components

  • Inspecting the 6502's emulator area!

    NYH-workshop06/18/2018 at 05:40 0 comments

    As said before, some games refused to work with it. I found out why: there are some problems in the 6502 emulator. Using Kevin Horton's NES test , it is shown that some of the instructions were incorrectly emulated! Worst of all, the "Indirect Y" crashed in this test!

    So, using the hints and the descriptions from the test, I managed to have the adc6502 and sbc6502 to temporarily ignore the decimal flag (it is not implemented in NES' processor) and then restore the flag state after calling.

    Apart from that, the bvs6502 has the addressing mode changed back to the relative6502, according to the 6502's information.

    Some of the games that previously didn't work properly had worked after I have fixed the emulator, but the NES test is still reporting that the "Indirect Y" is not wrapping up properly in JMP instruction, and "Indirect X" has a sta6502 not storing data where it is supposed to.

    Well, at least it's better than nothing! I'm gonna try to get this test error-free in the future.

    The github has been updated according to the changes too.

  • Introduction and some porting instructions!

    NYH-workshop06/03/2018 at 06:21 0 comments

    As being said in the log title. I had been doing this in an on-and-off manner since 2015 - I started this out using a STM32F4 discovery board with some screen on it and 8MB SDRAM. The code was obtained from the OpenEDV forums - with my limited Mandarin, I had to constantly use the Google translate so I won't get lost in that ocean!  It worked... but there's no sound, and with all kinds of troubles I went through in life, I shelved that for like two years without working on this one.

    It may seem as a lame excuse, and I admit - NES emulation is not something I can learn in a mere few weeks . Despite being such an old (and mostly being copied by Chinese factories for cheap knockoffs) architecture, it is still being a very popular console for everyone. I used to have a "Micro Genius" when I was a kid and spent some weekends playing those games.

    Back to the main story - I have taken that code again and try to see if any of the PIC32MZs could fit on it. It did - and I have used the beaglescout007's emulator code to start with. It worked - but some games were not emulated properly. I will have to put this first try on the Github someday!

    Then, curiously, I found that same emulator I took in 2015 and see if it also fit into that microcontroller. There are some major challenges there - especially the serial TFT and the sound generation mechanism.

    Let me point out some important matters regarding to the porting:

    1.) nes_main.cpp

    • inline void draw_frame(void) : Pushing the pixels line-by-line doesn't work in this emulation. I don't know why, and I'm pretty sure it has a lot to do with the slower SPI interface, as the original states that it is using a parallel TFT. I had to reserve 256x240x2 bytes (the 2 at the end is because for 16-bit color) for the frame buffer and then let the PPU emulator draw this in the memory line-by-line. This function sets up the DMA and then start streaming the pixels right away once the V-sync is done.

    2.) APU.cpp 

    • The nearest and the best one is by beaglescout007's APU emulator code. Some of the author's sound implementation in the code give me some clues on how I could slot in the thing without causing a lot of disasters in the emulation. 
    • It looks like whatever things done in the APU is being triggered by the PPU's scanline, so if there are any sound generation being done, it must be triggered at the certain scanline number:

     nes_main.cpp, NesFrameCycle():

           //Added APU routines:
           switch (PPU_scanline) {
            
            case SCAN_APU_CLK1: // 240Hz
            case SCAN_APU_CLK3: // 240Hz
              pNesX_ApuClk_240Hz();
              break;
    
            case SCAN_APU_CLK2: // 240Hz 120Hz
              pNesX_ApuClk_240Hz();
              pNesX_ApuClk_120Hz();
              break;
    
            case SCAN_APU_CLK4: // 240Hz 120Hz 60Hz
              pNesX_ApuClk_240Hz();
              pNesX_ApuClk_120Hz();
              pNesX_ApuClk_60Hz();
              break;
    
           default: break;           
         }

          I believe that I could put the same code into other emulators - and this is the first one I have tried so          far.

    • void ApuInit() : You need to start and initialize the timer and it's interrupts for the sound generation. Set up the PWM frequency for 24kHz. And of course, since different microcontrollers have different interrupt vectors and function, you need to place the body of the code in the void __USER_ISR timer2isr(void) into there.

    3.) Joypad.cpp:

    • I've used an NES mini classic controller which uses I2C protocol instead of the classic one with the shift register. 
    • NES_JoyPadReset(void) : The function captures the entire button states first and dump it into the 8-bit data form for the 6502 processor to be read.
    • NES_GetJoyPadVlaue(unsigned char JOYPAD_INPUT) : This function then takes the first bit out (the first button being A) and shift to the right once, and it wraps back afterwards. For reference, you can read there: http://blog.tozt.net/2014/10/15/writing-an-nes-game-part-3/

    So that concludes the log for today. There are some other issues like the sounds...

    Read more »

View all 2 project logs

Enjoy this project?

Share

Discussions

philtimmes wrote 01/04/2020 at 16:29 point

You can shave some of the time / SPI bandwidth updating the ILI9341, by only sending the pixels that have changed... This requires only 1 bit for every pixel to keep track of. Also, because you wouldn't be playing any frame sensitive speedruns, you could draw only every 2nd or 3rd frame to the ILI9341, and free up all that time for CPU. So if you have 9600 bytes to spare, you could do:

uint8_t DirtyPages[240][320/8]={0}; // 1 bit per pixel IS IT DIRTY?

on write to frame buffer:

void SetDirty(uint8_t Horiz, uint16_t Vert)

{

uint8_t VertOffsetByte=(uint8_t)Vert/8;

uint8_t VertOffsetBit=1<<(Vert%8)

DirtyPages[Horiz,VertOffsetByte]|=VertOffsetBit;

}

Then you can iterate through the changed pixels (You now have a map of dirty bits)

and send ili9341_drawpixel for the changed pixels.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates