# Sound synth with a 6502

## Метаданные

- **Канал:** Ben Eater
- **YouTube:** https://www.youtube.com/watch?v=jJi5EAWgyEM

## Содержание

### [0:00](https://www.youtube.com/watch?v=jJi5EAWgyEM) Segment 1 (00:00 - 05:00)

In my previous video, I got my 6502-based breadboard computer to make noise and even sort of play music, but it's pretty limited right now because all it can produce is a square wave at different frequencies. So, how can we get it to produce a wider variety of sounds? Well, here I've got the same LM 386 audio amplifier u and a speaker hooked up similar to how I had it hooked up to the computer in the last video. Uh, in this case, I've got it hooked up to a signal generator so we can see what some different waveforms uh might sound like. And just a quick reminder that I sell these kits to build your own 6502 computer, including the serial interface and programmer that I use. All that's available on my website, eater. net/6502. So, this is a square wave like what I was able to produce in the last video. And of course, we can change the frequency to get different notes, but that's basically all I was able to do in that last video. Uh, but even with the square wave, we can get some different sounds out of it. So, if you look at the square wave right now, it's on for the same amount of time that it's off for. If we change that to on for 40% of the time, it sounds a little different. 30%. Yeah. 20%. And 10%. So, you can see even with one frequency of a square wave, we can create a variety of different sounds just by changing that duty cycle. If I go back to 50%. We can also change the shape of the wave. So, if I go to a triangle wave, that gives a much more mellow sound. And then here's a saw to wave which gives yet another sound. So if we can get the computer to be able to programmatically choose some of those different sounds, it gives us a bit more variety to work with. And with a digital computer, you know, as I showed in the last video, generating a square wave is pretty straightforward. You know, it's digital, so bits are either a zero or a one on or off. So toggling a bit on and off at a particular frequency generates a square wave. In principle, changing the duty cycle to uh 10 20 30% or whatever to create different sounds is doable since it's still a square wave. Um though it's a little tricky. If we look at the data sheet for the 6522 chip, which is this chip that's generating the square wave, we're running timer one in freerun mode. And that has the effect of inverting the signal on PB7 with each counter timeout. So, if we set the timer to time out at double the frequency of the sound we want to produce and invert PB7 each time, uh, it's going to generate a square wave at the frequency we want. If we want to vary the duty cycle, so how much time it's on versus off, we'd have to wait a different amount of time between every other time we invert PB7, that's actually possible because this chip can also uh generate an interrupt each time the counter times out. And in response to the interrupt, we could run some code on the processor that changes the value of the latch timer. So right now we can set the timer to 1136 microsconds to produce a 440 Hz tone. Turn on some measurements here. And you can see it's off for 1138 micro and then on for 1138 micro, which gives us a frequency of about 440 hertz. If we still wanted a frequency of 440 htz, but a different duty cycle, uh we maybe want it on maybe for 250 microsconds and then off for the balance of that time. So, you know, 222 microsconds. Um that's still going to give you 440 hertz, but a different sound. So, to do that, we'd have to set the timer here to uh 250. Then, when it inverts the output, change the timer to 2022. Then, when it inverts the output again, change it back to 250. um and so on back and forth. To do this, the data sheet says that when the timer expires, not only does it invert the signal on PB7, it also sets bit six in the interrupt flags register and triggers an interrupt. And then it says when the microprocessor responds to the interrupt, um it can put new data in those latches and determine the period of the next half cycle. So, we just need to detect that interrupt and run some code that switches the timer back and forth between the long interval and the short interval. Right now, the only thing generating interrupts is the serial interface. And that's this wire right here that goes up to the interrupt request line of the CPU here. If we also want to get interrupts from the 6522, we need to wire that up somehow. And so this interrupt request line or IRQ line is active low. So this resistor here is holding it at 5 volts. Um, unless the serial UART down here uh pulls this line low to signal an interrupt. Well, if we replace this wire with a diode, or I guess a wire and a diode, um, so now we've got a wire uh going down uh through this diode to the same place as before. So, if the interrupt request goes low here, then current can flow this direction through the diode, pulling the IRQ line of the CPU low and signaling the interrupt. Otherwise, the resistor will still hold that high and there won't be any interrupt signaled. So, this uh works the same as before. Now we can add another diode coming from the interrupt pin on the

