AVR-DDSを使ったツートーンジェネレーターの製作

[公開:any]

[電子工作/AVR]
[電子工作/アマチュア無線]
[AVR,ATtiny861A,DDS,APB-3]

origin 2013-06-01


このツートーンジェネレーターは、とりあえず作ってみたもので、実際にはSSBトランシーバーの製作に使用したことがありません。ですから、使えるかどうかは不明です。(2013-06-01現在)


 SSBトランシーバーの自作等で増幅回路の直線性を確認するときに利用するツートーンジェネレーター(2トーンジェネレーター)を製作します。ネットで情報をあさるとオペアンプを利用した発振回路を2信号分作成して合成をした回路を見かけます。ただ、オペアンプで作ると周波数の大きな変更が難しいので、いろんな周波数を組み合わせることが難しくなります。PICやAVRなどによるDDS方式で2波を発生させるツートーンジェネレーターなら発振周波数の変更が比較的自由になるので、この方式で作成することにしました。(ツートンジェネレータを使用したことがないので、よくわからないのですが、発振周波数の組み合わせが自由に変更できることにメリットがあると仮定しています。)
 PICやAVRで直接DDS方式で交流信号を得るのは、「PIC-DDSの基礎実験」や「AVRを使ったDDS方式低周波発振器の製作」でノウハウがあります。AF帯とはいえ綺麗な正弦波を発生させるためにはサンプリング速度をできるだけ稼ぐ必要があるので、今回は、高速動作に有利なAVRを使用することにします。

 ブレッドボード上でAVRのATmega88Pを使用して実験しました。DAC(DIgital to Analog Converter)は抵抗を利用した8ビットのR-2Rラダー回路です。AVRは、8ビット分のポートを2セット使用してそれぞれ別のDACで2波を発生させ、オペアンプの加算回路により合成しています。(アナログ合成)

2トーンジェネレーター

 700Hzと2100Hzを出力したスペクトラムをAPB-3で確認します。APB-3は、このようなAF帯も問題なく測定することができるので便利です。スプリアスがやや多く見えますが、発振信号から60dB以上は低いので問題ないと判断しました。CRローパスフィルターのカットオフ周波数を2KHzとしているので全体的に右下がりのスペクトラムとなっています。

2トーンジェネレータースペクトラム

 DDS2系統の波形信号を、あらかじめAVR内部で加算処理して合成波を出力する方式(つまりデジタル合成)にすればDACは1回路ですみます。8ビットポートも1系統ですむため、ピン数の少ないAVRを使用できます。20ピンのATtiny861Aに変更して実験してみました。

