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.
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/
https://github.com/vishnubob/python-midi
Mario midi files here:
http://www.mariopiano.com/
Comments
Post a Comment