YM2149 sound generator, Arduino and fast pin switching
I spent my childhood with an Atari STe and its bleepy sounds. Recently, I purchased its sound generator on eBay: the YM2149 chip (made by Yamaha) or the AY-3-8910 (made by General Instruments).
I would like to make a chiptune MIDI synthesizer with it, but I thought it was a good start to play existing tunes.
There is a lot of .ym files available on the Internet. They are dumps of the sixteen YM registers at 50Hz, so it’s quite simple to stream the frames to an Arduino which will update the state of the YM2149. I wrote a short program in C# which sends registers data to the Arduino every 20 milliseconds. The only tricky part was to reorder interleaved data, you can find more details on Leonard’s website.
On the hardware side, the circuit is straightforward: a $3 USB to Serial CP2102 module sending raw register data to a Boarduino clone. Every 16 bytes received (8 registers of 8 bits), data is written to the YM2149. I used a 8 MHz crystal TTL/CMOS oscillator with a 74LS93 divider to generate a 2 MHz clock signal for the YM.
The Arduino code is quite simple too, it’s all about reading serial port data and writing it on eight pins. But I had to face an annoying problem: I heard a click sound every frame of data. After some investigations, I read in the YM datasheet that the BDIR pin and the BC1 pin of the control bus must be switched within a 50 nanoseconds time window. So, I had a look to these pins with my oscilloscope: the classic Arduino digitalwrite() method is really slow for a control bus!
Then, I remembered a post dealing with this issue on Bill Porter’s blog. Let’s look at these lovely low-level tricks! Bill provides two macros to set or clear a specific bit of a given port.
#define CLR(x,y) (x&=(~(1<<y))) #define SET(x,y) (x|=(1<<y))
These macros read the “x” port state, put its value in memory, change the “y” pin value to 1 or 0 and finally writes the new port value in the port register. I used SET(__BCPORT__,__BDIR__) and SET(__BCPORT__,__BC1__) where __BDIR__ and __BC1__ are the bit number of BDIR and BC1 ports on the port B. Bingo: no more audible clicks!
But things weren’t perfect, 124ns of delay is not less than 50ns. Why not going a bit further in port manipulation ? The compiler replaces SET(__BCPORT__,__BDIR__) with ( __BCPORT__ |= (1<< __BDIR__ )). I used this form and added __BC__ to this expression:
__BCPORT__ |= (1 << __BDIR__) + (1 << __BC1__)
The opposite instruction to set BDIR and BC1 to the low state is:
__BCPORT__ &= ~((1 << __BDIR__) + (1 << __BC1__))
As expected, the pin switching is now perfectly simultaneous.
Now I know how to talk with this chip, the next step is to make a MIDI synth with it. I can’t wait to hear those three oscillators in a fat unison lead sound. Stay tuned!
Update: Here are the schematics, made for a breadboard.