Close
0%
0%

Sega Genesis Native Hardware Chiptune Synthesizer

Use the actual Sega Genesis sound chips to play chiptune files!

Similar projects worth following
Listen to it playing here: https://hackaday.io/project/13361-sega-genesis-native-hardware-chiptune-synthesizer/log/53356-better-sounding-example

This board is a prototype for an open-source hardware VGM player.

The VGM file format stores instructions for sound chips, and VGM files for many popular games can be found online. An ATSAMD21G18A microcontroller will read these VGM files from an SD card and translate them into code to send to the onboard YM2612 and SN76489 chips. These chips were used by the original Sega Genesis hardware to make the sound effects and music in Genesis games.

An OLED screen will show a minimal user interface, for example the VGM files present on the SD card, Playlist status, and parameter settings.

I have included a MIDI Input subcircuit to allow future MIDI support.

Currently, the hardware for this project is complete and validated. I have created the PCB using OSHPark and purchased all the components from Mouser.

The software is currently very close to completion, and I am working out small timing issues and validating that most VGM files work. I will need some help validating more VGM files, so if you'd like to build a copy and help me experiment, feel free to send me a message!

vgmPlayerZeroZ.ino

ATSAMD21G18 Based Newer Version

ino - 22.50 kB - 02/11/2017 at 05:55

Download

SegaGenesisForeverStandaloneFinal.sch

ATSAMD21G18 Based Newer Version

sch - 355.69 kB - 02/11/2017 at 05:54

Download

SegaGenesisForeverStandaloneFinal.brd

ATSAMD21G18 Based Newer Version

brd - 270.77 kB - 02/11/2017 at 05:54

Download

vgmTest.ino

AtMega1284p Based Older Version

ino - 9.34 kB - 09/02/2016 at 00:55

Download

sn76489.h

AtMega1284p Based Older Version

h - 860.00 bytes - 08/28/2016 at 20:19

Download

