16bit・ノンオーバーサンプリングのDACが思った以上に良く鳴るので、デジタルアッテネータを搭載してみました。CS8412などのDAIFと組み合わせて使用できます。
音の鮮度を損なう部品は、何でしょうか。いくつか有ると思いますが、その一つにアッテネータ(可変抵抗)があります。でも、音量調節は生活で音楽を楽しむ上では必須です。ならば、DACのデジタル部分で音量調節をすれば、音の鮮度を損なわずにすむはずです。
専用DACチップにはデジタルアッテネータを搭載したものもいくつか有りますが、今回の製作では、これを1チップマイコンで実現してみます。オーディオ信号は片chあたり、44.1kHz周期で16bitのデータが到着します。これに256段階(8bit)の音量調節をするには、16×8=24(bit)の演算を行うことになります。44.1kHzで2ch分のデータに対して演算しますから、乗算は1回あたり11.3μ秒以内に実行する必要が有り、これはなかなか大変です。
今回使用した1チップマイコンは、ATMEL社のAVR 90S8535という8bitマイコンです。RAMとフラッシュメモリを内蔵しているため、外部にはオシレータを繋ぐだけで動作します。8bitバスが4本出ており、また10bit精度のA/Dコンバータを内蔵しているため、今回の製作にはぴったりです。ただし、演算速度が間に合わないため、推奨動作周波数の8MHzではなく、10MHzで動作させています。(データシートには、10MHz動作も記述されてはいます)
(図の白い部分はSCKに同期し、緑の部分は1チップマイコン用の10.0MHzに同期します)
DAIFからのSCK(クロック)とDATA(シリアルデータ)を16ビットパラレルデータに変換し、ラッチして出力します。
AVR 90S8535の10bit A/Dコンバータの出力10bitのうち、上位8bitを使用します。
シリアル→パラレル変換部の出力のイネーブル信号を制御します。
AVR 90S8535で、16bit×8bit=24bitの演算を行います。演算の開始タイミングはラッチタイミング生成部から受信します。
R=2R型DACでは0x0000〜0xFFFFのデータをアナログに変換しますが、DAIFからのデータは0x8000〜0xffff,0x0000〜0x7fffのデータとなっていますので、変換します。具体的には、最上位bit(MSB)を反転する処理を行います。
乗算器からの24bitパラレルデータを、ラッチタイミング生成部からのラッチ信号に従ってラッチします。
シリアル→パラレル変換部へのラッチタイミング出力として、LRCKの立ち上がりと立ち下がりで立ち上がるパルス信号を出力します。 ラッチ部でラッチするタイミングを生成します。RchにはLRCKを、LchにはLRCKの反転信号を出力します。 乗算器への演算開始タイミングとして、LRCKを出力します。
24bitパラレルデータをアナログ電圧に変換します。LchとRchで個別です。
D/A変換は、R=2R型を採用しています。説明は省きますが、出力電圧はHC574の出力電圧の1/3となります。5Vなので、出力は0〜1.7Vです。
HC595のラッチパルス用に、LRCKの立ち上がりと立ち下がりでそれぞれ立ち上がるパルス信号をHC86で生成しています。本来ならばパルス幅はフリップフロップで作るべきですが、素子数が増えるのが嫌だったので、HC86のEx-ORをインバータにして3素子をシーケンシャルに接続し、素子遅延で稼いでいます。行儀が悪いですが、まぁ動作していますし...
90S8535はA,B,C,Dの4系統の8bitバスがあり、A/D変換用のアナログ信号はAバスの8本の中からの入力ですので、16bitデータの入力としてD,Bを、24bit出力としてB,C,Dを使用します。演算開始は本来ならば割り込みを使用したいのですが、外部割り込みはDバスに割り付けられているため、Aバスで演算開始信号を入力し、プログラムでポーリングします。
パスコンの容量が大きいと電源電圧の立ち上がりが緩やかになり過ぎてマイコンの起動が不安定になることがありますので、リセット用のICを使用しています。
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円です)。
ただし、さすがにマイコンを使用した製作は一般性には欠けるかもしれません。でもがんばれば、素人でもこれくらいは手軽に手が届きます。アイデア次第ではいろんなことが出来そうですので、皆さんもチャレンジしてみてはいかがでしょうか。