2A03 Chip used as a synthesizer

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
2A03 Chip used as a synthesizer
by on (#66733)
For the past few days I've been hard at work trying to get kevtris' circuit up and running, and so far have the following:

http://kevtris.org/IMG_2306.JPG

This circuit, fully implemented. I have my AVR grabbing rising edges from the 3rd output of the 138, which lets it know when is the best time to update the data and address ports. Discussion about this can be seen here.

http://personal.stevens.edu/~jlupinsk/irc.txt

I wrote up some basic code to try to turn the noise channel on, but so far no luck:

http://personal.stevens.edu/~jlupinsk/code.txt

I have pin 2 of the 2A03 connected to a 386 op amp with a 20x gain, any higher than that and I pick up more buzz than actual sound. My 2A03 is connected as such:
Pin 2 -> 386 -> speaker
/RST toggled by my AVR
A0-A2 -> 74HCT138
GND is grounded
D0-D7 -> bank of 3 x 74HCT245's
CLK, using the original NES crystal with one of the unused 74HCT04 gates, as such: http://go.mibbit.com/thumb.php?x=32&y=32&url=http%3A%2F%2Fwww.mpdigest.com%2Fissue%2FArticles%2F2008%2FMar%2FCrystek%2Ffig1.jpg (This is probably an issue, but I couldn't find any circuit other than this which reliably uses a >10Mhz crystal)
Pin 30 is grounded
/NMI and /IRQ are pulled high
R/W going into the '138 and the gate
+5VCC coming from a clean switch mode PSU, with additional filtering caps

Most likely causes that it won't work:
Crystal circuit
Initialization of the CPU doesn't work
If statements are too slow, interrupts occur quicker than state function can iterate
Something about the actual audio out fromt he 2A03

Could anyone look over the code and offer some advice? I've commented most of it to make it easy to follow.
Re: 2A03 Chip used as a synthesizer
by on (#66876)
I don't know if this helps or not, but, I remember seeing someone (I think on instructables), wiring something in through the expansion port in the NES to make it work.

Okay, couldn't find that, but, this might Pique your interest:

http://www.risingstuff.com/forums/viewt ... f=5&t=1679

This mentions how you can modify the NES for famicom sound, and even make it where you can use famicom games.

jarek wrote:
For the past few days I've been hard at work trying to get kevtris' circuit up and running, and so far have the following:

http://kevtris.org/IMG_2306.JPG

This circuit, fully implemented. I have my AVR grabbing rising edges from the 3rd output of the 138, which lets it know when is the best time to update the data and address ports. Discussion about this can be seen here.

http://personal.stevens.edu/~jlupinsk/irc.txt

I wrote up some basic code to try to turn the noise channel on, but so far no luck:

http://personal.stevens.edu/~jlupinsk/code.txt

I have pin 2 of the 2A03 connected to a 386 op amp with a 20x gain, any higher than that and I pick up more buzz than actual sound. My 2A03 is connected as such:
Pin 2 -> 386 -> speaker
/RST toggled by my AVR
A0-A2 -> 74HCT138
GND is grounded
D0-D7 -> bank of 3 x 74HCT245's
CLK, using the original NES crystal with one of the unused 74HCT04 gates, as such: http://go.mibbit.com/thumb.php?x=32&y=32&url=http%3A%2F%2Fwww.mpdigest.com%2Fissue%2FArticles%2F2008%2FMar%2FCrystek%2Ffig1.jpg (This is probably an issue, but I couldn't find any circuit other than this which reliably uses a >10Mhz crystal)
Pin 30 is grounded
/NMI and /IRQ are pulled high
R/W going into the '138 and the gate
+5VCC coming from a clean switch mode PSU, with additional filtering caps

Most likely causes that it won't work:
Crystal circuit
Initialization of the CPU doesn't work
If statements are too slow, interrupts occur quicker than state function can iterate
Something about the actual audio out fromt he 2A03

Could anyone look over the code and offer some advice? I've commented most of it to make it easy to follow.

by on (#66879)
Have you tried writing a non-zero value to the noise channel's length counter (i.e. write 0xF8 to address 0x0F) ?

by on (#66885)
Exactly what mic_ said; you're not initializing the length counter.

If I were trying to debug this, I'd use the simplest possible code that showed me that it was working. I'd just have it write 1 to $4016, then look at the strobe output. If it was high, I'd then run it again but have it write a 0. If it was now low, I'd conclude that it was properly executing, then proceed to the APU stuff.

by on (#67111)
I thought what I wrote was the simplest possible code...

$4015 - 0x0F //Init APU

$400E - 0x85 //Turn on noise tone

$400C - 0x3F //Set volume to max

$401F - 0xAA //JMP out of bounds

Sorry if this isn't usually the way you guys show routines. The first value is the address, and the second is the data being written to that value. My code simply waits for the NES to tell the microcontroller that it is BRKing, giving me 16CLK to write those values into the appropriate registers on my uC. So, when the NES BRKs the first time, address 4015 has 0F written to it, then the next time it BRKs, 400E has 0x85 written to it, and so on and so forth. The other code is for debug LEDs.

Is there a simpler way to just get noise? You mentioned something about writing to the counter?

I checked the famicom mod thread, but it doesn't seem to make the NES provide the sound; it seems that the cartridge itself has an additional sound chip, which gets mixed into the NES sounds.

by on (#67114)
Much simpler way to test (I just tested this on my NES to be sure of the pin number):

$4016 - 0x01 // Make pin 39 of 2A03 high
$401F - 0xAA //JMP out of bounds

Once you see that this does what's expected, then run this one:

$4016 - 0x00 // Make pin 39 of 2A03 low
$401F - 0xAA //JMP out of bounds

If you insist on using the APU, try this:

$4015 - 0x0F //Init APU
$400E - 0x85 //Turn on noise tone
$400C - 0x3F //Set volume to max
$400F - 0x08 //Load length counter
$401F - 0xAA //JMP out of bounds

Years and years ago when I made my first devcart for the SNES, my first test that it was working was to enable interlaced video, a single bit in a register. When I saw the TV go from progressive to interlace, I was overjoyed.

by on (#67140)
jarek wrote:
Is there a simpler way to just get noise? You mentioned something about writing to the counter?


I consider it the 'key on' command. It sort-of is that I guess, because when you use volume envelopes, writing to $4003/$4007/$400F is what triggers it.

If you write to it once that's enough, IIRC the value to use was %00001xxx (0x08). But after that's initialized, any write to $4002/$4006/$400E will work by itself.

by on (#67149)
The APU basics page on the Wiki is perfect for what you're doing. It describes a subset of APU functionality that you can use without having to read anything else about it. It allows basic control of frequency, volume, and timbre of the four wave channels, without having to deal with the envelope, length counter, linear counter, or sweep.

by on (#67151)
Hey Jarek, what kind of equipment do you have available for debugging? Do you have a voltmeter or are you flying completely blind?

The example set of instructions you posted are pretty complicated for an initial bring up test, despite looking simple. You're trying to get audio out when you don't know:
1) If the processor is booting
2) If your oscillator circuit is working and stable
3) If the interface to the 2A03 works properly (I can vouch for that circuit, but there could always be a wiring mistake in practice)
4) If the timing of the Arduino is valid
5) If the audio output circuit is working how it should

I wouldn't try multiple instructions until you know that one instruction at a time works successfully. If you believe the chip is coming up properly (are you holding reset asserted while power comes up? you should), the simplest audio communication test is just write to $4011 and check the output voltage on the triangle/noise/dmc pin of the chip. That's what I was doing here: http://skrasoft.com/blog/?p=183
If you write alternating FF 00 FF 00 you'll get a square wave.

If you have any doubts about the clock circuit you are using, you should be able to get a 20 MHz oscillator from an electronics store. The 2A03 won't mind the slower speed. The thumbnail you posted is microscopic on my screen, so I can't read it to comment on it. You can probably make a Pierce oscillator with the crystal you have: http://en.wikipedia.org/wiki/Pierce_oscillator 20pf is probably a good place to start with the capacitors.

Your comments about the audio output connection don't mention the necessary 100 ohm pull down resistor on the audio output pin (each audio output pin gets one). Don't leave that out.

by on (#67165)
In fact, the simplest way to test if it's booting is to force $EA (NOP) on the data bus, and if everything is fine you should see the address bus incrementing after reset.

by on (#67166)
And if things don't go fine, you'll see the address bus... incrementing as well :)

by on (#67173)
Until it gets to $4015, $4016, $4017 and executes from the register bits, I'm not sure what will happen then.

by on (#67192)
blargg wrote:
And if things don't go fine, you'll see the address bus... incrementing as well :)

:?: Can you explain this?

by on (#67195)
I'm saying that if the code crashes or executes something you didn't expect, you might also see the address bus incrementing. The idea of the $4016 and $4011 tests is that you do something that causes a change that you know is unlikely to occur if the CPU is executing garbage or your instructions didn't get fed to it properly.

by on (#67211)
The purpose of my test is only to see if the clock is running on the CPU.

by on (#67418)
Great Success! That you all for your diag tips. Starting small with just watching pin 39 go high and then working up from there was a great idea! I have my bare 2A03 breadboarded and producing a 440Hz Square wave.

To summarize the past week of my work:

I tested a donor NES with Time Lord. It worked in good condition.
Taking a page from the NESpander, I desoldered and ripped out the expansion slot. From this I broke out the Data pins and +5V and GND.

I found a few vias which served well as breakout points for A0, A1, and A2, as well as R/W and M2(explained soon).

After connecting the somewhat complex "ROM" provided by kevtris to this broken-out NES system, I fired up the code suggested by blargg, and after a few reconnected wires and pokes and prods, my oscope showed a high on pin 39!

I moved on to the next step, which was trying out the noise channel, which also worked well.

Turning to the APU basics page, I tried out the square wave, but was getting odd results. After playing with my code and making it cleaner, but still with no result, I tried probing around for a different interrupt point. So far, I've found M2 on the CPU pinout works very well. Is this pin ever used for anything else?

After the square wave code was verified to be working, I ported the bare 2A03 I got from arcadecomponents to my breadboard, and reconnected the wires. Using one of the unused 74HCT04 inverters as a Pierce oscillator ( =) ) I achieved the same results as when I was using the actual NES.

I've moved on to trying a triangle wave, but it doesn't seem to work well. I'm trying to mimic this from the APU Basics page:
Code:
lda #<139
sta $400A

lda #>139
sta $400B

lda #%11000000
sta $4008
sta $4017


but all my speaker is doing is sputtering and occasionally spitting out some noises. I updated the code, here is the source again:

http://personal.stevens.edu/~jlupinsk/code.txt

Does anyone have any tips for getting the triangle wave generator to work? In the mean time I'll be designing a breadboard for both using a cartridge to make sounds (Let me know if you would be interested in an open-source MIDINes =) ), as well as a synth using the bare chip.

by on (#67421)
Maybe I'm not initializing the triangle correctly there. The linear counter is tricky to get initialized right, unfortunately.

Can you run a test where you feed $4011 an increasing byte value at a regular interval? This would give a clear indication of any bit errors, and how regular your timing is.

by on (#67422)
I just had one of those 3am 'moments of clarity', and gave my code a vast rewrite, and essentially squashed any ugly timing bugs:

http://personal.stevens.edu/~jlupinsk/code.txt

Could you show me how to do your test? Unfortunately I'm all tapped out at the moment, and can't think straight. My code is now easy to just throw values into.

by on (#67425)
Write $00 to $4011, delay about 8 usec, write $01 to $4011, delay 8 usec, write $02 ... up through $FF, then loop back to 0. You should get a nice clean 1 kHz near-saw wave (1000000 / 128 / 8, the 128 because the DAC is only 7 bits).

by on (#67437)
I don't need to load any length counters or initialize anything? I think that's where all my problems lie...

Alright, I'll give it a shot.

by on (#67438)
How to set up the APU

Make your CPU execute something like that code, and you won't have to worry about length counters again.

by on (#67439)
4011 is a direct line to the 7-bit DAC. By outputting values that slowly rise, then fall again, you get a tone. You're just streaming the PCM wave.

by on (#67441)
I'm not using assembly to tell the uC what to output to the 2A03, so let me run this by you guys to make sure I'm doing it correctly:

Code:
(address, data)
$4015 - 0F

$4000 - 30
$4001 - 08
$4002 - 00
$4003 - 00
$4004 - 30
$4005 - 08
$4006 - 00
$4007 - 00
$4008 - 80
$4009 - 00
$400A - 00
$400B - 00
$400C - 30
$400D - 00
$400E - 00
$400F - 00
$4010 - 00
$4011 - 00
$4012 - 00
$4013 - 00
$4014 - 00
$4015 - 0F
$4016 - 00
$4017 - 40


and then after that I

Quote:
Write $00 to $4011, delay about 8 usec, write $01 to $4011, delay 8 usec, write $02 ... up through $FF, then loop back to 0.


Let's give it a shot!

by on (#67442)
I tried running the whole initialization step and then outputting the saw wave, but nothing comes out:

http://personal.stevens.edu/~jlupinsk/sawcode.txt

It's really odd. If I use only

$4015 - 0F
$4002 - 17
$4003 - 00
$4000 - BF
$401F - AA

I can hear a nice square wave.

But if I do the whole initialization step, and then run that snippet above, nothing comes out.

I'm upgrading my processor soon (getting a 72Mhz ARM beast =3 ), so I'll try again, just in case it is a timing error, but in the mean time, does the initilization step look sound? ....hehe....sound...

Edit: Oh snap I got a saw wave to come out. I realized I have to loop the instructions to get the wave XP silly me. It works without any sort of initialization.

http://personal.stevens.edu/~jlupinsk/workingsawcode.txt

So I guess there is something wrong with the initialization step?

Edit 2: Got triangle wave working. I guess following the APU Basics page's code examples are valid only if you're actually programming the NES? Writing C0 to $4017 as suggested in the APU Basics page silences the channel.

by on (#67449)
You don't need any initialization code to use $4011. It's a minimal latch that drives the DAC.

As for the triangle, it's not a simple matter of "writing $C0 to $4017 silences triangle", since there's internal state involved. I suggest you move on and do some things with the other channels, and we can get the triangle issue worked out eventually. It might be due to a bug of the APU basics page (not initializing the triangle state fully). It's just not something I can mentally switch to to investigate right now.

by on (#67493)
jarek wrote:
...I'm upgrading my processor soon (getting a 72Mhz ARM beast =3 )...


Cortex M3?

by on (#67495)
http://www.robotshop.com/leaflabs-maple ... oller.html

this one specifically.

The init code is still not letting me make noise.

I really want to get the init code working, because without resetting by unplugging and plugging the whole thing back, I get erratic results from trying all the other channels.

by on (#67500)
jarek wrote:
http://www.robotshop.com/leaflabs-maple-32-bit-arduino-compatible-microcontroller.html

this one specifically.

The init code is still not letting me make noise.

I really want to get the init code working, because without resetting by unplugging and plugging the whole thing back, I get erratic results from trying all the other channels.


Oh nice, I just recently started on an STM32 dev board. It's the same chip. It's really powerful, especially with DMA.

I found my original code for testing channels. Since it was all very ad hoc just to make sure my CPLD interface was working, I didn't comment it very well. The register reads and writes are pretty clear, though.

The first thing to note is that the 2A03 is turned on while RES is held low. If I turned the 2A03 on while reset was high or floating, resetting after the fact didn't quite seem to work. I had to hold RES low (asserted), then power on the 2A03, then wait a couple milliseconds, then release RES. After that I could read and write registers. That was the only sequence that gave me consistent results.

Before the main loop I have this:
Code:
codeout(0x1F, 0x15);


Then I made small tests for each channel. This was for the triangle wave:

Code:
codeout(0x00, 0x11);    //max volume
codeout(0xFF, 0x08);
codeout(0x00, 0x0A);
codeout(0xF1, 0x0B);
for(n=0;n<=254;n++)
{
  codeout(n, 0x0A);
  Delay_us(5000);
}
codeout(0x00, 0x08);


Square wave 1:
Code:
codeout(0x7F, 0x00);
codeout(0x00, 0x01);
codeout(0x6F, 0x02);
codeout(0xF8, 0x03);
for(n=0;n<=254;n++)
{
  codeout(n, 0x02);
  Delay_us(3000);
}
codeout(0x3F, 0x00);
for(n=0;n<=254;n++)
{
  codeout(n, 0x02);
  Delay_us(3000);
}
codeout(0x00, 0x00);


Square wave 2:
Code:
codeout(0x7F, 0x04);
codeout(0x00, 0x05);
codeout(0x6F, 0x06);
codeout(0xF8, 0x07);
for(n=0;n<=254;n++)
{
  codeout(n, 0x06);
  Delay_us(3000);
}
codeout(0x3F, 0x04);
for(n=0;n<=254;n++)
{
  codeout(n, 0x06);
  Delay_us(3000);
}
codeout(0x00, 0x04);


Noise:
Code:
codeout(0x3F, 0x0C);
codeout(0xF8, 0x0F);
for(n=0;n<=15;n++)
{
  codeout(0x00 + n, 0x0E);
  Delay_us(10000);
  for(m=15;m>=1;m--)
  {
    codeout(m + 0x30, 0x0C);
    Delay_us(5000);
  }
}
codeout(0x30, 0x0C);


codeout(data, address) writes data to $4000 + address. So codeout(0x3F, 0x0C) will write 3F to 0x400C. Delay_us() is a delay function in microseconds. I believe this .wav file was from that test (starting with square waves):
http://www.skrasoft.com/blog/blogfiles/2A03/2A03run.wav

Hopefully some of that is useful.

by on (#67504)
Ah~

Finally got a scale to play from the square wave generator.

http://personal.stevens.edu/~jlupinsk/square1scale.wma

Careful! It's a direct line in from NES to mic jack, so it's very loud!

Now that hardware works perfectly, I'll be drawing up a real schema and send out to get a PCB made. I think I'll take these few days to rest my ears from listening to so many square waves T_T.

http://personal.stevens.edu/~jlupinsk/nessynth.txt

Here's the code. I'll post the schema when it's done, shouldn't take me too long. Big thanks to all who lent a helping hand.

by on (#68020)
Hi Jarek

I've been quietly following this thread in excitement! How's it going?

I'm currently controlling a SID with my arduino and have a NES in front of me urging to have it's APU abused.

I would love to get a hold of your schematics if possible some time.

I'm hoping to wire your circuit to a cartridge pcb like the repropak ( I'm assuming it's going to be feasible) so I can write to the apu without taking the nes apart. Leds + translucent carts = vu meters galore

Good luck and look forward to hearing from you!
best

Alex