SN76489 Arduino MIDI player

I've been thinking about retro 8-bit computers recently and came across the audio chip SN76489 (datasheet), which has been used in the BBC Micro, Sega Genesis and Master System (among many others).  The chips are capable of generating 3 independent frequencies simultaneously, as well as a 4th channel for noise.  They seemed easy enough to interact with, using a write pulse to load commands into the chip in a parallel-fashion.  I thought it would be fun to hook one up to an Arduino and play some retro game MIDI files through them!  It would be easy to take a few and make a MIDI synthesizer out of them too.


All code for this project can be found at my Github here: https://github.com/shepherdingelectrons/SN76489_player

It's easy to pick up a few cheaply from the usual sort of places, so I bought a pack of 5, and then had a read through the datasheet, which is always fun!


First things first, we need to provide a clock (pin 14) for the SN76489.  The frequency can be up to 4 MHz, and should be faster enough to generate the frequencies we want later.  Rather than use a crystal, I thought it would be fun to use the timer PWM hardware of an Arduino Nano to produce the signal.  The beauty of using the PWM hardware on the Arduino Nano, is that we can use it to do other things on it, without disturbing the clock.   Timer0 and Timer1 are taken up in the Arduino IDE for the millis() and delayMicroseconds() functions, and therefore I opted to use Timer2.

Great! This will create a square wave at the desired frequency on D11.  Now that we've got our clock set up, what's next?  We need to send some data bytes to the chip to configure the various channels.  For each channel, we can set the frequency/pitch and the attenuation level, as well as configure some aspects of the noise channel.  Composing these bytes is pretty straightforward, and is "left as an exercise for the reader" (except I provide all my source code so you don't even need to figure this out yourself!) :-)

However, there are some confusing aspects to this chip, and in the datasheet especially.  Some mistakes or rather sources of confusion in the datasheet are: 
  • Pin 6 is labelled as 'OE' in the above figure, but elsewhere in the datasheet it is labelled as 'CE' for chip enable.
  • There is a table for attenuation control, giving the level of volume attenuation in dB for a given value.  Confusingly attenuation 'OFF' means infinite attentuation (i.e. zero volume), rather than 'no attenutation', or 'attenuation off'.
  • D0-D7: This was my biggest irritation with this datasheet! The labelling of the pins D0-D7 suggests to me that bit 0 should be D0, the least significant bit (LSB) and D7 is the most significant bit (MSB) - in fact this is wrong.  A single reference in the datasheet tells us that D0 is the MSB (and therefore) that D7 is the LSB.   Maybe it's just me, but I find this incredibly counter-intuitive.
  • Pin 6 (OE/CE, chip enable) is an active low, and as I want the chip to be 'active' constantly I tied it low and pulsed WE (write enable) low and high to latch the D0-D7 data into the chip.  In fact pin 6 needs to be controlled separately and the IC seems not to work in this fashion.
Based on this I thought it would be useful to put together a crib-sheet based on the datasheet:
SN76489 communication byte format
To send commands to the chip we have to:
  • Pull the CE/OE (pin 6) low
  • Wait until READY (pin 4) goes low
  • Load an 8-bit command byte into D0-D7 - remember D0 is the MSB and D7 is the LSB
  • Pull the WE (pin 5) low, wait 100 microseconds, then pull it high.
  • Pull the CE/OE (pin 6) high
  • Wait until READY (pin 4) goes high
This is final schematic with everything hooked up.  I've also added a crude transistor amplifier for a 1Watt, 8 Ohm speaker I had laying around.  If you're curious about the 4 x 22 ohm resistors, they are rated for 1/4 Watt, so I used 4 in parallel to produce an approx. 6 Ohm, 1 Watt resistor.



So based on the schematic, let's define some constants:

#define CLOCK_PIN 11 // D11 - OCR2A
#define CHIP_EN 8 // D8
#define CLOCK_FREQ_MHZ 2000000L // 2 MHz
#define READY_PIN 2 // D2 
#define WRITE_EN 3 // D3

#define DATA0 14 //A0
#define DATA1 15 //A1
#define DATA2 16 //A2
#define DATA3 17 //A3
#define DATA4 4 // D4
#define DATA5 5 // D5
#define DATA6 6 // D6
#define DATA7 7 // D7

So let's make a function to send a byte to the SN76489 chip:

Great, now we can send a byte to the chip, let's send the correct bytes (see crib-sheet above) so that we can get some tones out!

By making a call to PlayNote with the MIDI pitch (0-127), MIDI velocity (0-127) and channel (0,1 or 2) we can play a tone on the given channel.  Note however that a duration isn't specified to the SN76489 - it will keep playing the note (or noise) until another note comes along on that channel, or the attenuation level is changed.  It is up to us to stop and starts notes if we want to play a tune (and to keep track of what channel to play on, if we are playing multi-phonic tracks).

We can also use the noise channel to make a kind of drum beat, by pulsing it in time with the music:

SendByte_SN76490(0b11110000); // Noise channel on
SendByte_SN76490(0b11100100); // Noise channel configure NF0 = NF1 = 0 = N/512 shift rate, FB = 1 = White noise

and switch off the noise channel with:

SendByte_SN76490(0b11111111); // Noise channel off
 
Phew, okay! We can control the chip and produce tones on each channel, as well as noise.  Now we need something to play!  Rather than over complicate things with MIDI files on an SD card and processing that, I decided to pre-process MIDI files with a Python script , then generate a C-style include file that contained the relevant playback information for a given tune.  The heavy lifting of this script was taken care of with an excellent MIDI library from vishnubob.  My script uses this library to open the MIDI file, process all the tracks to create a global list of notes, quantizes the notes and does a little basic compression to reduce the file size.  Finally it writes the sequence of MIDI notes into a C include file to play in the script.  The Arduino sketch then includes the files to play and plays the notes with the SN76489. Et voilà:

All code for this project can be found at my Github here: https://github.com/shepherdingelectrons/SN76489_player

Happy hacking!

Useful links
Python midi library here:
https://github.com/vishnubob/python-midi

Mario midi files here:
http://www.mariopiano.com/


Comments

Popular posts from this blog

Arduino and Raspberry Pi serial communciation

Getting started with the Pro Micro Arduino Board