MIDI to SN76489 to chip music, part 1: setting things up

Mathieu Favréaux · May 15, 2022

A while back, I started playing with SN76489 chips1. I’ve always been a bit fascinated with chip music, old 8-bit era computers and electronics, but never seriously dabbed into any of these things. So what began as a playful experiment quickly started to scratch some pretty serious itches.

This series (see part 1-B, part 2, part 3) will be a sort of idealized recollection of what took place over the past 6 months or so, where I tried to make music with these things. Apologies for the relative lack of pictures — I did not think for a moment that I would actually go anywhere with this.

There is a lot to talk about, so this will be split up: in this part 1, we will concentrate on setting up communication with a common SN76489 chip and getting some sound out of it. In part 2, we will start playing notes and we will set up MIDI control of the various channels. In part 3, we stick multiple chips together, build a mixer circuit and a case for our contraption and add even more features to our synth to make some music with it.

Chip music?

I guess we should start here. My understanding of what is called chip music today is music made at least partly with a set of simple waveforms such as pulse waves (square waves), triangle waves, and noise channels. We can probably throw FM synthesis in the mix as well. This is a legacy from 8-bit-era video games & computer systems: the limited capacity of the machines required the use of programmable sound generators (to which the CPU sends notes). Moreover, the cost constraints meant using few, basic chips. As a result, you get simplistic sounds on very few parallel voices.

Here are a few examples2:

  • 1942’s Main background music (BGM) — or rather ambience made with percussive elements:
1942's BGM. Source: VGMRips
  • Clean Sweep’s Sound 1 for background sound effects:
Clean Sweep's Sound 1. Source: VGMRips
  • Donkey Kong’s Game start with some triangle wave bass to keep you on your toes:
DonkeyKong Game Start. Source: VGMRips

These constraints, as is often the case, meant that talented people started to work around the limitations to get more and more juice out of these systems. A typical sound started to emerge, a kind of “scene” grew out of isolated silos, and you get gems everywhere you look. It’s fascinating.

  • Silver Surfer’s BGM on NES — arpeggios, fast rhythms, tone sweeps, the works:
Silver Surfer BGM 1. Source: VGMRips
  • Bomb Jack’s on Game Boy — different platform, different sound but similar principles:
Bomb Jack Bonus. Source: VGMRips

Nowadays I guess this sound is 100% associated with old video games. However, chip music is still being made today, sometimes on real hardware3, sometimes more as a reference incorporated into more complex songs. Here are two examples of the former:

I just scratched the surface here, and I could not be thorough even if I tried anyway. Hopefully that’s enough to give an idea. If you want to know more about the old days of chip music and its creators on the Japanese side of things, I found the documentary Diggin’ in the Carts to be interesting and approachable.

The SN76489 chip

Let’s get back to business.

This TI chip and its variants were used in various computers & game systems from that era: from arcade games, to the BBC Micro, to the Sega Master System & Game Gear. It has 4 channels: 3 square wave & 1 noise. It it sounds similar to what we’ve listen to above, but even more limited: no sample playback, no triangle wave, 50 % duty cycle pulse wave only.

They are still pretty easy to find today, and on some rainy day I purchased a batch on eBay. I settled on the AN variant, which accepts a 4-MHz clock.

A few SN76489AN chips
A few SN76489AN chips

First stages of communication: sending bytes

We need to try to talk to this thing, so let’s take a look at the chip’s datasheet.

Pin layout

This is the chip’s layout:

Datasheet's top view
Datasheet's top view

First, the data bus is made up of pins D0-D7. The chips needs an external clock signal, not only for operation but also as a way to derive the notes frequency — that’s on pin 14. The sound signal is then output on pin 7.

The remaining pins are used for synchronization:

  • CE on pin 6 (inverse logic): Chip Enable, unless pulled to ground the chip will ignore what’s happening on the data bus. It’s weirdly spelled OE in the above picture, but later referred to as CE.
  • WE on pin 5 (inverse logic): Write Enable, to be pulled to ground when data is ready on the bus.
  • READY on pin 4: high when the chip is done reading data.