View all 13 files

  • Better Sounding Example

    jareklupinski02/15/2017 at 06:54 0 comments

    Piped the output into my sound card's Line-In, take a listen!

  • Final Prototype Dump

    jareklupinski02/11/2017 at 05:35 0 comments

    I swear I'll organize these soon.

    #include <SPI.h>
    #include <SdFat.h>
    #include <SPIFlash.h>
    #include <Wire.h>
    #include <Fatlib/FatFile.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    #define SPI_SPEED SD_SCK_MHZ(50)
    //#define DEBUG
    //EEPROM COMMANDS
    #define MANID        0x90
    #define PAGEPROG     0x02
    #define READDATA     0x03
    #define READSTAT1    0x05
    #define WRITEENABLE  0x06
    #define GLOBALUNLOCK 0x98
    #define SUSPEND      0x75
    #define RESUME       0x7A
    #define JEDECID      0x9f
    #define RELEASE      0xAB
    #define BLOCK64ERASE 0xD8
    //PORTA
    #define CARD_CS ( 1 << 11 ) // 0
    #define MISO    ( 1 << 12 ) // 0
    #define SN_WE   ( 1 << 13 ) // 1
    #define YM_IC   ( 1 << 14 ) // 1
    #define YM_A0   ( 1 << 15 ) // 0
    #define YM_A1   ( 1 << 16 ) // 0
    #define YM_RD   ( 1 << 17 ) // 1
    #define YM_WR   ( 1 << 18 ) // 1
    #define SN_CS   ( 1 << 19 ) // 1
    #define YM_CS   ( 1 << 20 ) // 1
    #define MEM_CS  ( 1 << 21 ) // 1
    #define SDA     ( 1 << 22 ) // 1
    #define SCL     ( 1 << 23 ) // 1
    #define SN_RDY  ( 1 << 27 ) // 1
    #define YM_IRQ  ( 1 << 28 ) // 1
    //PORTB
    #define BACKBUTTON        ( 1 << 2 ) // 1
    #define BACKARDUINO       19
    #define PLAYBUTTON        ( 1 << 3 ) // 1
    #define PLAYARDUINO       25
    #define HOLD              ( 1 << 8 ) // 0
    #define NEXTBUTTON        ( 1 << 9 ) // 1
    #define NEXTARDUINO       16
    #define MOSI              ( 1 << 10 ) // 0
    #define SCK               ( 1 << 11 ) // 0
    #define MEM_WP            ( 1 << 22 ) // 1
    #define MIDI_IN           ( 1 << 23 ) // 1
    //DEBUG
    #ifdef DEBUG
    #define DEBUG_PRINT(...)   Serial.print(__VA_ARGS__)
    #define DEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
    #else
    #define DEBUG_PRINT(...)
    #define DEBUG_PRINTLN(...)
    #endif
    SdFat SD;
    FatFile vgmDir;
    FatFile vgmFile;
    StdioStream vgmStream;
    Adafruit_SSD1306 display;
    volatile uint32_t pause;
    volatile uint8_t backButtonFlag;
    volatile uint8_t playButtonFlag;
    volatile uint8_t nextButtonFlag;
    uint8_t nextSongFlag = 0;
    uint32_t numFiles;
    uint32_t vgmStreamPosition = 0;
    uint32_t pcmPosition = 0;
    uint32_t pcmPositionOffset = 0;
    uint32_t ident = 0;
    uint32_t eof = 0;
    uint32_t vers = 0;
    uint32_t snclk = 0;
    uint32_t ymclk = 0;
    uint32_t gd3Offset = 0;
    uint32_t totalSamples = 0;
    uint32_t loopOffset = 0;
    uint32_t loopSamples = 0;
    uint32_t rateHz = 0;
    uint32_t snFeedback = 0;
    uint32_t snShiftReg = 0;
    uint32_t snFlags = 0;
    uint32_t ym2612Clock = 0;
    uint32_t ym2151Clock = 0;
    uint32_t vgmDataOffset = 0;
    void TC3_Handler(void) {
      TcCount16* TC = (TcCount16*) TC3;
      if ( TC->INTFLAG.bit.MC0 == 1 ) {
        if ( pause ) pause--;
        TC->INTFLAG.bit.MC0 = 1;
      }
    }
    void backButtonTrigger() {
      backButtonFlag = 1;
    }
    void playButtonTrigger() {
      playButtonFlag = 1;
    }
    void nextButtonTrigger() {
      nextButtonFlag = 1;
    }
    void writeEnable() {
      PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
      SPI.transfer( WRITEENABLE );
      PORT->Group[PORTA].OUTSET.reg = MEM_CS;
    }
    void waitForFlashReady() {
      PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
      SPI.transfer( READSTAT1 );
      byte s = SPI.transfer( 0x00 );
      while ( s & 0x01 ) {
        s = SPI.transfer( 0x00 );
        DEBUG_PRINT( "." );
      }
      PORT->Group[PORTA].OUTSET.reg = MEM_CS;
    }
    void dumpPcmToFlash( uint32_t bytes ) {
      uint8_t buf[ 280 ];
      uint32_t i = 0;
      uint32_t y = 0;
      while ( i < bytes ) {
        writeEnable();
        waitForFlashReady();
        DEBUG_PRINTLN( "Erasing 64K Block Starting At " + String( i, HEX ));
        PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
        SPI.transfer( BLOCK64ERASE );
        SPI.transfer( i >> 16 );
        SPI.transfer( i >> 8 );
        SPI.transfer( 0 );
        PORT->Group[PORTA].OUTSET.reg = MEM_CS;
        waitForFlashReady();
        for ( int page = 0; page < 256; page++ ) {
          for ( int j = 0; j < 256; j++ ) {
            if ( y < bytes ) buf[ j ] = vgmStream.getc();
            else buf[ j ] = 0xFF;
            y++;
          }
          writeEnable();
          waitForFlashReady();
          PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
          SPI.transfer( PAGEPROG );
          SPI.transfer( i >> 16 );
          SPI.transfer( i >> 8 );
          SPI.transfer( i );
          for ( int k = 0; k < 256; k++ ) {
            SPI.transfer( buf[ k ] );
            i++;
          }
          PORT->Group[PORTA].OUTSET.reg = MEM_CS;
          waitForFlashReady();
          DEBUG_PRINTLN( "Wrote to flash page " + String( i, HEX ));
        }
      }
      DEBUG_PRINTLN( "Verifying:" );
      //for ( uint32_t k = 0; k < bytes; k++ ) { DEBUG_PRINT( readFromFlash( k ), HEX ); } PORT->Group[PORTB].OUTSET.reg...
    Read more »

  • Very Accurate Reproduction, Ironing Out Glitches now

    jareklupinski10/02/2016 at 18:56 0 comments

    #include <SPI.h>
    #include <SdFat.h>
    #include <SPIFlash.h>
    #include "wiring_private.h" // pinPeripheral() function
    #define ENABLE_EXTENDED_TRANSFER_CLASS 1
    #define CPU_HZ 48000000
    #define TIMER_PRESCALER_DIV 16
    
    #define MANID    0x90
    #define PAGEPROG     0x02
    #define READDATA     0x03
    #define FASTREAD   0x0B
    #define WRITEDISABLE 0x04
    #define READSTAT1    0x05
    #define READSTAT2  0x35
    #define WRITEENABLE  0x06
    #define SECTORERASE  0x20
    #define BLOCK32ERASE 0x52
    #define CHIPERASE    0x60
    #define SUSPEND      0x75
    #define ID           0x90
    #define RESUME       0x7A
    #define JEDECID      0x9f
    #define RELEASE      0xAB
    #define POWERDOWN    0xB9
    #define BLOCK64ERASE 0xD8
    
    //PORTA
    #define CARD_CS ( 1 << 11 ) // 0
    #define MISO ( 1 << 12 ) // 0
    #define SN_WE ( 1 << 13 ) // 1
    #define YM_IC ( 1 << 14 ) // 1
    #define YM_A0 ( 1 << 15 ) // 0
    #define YM_A1 ( 1 << 16 ) // 0
    #define YM_RD ( 1 << 17 ) // 1
    #define YM_WR ( 1 << 18 ) // 1
    #define SN_CS ( 1 << 19 ) // 1
    #define YM_CS ( 1 << 20 ) // 1
    #define MEM_CS ( 1 << 21 ) // 1
    #define SDA ( 1 << 22 ) // 1
    #define SCL ( 1 << 23 ) // 1
    #define SN_RDY ( 1 << 27 ) // 1
    #define YM_IRQ ( 1 << 28 ) // 1
    
    //PORTB
    #define BACKBUTTON ( 1 << 2 ) // 1
    #define PLAYBUTTON ( 1 << 3 ) // 1
    #define OSC_IN ( 1 << 8 ) // 0
    #define NEXTBUTTON ( 1 << 9 ) // 1
    #define MOSI ( 1 << 10 ) // 0
    #define SCK ( 1 << 11 ) // 0
    #define MIDI_IN ( 1 << 23 ) // 1
    
    //#define DEBUG
    #ifdef DEBUG
    #define DEBUG_PRINT(...)    Serial.print(__VA_ARGS__)
    #define DEBUG_PRINTLN(...)  Serial.println(__VA_ARGS__)
    #else
    #define DEBUG_PRINT(...)
    #define DEBUG_PRINTLN(...)
    #endif
    #define PCMSIZE 20000
    byte pcmData[ PCMSIZE ];
    
    int numFiles = 0;
    SdFat SD;
    File vgmFile;
    StdioStream vgmStream;
    SPIClass mySPI ( &sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3 );
    volatile byte done = 0;
    uint32_t fileSize;
    byte readPcmFromFlash = 0;
    volatile uint32_t commandPosition;
    volatile uint32_t pcmPosition;
    
    void waitForFlashReady() {
      PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
      SPI.transfer( READSTAT1 );
      byte s = SPI.transfer( 0x00 );
      while ( s & 0x01 ) {
        s = SPI.transfer( 0x00 );
        DEBUG_PRINT( "." );
      }
      PORT->Group[PORTA].OUTSET.reg = MEM_CS;
    }
    
    void getNumFiles() {
      SD.vwd()->rewind();
      Serial.println( "Looking for files");
      while (vgmFile.openNext(SD.vwd(), O_READ)) {
        numFiles++;
        vgmFile.close();
      }
    }
    
    void writeEnable() {
      PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
      SPI.transfer( WRITEENABLE );
      PORT->Group[PORTA].OUTSET.reg = MEM_CS;
      PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
      SPI.transfer( READSTAT1 );
      byte s = SPI.transfer( 0x00 );
      while ( s & 0x01 ) {
        s = SPI.transfer( 0x00 );
        DEBUG_PRINT( "." );
      }
      PORT->Group[PORTA].OUTSET.reg = MEM_CS;
    }
    
    void dumpToPcm() {
      for ( uint32_t addr = 0; addr < fileSize; addr++ ) {
        byte data = readFromFlash( addr );
        uint32_t s = 0;
        if ( data == 0x67 ) {
          addr++;
          data = readFromFlash( addr ); //0x66
          if ( data == 0x66 ) {
            addr++;
            data = readFromFlash( addr++ ); //Data Block Type
            for ( uint32_t j = 0; j < 4; j++ ) s += ( uint32_t( readFromFlash( addr++ ) ) << ( 8 * j ));
            if ( s > PCMSIZE ) {
              Serial.println( "PCM Data too large!" );
              readPcmFromFlash = 1;
              return;
            }
            readPcmFromFlash = 0;
            for ( uint32_t j = 0; j < s; j++ ) {
              pcmData[ j ] = readFromFlash( addr++ );
              DEBUG_PRINTLN( "Writing PCM Data to " + String( j, HEX ));
            }
          }
        }
      }
    }
    
    void dumpToFlash() {
      uint8_t buf[ 256 ];
      uint32_t addr = 0;
      while ( !vgmStream.feof() ) {
        writeEnable();
        DEBUG_PRINTLN( "Erasing 64K Block Starting At " + String( addr, HEX ));
        PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
        SPI.transfer( BLOCK64ERASE );
        SPI.transfer( addr >> 16 );
        SPI.transfer( addr >> 8 );
        SPI.transfer( 0 );
        PORT->Group[PORTA].OUTSET.reg = MEM_CS;
        waitForFlashReady();
        for ( int page = 0; page < 256; page++ ) {
          for ( int i = 0; i < 256; i++ ) {
            byte data = 0;
            if ( !vgmStream.feof() ) data = vgmStream.getc();
            buf[ i ] = data;
          }
          writeEnable();
          PORT->Group[PORTA].OUTCLR.reg = MEM_CS;
          SPI.transfer( 0x02 ); // Pageprog
          SPI.transfer( addr >> 16 );
          SPI.transfer( addr >> 8 );
          SPI.transfer( addr );
          for (...
    Read more »

  • A little bit faster now!

    jareklupinski09/06/2016 at 02:26 0 comments

  • Need To Upgrade To A Speedier Micro

    jareklupinski09/03/2016 at 03:51 0 comments

View all 5 project logs

Enjoy this project?

Share

Discussions

ian wrote 09/03/2017 at 21:45 point

hey there, i recently found your project on youtube.  i'm SUPER interested in building one of the current iterations, and SUPER interested in testing anything that would have midi control.  i've got a large amount of building experience and have no problem using through hole or SMD.  i build modular hardware regularly for my job.  please feel free to contact me directly through this site or on youtube where i commented with the same name.  really want to help test stuff out or get involved!  - ian

  Are you sure? yes | no

Greg Kennedy wrote 07/21/2017 at 16:15 point

Really great project.  Put Yuzo Koshiro on repeat : )

  Are you sure? yes | no

Elijah Lucian VO wrote 04/11/2017 at 15:43 point

Any idea when there will be midi input and/or control scematics available? I would love to have a little synth come from this!! Gotta find something to do with my YM2612. 

Thank you so much for doing this@!

  Are you sure? yes | no

Brian King wrote 04/04/2017 at 17:01 point

Hey, love this project and would love to try it myself. Dealing with circuit boards is not my specialty but I'm willing to try it! Do you still recommend ordering PCBs from OSHPark? I see I'll get 3 of them at a minimum, which isn't too bad as I'm likely to probably mess one up as my soldering skills aren't the best :/

Is there a program I should use to read the schematics knowing what components I'd need to order from mouser? Also I'm not too familiar with micro controller development. Would I need to JTAG a bootloader or will it be able to run the ino file as is? Last time I had to program to a chip was using straight assembly and polling registers >.>

Sorry if these are overly simplistic questions. I only have a basic knowledge of Electrical Engineering and embedded development.

Thanks for any help!

  Are you sure? yes | no

Nathan Stanley wrote 09/06/2016 at 03:42 point

Nice project! But where do you source your YM2612 chips from? Also, I'm not a programmer, but I gather from my friend that the VGM format is just a sequence of register writes separated by finely tuned number of NOPs.

  Are you sure? yes | no

jareklupinski wrote 09/06/2016 at 05:35 point

Ebay usually has a few sellers here and there with availability. Yup, VGM format is pretty much that. The challenge now is keeping up with the expected sample rate; keeping a low-power microcontroller fed with data at that rate is tricky :)

  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