Hints on multiplexing multiple timers on a 6522

Programming the 6502 microprocessor and its relatives in assembly and other languages.
User avatar
hjalfi
Posts: 107
Joined: 12 Oct 2017

Hints on multiplexing multiple timers on a 6522

Post by hjalfi »

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?
User avatar
GARTHWILSON
Forum Moderator
Posts: 8774
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by GARTHWILSON »

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?
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

+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é
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

Post by GlennSmith »

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 !
Glenn-in-France
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

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 !
That ordered list of events where only the next one is 'current' and needs to be checked is actually how we implemented the events list in the VICE Commodore emulator. That was a great help in getting it up to speed where host CPU time was still at a premium and you had to optimize every bit to get to 100% emulation speed.

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/
User avatar
hjalfi
Posts: 107
Joined: 12 Oct 2017

Re: Hints on multiplexing multiple timers on a 6522

Post by hjalfi »

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.
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

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/
User avatar
hjalfi
Posts: 107
Joined: 12 Oct 2017

Re: Hints on multiplexing multiple timers on a 6522

Post by hjalfi »

> Did you see this https://youtu.be/3SlRbYfyNRY ?

See it? I made it!
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

hjalfi wrote:
> Did you see this https://youtu.be/3SlRbYfyNRY ?

See it? I made it!
Oh wow! Sorry I missed that! (I was in a hurry when posting and didn't look at your link first in depth)

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/
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by barnacle »

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).
An audio engineer would certainly take exception to that statement; the ear is _amazingly_ good at hearing jitter; it translates directly as noise...

Are you generating tones by directly simulating an oscillator? Each frequency on a single oscillator, with perhaps some sort of level control?

Neil
User avatar
hjalfi
Posts: 107
Joined: 12 Oct 2017

Re: Hints on multiplexing multiple timers on a 6522

Post by hjalfi »

Quote:
An audio engineer would certainly take exception to that statement; the ear is _amazingly_ good at hearing jitter; it translates directly as noise...
This apparently turns out to be the case!

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
This adds up to be about 25us (not including any pulse generation). The pulses themselves want to be up to about 80us long.

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!
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

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?
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/
User avatar
hjalfi
Posts: 107
Joined: 12 Oct 2017

Re: Hints on multiplexing multiple timers on a 6522

Post by hjalfi »

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:

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
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.
barnacle
Posts: 1831
Joined: 19 Jan 2004
Location: Potsdam, DE
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by barnacle »

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
fachat
Posts: 1124
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: Hints on multiplexing multiple timers on a 6522

Post by fachat »

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é
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/
Post Reply