デジタルアッテネータ付きR=2Rラダー型ディスクリートDAC

16bit・ノンオーバーサンプリングのDACが思った以上に良く鳴るので、デジタルアッテネータを搭載してみました。CS8412などのDAIFと組み合わせて使用できます。



ねらい

音の鮮度を損なう部品は、何でしょうか。いくつか有ると思いますが、その一つにアッテネータ(可変抵抗)があります。でも、音量調節は生活で音楽を楽しむ上では必須です。ならば、DACのデジタル部分で音量調節をすれば、音の鮮度を損なわずにすむはずです。

専用DACチップにはデジタルアッテネータを搭載したものもいくつか有りますが、今回の製作では、これを1チップマイコンで実現してみます。オーディオ信号は片chあたり、44.1kHz周期で16bitのデータが到着します。これに256段階(8bit)の音量調節をするには、16×8=24(bit)の演算を行うことになります。44.1kHzで2ch分のデータに対して演算しますから、乗算は1回あたり11.3μ秒以内に実行する必要が有り、これはなかなか大変です。

1チップマイコン

今回使用した1チップマイコンは、ATMEL社のAVR 90S8535という8bitマイコンです。RAMとフラッシュメモリを内蔵しているため、外部にはオシレータを繋ぐだけで動作します。8bitバスが4本出ており、また10bit精度のA/Dコンバータを内蔵しているため、今回の製作にはぴったりです。ただし、演算速度が間に合わないため、推奨動作周波数の8MHzではなく、10MHzで動作させています。(データシートには、10MHz動作も記述されてはいます)

ブロック図


(図の白い部分はSCKに同期し、緑の部分は1チップマイコン用の10.0MHzに同期します)

回路図

シリアル→パラレル変換

HC595のラッチパルス用に、LRCKの立ち上がりと立ち下がりでそれぞれ立ち上がるパルス信号をHC86で生成しています。本来ならばパルス幅はフリップフロップで作るべきですが、素子数が増えるのが嫌だったので、HC86のEx-ORをインバータにして3素子をシーケンシャルに接続し、素子遅延で稼いでいます。行儀が悪いですが、まぁ動作していますし...


1チップマイコン

90S8535はA,B,C,Dの4系統の8bitバスがあり、A/D変換用のアナログ信号はAバスの8本の中からの入力ですので、16bitデータの入力としてD,Bを、24bit出力としてB,C,Dを使用します。演算開始は本来ならば割り込みを使用したいのですが、外部割り込みはDバスに割り付けられているため、Aバスで演算開始信号を入力し、プログラムでポーリングします。

パスコンの容量が大きいと電源電圧の立ち上がりが緩やかになり過ぎてマイコンの起動が不安定になることがありますので、リセット用のICを使用しています。


R=2Rラダー型DAC

24bitのR-2Rラダー型DACです。



電源

マイコン、Lch DAC、Rch DACの3系統の電源を生成しています。


プログラム

ATMEL AVRstudio 3.56でコンパイル可能です。

.include	"8535def.inc"

.def	tmp	= R16
.def	adc_h	= R17
.def	adc_l	= R18

.def	x_h	= r00
.def	x_m	= r01
.def	x_l	= r02
.def	y	= r03
.def	z_h	= r04
.def	z_m	= r05
.def	z_l	= r06
.def	oe_en	= r07
.def	oe_ds	= r08

.def	c0x00	= R09
.def	c0xff	= R10
.def	c0x80	= R11

;;; PORT A
.equ	hc595oe	= 0
.equ	lrck	= 1
.equ	adc_in	= 7

;;; data input  : DI_15-0 : PORT D,B
;;; data output : DO_23-0 : PORT B,C,D

	rjmp	reset	;1 $000 reset vector
	reti		;2 $001 INT0 
	reti		;3 $002 INT1 
	reti		;4 $003 TIMER2 COMP 
	reti		;5 $004 TIMER2 OVF 
	reti		;6 $005 TIMER1 CAPT 
	reti		;7 $006 TIMER1 COMPA 
	reti		;8 $007 TIMER1 COMPB 
	reti		;9 $008 TIMER1 OVF 
	reti		;10 $009 TIMER0 OVF 
	reti		;11 $00A SPI, STC SPI
	reti		;12 $00B UART, RX UART
	reti		;13 $00C UART, UDRE UART
	reti		;14 $00D UART, TX UART
	reti		;15 $00E ADC AD
	reti		;16 $00F EE_RDY EEPROM
	reti		;17 $010 ANA


