AD9851を使ったアンテナアナライザーの製作

[公開:any]

[電子工作/アマチュア無線]
[電子工作/AVR]
[電子工作/測定器]
[測定器,AD9851,DDS,ATmega328P,アンテナ,HT7750,LM358]

origin 2013-01-02



 アンテナの製作や調整に便利な測定器にアンテナアナライザーがあります。市販でもいくつか販売されています。Webで物色しているとF6BQUさんのサイトで自作例が見つかりました。大勢の方がここを参考に製作されています。
 このサイトでは、高周波出力にワンチップオシレーターICのLTC1799を使用して1〜30MHzに対応しています。また、発振周波数の測定には、マイコンを使った周波数カウンタを内蔵しています。LTC1799は秋月電子でも取り扱いがあり、500円程度で入手できます。ただ、出力波形が矩形波なのでスプリアスが大きいと思われます。測定には影響が少ないようですが、躊躇するところです。
 今回は、どうせマイコンが必要となるならと、専用DDS-ICを使用して50MHz帯のアンテナまで対応できるものを製作することにします。DDS-ICは、以前、DDSを使ったスイープジェネレーターの製作でアナログ・デバイセス社のAD9834を使用しました。AD9834は、供給クロックが75MHzまで対応しているので、無理をすれば30MHz程度までが実用範囲となります。50MHz帯のアンテナ測定には最低でも60MHz、できれば70MHzまで出力できるものが必要と考えました。そこで、AD9834の上位であるAD9851を使用することにしました。AD9851は、Digi-keyで約2300円(2012年12月現在)と高価なDDS-ICですが、クロック180MHz(30MHzの供給クロックを内部で6逓倍)と高速なので、70MHzは実用範囲と判断しました。

 さっそくAD9851を入手してブレッドボードで制御プログラムを作成しました。制御用のマイコンはAVRのATmega328Pを使用しました。AD9851は、TSSOPなのでDIPへの変換基板に実装しています。AD9851に必要なパスコンは、変換基板の裏に実装しています。AD9851のシリアル制御方法はAD9834とは少し違いますが、特に難しいところはありません。

AD9851アンテナアナライザー

 正常な制御が可能となったので、高周波発振回路のユニットをユニバーサル基板に作成しました。AD9851の出力は10mA程度です。50Ωで受けるにはレベルが低いので、出力レベルを稼ぐためにトランスで受けるようにしています。これでも出力は0dBm程度と低いのですが、1〜70MHzに対応する広帯域増幅回路がつくれそうもないので、このままLPFを通して出力としました。


 LPFはカットオフ周波数70MHzとした7次のチェビシェフフィルタです。スマートフォンのRF&Microwaveというツールを使って設計しました。その他、高周波関連のツールが含まれた便利なアプリケーションです。


 高周波ユニットの出力レベルをスペアナで測定しました。LPFの特性が良くわかります。低い周波数5MHz以下でのレベル低下は、出力にリアクタンス測定基準となる150pFのコンデンサを挿入したことによる損失です。インピーダンスマッチングが取れていないせいか、多少の変動はありますが、概ね0〜-10dBm(0.1mW)となりました。この程度のレベル変動であれば、手動でスイープしながらアンテナの同調周波数を検索する用途にも使えそうです。


 GigaStでスプリアスを見てみました。高い周波数で帯域内にややレベルの高いスプリアスが出力されますが、基本波より40dB程度低いので影響は少ないと思われます。


 高周波ユニット基板に測定用のコイルブリッジを実装してアンテナアナライザーとしての動作を確認しました。参考にしたWebサイトでは、測定表示にラジケータ(パネルメータ)を使用していますが、ケースへの組み込みが面倒なので、AVRのAD変換で読み取ってLCDに表示する方法を採用しました。LCDでのアナログメーター表示は、えるむ by ChaNさんの「汎用キャラクタLCD制御モジュール」を利用させていただきました。このモジュールには、キャラクタLCDでバーグラフを実現するlcd_put_bar関数が実装されているので簡単に高感度のアナログメーターが実現できます。


 AVRをのせた制御ユニットもユニバーサル基板で作成しました。測定表示のAD変換入力は、最大でも200mV程度と小さな値なので少しでも分解能をあげるためにAVRのAD変換リファレンスを内部1.1Vとしています。それでも十分ではなかったため、オペアンプで2倍に増幅しました。また、LCD表示のバーグラフもAD変換値の16ビット(1024)をすべて表示するのではなく、下位の8ビット(255)のみを表示して低レベルでの調整に特化させました。


 最終的な回路図です。AVRやAD9851は、電池2本の3V程度でも問題なく動作しますが、30MHzのクロックモジュールや高周波の出力レベルの関係から+5Vの電源が必要となります。このため、DC-DCコンバーターICのHT7750Aを使用して昇圧することにしました。180MHz動作のAD9851は、約100mA必要となるのでニッケル水素充電池2本の2.4Vからは5Vを維持できません。仕方なく電池3本を使用することにしました。電池4本にすると、通常の乾電池を使用した場合は、HT7750Aの入力制限の6Vを越える可能性があるため、電池3本としています。
 測定出力のダイオード検波以降は、途中でオペアンプを追加したため余分なローパスフィルタ等があります。


