Hints on multiplexing multiple timers on a 6522
Hints on multiplexing multiple timers on a 6522
I have four timers, and one 6522. I need to somehow map the four timers onto T1 or T2.
The normal way to do this is to set the hardware timer to min(t1, t2, t3, t4); then, when the timer fires, determine the amount of real time taken, subtract that from the four timers, process any that go negative, rinse and repeat.
Problems:
(1) I have very few cycles to deal with. This needs to be as fast as humanly possible.
(2) Determining the minimum is bad. I think the fastest is an unrolled comparison tree.
(3) Adjusting the timers is really bad as it's multiple 16-bit operations.
(4) Resetting the expired timers is also bad. Either I persist the information about which timers have expired until after I've done the adjustments, at which point I can just load them with the time until the next expiry, or else load them with newtime-min before the adjustments.
(5) I have actual work to do during the downtime between timer expiry, which is why I'm doing it this way rather than using a fixed tick.
That's adding up to a lot of 16-bit operations. I'm on a 1MHz 6502 and my timers will be working at audio frequencies, so cycles really matter.
An alternative approach is to have one 6522 timer in free-run mode, and use this to keep track of the current time. Now the timers don't need to be adjusted at all; I find the minimum, subtract the current time, and that's the value to program into the other timer. Except... on a 1MHz 6502, the timer's only 16-bit wide, so it'll roll over every 65.536ms. Finding the minimum needs to do a modulus comparison, and the normal way to do that is to subtract the bias, which means I still have all my 16-bit operations, just in a different place.
I feel like there's a different way to do this, somehow, but can't think what it is. Anyone want to suggest anything?
The normal way to do this is to set the hardware timer to min(t1, t2, t3, t4); then, when the timer fires, determine the amount of real time taken, subtract that from the four timers, process any that go negative, rinse and repeat.
Problems:
(1) I have very few cycles to deal with. This needs to be as fast as humanly possible.
(2) Determining the minimum is bad. I think the fastest is an unrolled comparison tree.
(3) Adjusting the timers is really bad as it's multiple 16-bit operations.
(4) Resetting the expired timers is also bad. Either I persist the information about which timers have expired until after I've done the adjustments, at which point I can just load them with the time until the next expiry, or else load them with newtime-min before the adjustments.
(5) I have actual work to do during the downtime between timer expiry, which is why I'm doing it this way rather than using a fixed tick.
That's adding up to a lot of 16-bit operations. I'm on a 1MHz 6502 and my timers will be working at audio frequencies, so cycles really matter.
An alternative approach is to have one 6522 timer in free-run mode, and use this to keep track of the current time. Now the timers don't need to be adjusted at all; I find the minimum, subtract the current time, and that's the value to program into the other timer. Except... on a 1MHz 6502, the timer's only 16-bit wide, so it'll roll over every 65.536ms. Finding the minimum needs to do a modulus comparison, and the normal way to do that is to subtract the bias, which means I still have all my 16-bit operations, just in a different place.
I feel like there's a different way to do this, somehow, but can't think what it is. Anyone want to suggest anything?
- GARTHWILSON
- Forum Moderator
- Posts: 8774
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Hints on multiplexing multiple timers on a 6522
How much granularity and jitter can you put up with? What you're describing sounds similar, but not identical, to what I describe in the cyclic-executive part of my article on simple methods for multitasking without a multitasking OS, for systems that lack the resources to implement a multitasking OS, or where hard realtime requirements would rule one out anyway, at http://wilsonminesco.com/multitask/#cycexec . All tasks are like people doing desk jobs in a large room with many workers, all watching the same clock on the wall to know when it's time to do some part of their job, without regard for others' target times. The alarms section just above it might be of interest too. Even if neither is directly suitable, maybe one or both will give ideas.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Hints on multiplexing multiple timers on a 6522
+1 for Garth's post.
In addition: 20kHz(assuming audio as you write it) means 50 cycles between events. You can do alot in 50 cycles.
2nd: are the timers related? I.e. in some kind of fixed relation to each other? In terms of frequency and/or offset. You could pre-compute a full period that has all timer events in there and just lookup the next timer value from the pre-computed table.
E.g. if Timer A has 200 ticks starting at 0 global clock, and T2 has 10 ticks starting at 5, you'd have timer events at
0, 5, 15, 25, ..., 185, 195, 200 and then starting the same sequence again. You could pre-compute the timer values and what timer is relevant on each event, and just increase the table index on every event.
André
In addition: 20kHz(assuming audio as you write it) means 50 cycles between events. You can do alot in 50 cycles.
2nd: are the timers related? I.e. in some kind of fixed relation to each other? In terms of frequency and/or offset. You could pre-compute a full period that has all timer events in there and just lookup the next timer value from the pre-computed table.
E.g. if Timer A has 200 ticks starting at 0 global clock, and T2 has 10 ticks starting at 5, you'd have timer events at
0, 5, 15, 25, ..., 185, 195, 200 and then starting the same sequence again. You could pre-compute the timer values and what timer is relevant on each event, and just increase the table index on every event.
André
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
-
GlennSmith
- Posts: 162
- Joined: 26 Dec 2002
- Location: Occitanie, France
Re: Hints on multiplexing multiple timers on a 6522
Lots of ideas !
A method I use in some automaton devices I have designed and built is to use just one clock, the fastest possible, and create a list of events (can be pre-calculated, as above, or not) with a timestamp. At each increment of the clock a quick check is made to see if the next timestamp matches current time and the event is processed. In my case the events are a linked list 'cos they correspond to many different types of event to be handled - but they could just be an index into a jump table... If an event is to be re-scheduled, the calculation (current time + delay = new timestamp), and insertion into the list is done outside of the interrupt routine.
IIRC I 'stole' this technique from the HP1000 RTE system !
A method I use in some automaton devices I have designed and built is to use just one clock, the fastest possible, and create a list of events (can be pre-calculated, as above, or not) with a timestamp. At each increment of the clock a quick check is made to see if the next timestamp matches current time and the event is processed. In my case the events are a linked list 'cos they correspond to many different types of event to be handled - but they could just be an index into a jump table... If an event is to be re-scheduled, the calculation (current time + delay = new timestamp), and insertion into the list is done outside of the interrupt routine.
IIRC I 'stole' this technique from the HP1000 RTE system !
Glenn-in-France
Re: Hints on multiplexing multiple timers on a 6522
GlennSmith wrote:
Lots of ideas !
A method I use in some automaton devices I have designed and built is to use just one clock, the fastest possible, and create a list of events (can be pre-calculated, as above, or not) with a timestamp. At each increment of the clock a quick check is made to see if the next timestamp matches current time and the event is processed. In my case the events are a linked list 'cos they correspond to many different types of event to be handled - but they could just be an index into a jump table... If an event is to be re-scheduled, the calculation (current time + delay = new timestamp), and insertion into the list is done outside of the interrupt routine.
IIRC I 'stole' this technique from the HP1000 RTE system !
A method I use in some automaton devices I have designed and built is to use just one clock, the fastest possible, and create a list of events (can be pre-calculated, as above, or not) with a timestamp. At each increment of the clock a quick check is made to see if the next timestamp matches current time and the event is processed. In my case the events are a linked list 'cos they correspond to many different types of event to be handled - but they could just be an index into a jump table... If an event is to be re-scheduled, the calculation (current time + delay = new timestamp), and insertion into the list is done outside of the interrupt routine.
IIRC I 'stole' this technique from the HP1000 RTE system !
André
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
Re: Hints on multiplexing multiple timers on a 6522
This is for a PFM audio engine. Each event is a pulse being generated on the speaker. The timeouts will keep changing as the voices change pitch and timbre.
...so for context, I did this: https://www.youtube.com/watch?v=B_SmzoQJBsQ It's a port of some music and an engine by Tim Follin for the Spectrum. You might notice it's horribly out of tune. This is because Follin's engine doesn't have any reliable timing; it's implemented as a state machine, where notes are timed based on the number of iterations through the state machine. Except, nothing in the state machine runs at a predictable speed; the timing depends on how much work is being done and so the pitch of each note depends on what other notes are being played. Follin, of course, is a genius and I think he just adjusted all the note data to compensate. Unfortunately, my port of his engine works differently and so the timing is all off.
The simplest thing would be to adjust the state machine so each iteration takes a fixed amount of time, but that's a bit problematic --- in the worst case where all four notes fire at the same time we end up doing quite a lot of work, and to do all that in a single iteration means that the timing granularity has to be very coarse, while will again affect tuning. I'm hoping that by using a tickless timer I can avoid having to worry about granularity at all.
Because this is audio I can tolerate a certain amount of jitter, provided that all jitter is compensated for (to keep the notes in tune).
Maybe I'd be best off reversing the problem. Instead of waiting for the next event, simply run my state machine as fast as possible but measuring how long each iteration takes. Subtract that from each timer, and if the timer goes negative then it fires. The bulk of the time will be spent performing subtractions (additions, really, as the timer counts down), but now the timers don't need to be ordered. That may be both cheaper and more robust. I'll have to try it and see what happens.
...so for context, I did this: https://www.youtube.com/watch?v=B_SmzoQJBsQ It's a port of some music and an engine by Tim Follin for the Spectrum. You might notice it's horribly out of tune. This is because Follin's engine doesn't have any reliable timing; it's implemented as a state machine, where notes are timed based on the number of iterations through the state machine. Except, nothing in the state machine runs at a predictable speed; the timing depends on how much work is being done and so the pitch of each note depends on what other notes are being played. Follin, of course, is a genius and I think he just adjusted all the note data to compensate. Unfortunately, my port of his engine works differently and so the timing is all off.
The simplest thing would be to adjust the state machine so each iteration takes a fixed amount of time, but that's a bit problematic --- in the worst case where all four notes fire at the same time we end up doing quite a lot of work, and to do all that in a single iteration means that the timing granularity has to be very coarse, while will again affect tuning. I'm hoping that by using a tickless timer I can avoid having to worry about granularity at all.
Because this is audio I can tolerate a certain amount of jitter, provided that all jitter is compensated for (to keep the notes in tune).
Maybe I'd be best off reversing the problem. Instead of waiting for the next event, simply run my state machine as fast as possible but measuring how long each iteration takes. Subtract that from each timer, and if the timer goes negative then it fires. The bulk of the time will be spent performing subtractions (additions, really, as the timer counts down), but now the timers don't need to be ordered. That may be both cheaper and more robust. I'll have to try it and see what happens.
Re: Hints on multiplexing multiple timers on a 6522
Did you see this https://youtu.be/3SlRbYfyNRY ?
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
Re: Hints on multiplexing multiple timers on a 6522
hjalfi wrote:
In the video you used the Shift Register to output good quality audio samples.
I'm not sure then I fully understand how your new approach should work. Do you want to generate the audio on system? Or do you want to go back from using the SR to bit banging?
André
Last edited by fachat on Wed Apr 30, 2025 9:00 pm, edited 1 time in total.
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
Re: Hints on multiplexing multiple timers on a 6522
hjalfi wrote:
Because this is audio I can tolerate a certain amount of jitter, provided that all jitter is compensated for (to keep the notes in tune).
Are you generating tones by directly simulating an oscillator? Each frequency on a single oscillator, with perhaps some sort of level control?
Neil
Re: Hints on multiplexing multiple timers on a 6522
Quote:
An audio engineer would certainly take exception to that statement; the ear is _amazingly_ good at hearing jitter; it translates directly as noise...
Yes, I'm trying to simulate an oscillator. I'm using a technique I learned about recently called PFM, where short spikes are generated at a frequency which corresponds to the note. It produces a sort-of rough edged squarewave with lots of overtones; by changing the pulse length you can change the timbre and volume. There's a whole genre of music called 1-bit music, mostly played on the Spectrum with a 4MHz Z80. I'm trying to do this on the PET.
I do have a proof of concept (see https://www.youtube.com/watch?v=B_SmzoQJBsQ) but it needs to be, well, in tune. Unfortunately I'm working with a 1MHz 6502 which has about half the raw power of the Spectrum... I've been experimenting with various things and I've discovered that:
- I need a timing granularity of below about 100us to keep the notes in tune.
- The PET interrupt latency is pretty big --- 35 (to enter) + 24 (to return) = 59us total.
The minimum logic I've found for playing a note is:
Code: Select all
loop
pulselength := 0
for each channel
ticks[channel]--
if timer[channel] := 0
pulselength := max(pulselength, pulselengths[channel])
ticks[channel] = period[channel]
endif
endfor
generate pulse of length pulselength
endloop
The annoying thing is that I know this can be done --- the linked video proves it! But I need to somehow get it working predictably. I also have other work I need to do in the background, which means either doing the playback from an interrupt handler and wasting 59us per 100us tick, or else calling out to the state machine which does the work from somewhere inside the playback loop... but that call takes 100us!
Re: Hints on multiplexing multiple timers on a 6522
I am not sure I understand your algorithm. To me it looks like pulse width modulation PWM and not pulse frequency modulation PFM https://en.m.wikipedia.org/wiki/Pulse-f ... modulation
Is what you call 'pulse width' the distance between two actual pulses?
What would be your planned pulse width (ie time with output 1) and what would be your pause width (ie time with output 0)?
Is 'generate pulse of length pulselength' then setting the timer for the next interrupt, so that pulselength would be the distance between interrupts = 1 pulse per interrupt?
When I thought about how I would implement it I was more like using PWM:
- use a clock granularity t (base frequency 1/t)
- use 8t as base interval
- use the shift register to output a pulse from v=0 to 8 t high and 8-v low for every base interval
- every 8t, mix the voices into a target pulse width, where each voice would be a signed int (byte) added together and then create a SR output value from that
That would relieve the CPU from having to handle each and every bit. And you could still have some higher bit frequencies 1/t than 100us. Not sure what base frequency would be required to achieve good audio output though. And it limits the dynamic as it has 8 output levels only (different pulse lengths, 7 even if you allow for all 0s but not all 1s).
Maybe you could also use the ahift register to your advantage?
Is what you call 'pulse width' the distance between two actual pulses?
What would be your planned pulse width (ie time with output 1) and what would be your pause width (ie time with output 0)?
Is 'generate pulse of length pulselength' then setting the timer for the next interrupt, so that pulselength would be the distance between interrupts = 1 pulse per interrupt?
When I thought about how I would implement it I was more like using PWM:
- use a clock granularity t (base frequency 1/t)
- use 8t as base interval
- use the shift register to output a pulse from v=0 to 8 t high and 8-v low for every base interval
- every 8t, mix the voices into a target pulse width, where each voice would be a signed int (byte) added together and then create a SR output value from that
That would relieve the CPU from having to handle each and every bit. And you could still have some higher bit frequencies 1/t than 100us. Not sure what base frequency would be required to achieve good audio output though. And it limits the dynamic as it has 8 output levels only (different pulse lengths, 7 even if you allow for all 0s but not all 1s).
Maybe you could also use the ahift register to your advantage?
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
Re: Hints on multiplexing multiple timers on a 6522
Now I think of it I suppose it is PWM; but all the documents about 1-bit music call it PFM... basically, to play a 256Hz note, I'm generating 15us pulses where the leading edges are 3906us apart. So that's 15us with output 1, and 3891us with output 0. I think they do it like this to make mixing easier.
There seem to be two main approaches to doing polyphonic music like this. You either timeslice the output at a high frequency, or else just OR (or XOR?) the different voices together. I tried the former but while it worked I couldn't get the timeslice frequency high enough to avoid noise. It also limits timing granularity. I've discovered that 100ms timing granularity is actually way too high. It sounds like it works until I try playing chords and then I find it's all horribly out of tune. 50ms is better, but it's still marginal.
So... I did get it working, by trying a misreading of your idea. Video: https://www.youtube.com/watch?v=AHU2cInIELc Code: https://github.com/davidgiven/ptracker/ ... src/main.S (Note that the obviously out-of-tune section is because the MIDI file I got the note data from is wrong. I don't think it's my fault!) This handles three voices by generating data a bit at a time, in batches of eight bits. It works, but again, it's marginal. There's only just enough CPU bandwidth left to run the player code and it grinds to a halt if I try and enable the fourth voice. I managed to generating one bit of data down to 20 cycles per voice:
This is then unrolled three times per voice; Y is a flag which if non-zero means that we shift one bit into an output variable. Repeat eight times gives us a complete byte of data, which is sent to the shift register.
(Edit: I've just spotted one.
But of course, that's not what you actually _said_... hmm. If I've understood you correctly this time, doing it with one sample per SR byte means I'm only doing the note calculation every 8t, which limits the timing granularity, which as I've discovered is critical. So I'm not sure that would help as I'd have to crank the bitrate so high to get decent timing granularity that there wouldn't be any benefit. But it does allow playback of 3-bit PCM data! I don't think this would be fast enough from an interrupt handler but it could well allow playback of something like MOD files, which would be very cool.
There seem to be two main approaches to doing polyphonic music like this. You either timeslice the output at a high frequency, or else just OR (or XOR?) the different voices together. I tried the former but while it worked I couldn't get the timeslice frequency high enough to avoid noise. It also limits timing granularity. I've discovered that 100ms timing granularity is actually way too high. It sounds like it works until I try playing chords and then I find it's all horribly out of tune. 50ms is better, but it's still marginal.
So... I did get it working, by trying a misreading of your idea. Video: https://www.youtube.com/watch?v=AHU2cInIELc Code: https://github.com/davidgiven/ptracker/ ... src/main.S (Note that the obviously out-of-tune section is because the MIDI file I got the note data from is wrong. I don't think it's my fault!) This handles three voices by generating data a bit at a time, in batches of eight bits. It works, but again, it's marginal. There's only just enough CPU bandwidth left to run the player code and it grinds to a halt if I try and enable the fourth voice. I managed to generating one bit of data down to 20 cycles per voice:
Code: Select all
.macro synth var, varp, varm
ldx \var ; 3 ; current note timer variable
dex ; 2
zif eq ; 2 ; macro turns into bne
ldx \varp ; 3 ; if expired, reset the timer to the note period
zendif
stx \var ; 3
cpx \varm ; 3 ; 'mark' length of pulse for this note
zif cc ; 2 ; macro turns into bcs
iny ; 2 ; make Y flag non-zero
zendif
.endm ; = 20
(Edit: I've just spotted one.
But of course, that's not what you actually _said_... hmm. If I've understood you correctly this time, doing it with one sample per SR byte means I'm only doing the note calculation every 8t, which limits the timing granularity, which as I've discovered is critical. So I'm not sure that would help as I'd have to crank the bitrate so high to get decent timing granularity that there wouldn't be any benefit. But it does allow playback of 3-bit PCM data! I don't think this would be fast enough from an interrupt handler but it could well allow playback of something like MOD files, which would be very cool.
Re: Hints on multiplexing multiple timers on a 6522
What's your output frequency range? Two octaves either side of middle C covers ~130Hz to ~1046Hz, (2093 for C7; 4186 for C8) and the higher your base tone the more critical the frequency or chords/harmonies will be really unpleasant.
I do wonder if there's a possibility of doing something similar to a continuous additive mix (where you have e.g. a sine wave sample and add a value (the inverse of the frequency) to get a current sample at any frequency. Two or three blocks with different harmonics might offer sufficient range? I need to think about this - it's still only half an idea. e.g. the tables might be as simple as a fast routine to calculate whether the output should be set or cleared, rather than an actual table in memory.
How are you mixing your voices? Just an OR (that works, but it's not wonderful) or the somewhat better separate outputs with resistive mixing?
Neil
I do wonder if there's a possibility of doing something similar to a continuous additive mix (where you have e.g. a sine wave sample and add a value (the inverse of the frequency) to get a current sample at any frequency. Two or three blocks with different harmonics might offer sufficient range? I need to think about this - it's still only half an idea. e.g. the tables might be as simple as a fast routine to calculate whether the output should be set or cleared, rather than an actual table in memory.
How are you mixing your voices? Just an OR (that works, but it's not wonderful) or the somewhat better separate outputs with resistive mixing?
Neil
Re: Hints on multiplexing multiple timers on a 6522
Using a single pulse every 3906us will give you a base 256Hz but with a lot of overtones. Try imagine a fast fourier transformation (FFT) of this. The best with a single pulse would be 1953us high and then 1953us low.
Now, try to imagine a quantized sine wave across the 3906us, e.g. like
0,1,2,3,3,4,4,4,3,3,2,1,0,-1,-2,-3,-3,-4,-4,-4,-3,-3,-2,-2
Using your base intervall to divide which I have conveniently taken as 3906us/24=163us.
So, every 163u you have to either output 0-8 pulses for PFM or one pulse with length varying from 0 to 8 for PWM.
The output capacitor would then integrate this to a much better approximation of a sine wave.
Of course you can use the shift register to help with that. I'd think that lookup tables could help greatly.
Now the caveats:
- I have actually no idea.how much of an audio signal integration will happen on the PET's speaker (which is a TTL driven piezo beeper only), so ymmv
- I have used a convenient base intervall of 163us for 256 Hz, depending on your desired frequency range you may need smaller intervals - but on the other hand could skip one or the other interrupt if you'd have to output the very same value
- the shift register can run at phi2/2 speed, so each bit can have as few as 4us, or a byte can have as little as 32us. That doesn't leave much room for other processing, but IIRC you can start the SR with a byte and immediately store a second byte to shift out (to be verified). 8 pulses would be 16 bits, so two bytes, so this should come in handy
- Starting the shift register 'immediately' requires special handling of the timer, as the timer is IIRC free running all the time, and the first bit starts at the next underflow. So, if the timer has just underflown, you'll wait 65536us until the next underflow. You can reset the timer to avoid that (I did for fast IEC serial but would have to look up the details)
- zero/quiet handling. You'll have to decide how to handle 'no audio'. I've deliberately left out above - as sine goes above and below zero, you'll have to offset it. But a zero value with offset would be e.g. 4 - 4 pulses/pulse length is not off. So you'll probably have to do some extra handling for quiet output.
Enough for now, hope that helps
André
Now, try to imagine a quantized sine wave across the 3906us, e.g. like
0,1,2,3,3,4,4,4,3,3,2,1,0,-1,-2,-3,-3,-4,-4,-4,-3,-3,-2,-2
Using your base intervall to divide which I have conveniently taken as 3906us/24=163us.
So, every 163u you have to either output 0-8 pulses for PFM or one pulse with length varying from 0 to 8 for PWM.
The output capacitor would then integrate this to a much better approximation of a sine wave.
Of course you can use the shift register to help with that. I'd think that lookup tables could help greatly.
Now the caveats:
- I have actually no idea.how much of an audio signal integration will happen on the PET's speaker (which is a TTL driven piezo beeper only), so ymmv
- I have used a convenient base intervall of 163us for 256 Hz, depending on your desired frequency range you may need smaller intervals - but on the other hand could skip one or the other interrupt if you'd have to output the very same value
- the shift register can run at phi2/2 speed, so each bit can have as few as 4us, or a byte can have as little as 32us. That doesn't leave much room for other processing, but IIRC you can start the SR with a byte and immediately store a second byte to shift out (to be verified). 8 pulses would be 16 bits, so two bytes, so this should come in handy
- Starting the shift register 'immediately' requires special handling of the timer, as the timer is IIRC free running all the time, and the first bit starts at the next underflow. So, if the timer has just underflown, you'll wait 65536us until the next underflow. You can reset the timer to avoid that (I did for fast IEC serial but would have to look up the details)
- zero/quiet handling. You'll have to decide how to handle 'no audio'. I've deliberately left out above - as sine goes above and below zero, you'll have to offset it. But a zero value with offset would be e.g. 4 - 4 pulses/pulse length is not off. So you'll probably have to do some extra handling for quiet output.
Enough for now, hope that helps
André
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/