RESET:	
	ldi	tmp, high(RAMEND)
	out	SPH,tmp
	ldi	tmp, low(RAMEND)
	out	SPL,tmp

	ldi	tmp,0x00
	mov	c0x00,tmp
	ldi	tmp,0x80
	mov	c0x80,tmp
	ldi	tmp,0xff
	mov	c0xff,tmp

	ldi	tmp,0x00
	mov	oe_en,tmp
	ldi	tmp,0x01
	mov	oe_ds,tmp

	; portA initialize
	ldi	tmp,0x01
	out	DDRA,tmp
	ldi	tmp,0x7f
	out	PORTA,tmp

	; portB initialize
	ldi	tmp,0x00	; input
	out	DDRB,tmp
	ldi	tmp,0x00	; no pullup
	out	PORTB,tmp

	; portC initialize
	ldi	tmp,0xff	; output
	out	DDRC,tmp
	ldi	tmp,0x00	; no pullup
	out	PORTC,tmp

	; portD initialize
	ldi	tmp,0x00	; input
	out	DDRD,tmp
	ldi	tmp,0x00	; no pullup
	out	PORTD,tmp

	; A/D converter initialize
	ldi	tmp,7		; A/D input pin 
	out	ADMUX,tmp
	ldi	tmp,0xe7
	out	ADCSR,tmp
	
	ldi	adc_h,0		; clear ADC value
	ldi	adc_l,0

loop:
	sbis	PINA,lrck	; Lch check
	rjmp	PC+2
	rjmp	PC-2
	rcall	calc_mul

	in	adc_l,ADCL	; input A/D value
	in	adc_h,ADCH
	ror	adc_h		; 10bit -> 8bit
	ror	adc_l
	ror	adc_h
	ror	adc_l

	sbic	PINA,lrck	; Rch check
	rjmp	PC+2
	rjmp	PC-2
	rcall	calc_mul

	rjmp	loop

calc_mul:
	out	DDRB,c0x00	; portB input
	out	DDRD,c0x00	; portD input
	out	PORTA,oe_en	; enable shifter OE

	mov	tmp,adc_l	; calculate offset
	inc	tmp
	lsr	tmp
	mov	z_h,c0x80
	sub	z_h,tmp 
	cpi	adc_l,255
	brne	PC+2
	clr	z_h
	mov	z_m,c0x80
	sbrs	adc_l,0
	clr	z_m
	clr	z_l
	mov	y,adc_l
	
	in	x_m,PIND	; input value high
	in	x_l,PINB	; input value low
	out	PORTA,oe_ds	; disable shifter OE
	out	DDRB,c0xff	; portB output
	out	DDRD,c0xff	; portD output

	mov	x_h,c0x00
	eor	x_m,c0x80	; invert MSB

	ror	y		; multiply z=x*y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h
	lsl	x_l
	rol	x_m
	rol	x_h

	ror	y
	brcc	PC+4
	add	z_l,x_l
	adc	z_m,x_m
	adc	z_h,x_h

	out	PORTD,z_l
	out	PORTC,z_m
	out	PORTB,z_h

	ret

調整

R=2Rラダー型DACの可変抵抗を、静かな音楽を聴きながら、もっともノイズの小さくなる抵抗値にします。可変抵抗は、なるべく細かい調整ができるような多回転型のものにしましょう。

音質

前回のディスクリートDACと同じく、いたって普通の音がします。ただし、ゲインの高いパワーアンプと直結すると、ボリュームを絞った時にデジタルノイズが目立ってくるようです。まぁ、その場合はI/V変換用の抵抗を分割して、分圧して出力することで回避できます。

さいごに

本来ならば、FPGAで乗算器を作成するべきでしょう。でも、FPGAはPLCCやQFP、BGAなので、素人がハンダ付けするには辛いですし、コストパフォーマンスも1チップマイコンの方が優れています (AVR 90S8535は800円です)。

ただし、さすがにマイコンを使用した製作は一般性には欠けるかもしれません。でもがんばれば、素人でもこれくらいは手軽に手が届きます。アイデア次第ではいろんなことが出来そうですので、皆さんもチャレンジしてみてはいかがでしょうか。