DC-DCコンバータの影響でAD9851の出力が発振気味になります。AD9851のアナログ電源にRFCを入れてパスコンを増やすなどの対策をとりました。(2013-05-31)


 ケースは、タカチのMB-3を使用しました。ラジケータを使用しないのでスペースに余裕があります。電池は交換が簡単にできるように外部にふたつきの電池ボックスを固定しました。


 大きなノブは周波数を変更するロータリーエンコーダーです。周波数は1KHz単位での可変としました。また、プッシュスイッチで可変単位を1KHz、10KHz、100KHz、1MHzとアップ・ダウンできるようにしています。ニッケル水素充電池を使用する場合は、過放電に注意しなくてはなりません。このため、LCDの1行目のあきスペースに電池電圧を表示するようにしました。


 50Ωのダミーロードを接続して抵抗分とリアクタンス分のヌル点を探します。測定範囲でほぼ一定の結果が得られました。LCDのバーグラフ表示の分解能が高いためラジケータよりも見やすく微妙な調整がやりやすいと感じました。きっちりとした校正を行えばある程度の精度がありそうですが、アンテナ調整に絶対値の測定はそれほど必要ないと思います。


 ダミーロードをはずして測定端を開放するとすべて反射されて測定表示はフルスケールとなります。LCDでは、下位の8ビットのみをキャラクタ16桁で拡大表示しているので反射波の検波出力が274mV(オペアンプでの2倍を含めて)以上はフルスケールとなります。


 実際のアンテナで測定してみました。7MHzの短縮ホイップは、抵抗分とリアクタンス分をヌル点とした状態で周波数を可変すると7.020MHzでメーター表示が最も小さくなります。つまり、この周波数で、アンテナの実数部が50Ω、虚数部が0Ωとなり完全に整合した状態です。(・・・この辺の理解は怪しいです。)


 完全に整合する周波数よりも100KHz程度高い周波数では、放射抵抗に大きな変化はないので実数部を示す抵抗分はほとんど変更がありません。しかし、虚数部は、必要となるアンテナの長さよりもエレメントが若干長いため、誘導性リアクタンスを帯びます。このため、メータ表示を最小とするには、ポリバリコンをヌル点よりXL側に調整する必要があります。実際のダイポールアンテナではエレメントを短く切り詰めるか、短縮コンデンサを追加して誘導性リアクタンスを打ち消す必要があります。(写真のXLとXCのラベル位置が逆になっています。)
 逆に100KHz程度低い周波数では、抵抗分に大きな変化はありませんが、エレメントが若干短いため容量性リアクタンスを帯びます。このため、リアクタンスをXC側で整合を取る必要があります。実際のダイポールアンテナの場合は、エレメントを長くするか延長コイル(ローディングコイル)を使いして容量性リアクタンスを打ち消す必要があります。
 このように、目的周波数に対してアンテナのインピーダンスを測定することで、アンテナの調整の方向付けを容易に確認することができます。


 AVRのソースプログラムです。AVRStudio6で作成しました。ロータリーエンコーダーはピン変化割り込み、液晶表示やAD変換は100msごとのタイマー割り込みとしています。バイナリのサイズは8Kbyte弱なのでATmega88でも問題ありません。(試してはいません)
 アンテナの調整には時間がかかります。アンテナアナライザーの電池の消費を避けるため電源をこまめにOFF・ONをするとそのたびに周波数が初期化されては面倒なので、必要に応じて初期周波数をEEPROMにメモリーする機能も搭載しました。周波数ステップUPスイッチを2秒程度押し続けると、そのときの周波数をメモリーします。電源をOFFしても次にONしたときは、その周波数がセットされるようになっています。AVRにプログラムを書き込んで最初に電源を入れるときは、65.535MHz(0xFFFF)がセットされます。普段、初期周波数としたい周波数に変更して書き込めば次からはその周波数になります。
 周波数の変更から測定までAVRのプログラミングで制御可能です。気合を入れてソフトウェアを作れば高度に自動化した測定も可能となります。グラフィック液晶を使って特性グラフを描画させるなどの応用も可能です。