The inverse logic thing means that the voltage you set is opposed to the logic: pull low for true, and high for false.

Ergo, sending a byte to the chip is a matter of:

  1. Setting CE to true to select the chip (this can be wired to ground if there’s only one chip)
  2. Putting our data byte on D0 to D7.
  3. Setting WE to true to indicate that data is ready.
  4. Waiting for READY to be true.
Data transfer timing
Data transfer timing

Vague memories from school 15 years ago tells me this is pretty standard stuff. So, time to get the Arduino out?

Data layout

Wait! What should we send?

The chip is controlled by setting its internal registers, which is done by talking a simple protocol on the data bus. There are 8 registers:

  • 10-bit registers for channel 0-2’s frequency control.
  • a 3-bit register for channel 3 noise type.
  • 4-bit registers for each channel’s attenuation (volume).

In order to set a register value, the most significant bit (MSB) is set to 1, the 3 next bits select the register, and the rest is devoted to the register’s content. For the 10-bit registers, data is sent in two stages, and the MSB is set to 0 to indicate the second stage.

Register's sending format
Register's sending format

Note how D7 is the LSB and D0 the MSB, “big-endian” fashion.

Clock

We also need a clock signal for this whole thing to work. I don’t have a quartz around, and I sure don’t know how to make a proper clock circuit, so I looked for other options.

It turns out4 the Arduino’s SoC is quite flexible and you can use it to generate pretty fast signals with counters & interrupts. Let’s get a deeper look at the ATmega328P datasheet to see precisely what we can do.

The ATmega328P has multiple internal timers, with different resolutions and functionalities. They can be configured to trigger interrupts on delays, compare values, generate waves via PWM, at high frequencies (close to the SoC clock) while letting the CPU do something else. Timer 0 is used by regular Arduino functions such as delay, so we must leave it alone. However Timer 1 and 2 are up for grabs.

Timer 1 supports a mode called Clear Timer on Compare Match (CTC). When properly configured, we can get the following behavior:

  • The timer increments a counter in TCNT1 at each clock cycle
  • When the value reaches a top value stored in OCR1A:
    • The counter TCNT1 is set back to 0.
    • The timer event happens: in our case, we want the output pin OC1A to be toggled.

The maximum output frequency can therefore be obtained by setting OCR1A (the top value) to 0: the output pin gets toggled at each clock cycle, so a full period takes 2 cycles. Hence, the maximum clock we can generate is 8 MHz on our 16 MHz SoC. In our case we want to generate a 4 MHz clock signal: we must set the top value OCR1A to 1 so the timer is reset every other clock cycle.

In the general case, the output signal’s frequency will be fout=fSoC2(1+OCR1A)f_{out} = \frac{f_{SoC}}{2(1 + \mathit{OCR1A})}5.

To configure this mode properly, according to the datasheet we need the following parameters:

  • WGM1 (4 bits) = 4 = 0b0100 for CTC mode compared against OCR1A5
  • COM1A1 (1 bit) = 1 for toggling the output on compare match5
  • Disable the interrupts that could be generated to the CPU5 — simply have the signal on the output pin.
  • CS1 (3 bits) = 1 = 0b001 to disable the prescaler6 that can change the incrementing speed, but is too coarse for our use.

Those values are spread over multiple registers, so we must set bits individually:

  • TCCR1A stores COM1A1 and WGM1 bit 0 & 17,
  • TCCR1B stores CS1 and WGM1 bit 2 & 38,
  • TIMSK1 stores the various interrupt enable flags9.

In the end, we need something like the following code:

void setupClock()
{
    // Setup the clock to drive the SN76489
    pinMode(clockOutputPin, OUTPUT);

    // Set Timer 1 CTC mode with no prescaling.  OC1A toggles on compare match

    // WGM1 = 0100: CTC Mode against OCR1A
    // WGM1 bits 1 and 0 are in TCCR1A
    // WGM1 bit 2 and 3 are in TCCR1B

    // COM1A0 sets OC1A (arduino pin 9) to toggle on compare match
    TCCR1A = ( (1 << COM1A0));

    // Set Timer 1  No prescaling
    TCCR1B = ((1 << WGM12) | (1 << CS10));
    // Make sure Compare-match register A interrupt for timer1 is disabled
    TIMSK1 = 0;
    // Counter top value
    OCR1A = 0;
}

Testing setup

Finally, we can get the Arduino out.

Setting up a data bus on a breadboard is easy to get wrong, so at this stage we want to keep things simple to test our setup. We’ll simply set channel 3 to max volume, and then cycle between noise values 0 to 7 with a small delay in-between. This means we’ll send the following commands:

Hex MSB Register Value
0xF0 0b1 0b111: Noise Attenuation 0b0000: 0 (max volume)
0xE0 0b1 0b110: Noise 0b0000: Periodic, N/512
0xE1 0b1 0b110: Noise 0b0001: Periodic, N/1024
  0b1 […]  
0xE7 0b1 0b110: Noise 0b0111: White, Tone Generator #3 output
  0b1 Cycle back…  

Here is one picture I took of the setup in a flash of clarity of mind:

First tests on the breadboard
First tests on the breadboard

You can barely make out the pin layout, but it does not matter too much: there are enough pins on a simple Arduino to dedicate one to each chip’s pin. Note I decided to revert the big-endian-looking data bus into little endian on the Arduino side, because my brain is wired that way and the code looks simple to me:

void writeByte(byte value)
{
  for (int i=0; i < 8; ++i) {
    digitalWrite(DATA_BUS_PIN0 + i, (value >> i) & 1); 
  }

  // [...] Wait for write enable

One trivial detail: I pulled CE to ground via a pull-down resistor. I think it was 4.7 KΩ? At 5 V, a value of ~5 K drains an acceptable ~1 mA, and this value seems to work well.

And finally, this is what is sounds like through the feeble speaker you see on the picture:

First noise successful test.

Our first milestone has been reached!

Mistakes were made

Now, don’t think for a minute that this went smoothly. It did not, and I did a fair share of rookie mistakes.

First, notice how pin 0 & 1 are not wired on the Arduino? That’s because pin 0 & 1 on the Arduino are dedicated to the serial transmission if you set it up (which I did, because logs). So of course, if you wire them up to the chip, you’re going to have a bad time. This is reflected in the actual writeByte code.

A second, big issue I had was with dealing with the READY signal. I could not, for the life of me, see it pull up! It was just… standing there, flapping in the breeze. I’m probably missing something trivial though. I may revisit this in the future, if I put a chip back on the breadboard. In any case, after a while I decided to cut my losses and ignore the signal, use a delay instead, and move on.

Next steps

We’d now be ready to make the chip play some simple tunes on one of the other channels, or do some cool sweep. In part 2, we’ll set up the MIDI side of things so we can control the board from a keyboard or a DAW, and improve from there.

But first, in part 1-B, we’ll dive into the little amplifier you can vaguely see on the picture above.

  1. I’m certainly not the first one to do so! There are plenty of examples to be found:

  2. There are a few communities dedicated to extracting & preserving these artifacts in the form of, for example, VGM files. VGMRips is a good starting point. In this page, I reproduced short samples of mp3s you can listen to there, in the hope that it’s OK. 

  3. The original GameBoy is especially appreciated for its versatility & textures, and got popular notably through LSDJ, a tracker made for the hardware. 

  4. This thread on the Arduino forum was the trigger. 

  5. §15.9.2 Clear Timer on Compare Match (CTC) Mode page 100-101.  2 3 4

  6. §16.1 Timer/Counter0 and Timer/Counter1 Prescalers: Internal Clock Source page 114. 

  7. §15.11.1 TCCR1A 

  8. §15.11.2 TCCR1B 

  9. §15.11.8 TIMSK1 

Twitter, Facebook