DDS 2トーンジェネレーター

 700Hzと2100Hzを合成出力したスペクトラムです。CRローパスフィルターを見直したのと、オペアンプによるアンプとバッファ(ボルテージフォロア)を入れているので、全体としてノイズフロアが上昇していますが、2波出力後の合成と大きな違いはなさそうです。


 DDS方式なので、発振周波数の精度や安定度は、AVRのクロックに使用している水晶発振子の精度や安定度に左右されます。2波の周波数を同じ1000Hzにして、APB-3で発振周波数を測定してみました。当然のことながら正確で安定しています。10分程度見てみましたが、小数点以下2桁まではまったく変動はありません。ただし、通常、ツートーンジェネレーターに、ここまでに精度や安定度は必要ないでしょう。


 ここまで、DDSのサンプリング速度を160KHzとしてきましたが、この速度では、AVRで実行したい他の機能に影響がでるので、サンプリング速度を下げる必要がありました。DDSで生成する最大周波数は3KHz程度なので、サンプリング定理から考えても10倍以上の40KHz程度のサンプリング速度で十分かと思い試してみると、とても実用になるものではありませんでした。サンプリング速度が80KHz以上ならAPB-3で見たスペクトラムに大きな違いは感じられません。ただ、APB-3には、アベレージング(平均化処理)がないので、ノイズレベルの比較が困難です。
 AF帯のスペクトラム測定は、パソコンのWaveSpectraがあります。APB-3とWaveSpectraのスペクトラムを比較測定してみました。信号レベルは、WaveSpectraが1dB程度高めに測定されますが、ほぼ同じようなスペクトラムが観測できます。WaveSpectraには、ひずみ率計算やアベレージングがあるので高度な測定が可能です。


 WaveSpectraのアベレージングで、サンプリング速度によるノイズレベルの変動を見てみました。最初は、サンプリング速度160KHzです。2100Hzの全高調波ひずみ率は、0.119%となりました。


 サンプリング速度80KHzです。APB-3では、大きな違いはわかりませんでしたが、全高調波ひずみ率も0.154%と悪くなっています。全体として細かなスプリアスが見られます。ただ、発振周波数とサンプリング速度の組み合わせで位相ノイズが大きく変化するため、この結果だけでは判別できません。しかし、他の発振周波数の場合も、細かなスプリアスの増加が目に付きます。


 サンプリング速度100KHzです。全高調波ひずみ率も0.132%とサンプリング速度に応じた結果になりました。ノイズレベルも160KHzと大きな違いは見られません。AVRの処理も十分な余裕があります。これらの結果から、最終的にサンプリング周波数100KHzとしました。


 回路図です。出力スペクトラムから判別する信号純度は、DAC+CRローパスフィルターのみが一番良い結果が得られます。しかし、この状態では、出力インピーダンスが高いので低インピーダンスの負荷を接続することができません。したがって、オペアンプで増幅し、さらにボルテージフォロアのバッファを入れました。ここで使用するオペアンプは、入力もrail-to-railに対応したものが必要です。今回はLMC6482AINを選択しました。出力レベル調整のボリューム挿入場所は、アンプ入力やバッファ入力などいろいろ試しましたが、最終的に出力に入れることで一番よい結果が得られました。

AVR-DDSツートーンジェネレーター回路図

 共立電子オリジナルのユニバーサル基板で作成しました。周波数変更するスイッチは、使用頻度が少ないと考えて基板上にタクトスイッチで実装しました。ケース取り付けのLED内蔵プッシュスイッチで出力のオン・オフをします。基板上のLEDは、信号出力時に点灯させます。


 ケースは、秋月電子のABS樹脂ケース中サイズです。このケース、以前はポリカーボネート製だったのですが、今回調達したらABS樹脂製に変更になっていました。(以前と比較すると)やや透明度が悪く、くすんだ材質ですが、ポリカーボネートよりやわらかく加工性が良かったです。出力コネクタは、手持ちのケーブル等を考慮してBNCコネクタとしました。