AD9851のシリアルモードへのシーケンスを省略していましたが、まれにシリアルモードへの切り替えに失敗することがあったので処理を追加しました。(2013-01-20)


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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
 * antenna_analyzer.c
 *
 * Created: 2012/12/16 20:04:50
 *  Author: www.henteko.org
 */ 

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

#include "hd44780.h"    // from ELM by ChaN(http://elm-chan.org/index_j.html)

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

#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|0b111)
#define ADC_START  (ADC_ENABLE|_BV(ADSC))

#define PLL_PORT    PORTD
#define PLL_CLOCK    PD0
#define PLL_DATA    PD1
#define PLL_LE        PD2
#define FREQ_STEP   23.86092942    // 2^32 / 180MHz(DDS clock)

void delay_ms(unsigned int t) {    while(t--)  _delay_ms(1); }

FILE *fp;                    // for fdevopen()
unsigned long freq;            // frequency
unsigned long diff;            // frequency step
unsigned int batt;            // battery status
unsigned int lev;            // signal level    
unsigned char renc_now, renc_old;     // value of rotary encoder
unsigned char pos;            // lcd cursor position
unsigned long max, min;        // frequency max and min
volatile unsigned char tm;    // timer interrupt count
static unsigned int fmem __attribute__((section(".eeprom")));

// get ADC
unsigned int get_adc(unsigned char ch)
{
    ADMUX = ch;
    ADCSRA = ADC_START;
    loop_until_bit_is_set(ADCSRA, ADIF);

    return ADCW;
}

// AD9851 DDS serial control setup
void dds_init()
{
    sbi(PLL_PORT, PLL_CLOCK);
    cbi(PLL_PORT, PLL_CLOCK);
    sbi(PLL_PORT, PLL_LE);
    cbi(PLL_PORT, PLL_LE);
}

// AD9851 DDS dataset
void dds_dataset(unsigned char data)
{
    int i;

    for(i = 0; i < 8; i++) {
        if(data & 0x01)
            sbi(PLL_PORT, PLL_DATA);
        else
            cbi(PLL_PORT, PLL_DATA);
        sbi(PLL_PORT, PLL_CLOCK);
        cbi(PLL_PORT, PLL_CLOCK);
        data >>= 1;
    }
}

// AD9851 DDS control 
void freq2serial(unsigned long freq, unsigned char mode)
{
    unsigned int i;
    unsigned long data;
    
    data = (double)(freq * FREQ_STEP * 1000);
    for(i = 0; i < 4; i++) {
        dds_dataset(data & 0xff);
        data >>= 8;
    }
    dds_dataset(mode);    
    sbi(PLL_PORT, PLL_LE);
    cbi(PLL_PORT, PLL_LE);
}

// Pin change interrupt
ISR(PCINT1_vect)
{
    if(bit_is_set(PINC, PC5)) {
        if(bit_is_clear(PINC, PC4))
            renc_now = 0;
        else 
            renc_now = 1;
    } else {
        if(bit_is_set(PINC, PC4))
            renc_now = 2;
        else
            renc_now = 3;
    }

    if((renc_now + 3 - 1) % 3 == renc_old) {
        if(freq >= diff) {
            if((freq - diff) >= min)
                freq -= diff;
        }
    }
    if((renc_now + 3 + 1) % 3 == renc_old) {
        if((freq + diff) <= max)
            freq += diff;
    }
    renc_old = renc_now; 

    freq2serial(freq, 0x01);
}