### [5:00](https://www.youtube.com/watch?v=jJi5EAWgyEM&t=300s) Segment 2 (05:00 - 10:00)

6522 chip. And this will kind of work the same way. And I kind of had to move things around a little bit here, but um it basically works the same way. We've got if this uh 6522 interrupt pin, which is this pin right here, goes low, then current can flow through this diode that's right here, which then uh will allow current to flow from the IRQ line, pulling it low through that diode to the interrupt line on the 6522. And I had to move this diode. It's still pulling this same pin high. It's just I only had two spots over here. So if you follow this wire over here, um this resistor then still um ties it high. So now either chip can signal the interrupt. Uh right? So this resistor is holding this high. Um and then if this chip signals an interrupt, it'll pull it low. it can pull it low. But if one chip is signaling an interrupt by pulling its interrupt line low and the other chip uh is not and it's high um then the diodes work to isolate them to prevent current from flowing from high to low and causing any problems that would otherwise result from hooking two outputs like that together. And so now either of these chips can signal an interrupt and the data sheet actually talks about this. you I'm using the 65C22S uh which has this what's called a totem pole output this stack of transistors here and so there's an output transistors that either actively pull the output high or um pull it low uh depending on the state of the IRQ signal and that's pretty standard for most logic outputs on most chips but the older 65C22N had this uh open drain output with a pull down only and that was nice because you could uh just wire several of these IRQ lines together with an external pull up resistor and then the whole collection of them would either be pulled up by that resistor or you know one or more any of these could actively pull it low to signal an interrupt. Uh but with the newer design the data sheet says that you know um a solution is to place a low voltage diode in series with the totem pole output and that's what we just did. Now let's take a look at the current interrupt handler. And here it is. And the first thing we do other than saving registers on the stack is read ACIA status. That's because reading the status register acknowledges and resets the interrupt from the serial interface. So here's the data sheet for the UART. And you can see for the status register, it says that bit 7 goes to one whenever interrupt condition occurs and then goes back to zero when this uh status register is read. So we read the status register here to acknowledge that interrupt, but we don't actually look at what we read. We just move on and read the data. And you can see from the comment here that that's because until now incoming data on the serial port was the only possible source of interrupts. But now that that's not true anymore, we actually need to check to see if the interrupt actually came from the serial interface before doing the rest of this stuff. So to do that, we've got this ACIA status. Um, so if the ACIA or the UART chip in fact caused an interrupt, bit seven of the status bit will be set. And bit seven, the top bit is also considered to be the sign bit. So if the A register were meant to contain a signed seven bit number, that top bit indicates whether it's negative or not. Which means we could do a uh BMI or branch if minus instruction or a BPL for branch if positive instruction to do something different based on how that bit is set. In this case, I'll do bpl to not full, which will skip the rest of this code if we're somehow in the interrupt handler, but the interrupt wasn't caused by the UART. Likewise, when we finish handling this incoming data or if, you know, we skipped all this, we want to check to see if the 6522 timer expiring is possibly what caused an interrupt. To do that, we need to check the 6522's uh interrupt flag register. This is register 0D. So we need to go up to the top of this file here and define IFR the interrupt flags register at address 600D since the 6522 addresses on my computer start at 6000. Then in the interrupt handler after possibly handling incoming data from the serial port we can load the interrupt flags register and then branch if positive to a new label IRQ exit and then that label will be here at the end of the interrupt handler. because same as the UART, bit seven of the interrupt flag register indicates whether the 6522 caused an interrupt request. So this uh branch instruction says if there's no interrupt from the 6522, then jump down here and exit the interrupt handler. Then anything we put in here will run each time the timer expires. In other words, each time the output to our speaker inverts or every half cycle. So in here, if we change the timer duration every half cycle, we can get whatever shaped square wave we want. So to get a square wave with an arbitrary duty cycle, we need to know basically three things. the time for this half cycle, and then we'll have

### [10:00](https://www.youtube.com/watch?v=jJi5EAWgyEM&t=600s) Segment 3 (10:00 - 15:00)

