Playing VGMs on an STM8S

Monday, 14th December 2009

Following the STM8S tutorial in my previous post, I've tried to put the chip to some practical use. My initial experiments into producing a video signal proved unsuccessful; I managed a static image using hard-coded delay loops, but when trying to use interrupts to trigger the generation of scanlines the timing was all wrong and without an oscilloscope or a working simulator I couldn't find out what was wrong. I decided to turn my attention from picture to sound.

Photo of VGM player

VGM files store game music by logging the data written to the sound chips inside the console or computer directly along with the delay between writes. This results in reasonably small files that are capable of producing excellent sound quality, depending on the way the sound chips are emulated (or, in some cases, not emulated).

I've chosen to focus on the SN76489, a simple sound chip found in a variety of machines including the Sega Master System and BBC Micro. Three of its four sound channels are simple square wave tones, implemented as a 12-bit decrementing counter that flips the state of its output every time it underflows and is reset. Changing the value that is preloaded into the counter when it is reset changes the period of the output square wave, resulting in a change of pitch.

Square waves

The fourth channel proves rather more of a challenge. It uses a shift register (15- or 16-bit depending on the particular version of the chip) instead of a simple tone counter, and has two modes. When generating periodic noise a single bit shuttles around the shift register, generating a 1/15th or 1/16th duty cycle square wave. This has effect of producing a lower pitch with a distinctive "buzzy" timbre. The other mode is white noise, which uses a feedback system to generate pseudo-random noise.

The emulated SN76489, or PSG, has been implemented in two parts. The first is an interrupt handler written in STM8S assembly for speed. This is executed approximately 44,100 times a second (44.1kHz is the internal time step used in VGM files) and is used to update the internal PSG counters and shift register and generate the output level for that particular sample. Two output levels are generated as I've implemented the Game Gear's stereo extension to the PSG (this simply lets you switch individual channels on or off for each ear). These levels are loaded into capture compare registers for TIM2, which is used in PWM mode to generate the analogue output signals.

The rest of the code is written in C. This includes the second part of the emulated PSG, which handles bytes written to the PSG and updates its internal registers as appropriate.

VGM player circuit

Due to a 16KB limitation with the free version of the Cosmic compiler (and the 32K physical limitation of the microcontroller itself) the VGM file is stored on external EEPROMs which are accessed over the I2C bus via the microcontroller's I2C peripheral. As I don't have any large single EEPROMs, I've used two 32KB EEPROMs, one at address 0xA0 and the other at 0xA2. When the read pointer overflows one EEPROM it automatically steps to the next EEPROM. In theory any size could be supported using this code, but I've used 16-bit variables for all of the file pointers introducing a 64KB limit – this should be easily fixable, but I don't own enough memory to test the code myself, so I've left it as it is for the moment.

// The program I use to split VGM files into 32KB chunks.
// Bear in mind that most VGMs are compressed (VGZ): you'll need to decompress them first.
// You can use 7-zip to do so.
using System.IO;
class Program {
    static void Main(string[] args) {
        var SourceFile = @"D:\Documents\Documents\VGM\StrykersRun-title";
        using (var r = new BinaryReader(File.OpenRead(SourceFile))) {
            for (var i = 0; i < int.MaxValue; ++i) {
                var data = r.ReadBytes(32 * 1024);
                if (data.Length == 0) break;
                File.WriteAllBytes(string.Format("{0} [{1}].bin", Path.GetFileNameWithoutExtension(SourceFile), i), data);
            }
        }
    }
}

To take advantage of the delay between PSG accesses I've implemented a very simple buffering system that queues up a few bytes in advance from the EEPROM. This works well for music, but sampled audio (which involves updating the PSG very rapidly) doesn't work as the code spends too much time waiting for data to be transferred from the EEPROMs.

I've included some recordings of the output below.

The source code can be downloaded from here. If you do try to run it you'll find that it tends to hang when trying to initialise the EEPROM; this is due to the I2C bus being left in an active state by forcefully terminating the program before debugging. I find it helps to program the board, disconnect then reconnect the power supply to the EEPROMs to reset them, then hitting continue in the debugger.

FirstPreviousNextLast RSSSearchBrowse by dateIndexTags