// Timer1 compare match interrupt
ISR(TIMER1_COMPA_vect)
{
    tm = 1;
}

// lcd write routine
void lcd_output(void)
{
    lcd_locate(0, 0);
    fprintf(fp, "F:%6.03fMHz ", freq * 0.001);
    lcd_put_fuel(5, 4);
    fprintf(fp, "%2.1f", batt * 0.004883);
    lcd_locate(1, 0);
    lcd_put_bar(lev, 16, 5);
    lcd_locate(0, pos + 2);
}

int main()
{
    unsigned char sw0_state, sw1_state;
    unsigned int sw0_count, sw1_count;

    DDRB = 0b11111111;            // LCD
    DDRC = 0b00000000;            // routary encoder input 
    DDRD = 0b00011111;            // PD0-3 SW input
    PORTD = 0b11100000;            // PD0-3 pull up
    PORTC = 0b11111100;

    dds_init();

    diff = 1;
    max = 70000;
    min = 1000;
    pos = 5;
    sw0_state = sw1_state = 0;
    sw0_count = sw1_count = 0;
    tm = 0;

    PCICR = _BV(PCIE1);
    PCMSK1 = _BV(PCINT12)|_BV(PCINT13);
      
    TCCR1B = 0b01001011;    // Timer1 prescaler 1/64
    TIMSK1 = _BV(OCIE1A);    // Timer1 compare match A interrupt
    OCR1A = 12500;            // 8MHz / 64 = 125000. 100ms / (1/125000) = 12500.

    delay_ms(500);

    // read frequency from EEPROM
    eeprom_busy_wait();
    freq = eeprom_read_word(&fmem);
    if(freq > 70000 || freq < 1000)
        freq = 10000;
    freq2serial(freq, 0x01);

    lcd_init();
    fp = fdevopen(lcd_putc, NULL);        // LCD output file disc.
    lcd_cursor(CSR_UNDER);

    sei();

    while(1) {
        
        if(tm) {
            tm = 0;
            lev = get_adc(0xc1);    // first time is canceled
            delay_ms(7);
            lev = get_adc(0xc1);    // 0xc0  REFS1,REFS0 and MUXs
            delay_ms(2);
            lev += get_adc(0xc1);
            delay_ms(2);
            lev += get_adc(0xc1);
            lev = (int)lev / 3;
        
            batt = get_adc(0x42);    // first time is canceled
            delay_ms(5);
            batt = get_adc(0x42);
            delay_ms(2);
            batt += get_adc(0x42);
            batt = (int)batt / 2;

            lcd_output();
        }
                
        // UP-----------------------------------------
        if(bit_is_clear(PIND, PD5)) {
            sw0_state = 1;            
            delay_ms(10);
            sw0_count++;
        }
        if(sw0_state && bit_is_set(PIND, PD5)) {
            sw0_state = 0;
            sw0_count = 0;
            if(diff == 1) {
                diff = 10;
                pos = 4;
            } else if(diff == 10) {
                diff = 100;
                pos = 3;
            } else if(diff == 100) {
                diff = 1000;
                pos = 1;
            } else if(diff == 1000) {
                diff = 1000;
                pos = 1;
            }
        }
        // DOWN---------------------------------------
        if(bit_is_clear(PIND, PD6)) {
            sw1_state = 1;            
            delay_ms(10);
        }
        if(sw1_state && bit_is_set(PIND, PD6)) {
            sw1_state = 0;
            if(diff == 1000) {
                diff = 100;
                pos = 3;
            } else if(diff == 100) {
                diff = 10;
                pos = 4;
            } else if(diff == 10) {
                diff = 1;
                pos = 5;
            } else if(diff == 1) {
                diff = 1;
                pos = 5;
            }
        }
        // frequency memory to EEPROM
        if(sw0_count > 100) {
            sw0_state = 0;
            sw0_count = 0;
            eeprom_busy_wait();
            eeprom_write_word(&fmem, freq);
            lcd_locate(1, 0);
            fprintf(fp, "Write EEPROM....");
            delay_ms(800);            
        }
    }
}

▲ページ Top へ...