2トーンジェネレーター2トーンジェネレーター

 当然のことながら、組み合わせの周波数によってスプリアスの周波数や強度が異なります。これは、1200Hzと2100Hzの2波出力時ですが、5800Hz付近にやや大きなスプリアスが見られます。ただ、これだけ離れれば、SSBトランシーバーのクリスタルフィルタ等で減衰するので大きな影響は無いでしょう。


 2波の周波数を同じにすれば、シングルトーンのAF発信機なります。初めは波形合成していたのですが、波形計算上、周波数によっては、信号レベルが小さくなることがありました。最終的には、同一周波数時は、1波のみ出力するようにプログラムしました。2波出力時に比べ、理論とおりに信号強度が6dB上昇しています。



 AVRのソースコードです。AVRStudio6で開発しました。ツートーンのデフォルトの周波数は、700Hzと2100Hzとしています。周波数は、それぞれのスイッチにより100Hzづつインクリメント可能で、500Hzから3000Hzまで個別に設定可能です。また、出力オン・オフスイッチを長押しすることで、デフォルト周波数にリセットすることができます。2波の合成は、加算して平均化(左1ビットシフト)して求めています。サンプリング周波数は、100KHzとしましたが、200KHzでも動作します。ただし、あまりサンプリング速度を上げると、ほとんどの処理時間を割り込みにとられることになるので、スイッチ入力などの動作が不安定となります。DDSで使用する波形テーブルデータは、1種類で256byteと小さいので通常のSRAM上においています。(ATtiny861AのSRAMは、512byteなので、余裕です。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * AVR DDS 2tone generator
 *
 * Created: 2013/05/21 15:17:49
 * Author: www.henteko.org
 */ 

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

#define cbi(addr,bit)     addr &= ~(1<<bit)
#define sbi(addr,bit)     addr |=  (1<<bit)

#define SF         100000         // Sampling Frequency 100KHz
#define ACCU    65536        // Accumulator
#define MAX        3000        // max frequency
#define MIN        500            // minimum frequency
#define DEF_LOW        700        // default upside frequency
#define DEF_HIGH    2100    // default downside frequency
#define FREQ_STEP    100        // frequency incremental step

unsigned int delta1, delta2;        // incremental value
unsigned int phe1, phe2;            // phase
unsigned int pos1, pos2;            // wave table position

const unsigned char sinewave[] =    // sine wave table 256 values
{
0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,
0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc,
0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3,
0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83,
0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51,
0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27,
0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a,
0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08,
0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23,
0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c,
0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c,
};

//----------------------------------------------------------------
// millisecond order delay
// ---------------------------------------------------------------
void delay_ms(unsigned int t)
{
    while(t--) {
        _delay_ms(1);
    }
}

//----------------------------------------------------------------
// Timer1 Compare match interrupt (DDS output)
// ---------------------------------------------------------------
ISR(TIMER0_COMPA_vect)
{
    phe1 += delta1;        // phase accumulator
    pos1 = phe1 >> 8;        // division 256
    phe2 += delta2;        // phase accumulator
    pos2 = phe2 >> 8;        // division 256

    if(delta1 == delta2)
        PORTA = sinewave[pos1];
    else
        PORTA = (sinewave[pos1] + sinewave[pos2]) >> 1;    // averaging
}

//----------------------------------------------------------------
// main routine
// ---------------------------------------------------------------
int main()
{
    unsigned long freq1, freq2;    // DDS output frequency[Hz]
    unsigned char sw0_state, sw1_state, sw2_state;
    unsigned int tim;    
    unsigned char intflag;        // interrupt enable flag

    DDRA = 0b11111111;        // 8bit DDS output
    DDRB = 0b11111000;        // PB0,1,2 SW input
    PORTB = 0b00000111;        // pull up

    TCCR0A = 1;                // CTC0 enable
    TCCR0B = 0b00000001;    // Timer0 prescaler 1/1
    TIMSK = _BV(OCIE0A);    // Timer0 compare match A interrupt Enabled
    OCR0A = 199;            // clock 20MHz sampling 100kHz 20000000/100000=200(-1) = 199  
    
    sw0_state = sw1_state = sw2_state = 0;
    tim = 0;
    intflag = 0;
    phe1 = phe2 = 0;
    freq1 = DEF_LOW;        // default upside frequency
    freq2 = DEF_HIGH;        // default downside frequency
    
    sei();
    intflag = 1;
    sbi(PORTB, PB3);
    
    while(1) {
        delta1 = freq1 * ACCU / SF;
        delta2 = freq2 * ACCU / SF;

        // Low frequency increment
        if(bit_is_clear(PINB, PB1)) {
            sw0_state = 1;            
            delay_ms(10);
        }
        if(sw0_state && bit_is_set(PINB, PB1)) {
            sw0_state = 0;
            freq2 += FREQ_STEP;
            if(freq2 > MAX)
                freq2 = MIN;
        }
        // High frequency increment
        if(bit_is_clear(PINB, PB2)) {
            sw1_state = 1;
            delay_ms(10);
        }
        if(sw1_state && bit_is_set(PINB, PB2)) {
            sw1_state = 0;
            freq1 += FREQ_STEP;
            if(freq1 > MAX)
                freq1 = MIN;
        }
        // signal on/off
        if(bit_is_clear(PINB, PB0)) {
            sw2_state = 1;            
            delay_ms(10);
            tim++;
        }
        if(sw2_state && bit_is_set(PINB, PB0)) {
            sw2_state = 0;
            tim = 0;
            if(intflag) {
                cli();
                intflag = 0;
                cbi(PORTB, PB3);
            } else {
                sei();
                intflag = 1;
                sbi(PORTB, PB3);
            }            
        }
        // reset frequency
        if(tim > 100) {
            sw2_state = 0;
            tim = 0;
            freq1 = DEF_LOW;
            freq2 = DEF_HIGH;
        }
    }
}

▲ページ Top へ...