Hello everyone, this is my first post here. I am working on a project that will interface a 2A03 with a microcontroller to make a synthesizer module. It will bring together what I already know about analog circuits and hopefully give me some new skills on the digital side.
Right now my biggest problem is figuring out what data I need to send to the 2A03 to get the audio up and running without any other chips (except a microcontroller) present. I have never worked with a bare CPU before. I have read over the addresses and data values to work with the sound channels, but what else is necessary? Does the 2A03 need specific instructions to be initialized? Once it is running is it just a matter of writing new data bits to the audio channel addresses?
If I can get the software figured out then I should be good to go. I'm an electrical engineer, but not a computer engineer. If anyone can help me it would be greatly appreciated. If anyone is interested I can try to document the project as it progresses.
The 2A03 is just a 6502 family CPU with different I/O and no decimal mode. As with any 6502 family CPU, you need ROM in at least the upper 256 bytes or so of address space ($FF00-$FFFF), in order to have somewhere to put the reset and interrupt vectors. If you want to actually do anything, you need RAM in at least the lower 512 bytes ($0000-$01FF).
But you don't even need the upper address lines connected to anything. Notably, the Atari 2600 leaves A13 through A15 disconnected,[1] mapping the ROM into $1000, $3000, $5000, $7000, $9000, $B000, $D000, and $F000. It also puts I/O into $0000-$007F and maps the 128 byte RAM into both $0080-$00FF and $0180-$01FF.
[1] The 6502 variant used by the 2600 actually comes in a package that doesn't even have pins for A13-A15, /IRQ, /NMI, and a few other 6502 functions. Pins make up a significant fraction of the price of a chip.
What you mention isn't possible unless you are able to simulate ROM with the microcontroller or something, wich doesn't much sound like doable.
The 2A03 is NOT a sound chip, it is a microprocessor with a built-in sound chip. There is NO I/O pins that allow you to directly write to the sound regs ($4000-$4015), instead you should have a programm that is read through the microprocessor's data and adress I/O, and that programm should write to it.
In the best case your programm would be something utterly simple like an instruction that constantly jump on itself, and then you replace it by a sound register write when you want to write to it before coming back to your instruction that jumps on itself. So you will need at least 8 bytes of RAM (or pseudo-RAM emulated by something), 2 for "LDA #$xx" (where xx can be modified by the microcontroller), 3 for "STA $40yy" (where yy can also be modified by the microcontroller), and 3 for "JMP $fffb". Assuming you map those bytes at $fff6 to $fffd, the reset vector will automatically point on $fffb, wich is the instruction jmp $fffb. When the microcontroller wants to write to a sound register, it have to first set the xx data and yy adress to its needed values, then replace the $fb in "jmp $fffb" per a $f6, and then almost immediately set it to $fd again to avoid multiple writes.
Another (and probably better) solution would be to have a small RAM chip (say 8 KB) that is mapper in 6502 memory map, and have a more complex programm running here. I don't know how the main microcontroller could write to it tough.
Bregalad wrote:
There is NO I/O pins that allow you to directly write to the sound regs ($4000-$4015), instead you should have a programm that is read through the microprocessor's data and adress I/O, and that programm should write to it.
I'm having some problems understanding the processor, I think. Please bear with my ignorance. My original thought was that using the microcontroller I could set the 2A03's address bus pins to the address I want to write (such as $4007), the data bus pins to the data I want to write (such as $F8), enable pin 34 for writing, and that data would go into that address at the next clock cycle or so. By having the microcontroller write different data to different addresses (it is actually reading in analog voltages and converting them to digital - this part is done) I could update frequency values, duty cycles, etc. Is it not possible to do that simply, putting the bulk of the work into the microcontroller's program? Do I have to instead write a program to the 2A03 and then have the microcontroller constantly update that program's code?
No, no, no ! Adress pins are OUTPUT on a microprocessor, as well as R/W ! You could do this only if the sound chip was on the outside of the processor, and it's not.
Thank you for clearing that up! I've been working with Atari Pokey chips too, and on those the address pins are inputs so I mistakenly thought everything worked that way.
So now I need to learn how to get code to the 2A03, or how to make the 2A03 execute code. I imagine this stuff is pretty basic, so do you have a link or anything where I could get started reading? I am trying to learn as much as I can.
Thank you for helping me.
To use the 2A03 like a traditional discrete sound chip, you can have some glue logic simulate an 8-byte ROM. I think it'd look like this, where the operand of JMP overlaps the reset vector:
$FFF6: $A0 (LDY #$xx)
$FFF7: data value
$FFF8: $8C (STY $40xx)
$FFF9: address value
$FFFA: $40 (high byte of address)
[here the 2A03 waits a cycle to complete the read)
$FFFB: $4C (JMP $FFF6)
$FFFC: $F6
$FFFD: $FF
As for how to get the 2A03 to execute code in the traditional fashion, you could look up
6502 hardware projects.
Have something which feeds data to the 2A03 as memory reads. All it needs to feed is $A2 xx $8C yy $40. This corresponds to LDY #xx, STY $40yy. Then your circuit could just keep asserting NMI every 6+7 clocks. It would need to feed some data for the NMI vector read, but that wouldn't need to be anything specific as long as the values didn't fall within I/O space. I bet a pretty minimal circuit could feed this fake instruction stream to the 2A03.
Yesterday a parts order from Digikey came in, so I now have a 2A03 powered and clocked. If I check the address bus pins after it's been running, they read $FFFF. Does that mean the chip has already stepped through all the address values? When it turns on does it just count up through the address values until it reaches the end or gets an instruction to do otherwise?
If I do the glue logic for an 8-byte ROM, is there a special reason to trigger the data starting at $FFF6 ? A table I found online shows program ROM from $8000-$FFFF, does that mean that at all those address values the 2A03 is potentially taking in instructions/etc on the data pins?
I've been reading about how the NMI works here (
http://www.6502.org/tutorials/interrupts.html), but I need more clarity. When I trigger an NMI, the program jumps to the address location set by the NMI vector, is that right? At that address is it only executing program data already on the chip, or can it read instructions from the data pins? How do I set the NMI vector?
Again, thanks to everyone for helping me. I'm sure these are beginner questions.
skrasms wrote:
If I do the glue logic for an 8-byte ROM, is there a special reason to trigger the data starting at $FFF6 ?
One of the first things that any 6502 does when powering up is read an address from the reset vector, and then jump (set the program counter) to that address. This reset vector happens to be located at $FFFC and $FFFD, which is ordinarily ROM. I chose $FFF6 so that the JMP instruction to handle the next byte would overlap the reset vector.
Quote:
A table I found online shows program ROM from $8000-$FFFF, does that mean that at all those address values the 2A03 is potentially taking in instructions/etc on the data pins?
The 6502 family can execute code from any address, except possibly an on-chip I/O area. The 6510 in Commodore 64 computers has an I/O area, and so does the 2A03. Any other ROM or RAM address can contain code. (It's possible to jump to the I/O area, but here be dragons.)
Quote:
When I trigger an NMI, the program jumps to the address location set by the NMI vector, is that right? At that address is it only executing program data already on the chip, or can it read instructions from the data pins?
No program data is stored on the 2A03 chip.
Quote:
How do I set the NMI vector?
By putting values into $FFFA-$FFFB, which is ordinarily ROM. In the case of glue logic faking the presence of a ROM, this involves putting the low byte on the data bus when the value on the address bus is $FFFA, and putting the high byte on the data bus when the address is $FFFB.
Code on the NES is usually $8000-$ffff because it's where its the most easier to map. It is necessary to have at least 6 bytes of ROM at $fffa-$ffff for the NMI, reset and IRQ vectors. (NMI and IRQ aren't required if you don't use those interrupts, any NES game will have to use NMI, but technically nobody forces you to use it, on a hardware project if you put NMI and IRQ pin high (with a coupling cap) you will have no problem).
It is possible to map ROM anywhere on the NES, but you don't want it to conflict with anything else. Since $4000-$4014 are write only and (correct me if I'm wrong) returns open bus, it is possible to map ROM here (not RAM tough). $4015-$4017 are read/write, so you cannot map ROM here because this will conflict (altrough not all bits are used). It is possible to map ROM to $4018-$7ffff as well as $8000-$ffff with proper hardware (unless $4015-$4017 mirrors somewhere else, I don't remember in fact).
The chip itself does not know what hardware is arround him, so if you power a 2A03 alone it will read from the reset vector, read open bus (most likely FFFF) and will jump to $ffff, read it, found $ff and since this is no valid instruction it will freeze or something. Prior to this the chip should try to write to $1ff and $1fe or something, phushing it's older adress and processor status as reset is also an interrupt. If you didn't connect /IRQ and /NMI to high, I'd be afraid things are much worse.
skrasms wrote:
Hello everyone, this is my first post here. I am working on a project that will interface a 2A03 with a microcontroller to make a synthesizer module. It will bring together what I already know about analog circuits and hopefully give me some new skills on the digital side.
Right now my biggest problem is figuring out what data I need to send to the 2A03 to get the audio up and running without any other chips (except a microcontroller) present. I have never worked with a bare CPU before. I have read over the addresses and data values to work with the sound channels, but what else is necessary? Does the 2A03 need specific instructions to be initialized? Once it is running is it just a matter of writing new data bits to the audio channel addresses?
If I can get the software figured out then I should be good to go. I'm an electrical engineer, but not a computer engineer. If anyone can help me it would be greatly appreciated. If anyone is interested I can try to document the project as it progresses.
Welll, you're in luck. I worked out a small TTL schematic that will do the trick. It involves no RAMs and no ROMs, and I doubt you could make it any smaller unless you used a CPLD, PEEL, PLD, or FPGA.
It's 6 chips.
The pic is here:
http://kevtris.org/IMG_2306.JPG
How it works:
The above circuit forms some opcodes that the CPU will execute, as well as stuffing in some bytes for the opcodes. Everything is mapped into memory over and over, every 8 bytes.
To the CPU, it looks like this:
0000: A0
0001: <data byte from latch>
0002: 8C
0003:<address byte from latch>
0004: 40
0005: 00
0006: 00
0007: 00
So, on reset, the CPU fetches data from FFFC and FFFD and will pull the reset vector 0040h (since it's coming from addresses 4 and 5).
This will get the ball rolling, and the CPU will perform the following three opcodes:
0040: LDY #<data byte>
0042: STY 040<address byte>
0045: BRK
the BRK then fetches the address from 6 and 7 which points to 0000h.
0000: LDY #<data byte>
0002: STY 040<address byte>
0005: BRK
and the cycle repeats, over and over again. Your microcontroller or whatever can simply monitor one of the read toggles on one of the latches to figure out when the chip has performed the write. This will allow for single write precision. When you are done writing, simply set the address to some non-sound register value such as 03fh or 0ffh or something. Technically, you only need the lower 5 bits of the address, and you can hardwire the upper 3... Then, address 01fh can be used for "no write", since there's no register sitting at 0401fh.
* * * * *
I dunno if it was mentioned already, but why don't you just use the 2A03 itself as your CPU? It's got more than enough horsepower for what you're wanting to do most likely.
My, it's amazing how you got something that small and that smart.
And the guy probably doesn't know about programming 6502, nor does he want external ROM/RAM on the circuit.
Thank you very much for that detailed post, kevtris!
The reason I'm using a microcontroller is because I need a minimum of 6 channels A/D (pitch and volume for each channel), in addition to reading in several rotary encoders. There is also a lot of math in the pitch mapping that I need, and I would not know where to begin without being able to code that in C.
Your schematic looks great. I think I understand the process, but I'll need some more time to digest the whole thing
By the way, you're in Indianapolis? That isn't so far from me up here at Purdue West Lafayette.
That "discrete ROM" approach is really simple. The '138 decodes the address into 8 individual enable lines, each of which "enable" that byte of the ROM. The two data values you provide are simple. The other values are gated by the remaining '245, and generated by a clever combination of gates (and choice of which opcodes to use).
blargg wrote:
That "discrete ROM" approach is really simple. The '138 decodes the address into 8 individual enable lines, each of which "enable" that byte of the ROM. The two data values you provide are simple. The other values are gated by the remaining '245, and generated by a clever combination of gates (and choice of which opcodes to use).
Yes, I think I understand the process. The problem is that I am not able to code the logic into a microcontroller, which makes me think there is something I am missing. I was hoping to simulate the process, but whenever I connect the 2A03 to my microcontroller and reset it the results are always different. Sometimes the address pins do not change at all, other times they cycle but the read/write pin always stays high. Here is what my microcontroller code looks like, it's basically a truth table.
Code:
void data2bus(unsigned data);
void codeout(unsigned dat, unsigned add);
void data2bus(unsigned data){
LATF = (data & 0xF0)>>4;
LATD = data & 0x0F;
}
void codeout(unsigned dat, unsigned add){
switch(LATB)
{
case 0x0 : data2bus(0xA0);
case 0x1 : data2bus(dat); //data to write
case 0x2 : data2bus(0x8C);
case 0x3 : data2bus(add); //lower byte of address
case 0x4 : data2bus(0x40);
case 0x5 : data2bus(0x00);
case 0x6 : data2bus(0x00);
case 0x7 : data2bus(0x00);
}
}
void main() {
ADPCFG = 0xFFFF; //--- turn off A/D inputs
TRISB = 0xF; //input 3 LSB of address data to port B
TRISC.F13 = 1; //RW pin connection
TRISD = 0xF; //PORT D for LSB of data, start as input
TRISF = 0xF; //PORT F for MSB of data, start as input
while(1){
if (LATC.F13){ //if read pin is high
TRISD = 0x0; //set data latches to output
TRISF = 0x0;
codeout(0x10, 0x10);//write some data
}
else{
TRISD = 0xF; //set data latches as input
TRISF = 0xF;
}
}
}//~!
First off, your switch statement is missing breaks, so every one falls through. Second, is the microcontroller fast enough?
blargg wrote:
First off, your switch statement is missing breaks, so every one falls through. Second, is the microcontroller fast enough?
Not sure how I forget every break statement, thanks for pointing that out. The micro is running at 40 Mhz right now, do I need it faster than that?
Maybe. I would go about this project by programing a generic sound generator for 2A03 and interface it to the MCU via a few control registers that the 2A03 periodically services. Are you sure you even need a MCU? Thought of having the 2A03 in control and giving the MCU only the difficult tasks?
skrasms wrote:
blargg wrote:
First off, your switch statement is missing breaks, so every one falls through. Second, is the microcontroller fast enough?
Not sure how I forget every break statement, thanks for pointing that out. The micro is running at 40 Mhz right now, do I need it faster than that?
I highly doubt you'll get it to work this way. You pretty much need the TTL circuit. The micro isn't going to be fast enough.
Even IF it was, the micro could never do anything but feed the 2A03. It cannot do any kind of processing since it's going to be too busy.
If you wanna try it still though, you're going to have to program your micro in asm, since C is a crap shoot when it comes to wringing every cycle out of the code.
It will be an absolute cycle timed mess to get it working I think.
I was thinking that maybe he could get it working if his micro's clock was some integer multiple of the 2A03's, and he could keep it occupied most of the time without having to feed it different bytes. Maybe some fixed byte value that gets it stuck in a loop, which he can then get it out of reliably. It would be neat to babysit the 2A03 with just the micro's built-in I/O ports.
blargg wrote:
I was thinking that maybe he could get it working if his micro's clock was some integer multiple of the 2A03's, and he could keep it occupied most of the time without having to feed it different bytes. Maybe some fixed byte value that gets it stuck in a loop, which he can then get it out of reliably. It would be neat to babysit the 2A03 with just the micro's built-in I/O ports.
True, true. you could feed it 4ch which would get it stuck in a jump loop:
4C4C: 4C 4C 4C
though, getting it into and out of the loop reliably might be difficult to do. You could watch the lower few address lines I guess. In any event, it will never work programming it in C. You do not get any kind of cycle by cycle control which is absolutely required.
And yah, you'd have to synch both of them pretty much to the same clock for any hope in hell of this working. Running both off the same clock would work- or some multiple thereof. The NES CPU isn't going to whine too much if he clocks it at 20MHz instead of 21.47727, so that could make it easier to lock it to the CPU (he said ran at 40MHz). Just a little adjustment of the note tables is all that needs to be done.
DPCM cannot be used though regardless of how he does it, but I dunno if that will be a problem or not.
4C, perfect! OK, so here's the wiring:
40 MHz micro clock goes to micro, divide-by-2 to get 20 MHz, which goes to 2A03.
Micro's 8-bit out port goes to 2A03's data bus.
2A03's A0 or A1 line goes to input bit on micro.
So total usage on micro is 8 output pins and one input.
Algorithm:
- Keep $4C on the data bus at all times (might need to have 2A03's reset delayed slightly compared to micro's, easy with an R-C delay).
- When you're ready to write to 2A03 sound register, wait until input goes high, then wait a little bit more. The 2A03 will be in a loop of JMP $4C4C, so the low two address bits will be cycling through a 00, 01, 10 sequence, making either suitable.
- Send the following sequence: A0 xx 8C yy 40 xx 4C, where xx is the data and yy is the register. The writes should be timed so that each one occurs every 24 micro clocks (assuming it's running at 40 MHz). The last 4C byte should stay on the output bus indefinitely. Note that the byte just before the last is the data value repeated again. This is because the 2A03 will be writing to the sound hardware during that clock, and you'd rather not be competing with it. You might have to put resistors between the micro and the 2A03 data bus, since there will be some mis-synchronization during the next to last byte when it's writing.
- Once done, 4C stays on the bus and your micro can go do other things and not worry about the 2A03.
You'll have 24 micro clocks between each data byte, which should be plenty.
DPCM can't be used no matter how it's done? Or is that only a problem if I use the microcontroller to tend the 2A03 directly?
Since the microcontroller already has other data to be concerned with, and I don't know any assembly, Kevtris' circuit seems perfect. I put it together this week, and it's getting the 2A03 to loop instructions.
I haven't made the MCU code to feed the 245's yet. I imagine it's a matter of having the MCU update the address and data values into the 245's on each low->high transition of the enable to the address 245. Would there be any reason not to add a latch in front of one of the 245's to free up pins on the MCU? That way I would only need 8 output pins + 1 for handling the latch, instead of 16 output pins.
My thought process so far is:
1) Address 245 enable transition low to high triggers interrupt on MCU
2) MCU already has in memory what the next address/data values to write are, and sets 8 pins to the next data byte
3) Those 8 pins are connected to latch in front of the data 245, and another pin is used to latch the data
4) The same 8 pins are also connected directly to the address 245, and once the data byte is latched they switch to the address byte and hold until the next interrupt
5) Repeat
Does that sound reasonable in terms of logic and timing?
If you want DMC, you'll need to put a ram chip in there, some way of sending data to the ram chip, then some way to tell the cpu to boot. Then it's just NES programming from there.
Or, just feed your sample bytes to $4011 (register $11 with the interface). For DMC authenticity, each successive byte should be either +2 or -2 from the previous, within the range 0 to 126.
But don't these microcontrollers already tend to have a DAC on them, to which one could feed values from 0 to 63?
Quote:
But don't these microcontrollers already tend to have a DAC on them, to which one could feed values from 0 to 63?
Not that have non-linearity that affects the volume of the 2A03's triangle and noise channels. If one didn't care about the 2A03's quirks, one could do all the channels with a microcontroller alone.
Not to discourage anyone because this is a healthy technical obstacle, but why not just supply the 2A03 with some RAM and upload your native code to that? It would certainly be a hell of a lot easier than feeding the 2A03 on the cycle level. Plus, said device would be able to natively handle random NSFs as well as run custom code that would then watch the external interface for commands. Plus, if you wanted to, you could modify a random NSF to also watch for external input to modify the output, to create a sort of pseudo "circuit bent" effect (of course, it wouldn't really be circuit bending unless you designed it into the hardware but it'd nonetheless be interesting, hook into the NSF right before it writes to the audio registers and have it modify the data).
Quote:
Not to discourage anyone because this is a healthy technical obstacle, but why not just supply the 2A03 with some RAM and upload your native code to that? It would certainly be a hell of a lot easier than feeding the 2A03 on the cycle level.
How do you get your code into the RAM? How does microcontroller communicate with 2A03? That's several chips: 2A03, RAM, ROM, address decoding, I/O chip, and having to write and debug the 2A03 software.
As the
author stated, the microcontroller is needed for its multi-channel A/D and ability to program in C. I still think the microcontroller-only approach is the best (and coolest), using no extra chips.
If you're only making a music engine, I'd bet a 6502 C compiler would be fast enough.
Dwedit wrote:
If you're only making a music engine, I'd bet a 6502 C compiler would be fast enough.
The problem is that it's not only a music engine; it's made to interface with CV signals on a modular synthesizer, in addition to reading rotary encoders. For example, synth modules use a standard pitch control format of +1V = +1 octave. So I need to sample an analog voltage and scale the digital result to be base 2 logarithmic before it can set the pitch of an NES channel. I would not know where to begin doing that math in assembly, but I can write it out quickly in C
Sorry I have been quiet for a while. I put the NES project on a little break while I worked on an Atari synth module. A lot of the microcontroller code will be the same for both. The Atari synth is almost done except for a single algorithm speed problem.
Speaking of which, is there a fast method to calculate 2 to a rational power? The C pow(2, x) compiled on my micro takes almost a full millisecond to execute! I wrote my own function that's accurate to 0.06% using a mix of math and small lookup tables, but it still takes around 200 micro seconds. I hope asking about such is not going too far off topic on this forum.
skrasms wrote:
The problem is that it's not only a music engine; it's made to interface with CV signals on a modular synthesizer, in addition to reading rotary encoders. For example, synth modules use a standard pitch control format of +1V = +1 octave.
Or 1/1200 V per cent of pitch.
Quote:
So I need to sample an analog voltage and scale the digital result to be base 2 logarithmic before it can set the pitch of an NES channel. I would not know where to begin doing that math in assembly
Lookup tables. Lots of lookup tables.
tepples wrote:
skrasms wrote:
So I need to sample an analog voltage and scale the digital result to be base 2 logarithmic before it can set the pitch of an NES channel. I would not know where to begin doing that math in assembly
Lookup tables. Lots of lookup tables.
I wanted to make a lookup table that covered all 12 bits, but I cannot really fit more than a couple hundred values into the micro's RAM. Right now my code is based on table lookups and somewhat simple math. It takes the exponent and breaks it into individual digits, then uses lookup tables to get 2 to that digit.
For example, 2^3.124
= (2^3)*(2^0.1)*(2^0.02)*(2^0.004)
= two1[3] * two01[1] * two001[2] * two0001[4]
Code:
double two1[] = {1.000000000000000, 2.000000000000000, 4.000000000000000, 8.000000000000000, 16.000000000000000, 32.000000000000000, 64.000000000000000, 128.000000000000000, 256.000000000000000, 512.000000000000000};
double two01[] = {1.000000000000000, 1.071773462536293, 1.148698354997035, 1.231144413344916, 1.319507910772894, 1.414213562373095, 1.515716566510398, 1.624504792712471, 1.741101126592248, 1.866065983073615};
double two001[] = {1.000000000000000, 1.006955550056719, 1.013959479790029, 1.021012125707193, 1.028113826656067, 1.035264923841378, 1.042465760841121, 1.049716683623067, 1.057018040561380, 1.064370182453360};
double two0001[] = {1.000000000000000, 1.000693387462581, 1.001387255711335, 1.002081605079633, 1.002776435901078, 1.003471748509503, 1.004167543238973, 1.004863820423785, 1.005560580398468, 1.006257823497782};
double two00001[] = {1.000000000000000, 1.000069317120377, 1.000138639045616, 1.000207965776052, 1.000277297312018, 1.000346633653845, 1.000415974801869, 1.000485320756421, 1.000554671517834, 1.000624027086444};
double mypow(double val);
double mypow(double val)
{
unsigned dig[5];
double old = val;
if(val<0) val = -val;
dig[0] = (unsigned)val;
dig[1] = ((unsigned)(val*10))%10;
dig[2] = ((unsigned)(val*100))%10;
dig[3] = ((unsigned)(val*1000))%10;
dig[4] = ((unsigned)(val*10000))%10;
if(old<0) return(1 / two1[dig[0]] / two01[dig[1]] / two001[dig[2]] / two0001[dig[3]] / two00001[dig[4]]);
else return(two1[dig[0]] * two01[dig[1]] * two001[dig[2]] * two0001[dig[3]] * two00001[dig[4]]);
}
The debugger shows that extracting digits from the input the way I am doing it takes 25+ usec per digit, though, so that's a bit of a bottleneck.
skrasms wrote:
I wanted to make a lookup table that covered all 12 bits, but I cannot really fit more than a couple hundred values into the micro's RAM. Right now my code is based on table lookups and somewhat simple math. It takes the exponent and breaks it into individual digits, then uses lookup tables to get 2 to that digit.
I noticed that your code extracts the decimal digits. Try extracting hexadecimal digits instead; division by 16 is usually much faster than division by 10 on cheap processors. In addition, double is slower than a
fixed-point data type unless your processor has specific circuitry (called an FPU) for handling doubles.
I had to take a break from this project because of school and graduation, but I'm back on it now
Now that I can write registers, I am able to get sound out from the Triangle, Noise, and DMC channels. I tried this order for using the triangle channel:
Write 0x7F to $4008
Write 0x11 to $400A
Write 0x30 to $400B
Any time I write to $400A the triangle channel goes silent. Is that supposed to happen? Writing to $400B brings it back at the updated frequency.
In the same way, after I write to $400E the noise channel goes silent. It comes back when I write any arbitrary data to $400F.
So this process works:
Write 0x1F to 0x400C
Write 0x88 to 0x400E
Write 0xXX (any value) to 0x400F
But this process does not:
Write 0x1F to 0x400C
Write 0x88 to 0x400E
Is that normal behavior?
Oops, it looks like my writing process was also writing some garbage data between iterations. It seems to be working now