to figure out which phase we're in, either this one or this one. So I'm going to go into zero page. s and reserve a couple bytes in the zero page, which is the first 256 bytes of RAM. Um, and reserve a couple bytes in there to store those things. So down here at the bottom of that file, I'll first do if eater and end if. Uh, so you know that whatever I'm adding here only gets built into Microsoft Basic for my computer. And I'll make a variable sound one and reserve two bytes for that to keep track of the first interval and sound two with two bytes to keep track of the second interval. So I'll save that and go back to the interrupt handler. And what we're going to do here is, you know, the interrupt handler is called each half cycle and we need to figure out which half we're in. Well, the output to the amplifier is pin PB7 here. So, we can just load the contents of port B into the A register. And then bit seven of this will either be high or low depending on which phase we're in. Conveniently, like we've seen before, bit 7 is the minus flag. So, we can branch if the minus flag is set to somewhere to say that if PB7 is set, we'll reset the timer to whatever is in that sound two variable that we just defined in the zero page. Otherwise, we'll drop down here and we'll set the timer to whatever is in the sound one variable. And to do that, the timers are two bytes. So we'll load the second bite of sound one and store that in timer one latch low. Then we'll load the first bite of sound one and store that in timer one latch high. So loading those timer latches, we'll set the timer up for the next half cycle. And then for the other phase, we're jumping to sound two, where we'll need to do the same thing, but load the timer latches from the sound two variable. So, we'll load the second bite of sound two and store that in timer one latch low. Then, we'll load the first bite of sound two and store that in timer one latch high. And if we're using uh sound one, at the end of this, we need to jump down to skip the sound two code. So, we'll just jump to interrupt request exit, and that'll jump down here to the IRQ exit label. The other thing we need to do is acknowledge the interrupt. So for the serial interface, reading this ACIA status register here was enough to clear the interrupt. So if more data comes in, you'll still get another interrupt. That's why before when the serial port was the only possible source of interrupts, I still had to read this ACIA status register for the timer interrupt. Just reading the interrupt flags register here is uh not enough. Depending on what caused the interrupt, there's a table here that tells you what you need to do in order to clear that interrupt. And in our case, we're assuming that any interrupt that we get from the 6522 is timer one expiring. So to clear that interrupt, we either need to read from uh the timer one counter low bite, which we could do. Or if we write to the timer one uh latch high bite, that'll also clear the interrupt. And we're already doing that. Either way, if we got an interrupt from the 6522, we're writing to timer one latch high either here or here. And so that should work for our interrupt handler. The question now is how do we set the sound one and sound two variables to what we want in order to get the sound that we want. Let's look at the Microsoft basic beep instruction that I added in my last video. So this instruction takes two parameters. A timer value for how long each half cycle is, say 1136 uh micro and then a duration for the square wave, say 100. If each half cycle is 1136, then the full cycle is going to be 2272 and 1 over 2272 microsconds is about 440 hertz. So that's how it works. Now, what we want to be able to do is say something like beep 1136 100. So we can independently set the duration of each half cycle. Obviously, that doesn't work just yet, but let's take a look at the code for the beep instruction. Here, we're setting the timer up with the first parameter. And we can also just store this into the sound one variable. So, we get the low bite and store that in the timer 1 counter low register. Well, we can also store it in the second bite of sound one. Then, when we get the high bite, we store it in timer 1 counter high. We can also store that in the first bite of sound one. And then we'll have to add some additional code to get the second parameter. and store that in sound 2. And to get another parameter, there are some Microsoft basic sub routines that I've covered in another video that will do this easily for us. So we need to call uh checkcom to tell the parser that we're expecting a comma. then formulate the eval to parse a number variable or something whatever the next parameter is given as and then call make int to turn that parameter into an integer which we can then get at uh the floatingoint accumulator address plus4 and plus three just like I'm already doing for the

### [15:00](https://www.youtube.com/watch?v=jJi5EAWgyEM&t=900s) Segment 4 (15:00 - 20:00)

