2010年6月29日火曜日

Si570 "ANY-RATE"プログラマブル発振器(4)

実装状況
PICマイコン用に設計された基板に、無理矢理AVRを取り付けたので、ジャンパ線ばっかりになっています。




回路図


2010年6月28日月曜日

Si570 "ANY-RATE"プログラマブル発振器(3)

制御プログラム
制御用として、AtmelのATtiny85という8ピンのマイコンを使いました。このマイコンには、USI(Universal Serial Interface)というシリアル通信機能が組み込まれていますが、汎用ゆえ使い方が難解です。そこで、がた老AVR研究所温度ロガーで公開されていたsoft_I2C.cの主要ルーチンをほぼそのまま利用させて頂きました。また、JimCom氏のソースコードも参考にさせて頂いています。この場をお借りしてお礼申し上げます。



メインルーチン Si570.c(一部、#includeに続く括弧を全角に変更しています)
#include <avr/io.h>
#include
avr/interrupt.h
#include stdlib.h
#include avr/sleep.h
#include avr/pgmspace.h
#include inttypes.h

#define  I2C_FUNCTIONS
#include ”soft_I2C.h”

/*
    PB5 I (Reset)
    PB4 I DIP SW 1-1
    PB3 I DIP SW 1-2
    PB2 I/O SCL/(ISP SCK)
    PB1 I/O (ISP MISO)
    PB0 I/O SDA/(ISP MOSI)
*/


uint8_t I2C_comd(uint8_t cmd)
{
    I2C_start_cond();
    if( I2C_status_flag != 1) return(I2C_status_flag);

    I2C_Master_send(Si570_ADR);
    if( I2C_status_flag != 1) return(I2C_status_flag);

    I2C_write(cmd);
    if(I2C_status_flag !=1) return(I2C_status_flag);
    return(1);
}


void I2C_put(uint8_t cmd, uint8_t data)
{
    if( !(I2C_status_flag = I2C_comd(cmd)) ) return;
    I2C_write(data);
    if(I2C_status_flag != 1) return;
    I2C_stop_cond();
}


void init_si570(void)
{
    // Freeze DCO
    I2C_put(137,0x10);

    // Set HSDIV, N1, RFREQ
    switch (((bit_is_clear(PINB,4))<<1) | (bit_is_clear(PINB,3))) {
        case 0:    // 122.880MHz

            I2C_put(7, 0x21);        // HSDIV=1, N1=7
            I2C_put(8, 0xC2);        // RFREQ=0x02B021DE77
            I2C_put(9, 0xB0);
            I2C_put(10, 0x21);
            I2C_put(11, 0xDE);
            I2C_put(12, 0x77);
            break;

        case 1:    // 73.728MHz

            I2C_put(7, 0xE1);        // HSDIV=7, N1=5
            I2C_put(8, 0x42);        // RFREQ=0x02A9404015
            I2C_put(9, 0xA9);
            I2C_put(10, 0x40);
            I2C_put(11, 0x40);
            I2C_put(12, 0x15);
            break;

        case 2:    // 61.440MHz

            I2C_put(7, 0x23);        // HSDIV=1, N1=15
            I2C_put(8, 0xC2);        // RFREQ=0x02B021DE77
            I2C_put(9, 0xB0);
            I2C_put(10, 0x21);
            I2C_put(11, 0xDE);
            I2C_put(12, 0x77);
            break;

        case 3:    // 12.288MHz

            I2C_put(7, 0xE8);        // HSDIV=7, N1=35
            I2C_put(8, 0xC2);        // RFREQ=0x02A9404015
            I2C_put(9, 0xA9);
            I2C_put(10, 0x40);
            I2C_put(11, 0x40);
            I2C_put(12, 0x15);
            break;

      default:    // 122.880MHz
            I2C_put(7, 0x21);        // HSDIV=1, N1=7
            I2C_put(8, 0xC2);        // RFREQ=0x02B021DE77
            I2C_put(9, 0xB0);
            I2C_put(10, 0x21);
            I2C_put(11, 0xDE);
            I2C_put(12, 0x77);
    }

    // UnFreeze DCO
    I2C_put(137, 0x00);

    // Set New Freq
    I2C_put(135, 0x40);
}

int main(void)
{

    //    出力ポート初期化
    //    入力ポート、1:プルアップ
    PORTB = 0b011101;

    //     ポート設定 0:入力, 1:出力
    DDRB = 0b000101;

    // USIインタフェースの初期化
    I2C_Master_init();

    // Si570の設定
    init_si570();

    while(1);    // 無限ループ
}


soft_I2C.c(変更箇所のみ抜粋
//#include <avr/iotn861.h>
//#define  I2C_FUNCTIONS

void I2C_Master_init()
{
//  USIPP = I2C_USIPOS;


soft_I2C.h(変更箇所のみ抜粋)
//#define RTC8564_ADR   0xA2   //8564 RTC
#define Si570_ADR   0xAA    // Si570 I2C Address: 0x55<<1
#define I2C_PORT      PORTB
#define I2C_PIN       PINB
#define I2C_DDR       DDRB
#define I2C_SDA       PB0
#define I2C_SCL       PB2

ところで、HSDIVとN1の分周比と、実際にレジスタに書き込む値とは若干異なっています。
HSDIV
3ビットで表現しますが、少し変則的です。
000 = 4
001 = 5
010 = 6
011 = 7
101 = 9
111 = 11

N1
7ビットで表現します。分周比から1を引いた値をセットします。
0000000 = 1
1111111 = 128

2010年6月26日土曜日

Si570 "ANY-RATE"プログラマブル発振器(2)

Si570の出力周波数
出力は、4~5GHz帯のDCO(Digitally-controlled Oscillator)をディバイダで分周して得られます。周波数は次式で求められます。


RFREQ・HSDIV・N1レジスタで任意の周波数を選べますが、いくつか制約もあります。

  • DCO周波数範囲: 4850~5670MHz
  • XTAL周波数: 114.285MHz
  • 出力周波数: 10~280MHz (Cグレード、LVDS出力の場合)





出力周波数の変更
出力周波数を微調整(±3500ppm以内)するときは、RFREQレジスタに書き込むだけで変更できます。±3500ppmを越えた周波数とするには、いったんDCOを停止させ、各レジスタの設定後、再スタートさせる必要があります。


 

レジスタ値の選定
周波数設定に関するレジスタの制約は次の通りです。
  • RFREQ: 38ビット
  • HSDIV: 4, 5, 6, 7, 9, 11
  • N1: 1と128までの偶数
データシートによると、消費電力を考慮してDCO周波数を出来るだけ低くすること。また、N1を小さく、かつ、HSDIVを大きくしたときに消費電力が最小になるとのことです。
次に、RFREQレジスタの上位10ビットは整数部、下位28ビットは小数部に割り当てられています。小数部を含む10進数値を2進数の整数へ変換するには、2^28を掛ければ良いそうです。
 


次に、4つの周波数について検討してみます。

(1)122.880MHz
  • 出力周波数 = DCO周波数/(HSDIV×N1) より、DCO周波数4850MHzとするときの HSDIV×N1 は、4,850MHz/122.880MHz ≒ 39.5。
  • いくつかの組み合わせの中から、HSDIV=5、N1=8を選定。
  • DCO周波数 = 出力周波数×HSDIV×N1 = 122.880MHz×8×5 = 4,915.2MHz
  • RFREQ = DCO周波数/XTAL周波数 = 4,915.2MHz/114.285MHz = 43.0082688より、2^28を掛けると、11544944248。これを16進数に変換すると、0x02B021DE77となる。
(2)73.728MHz
  • HSDIV=11、N1=6、RFREQ=0x02A9404015
(3)61.440MHz
  • HSDIV=5、N1=16、RFREQ=0x02B021DE77
(4)12.288MHz
  • HSDIV=11、N1=36、RFREQ=0x02A9404015



周波数の変更手順
今回は、Si570をFPGAのシステムクロックとして使います。アプリケーションに応じた周波数を出せればよいので、電源投入時にDIPスイッチの設定に応じて周波数を変更します。I2Cによる、設定変更手順の流れは次の通りです。
  • Freeze DCO
  • RFREQ, HS_DIV, N1を更新
  • UnFreeze DCO
  • (10ms以内に) NerFreqビットをセット

Si570のレジスタ(データシートより)

2010年6月20日日曜日

岐阜県神岡・老田屋の飛騨ラーメン

久しぶりに岐阜県の神岡を訪れました。この町は、岐阜県北部富山県境近くに位置しており、かつて神岡鉱山の町として栄えていました。最近では、ノーベル賞を受賞された小柴博士の研究の舞台として鉱山跡地を活用したニュートリノ観測施設『カミオカンデ』が脚光を浴びました。

この町を訪れるときは、決まって老田屋のラーメン(中華そば)を買って帰ります。飛騨地区では高山と近郊に中華そばの有名ブランドがいくつかありますが、私の舌にいちばんマッチしているのが老田屋です。それもこのストレートスープ付きがベスト!と思っています。今回は、道の駅スカイドーム神岡で購入しました。他にも、バロー神岡店(もと神岡鉱山購買部?)でも売っていたと思います。





老田屋(2002年2月撮影)


神岡のことを調べていたら、スーパーカミオカンデ一般公開に参加したときの写真が出てきました。写真は2001年8月のもので、壁一面に取り付けられたガラス球のようなものが浜松ホトニクス製の光電子増倍管です。この3ヶ月後の11月には不幸な事故により、このセンサの多くが破損してしまいます。
ところで、このスーパーカミオカンデ(神岡宇宙素粒子研究施設)について『神岡鉱山の地下1000m』という説明をよく見かけます。私は、エレベータか何かで地下奥深くまで潜っていくイメージを持っていたのですが、実際には鉱山の坑口から水平に奥深く入っていったところにカミオカンデはありました。どうもその上に、高い山があるということのようです。


保守中のスーパーカミオカンデ上部から中を臨む




カミオカンデには、このような坑道を通って行きます


このスーパーカミオカンデなどを含む見学に興味をお持ちの方は、毎年夏頃に開催されるジオスペースアドベンチャーに参加されてはいかがでしょうか。残念ながら今年度の募集は締め切られていますが、他にも東京大学宇宙船研究所主催で中高生を対象としたイベントはまだ募集中のようです。いずれも人気を集めているものと思いますので、普段からのチェックが肝心です。

2010年6月16日水曜日

Si570 "ANY-RATE"プログラマブル発振器(1)

ヘッドホンアンプのプロジェクトが一区切りしたので、JimCom氏設計のSDR受信機基板を完成させようと思っています。この基板のマスタクロックには、Silicon LaboratriesSi570が採用されています。Si570は、プログラマブル型の水晶発振モジュールでI2Cで周波数を可変でき、しかも低ジッタであることが知られているため、他の自作SDR受信機でも多くの採用例があります。(アイキャスエンタープライズUSBシンセサイザーキットなど)


Si570イメージ図(データシートより)


データシートによると、4~5GHzの内蔵DCOを適宜分周して出力を得ているとあります。最近は凄いデバイスがあるものです。びっくりしました。ちなみに消費電流は100mA前後となっています。そりゃ大食いになるよね。


Si570内部ブロック図(データシートより)



Si570位相ノイズ(データシートより)



ところで、このデバイスの入手には少し困りました。キットなら前述のサイトで入手出来ますが、デバイス単体を在庫している小売店が見あたらないのです。海外の個人サイトで頒布している例は散見されますが、言葉の壁と海外の見知らぬ個人との取引には抵抗を感じます。そこで、『在庫なし・お取り寄せ』だったMouser Electronicsにオーダしてみました。かなりの長納期を覚悟していましたが、結果的には3週間ほど、1月上旬にオーダーして1月末には手元に届きました。
Si570は出力電圧、周波数範囲、安定度、デフォルト周波数等を任意に指定してオーダーできます。私が購入したのは570BBC000141DGという型番のもので、LVDS出力・安定度20ppm・周波数範囲10-280MHz・デフォルト発振周波数56.32MHz・I2Cアドレス0x55(0d85)となっています。(型番が570・・・で始まっており、MouserでSi570として検索してもヒットしませんので注意!)


指先大のデバイス3個が大きな段ボールで届きました





これがSi570、たった5×7mmの大きさ


次回以降は、Si570をAVRマイコンで制御することにチャレンジしてみようと思います。


2010年6月11日金曜日

ヘッドホンアンプ: ようやく完成!

ようやく完成しました

試聴アルバムは、hayato kaoripluma。今のところ2010年のベストアルバムです。
ヘッドホンアンプの音質としては、TPA6120A2と余裕のある電源を組み合わせた効果は体感できます。ポータブル機器のそれとは違い、低域まで特性が伸びているためで しょうか、ウインドウズのマウスのクリック音でさえも聞こえ方が違います。もちろん聞き慣れたアルバムも印象が違います。低域のパワフル感と歪み感も少なめで楽しいです!






 

まとめ
 外観上は測定器っぽい感じですが、FPGA FMチューナと同じ幅とデザインにできました。ツマミはシルバーですがベージュ色が良かった。目標のひとつであったデジタル臭さのない外観と操作性は実現できたと思っています。

2010年6月8日火曜日

ヘッドホンアンプ: 特性確認・ポップノイズ・電源の突入電流・ヒートシンク温度

特性確認
ASUSのXONAR D1サウンドカードとWaveSpectraを使ってチェックしてみました。ところが、XONAR D1のD/A出力が不調らしくサウンドカード単体のループバックテストでは周波数特性がメチャクチャ。
XONAR D1のA/D変換は問題ないのか、という一抹の不安は残るもののFN1242でD/Aさせた出力をD1のA/Dに接続して周波数特性を測ってみたところ、FN1242のデータシートに沿った感じのもっともらしい特性が得られました。

系統は、次の通りです。

S/PDIF信号(48ksps)

FN1242 D/Aコンバータ基板
↓  
SSM2404セレクタ+LM4562バッファアンプ
↓  
PGA2311電子ボリューム
↓   
LM4562バッファアンプ
↓   
PCのサウンドカード(192ksps)



D/Aコンバータの周波数特性(アンバランス・ライン出力)


XONAR D1の周波数特性(ループバック)


次はS/N比です。系統は周波数特性と同様です。
S/PDIF信号テスト信号とPCのサウンドカードのサンプルレートをそれぞれ、48ksps/48ksps、192ksps/192kspsの組み合わせて、S/Nを比較しました。結果は、48kspsでS/N 95.7dB、192kspsで77.3dBでした。192kspsのときにS/Nが悪く見えるのは、FN1242の折り返しや帯域外ノイズの影響を受けたためと考えています。
FN1242データシートには、44.1kspsでS/N 102dBと記載されていますが、実測で6dB悪くなったのは
付属回路が増えたことと実装上の問題によるものなんでしょうか。ノイズレベルなどまともに計算していないのでどの程度が正解なのかわかりませんが、私的には電源ハムの影響を恐れていたので90dB以上とれた!って感覚です。

S/Nの測定は、種々のフィルタを使用する場合があるので測定条件が明確でないと結構くせものです。FN1242は測定帯域20kHzとしか書いていませんが、A特性(A-weighted)のフィルタなど、ヒトの聴覚特性を考慮した補正を行う場合があります。この補正は、中音域は感度高め、低高音域は感度低めにはたらくので、例えば高域に多くのノイズが存在する信号では、フィルタを通したほうが見かけのS/N比が有利に見えることになります。 ただFN1242の場合は、同業他社よりもスペックが低めの表示なので補正なしのすっぴんの測定値ではないか、と想像しています。



-3.27dBrms(0dB FS/48ksps)



-98.94dBrms(MUTE/48ksps)



-3.27dBrms(0dB FS/192ksps)



-80.52dBrms(MUTE/192ksps)


ヘッドホンアンプ出力のノイズをオシロで測ってみました。まず、ヘッドホンを接続して端子電圧が0.2Vrmsになるようボリュームをセット。入力を無信号にしてセレクタを切替えてノイズを測定しました。結果、0.3~0.7mV程度と大きいです。S/Nでいうと60dBを割り込む程度。でも可聴帯域外なのでヘッドホンではまったく聴こえません。ただ、オシロでの測定は出力端子にプローブをただパラ接続しているだけですが、単にそれだけでいいのか(コモンモードノイズの影響を受けていないか)問題を感じていて結果を額面通り受け取れないなと思っています。


ヘッドホンアンプ出力 0.2Vrms


ヘッドホンアンプ出力 0.7mVrms(USB入力・無信号)


ヘッドホンアンプ出力 0.45mVrms(ライン入力・無信号)



ヘッドホンアンプ出力 0.28mVrms(MUTE)



入力切替時のノイズ

セレクタスイッチを切り替えるとノイズが出ます。通常のボリューム位置ならさほど気になりませんが、どうしてこのような波形になるのかよくわからないです。



ヘッドホン出力端子の波形



電源の突入電流
0.5Aのヒューズが何故飛んだのか、ジャンクで入手した日置の電流出力型クランプセンサを使って電流を測ってみます。取説によると定格1次電流100A・定格2次電流0.1Aということで、電源設備で言うところのCT(カレントトランス;計器用変成器)ですね。こういうものは2次側を開放して使うと、高圧が出て危険なのでかならず抵抗とか電流計とかをつないでおかなくてはいけないです。今回は、たまたま手持ちにあった75Ωの映像用終端抵抗器をつないで、その両端に発生した電圧をオシロで観測しました。
 クランプセンサの変流比は1000:1なので、75Ωの抵抗に流れる電流は、センサの1次側の1000分の1です。このため1次電流は(1000/75)×抵抗の電圧となります。
電源投入時の突入電流の波高値は0.28Vなので1次側は(1000/75)×0.28=3.7A(実効値2.6A)。また、定常時の1次側の波高値は(1000/75)×0.018=0.24Aとなります。

次にヒューズの特性なんですが、いくら突入電流とはいえたかだか定格の数倍の電流が瞬間的に流れるだけで溶断するのか、というところに疑問を持ちました。
 よく電子部品販売店で見かける富士端子工業の資料を調べてみました。同社のカタログには、定格電流値に対する溶断電流と時間の関係を示すグラフ(溶断特性曲線表; Itカーブ)がタイプ別(普通溶断型・速断型・耐ラッシュ型)に掲載されています。今回のヒューズは、どのタイプかは不明ですが、普通溶断型の場合、実効値2.6Aで40msで溶断するようです。40msというと、商用電力の約2サイクル分です。電流波形の実測値から判断すると、ギリギリで「セーフ」だと思いますが、経年劣化も考えると「アウト」だったのかもしれません。いずれにせよ0.5Aの普通溶断型では、余裕があるとは言えないことがわかりました。トランスメーカが推奨する耐ラッシュ型(遅延型?)が使えれば安心でが入手しづらいので普通溶断型の1Aくらいにしておこうかと思っています。


トランス1次側の電流波形(電源投入時)



トランス1次側の電流波形(定常時)



 測定風景

 
ヒートシンクの温度
室温25.1度、蓋を開けた無風状態で測定してみました。設計値に対して当たらずとも遠からじ、と言ったところでしょうか。これなら暑い夏でもなんとか使えそうです。

  • +12V電源: 41.3度(温度上昇16.2度; 設計値19度)
  • -12V電源: 40.2度(温度上昇15.1度; 設計値19度)
  • +5V 電源: 36.5度(温度上昇11.4度; 設計値18度)
  • TPA6120A2: 41.8度(温度上昇16.7度; 設計値14度)

2010年6月5日土曜日

ヘッドホンアンプ: PGA2311制御プログラム

PGA2311の制御
AVRマイコンで電子ボリュームPGA2311とセレクタを制御します。やってることは簡単なので大したことはないだろうと思いきや、経験が浅いせいもあり思いのほか大変でした。

PGA2311はSPIで8ビットのデータを送って利得を制御できます。利得は次式の通り0.5dBステップですが、N=0のときはミュート動作となります。


 利得の計算式(PGA2311データシートより)


 SPIのフォーマットは、次の図のようになっており、左右のチャンネルを別々に設定できます。 なお、SDOは別のPGA2311を従属接続する場合の端子なので従属接続しないときは無視できます。

 SPIのフォーマット(PGA2311データシートより)


今回使用したAVRにはビルトインのSPIインターフェースが備わっていますが、その機能を使うにはヒューズビットの変更が必要で、あいにく手持ちのAVR ISP mkIIでは使用できませんでした。仕方がないので、次のような波形をソフトウエアで発生することにしました。(私のやり方です。これで良いのかはわかりません。)



  AVRで発生させたSPI波形



上記の波形を発生させるためのコードは次の通りです。このルーチンでは、8ビットのATT値を引数にして、左右チャンネルに同じデータを送ることを前提に、SCLKとSDIを操作しています。CSの設定は別途必要です。

 void pga2311(uint8_t ATT)
{
    int8_t n, m;

    PORTD &= ~_BV(7);    // SCLK -> 0

    for (n = 0; n < 2; n = n + 1) {
        for (m = 7; m >= 0; m = m - 1) {

            // SDI write
            if(bit_is_set(ATT, m)){
                PORTB |= _BV(0);
            }else{
                PORTB &= ~_BV(0);
            }

            // MC write
            PORTD |= _BV(7);    // SCLK -> 1
            PORTD &= ~_BV(7);    // SCLK -> 0
        }
    }

    PORTB &= ~_BV(0);    // SDI -> 0
}

制御プログラムの説明
小規模なプログラムのはずですが、作成しているうちにこんがらがってきました。そこで、フローチャートを書いてみることにしました。結構手間でしたが、頭の中が整理できて動作チェックもしやすく、初心者にとっては結果的には早道だったかもしれないと感じました。

 マイコンは、ATMELのATMEGA8Aで、クロックは内蔵RCオシレータの1MHz。内蔵のA/Dコンバータでボリュームの電圧を読み取っています。主な機能というか、工夫は次の通りです。

  • ボリュームの利得変化は対数カーブ(アナログライクな操作感)
  • デジタル入力で入力アンロックのときミューティング動作(デジタル無入力時のノイズ対策)
  • 上記でアンロック時はセレクタLEDが消灯して入力の接続状況がわかる
  • 電源投入から0.5秒遅れてヘッドホン出力のリレーが動作(ポップ音対策)
  • セレクタ切替時のミューティング動作※(ポップ音対策)
  • ボリュームが操作されていないときは、PGA2311とのSPI通信を停止(ノイズ予防策)
  • ライン入力に限り半固定抵抗でアナログライン出力の±8dBレベル調整可
※入力オフセット電圧差によるポップ音対策ですが、ミューティング時間を数ms以上に設定すると動作がおかしくなる事象があり、とりあえずミューティング時間2msとしています。


プログラムの流れとしては、初期化ルーチンでデバイスの設定を行うほか、ヘッドホン出力のリレーの遅延動作を行っています。セレクタスイッチの読み込みや、ボリューム電圧のA/D変換、PGA2311の制御など主な処理は、5msごとにタイマ割り込みをかけて行っています。割り込みハンドラのフローチャート(概要)は次のようになっています。





メモリ使用量

  
実際のソフト

 AVRのソフトを書くのは、これが3度目です。とりあえず動いた、という程度の完成度ですので、その前提でご覧下さい。開発環境は、WinAVR 2009.03.13版です。

#define F_CPU 1.000E6  // マスタクロック1MHz、内蔵オシレータ

#include
#include
#include
#include

/*
    PD7 O SCLK (PGA2311)
    PD6 O OUTPUT_RELAY (0: Disable, 1: Enable)
    PD5 I SELECTOR SW[6] "line2"
    PD4 I SELECTOR SW[3] "opt2"
    PD3 I SELECTOR SW[2] "opt1"
    PD2 I SELECTOR SW[1] "usb"
    PD1 O LED, "INPUT ENABLE" (0: Disable, 1: Enable)
    PD0 O SEL_DIN (Main Board: Select DAC Input)

    PC6 - (ISP: RESET)
    PC5 O SEL_AIN1 (Main Board: Select Line1 Input)
    PC4 I DAC_ERROR
    PC3 O DIGIIF_SEL[1]   
    PC2 O DIGIIF_SEL[0] (0:opt3, 1:opt1, 2:usb, 3:opt2)
    PC1 O SEL_AIN2 (Main Board: Select Line2 Input)
    PC0 I (A/D)TRIM_LINE2

    PB7 I SELECTOR SW[5] "line1"
    PB6 I SELECTOR SW[4] "opt3"
    PB5 - (ISP: SCK)
    PB4 - (ISP: MISO)
    PB3 - (ISP: MOSI)
    PB2 O CS1 (PGA2311_HPA)
    PB1 O CS2 (PGA2311_LINE)
    PB0 O SDI (PGA2311)

    ADC7 I (A/D)TRIM_LINE1
    ADC6 I (A/D)VOLUME
*/

static uint8_t current_sel_sw_state;
static uint8_t idle_count = 0;

volatile uint8_t att_value[256] = {    // 対数カーブ
        0x00,0x00,0x1F,0x2E,0x39,0x41,0x47,0x4D,0x52,0x56,0x5A,0x5D,0x61,0x63,0x66,0x69,
0x6B,0x6D,0x6F,0x71,0x73,0x75,0x77,0x78,0x7A,0x7B,0x7D,0x7E,0x7F,0x81,0x82,0x83,
        0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x91,0x92,
        0x93,0x94,0x94,0x95,0x96,0x96,0x97,0x98,0x98,0x99,0x9A,0x9A,0x9B,0x9C,0x9C,0x9D,
        0x9D,0x9E,0x9E,0x9F,0xA0,0xA0,0xA1,0xA1,0xA2,0xA2,0xA3,0xA3,0xA4,0xA4,0xA4,0xA5,
        0xA5,0xA6,0xA6,0xA7,0xA7,0xA8,0xA8,0xA8,0xA9,0xA9,0xAA,0xAA,0xAA,0xAB,0xAB,0xAC,
        0xAC,0xAC,0xAD,0xAD,0xAE,0xAE,0xAE,0xAF,0xAF,0xAF,0xB0,0xB0,0xB0,0xB1,0xB1,0xB1,
        0xB2,0xB2,0xB2,0xB3,0xB3,0xB3,0xB4,0xB4,0xB4,0xB4,0xB5,0xB5,0xB5,0xB6,0xB6,0xB6,
        0xB6,0xB7,0xB7,0xB7,0xB8,0xB8,0xB8,0xB8,0xB9,0xB9,0xB9,0xB9,0xBA,0xBA,0xBA,0xBA,
        0xBB,0xBB,0xBB,0xBC,0xBC,0xBC,0xBC,0xBC,0xBD,0xBD,0xBD,0xBD,0xBE,0xBE,0xBE,0xBE,
        0xBF,0xBF,0xBF,0xBF,0xBF,0xC0,0xC0,0xC0,0xC0,0xC1,0xC1,0xC1,0xC1,0xC1,0xC2,0xC2,
        0xC2,0xC2,0xC2,0xC3,0xC3,0xC3,0xC3,0xC3,0xC4,0xC4,0xC4,0xC4,0xC4,0xC5,0xC5,0xC5,
        0xC5,0xC5,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC7,0xC7,0xC7,0xC7,0xC7,0xC8,0xC8,0xC8,
        0xC8,0xC8,0xC8,0xC9,0xC9,0xC9,0xC9,0xC9,0xC9,0xCA,0xCA,0xCA,0xCA,0xCA,0xCA,0xCB,
        0xCB,0xCB,0xCB,0xCB,0xCB,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCD,0xCD,0xCD,0xCD,0xCD,
        0xCD,0xCD,0xCE,0xCE,0xCE,0xCE,0xCE,0xCE,0xCE,0xCF,0xCF,0xCF,0xCF,0xCF,0xCF,0xD0
        };

void wait_ms(uint16_t t) {

    while (t--) _delay_ms(1);     //1ms

}


void init_devices(void)
{

    //     ポート設定 0:入力, 1:出力
    DDRB = 0b00000111;
    DDRC = 0b0101110;
    DDRD = 0b11000011;

    //    出力ポート初期化
    //    入力ポート、1:プルアップ
    PORTB = 0b11000110;
    PORTC = 0b0100000;
    PORTD = 0b00111101;

    // CTC動作
    TCNT1 = 0;    //    タイマ1の初期値設定
    OCR1A = 5000;    //    タイマ/カウンタ1比較レジスタA, 5ms

    // 15.11.1 タイマ/カウンタ1制御レジスタA
    TCCR1A = 0b00000000;

    // 15.11.2 タイマ/カウンタ1制御レジスタB
    //    WGM13:0, WGM12:1 CTC top=OCR1A
    //    Clock: w/o prescale
    TCCR1B = 0b00001001;

    // 15.11.8 タイマ/カウンタ1割り込みマスクレジスタ
    TIMSK = 0b00010000;

    wait_ms(500);        // Wait: 500ms

    // 出力リレー
    PORTD |= _BV(6);    // Output Relay Enable

}



void pga2311(uint8_t ATT)
{
    int8_t n, m;

    PORTD &= ~_BV(7);    // SCLK -> 0

    for (n = 0; n < 2; n = n + 1) {
        for (m = 7; m >= 0; m = m - 1) {


            // SDI write
            if(bit_is_set(ATT, m)){
                PORTB |= _BV(0);
            }else{
                PORTB &= ~_BV(0);
            }

            // MC write
            PORTD |= _BV(7);    // SCLK -> 1
            PORTD &= ~_BV(7);    // SCLK -> 0
        }
    }

    PORTB &= ~_BV(0);    // SDI -> 0
}



uint8_t selector_proc(void)
{

    uint8_t capture_sw;

    capture_sw = (bit_is_clear(PIND,5))<<5 | (bit_is_clear(PINB,7))<<4 | (bit_is_clear(PINB,6))<<3
                | (bit_is_clear(PIND,4))<<2 | (bit_is_clear(PIND,3))<<1 | (bit_is_clear(PIND,2));

    if (!((capture_sw == 0b000001) || (capture_sw == 0b000010) || (capture_sw == 0b000100)
        || (capture_sw == 0b001000) || (capture_sw == 0b010000) || (capture_sw == 0b100000))) {

        current_sel_sw_state = capture_sw;
        return 1;
    }

    if (capture_sw != current_sel_sw_state) {

        current_sel_sw_state = capture_sw;
        wait_ms(5);        // Wait: 5ms


        if (capture_sw != ((bit_is_clear(PIND,5))<<5 | (bit_is_clear(PINB,7))<<4 | (bit_is_clear(PINB,6))<<3
                    | (bit_is_clear(PIND,4))<<2 | (bit_is_clear(PIND,3))<<1 | (bit_is_clear(PIND,2)))) {

            current_sel_sw_state = capture_sw;
            return 1;
        }

        // Mute --------------------------------------------------------------------------------------------
        PORTB &= ~_BV(1);    // CS2 -> 0
        pga2311(0);
        PORTB |= _BV(1);    // CS2 -> 1

        PORTB &= ~_BV(2);    // CS1 -> 0
        pga2311(0);
        PORTB |= _BV(2);    // CS1 -> 1

        idle_count = 0;        // idle_count リセット

        // Selector SW Set ---------------------------------------------------------------------------------
        switch(current_sel_sw_state) {
       
            case 0b000001 :        // "usb"
           
                PORTD |= _BV(0);    //     Main Board: Select DAC Input
                PORTC &= ~_BV(5);
                PORTC &= ~_BV(1);
                PORTC |= _BV(3);    //     DIGIIF_SEL: usb
                PORTC &= ~_BV(2);
                break;

            case 0b000010 :        // "opt1"

                PORTD |= _BV(0);    //     Main Board: Select DAC Input
                PORTC &= ~_BV(5);
                PORTC &= ~_BV(1);
                PORTC &= ~_BV(3);    //     DIGIIF_SEL: opt1
                PORTC |= _BV(2);
                break;

            case 0b000100 :        // "opt2"

                PORTD |= _BV(0);    //     Main Board: Select DAC Input
                PORTC &= ~_BV(5);
                PORTC &= ~_BV(1);
                PORTC |= _BV(3);    //    DIGIIF_SEL: opt2
                PORTC |= _BV(2);
                break;

            case 0b001000 :        // "opt3"

                PORTD |= _BV(0);    //     Main Board: Select DAC Input
                PORTC &= ~_BV(5);
                PORTC &= ~_BV(1);
                PORTC &= ~_BV(3);    //     DIGIIF_SEL: opt3
                PORTC &= ~_BV(2);
                break;

            case 0b010000 :        // "line1"

                PORTD &= ~_BV(0);
                PORTC |= _BV(5);    //     Main Board: Select Line1 Input
                PORTC &= ~_BV(1);
                PORTC &= ~_BV(3);    //     DIGIIF_SEL: opt1
                PORTC |= _BV(2);
                break;

            case 0b100000 :        // "line2"
           
                PORTD &= ~_BV(0);
                PORTC &= ~_BV(5);
                PORTC |= _BV(1);    //     Main Board: Select Line2 Input
                PORTC &= ~_BV(3);    //     DIGIIF_SEL: opt1
                PORTC |= _BV(2);
                break;

            default :

                PORTD &= ~_BV(0);
                PORTC &= ~_BV(5);
                PORTC &= ~_BV(1);
                PORTC &= ~_BV(3);    //     DIGIIF_SEL: opt1
                PORTC |= _BV(2);
                break;
        }
    }


//    wait_ms(2);        // Wait: 2ms

    // LED, "INPUT ENABLE"
    if ((current_sel_sw_state == 0b100000) || (current_sel_sw_state == 0b010000) || (bit_is_clear(PINC,4))) {
        PORTD |= _BV(1);   

    }else{
        PORTD &= ~_BV(1);
        return 1;
    }

    return 0;

}



void attenuation_proc(void)
{

    static uint8_t current_volume = 0;
    uint8_t current_trim1 = 0, current_trim2 = 0, main_volume, line_trim;


    // Selector SW: Line1
    if (current_sel_sw_state == 0b010000) {

        // ADC7: TRIM_LINE1選択
        ADMUX = (_BV(REFS0) | _BV(ADLAR) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0));

        // A/D変換実行
        ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADPS1) | _BV(ADPS0));       

        // A/D変換完了まで待機
        while(ADCSRA & _BV(ADSC));


        // 前値との比較
        if (abs((int16_t) current_trim1 - (int16_t) ADCH) > 2) {
            idle_count = 0;        // idle_count リセット
        }

        current_trim1 = ADCH;

    }


    // Selector SW: Line2
    if (current_sel_sw_state == 0b100000) {

        // ADC0: TRIM_LINE2選択
        ADMUX = (_BV(REFS0) | _BV(ADLAR));   

        // A/D変換実行
        ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADPS1) | _BV(ADPS0));       

        // A/D変換完了まで待機
        while(ADCSRA & _BV(ADSC));

        // 前値との比較
        if (abs((int16_t) current_trim2 - (int16_t) ADCH) > 2) {
            idle_count = 0;        // idle_count リセット
        }

        current_trim2 = ADCH;

    }


    // ADC6: VOLUME選択
    ADMUX = (_BV(REFS0) | _BV(ADLAR) | _BV(MUX2) | _BV(MUX1));   

    // A/D変換実行
    ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADPS1) | _BV(ADPS0));       

    // A/D変換完了まで待機
    while(ADCSRA & _BV(ADSC));



    // 前値との比較
    if (abs((int16_t) current_volume - (int16_t) ADCH) > 2) {
        idle_count = 0;        // idle_count リセット

    } else if (idle_count < 200) {
        idle_count++;

    } else {
        return;

    }

    current_volume = ADCH;

    // Selector SW: Line1
    if (current_sel_sw_state == 0b010000) {

        main_volume = att_value[current_volume] + (current_trim1 >>3);
        line_trim = 188 + (current_trim1 >>3);

    // Selector SW: Line2
    } else if (current_sel_sw_state == 0b100000) {

        main_volume = att_value[current_volume] + (current_trim2 >>3);
        line_trim = 188 + (current_trim2 >>3);

    // Selector SW: Others
    } else {

        main_volume = att_value[current_volume] + 16;
        line_trim = 204 + (current_trim2 >>3);

    }

    // ヘッドホンアンプ ボリューム制御
    PORTB &= ~_BV(2);    // CS1 -> 0
    pga2311(main_volume);
    PORTB |= _BV(2);    // CS1 -> 1

    // ライン出力 ボリューム制御
    PORTB &= ~_BV(1);    // CS2 -> 0
    pga2311(line_trim);
    PORTB |= _BV(1);    // CS2 -> 1

}



