[Electronics]  [Files]   [Links]

 
Electronics » the Z80 project » soundcard..

I wasn't that statisfied with my PIC-based sound board - it had quite a bit of noise in its output, was cumbersome to update the sound channels, had untidy microcontroller code and was generally inelegant in design. Still, as a first try it could've been worse.


Soundboard Version 1 - Hmmmmm..

My original idea was to emulate the C64's SID chip (there's an interesting interview with its creator Bob Yannes here) but the PIC microcontrollers I was using weren't fast enough. The Ubicom SX28 with its 75MHz capability, on the other hand, would be.. Simple waveforms like sawtooth, triangle and variable pulse could be generated using the phase accumulating oscillator principle (desc below) and white noise would just need a random number generator. In addition, sampled sound could be played using the the SX's internal SRAM buffer with a Z80 interrupt generated every half-buffer period to get wave data updates.

A few sums revealed that 4 channels would easily be possible at a clock speed of 64MHz and these could be mixed digitally in the microcontroller. This would save having multiple R2R DACs and analogue mixing on the PCB. Also, as the SX has more ports than the PICs I was originally using, it would be easier to update the various sound parameters from the Z80 CPU.

One disadvantage in all this would be a drop in the "purity" of the sound output - it would look (on a scope) like a digitized recording of a SID chip rather than the SID itself. Additionally the digital mixing would reduce the sound resolution unless I had greater than 8 bit output (which I briefly considered but decided it wasn't worth the additonal external logic). In any case I guessed such issues wouldn't be a huge deal - SID emulators on the Amiga always sounded OK and the Soundblaster code I wrote which emulated the Amiga's sound hardware for Giddy3 was also quite acceptible - even at 22KHz output rate. (I'm still not looking for anything like HiFi quality here!)

The Microcontroller Code

I designed my SX sound code to run on the RTCC interrupts at a fixed rate (I left the register update code running "in the background"). One channel's output is calculated every interrupt and all four are mixed every forth to produce the final output for the DAC. The maximum interrupt frequency was arrived at roughly by measuring the longest path through the sound routine code and dividing by 4 (leaving some cycles over for the IRQ overhead and register update code to do its thing when needed). The simple waveforms only took a dozen or so clock cycles each, but they then needed to be scaled to control their volume. White noise was slower with the processing of a 24 bit linear feedback register (desc below) to provide the numbers. The sampled sound playing code took the most SX cycles due to it setting/caching internal pointers and creating the external signals for the Z80 IRQ side* The worst case (including scaling) was about 150 cycles per SX IRQ - but this still meant the SX at 64MHz could update its output at around 100KHz - more than double CD rate (but only 8 bit of course).

(* In my design only one channel can play interrupt-based sampled sound at a time in order to keep the interrupt signalling simple. With some more external logic all four channels could theoretically play IRQ-based samples - their buffer refill signals could set four flipflops and the OR-sum of those outputs would go to the CPU's IRQ line. The CPU would then need to poll the flipflops to see which channel buffer needs new data. I think double buffering the channels in such a system would be pushing things a tad though, what with the SX's limited SRAM).

As mentioned, the way I created the simple waveforms was to use a SID-like phase accumulator. In my case this was just a 24 bit counter to which I add a 16 bit value (directly related to the desired output frequency) each sample period. I then take bits 12-20 as my 8 bit output..

  
Osc hi:   Osc mid:   Osc lo:
=============================
00000000  00000000   00000000  <- Accumulator
              +
          Freq hi:   Freq Lo:
          00000000   00000000  <- Frequency control
----------------------------          
    0000  0000
    !        !
    '--------'
         !
         '---------------------> 8 bit output
The raw output from the above would be a sawtooth shape, ie: rising and then suddenly dropping to the lowest point again. To make a triangle wave, I test bit 7 of the output and - if 1 - invert the other bits and shift it left. For a variable pulse width wave, I compare the output to an 8 bit pulse-width value held in a register, if it's greater than this I set the output to the channel's volume register and if lower, the output equals zero. This way, the pulse waves dont need any seperate scaling. For the white noise (and samples) I update the output if there's any carry from the Osc_mid byte of the accumulator.

The linear feedback register used for the white noise is just 3 initially random bytes being shifted to the right with the MSB being fed the result of a XOR of various bits in the register.

>>>>>> Rotate Right >>>>>>
MSB                    LSB
00000000 00000000 00000000
!                   !  ! !
'----------<--------+--+-+   <- XOR'd bits
Volume scaling: There's no divide or multiply instructions on the SX of course so my scaling routine uses the "shift and add" method (the equivalent to long multiplication by hand, in binary) to multiply the output by the volume to create a 16bit value, the most significant byte of the result is then the new output.

Mixing the four channels is as simple as adding the four 8 bit outputs together and dividing by 4 (two right shifts). As the mixing only takes place every fourth interrupt, it occured to me that a digital filter could be implemented by dividing the difference between one output value and the next by 4 and using the result as a delta to modify the output EVERY interrupt and help smooth out the aliasing. The effect proved negligible though, I guess because the output was already being updated above 100KHz (and with sampled sound, the slope wasn't between one actual sample and the next, just output updates). Anyway, the final value from the mixer routine is presented to an output port on the SX microntroller which is connected directly to a DIY digital to analogue converter - a simple "R2R" resistor ladder:

The Sound board circuit:


R2R resistor DAC diagram

In a R2R resistor ladder, each output bit is connected to a series of resistors in such a way that each adds its binary weight to the total voltage at the end. From standard logic supply voltages, this gives a range of 0 to 5 volts in 256 steps (I used 10K and 20K resistors for my DAC - its not particularly critical). TV audio spec requires a 1v peak-to-peak signal, but its easy to scale it lower with a potential divider. I used a 50Kohm trimmer to set the level and buffered the output with a 7611 op-amp wired as a voltage follower. The output of the buffer is then just connected to the TV audio-in via a 1k resistor and 1uf capacitor. Here's a(nother badly scribbled) schematic of the circuit.

My previous soundboard used a pretty slow method of sending data to the sound registers. This time all I do is latch (with 574 ICs) the Z80 databus and the high 8 bits of the address bus when I write to the sound port using the Z80 "OUT (c),a" instruction. (During this instruction, the Z80's B register is presented to the high 8 bits of the address bus and the A register to the data bus.) Once these bytes are latched the SX can just toggle the latch chips' output enables to get either the register address (for the SX's FSR) or the data to place into it (mov to INDF).


Sound board version 2 - better!

I also make any write to the sound port set a "busy" flag on a 74 flipflop and get the SX to clear it when the new data has been accepted. Any Z80 sound routine reads this flag through a 245 buffer before attempting to send any more data. Another line from the SX to the 245 buffer allows the Z80 to tell which half of the sample buffer is being used when playing digitized sound. The remaining bus lines of the 245 were pulled high via resistors and sent an (old) standard Atari 2600 joystick port:)

Further reading / demo tunes etc can be found here