Phil Pilgrim (PhiPi)
07-03-2009, 04:52 AM
There have been several threads regarding speaker output for the Propeller. Most involve analog amplification of filtered DUTY-mode output from one of the counters. There's another kind of amplifier called "Class D" that sends PWM output directly to the speaker, letting the voice coil's inductance and speaker cone's physical inertia do all the filtering. It occurred to me that a MOSFET driver chip might have enough oomph to drive an 8-ohm speaker. If so, it would then be a simple matter of driving its inputs with the proper signals to drive the speaker differentially, H-bridge fashion:
http://forums.parallax.com/attachment.php?attachmentid=62005
Here's the circuit I used. I selected a Micrel MIC4469 becasue it had some useful logic on its inputs. It also has built-in protection diodes on the outputs for inductive loads. Notice that two outputs from each side are paralleled to provide more current drive. The "sign" input basically selects which side of the speaker receives the DUTY modulation. When the sign is set (negative audio swing), the duty will be more than 50%. That's just right, because it goes into the inverting input of the lower half of the driver.
http://forums.parallax.com/attachment.php?attachmentid=62006
Here is the test program. It generates a sinewave, which my 6W 8-ohm speaker reproduced with very reasonable volume. It was loud enough to annoy my cat Browser from his top-shelf sleeping perch (photo below) and send him out the door. (He needs more exercise anyway.) The driver chip did not even get warm, which is one of the advantages of Class D amplification.
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
duty_pin = 0
sign_pin = 1
sine = $e000
VAR
long waveform[*64]
byte cogno
PUB demo | amplitude, i, phase, t, f
repeat i from 0 to 63
waveform[*i] := sin(i << 7) * 32767
amplitude~
start(duty_pin, sign_pin, @amplitude)
f := clkfreq / 30000
t := cnt
repeat
waitcnt(t += f)
amplitude := waveform[*i := (i + 1) & 63]
PUB start(dutypin, signpin, ampaddr)
'' Start the class D output driver.
'' dutypin: Prop pin number (0 - 27) for DUTY output.
'' singpin: Prop pin number (0 - 27) for SIGN output.
'' ampaddr: Hub address of a LONG containing the instantaneous audio amplitude.
stop
|< dutypin
|< signpin
return cogno := cognew(@classD, @dutypin) + 1
PUB stop
'' Stop the class D output driver.
if (cogno)
cogstop(cogno - 1)
cogno~
PRI sin(x) : value
'' Sine of the angle x: 0 to 360 degrees = $0000 to $2000
if (x & $fff == $800)
value := $1_0000
elseif (x & $800)
value := word[*sine][*-x & $7ff]
else
value := word[*sine][*x & $7ff]
if (x & $1000)
value := -value
DAT
org 0
classD mov amp,par 'Assumes A0 and A1 for output.
rdlong dutymask,amp 'Get the duty pin mask.
add amp,#4
rdlong signmask,amp 'Get the sign pin mask.
add amp,#4
rdlong ampladdr,amp 'Get the amplitude address.
mov dira,dutymask
or dira,signmask
:loop add phs,amp wc '[* 4] Add amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
rdlong new_amp,ampladdr '[* 1 + 7] Get the next amplitude value.
add phs,amp wc '[* 4] Add current amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
shl new_amp,#1 wc '[* 4] Multiply new amplitude value by 2, saving the sign in carry.
muxc out,signmask '[* 4] Set the sign value in out.
add phs,amp wc '[* 4] Add current amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
add phs,new_amp wc '[* 4] Add new amplitude value to software counter phase.
muxc out,dutymask '[* 4] Carry is duty output: save in out.
nop '[* 4] Delay for constant 200ns pulse timing.
mov outa,out '[* 4] Output new duty and sign simultaneously.
mov amp,new_amp '[* 4] Now, set current amplitude to new amplitude.
jmp #:loop '[* 4] Ad infinitum.
'----
'[*64] Clocks for loop, or four hub cycles.
phs res 1 'Phase of the software counter.
amp res 1 'Cog copy of amplitude.
new_amp res 1 'Cog buffer for new amplitude.
out res 1 'Buffer for outa.
dutymask res 1 'Pin mask for duty output.
signmask res 1 'Pin mask for sign output.
ampladdr res 1 'Hub address of the instantanous audio amplitude.
I tried connecting the MIC4469's Vdd pin to Vin and, as expected, the volume got much louder, but with some audible distortion. I have a feeling that there are inductive issues which cause the distortion that need to be addressed somehow. (Perhaps Beau can weigh in on this issue.) Anyway, it was an interesting experiment which proves the principle, at least, that minimal-component Class D audio amps are possible, given the right kind of output from the Prop.
-Phil
Addendum: I changed the code to make it a proper object and doubled the DUTY output level, effectively qudrupling the available output power. It made a big difference, and I didn't hear the distortion I did with the higher (Vin) supply voltage. I also changed the schematic to include pulldowns on the driver inputs. This will keep the driver in a quiescent state when the Prop pins are tri-stated, protecting the driver from a continuous over-current condition. For maximum protection, capacitively coupling the Prop outputs might also be recommended.
Addendum 2: I changed the source code once more to update the DUTY mode ouput at regular 200ns intervals (assuming 80MHz clock).
_
Post Edited (Phil Pilgrim (PhiPi)) : 7/4/2009 8:51:30 PM GMT
http://forums.parallax.com/attachment.php?attachmentid=62005
Here's the circuit I used. I selected a Micrel MIC4469 becasue it had some useful logic on its inputs. It also has built-in protection diodes on the outputs for inductive loads. Notice that two outputs from each side are paralleled to provide more current drive. The "sign" input basically selects which side of the speaker receives the DUTY modulation. When the sign is set (negative audio swing), the duty will be more than 50%. That's just right, because it goes into the inverting input of the lower half of the driver.
http://forums.parallax.com/attachment.php?attachmentid=62006
Here is the test program. It generates a sinewave, which my 6W 8-ohm speaker reproduced with very reasonable volume. It was loud enough to annoy my cat Browser from his top-shelf sleeping perch (photo below) and send him out the door. (He needs more exercise anyway.) The driver chip did not even get warm, which is one of the advantages of Class D amplification.
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
duty_pin = 0
sign_pin = 1
sine = $e000
VAR
long waveform[*64]
byte cogno
PUB demo | amplitude, i, phase, t, f
repeat i from 0 to 63
waveform[*i] := sin(i << 7) * 32767
amplitude~
start(duty_pin, sign_pin, @amplitude)
f := clkfreq / 30000
t := cnt
repeat
waitcnt(t += f)
amplitude := waveform[*i := (i + 1) & 63]
PUB start(dutypin, signpin, ampaddr)
'' Start the class D output driver.
'' dutypin: Prop pin number (0 - 27) for DUTY output.
'' singpin: Prop pin number (0 - 27) for SIGN output.
'' ampaddr: Hub address of a LONG containing the instantaneous audio amplitude.
stop
|< dutypin
|< signpin
return cogno := cognew(@classD, @dutypin) + 1
PUB stop
'' Stop the class D output driver.
if (cogno)
cogstop(cogno - 1)
cogno~
PRI sin(x) : value
'' Sine of the angle x: 0 to 360 degrees = $0000 to $2000
if (x & $fff == $800)
value := $1_0000
elseif (x & $800)
value := word[*sine][*-x & $7ff]
else
value := word[*sine][*x & $7ff]
if (x & $1000)
value := -value
DAT
org 0
classD mov amp,par 'Assumes A0 and A1 for output.
rdlong dutymask,amp 'Get the duty pin mask.
add amp,#4
rdlong signmask,amp 'Get the sign pin mask.
add amp,#4
rdlong ampladdr,amp 'Get the amplitude address.
mov dira,dutymask
or dira,signmask
:loop add phs,amp wc '[* 4] Add amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
rdlong new_amp,ampladdr '[* 1 + 7] Get the next amplitude value.
add phs,amp wc '[* 4] Add current amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
shl new_amp,#1 wc '[* 4] Multiply new amplitude value by 2, saving the sign in carry.
muxc out,signmask '[* 4] Set the sign value in out.
add phs,amp wc '[* 4] Add current amplitude value to software counter phase.
muxc outa,dutymask '[* 4] Carry is duty output.
add phs,new_amp wc '[* 4] Add new amplitude value to software counter phase.
muxc out,dutymask '[* 4] Carry is duty output: save in out.
nop '[* 4] Delay for constant 200ns pulse timing.
mov outa,out '[* 4] Output new duty and sign simultaneously.
mov amp,new_amp '[* 4] Now, set current amplitude to new amplitude.
jmp #:loop '[* 4] Ad infinitum.
'----
'[*64] Clocks for loop, or four hub cycles.
phs res 1 'Phase of the software counter.
amp res 1 'Cog copy of amplitude.
new_amp res 1 'Cog buffer for new amplitude.
out res 1 'Buffer for outa.
dutymask res 1 'Pin mask for duty output.
signmask res 1 'Pin mask for sign output.
ampladdr res 1 'Hub address of the instantanous audio amplitude.
I tried connecting the MIC4469's Vdd pin to Vin and, as expected, the volume got much louder, but with some audible distortion. I have a feeling that there are inductive issues which cause the distortion that need to be addressed somehow. (Perhaps Beau can weigh in on this issue.) Anyway, it was an interesting experiment which proves the principle, at least, that minimal-component Class D audio amps are possible, given the right kind of output from the Prop.
-Phil
Addendum: I changed the code to make it a proper object and doubled the DUTY output level, effectively qudrupling the available output power. It made a big difference, and I didn't hear the distortion I did with the higher (Vin) supply voltage. I also changed the schematic to include pulldowns on the driver inputs. This will keep the driver in a quiescent state when the Prop pins are tri-stated, protecting the driver from a continuous over-current condition. For maximum protection, capacitively coupling the Prop outputs might also be recommended.
Addendum 2: I changed the source code once more to update the DUTY mode ouput at regular 200ns intervals (assuming 80MHz clock).
_
Post Edited (Phil Pilgrim (PhiPi)) : 7/4/2009 8:51:30 PM GMT