ISR (TIMER1_COMPA_vect) {
//C:\WinAVR-20090313\avr\include\avr\iom8.h

    if (selector_proc() == 1) {

        cli();    // 割り込み禁止

        // Mute
        PORTB &= ~_BV(1);    // CS2 -> 0
        pga2311(0);
        PORTB |= _BV(1);    // CS2 -> 1

        PORTB &= ~_BV(2);    // CS1 -> 0
        pga2311(0);
        PORTB |= _BV(2);    // CS1 -> 1

        sei();    // 割り込み許可

    }else{

        cli();    // 割り込み禁止
        attenuation_proc();
        sei();    // 割り込み許可

    }

}


int main(void) {

    init_devices();

    //    割込 初期設定
    SREG |= _BV(7);    // 全割込許可

    sei();    // 割り込み許可

    while(1);    // 無限ループ

}

ヘッドホンアンプ: 最終版回路図

AVRの交換

壊れたATMEGA8L(3.3V)の代わりに、ATMEGA8A(5V)に交換することにしました。5V系→3.3V系のレベルシフトは、Webの記事を参考にして10kΩの抵抗を挿入することでクリア。大事にならずにすみました。


壊れたAVRを何とか剥がして


無事交換



最終版の接続図

今までの細かな修正を踏まえた接続図を示します。