first parameter. So load FAC plus4 and then store that in the second bite of sound two and load FAC plus three and store that in the first bite of sound two. And so that'll let us set sound one and sound two to whatever we want to get different sounds. And I think the only other thing we need to do is make sure we enable interrupts whenever we're making sound. Because right now we're starting the square wave to start making sound, but this just enables that square wave on PB7. are not necessarily enabling the interrupts each time PB7 toggles. To do that, we have the uh interrupt enable register. And the way it works is there are all different interrupts that the 6522 can produce. We're only dealing with timer one interrupts. So to enable interrupts, it says if bit 7 is a one, then each one in bits 0 through six enables the corresponding interrupt. So we'd set this to one, and then the rest zero. That would enable timer one interrupts. So that looks something like this, which in hex would be a C 0. Then to disable timer 1 interrupts, it says if bit 7 is a zero, then each one in bits 0 through six disables the corresponding interrupt. So to disable timer 1 interrupts, we do 0 1 and then all the rest zeros. So that would be 40 hex to disable interrupts. So now in the code where we start the square wave, we also want to enable interrupts. So we have to write a C 0 to the interrupt enable register. And conveniently we already have a C 0 in the A register here. So we just need to store that into the interrupt enable register. Then when we stop the square wave, we can turn off interrupts. So we'll load a with 40hex and then store that to the interrupt enable register to turn off interrupts. And that should be all we need. So let me save that and build it. And it looks like we forgot to define some things. Oh yeah, the interrupt enable register that we just added as well as the timer one latch high and low. So I is the uh interrupt enable register which is register E. So go back to sound. s and then up here I'll define ER as 600E. Then we've got the timer one latches. So timer one low order latch is register six. timer one high seven. So go to BIOS and we'll just put those here. So T1 latch low is at 66 and T1 latch high is at 67. So let's save that. Build it. There we go. And I'll write that to an EPROM. Get the EPROM back into the computer. Reset it. And if I run address 800 0, which is where basic is, I should be able to do beep 1136 uh say 100. And that gives me a 440 Hz tone. And here it is on the scope. And you can see it's about 440 Hz. And the uh negative side here is 1138 microsconds. Close enough. Positive is also the same about 1138 uh microsconds. So that's pretty close to what we asked for. Let's try beat 2022 250 100. And this is also going to be 440 hertz. And sure enough, it's about 440 htz. But now the positive width here is uh you know close to that 250 micros we asked for. and the negative width is the rest which is the 2,22 or so microsconds that we asked for. So it's the same note but it sounds different. And just so you can hear that more clearly, you know, here's a few more examples of, you know, a bunch of different 440 Hz sounds that we can now make. And so those are all 440 htz, same note, different sound. And here's a little bit longer example of, you know, playing the same notes uh using two different duty cycles. You can definitely tell the difference in sound. And so that does kind of sound like two different instruments playing the same notes, but so far at least, you know, as long as all we're doing is changing the duty cycle, we're still fundamentally dealing with square waves. And that's still going to limit the different sounds we can make. But, you know, at the beginning of this video, I also talked about triangle waves and saw waves, and we can approximate those reasonably well just by using a low pass filter and, you know, what we've already got. So right now our square wave signal is coming out here and going through this potentiometer that I'm using as kind of a volume control and then into the amplifier. If I just add a capacitor and this is a 10 microfarad capacitor here. If I just add that between the um signal going in to ground, the capacitor and potentiometer will act as a kind of a low pass filter turning the square

### [20:00](https://www.youtube.com/watch?v=jJi5EAWgyEM&t=1200s) Segment 5 (20:00 - 21:00)

wave into more of a triangle wave because when this output turns on uh rather than immediately going to 5 volts the current will flow into the capacitor to uh you know charge it up and the voltage will rise more slowly. Then when this output uh turns off rather than dropping immediately to 0 volts current will flow you know the capacitor will be charged up so current will flow out of the capacitor through the resistor the voltage will drop off more slowly. The result is that with a 50% duty cycle square wave this will turn it into something more like a triangle wave. So let me run that same program again. And you can see for the first half of that it's almost a perfect triangle wave. It's pretty close. uh for the second half where we're outputting more of a 20% duty cycle or whatever, it's not really a triangle or a saw to you. I could probably use some diodes or something um and different size resistors to make the charge rate and discharge rate different to create more of a saw to uh but even the way it is, it's you know, we're still getting different sounds. There's two different sounds there. And then if I remove the capacitor, there's another two different sounds there. And that's maybe as far as I want to go with this for now because in the next video I want to look at this, the Commodore SID chip. It's designed to work well with the 6502 processor and it can generate several different types of waveforms including variable pulse, which refers to different duty cycle square waves like we've been looking at in this video, as well as triangle saw to. It's got a bunch of other capabilities to explore as well, but this is definitely a project for another video. But, uh, as always, thanks to all my patrons who help make these videos possible. You know, I really appreciate your support. And if you'd like to join them, check out the link in the description. Thanks.

---
*Источник: https://ekstraktznaniy.ru/video/20723*