AD9834を使った実験用DDS-VFOの製作

[公開:any]

[電子工作/PIC]
[電子工作/アマチュア無線]
[AD9834,PIC18F14K50,PIC,PIC18F,DDS,APB-3]

origin 2013-05-09


 受信機や送信機を作るために周波数混合や周波数変換の回路をブレッドボードでテストするときにDDS-ICのVFOを使うと局発やIFの周波数を自由に設定できて便利です。NE612やTA7358などのDBM機能を有したICへの入力ならDDS-ICの出力信号レベルで問題なく動作しました。


 ブレッドボードに組んだAD9834を使用したVFOの10MHz出力時のスペクトラムをAPB-3で測定しました。同調回路を持たないのでスプリアスが多く見られますが、ブレッドボード上に受信機を組み立てて、実際に受信してみても、意外なことにスプリアスの影響はあまり感じられませんでした。ブレッドボード上の受信機の性能では影響が感じられないということですね。いずれにしろ、発振周波数に近接するもの以外は、混合や変換後の同調回路やフィルタなどで十分に除外可能と思われます。


 混合回路などいろいろと試したいのですが、そのたびにブレッドボード上にDDS-VFOを組んでいては大変です。また、クリスタルフィルターを変更したりするたびに、ソースプログラムを変更してIFを書き換えるのも面倒です。よって、実験用DDS-VFOとして製作しておくことにしました。DDSは、アナログデバイセスのAD9834を使用しますが、今回は完成品として市販されているストロベリーリナックス社の小型DDSモジュールキットを購入しました。AD9834単品が1個1000円以下で購入可能ですが、キットは、1台2500円程度とやや値が張ります。しかし、67.108864MHzのクロックモジュールが搭載されていることやコンパクトな専用基板に組み立て済みであることを考えるとこれだけの価値はありそうです。

ストロベリー・リナックス小型DDSモジュールキット

 ブレッドボード上のDDSは、AVRのATmegaシリーズでコントロールしていましたが、今回は、AD9834の制御と液晶表示のみと機能が単純なのでピン数の少ないマイコンを利用します。AVRのATTinyシリーズがよさそうですが、手持ちがなかったので、PICを使用することにしました。


 PICは、20ピンのPIC18F40K50を使用しました。はじめはPIC18F1320で作り始めたのですが、入出力ポートの設定自由度が低いのでピン数が不足となり、途中で変更しました。DDSの出力は、50Ω負荷で-15dBm程度と小さなレベルです。トランジスタやFETで広帯域増幅回路をテストしましたが、スプリアス強度が増してVFOとしては使いにくくなると判断しました。もし、レベルが低いのであれば、実験回路側で同調回路をもった増幅回路を設けることにします。(エミッタフォロアやソースフォロアなどでインピーダンス変換回路を入れたほうがよいかもしれません。)

PIC18F_AD9834_DDS-VFO回路図

 部品数も少ないため、秋月電子の小さい基板で作成しました。ケースも秋月電子のポリカーボネートケース中サイズです。液晶は白文字の小型液晶を使いましたが、バックライトの漏れがひどく見た目は良くありません。かといって、この液晶はバックライト無しでは、文字が読めません。


 液晶では、VFOとして受信・送信周波数の表示と、中間周波数分シフトしたDDSが出力する周波数の2つを同時に表示するようにしています。こうしておけば受信機などの実験中にIFとDDSの発振周波数から目的周波数を計算しなくてもすみます。
 IF設定モードでIFを8MHzに設定した場合、目的周波数に7MHz付近を設定するとVFOからは、15MHzの信号が出力されます。


 IFには、負の値も設定可能です。使用する中間周波数にあわせて自由に設定可能です。


 DDSの出力範囲として1Hzから33MHzまでとしていますので、それ以外の設定ではエラーとなります。


 手動で目的周波数や中間周波数をEEPROMに書き込むことで、電源を入れなおしても設定周波数が保持できるようにしました。


 完成したDDS-VFOのスペクトラムをAPB-3で測定結果です。広帯域ではスプリアスが目立ちますが、狭帯域で見る限り、VFOとしての信号純度は問題ないと判断しました。

DDS-VFOスプリアス広帯域DDS-VFOスプリアス狭帯域

 TA7358Pを使用してDC受信機を組み立ててSGからのAM変調信号を受信してみましたが、30MHzまでクリアに受信できます。また、DC受信機で、実際にアンテナを接続して3.5MHz帯や7MHz帯のアマチュア無線を受信してみましたが、問題はありませんでした。ただ、電源の質にシビアです。スイッチング電源のACアダプターを電源に使うとキャリアにノイズが混じることがありました。(3端子レギュレータを使用した電源では、特に問題ありません。)
 なお、このDDSモジュールの出力はAD9834からの直接出力なのでDCが載っています。必要に応じてコンデンサでDCカットする必要があります。


 参考までに(あまり参考になりませんが・・)作成したプログラムです。パソコンが変わったため、PICの開発環境を改めて構築しました。MPLAB Version8.90にCコンパイラはフリーのC18をインストールしました。C18は、6ヶ月間だけ製品版と同じ最適化が利用できるものもありましたが、今回は最適化の機能削除版であるLITE版をインストールしました。最近ではAVRの開発環境が高機能となったため、MPLABがシンプルに感じます。Pickit2による書き込みの連動もできて使いやすい環境です。
 ロータリーエンコーダーの読み取りは、ピン変化割り込みとしました。もし、チャタリングが多いようなら、ピン変化割り込みをやめて、タイマー割り込みとして、読み取りにディレイを持たせるなどの工夫が必要です。
 スイッチは、周波数ステップのアップダウンに1個ずつ割り当てています。ステップアップスイッチを長押しするとIFセットアップモードと通常のVFOモードへの切り替えになります。ステップダウンスイッチを長押しするとEEPROMへの書き込みとなります。また、IFセットアップモードでステップダウンスイッチを長押しすると設定初期化となり、VFO周波数が10.000.000MHz、IFが0MHzにリセットされます。発振出力の停止スイッチはありませんが、IFセットアップモードでDDSをスリープさせるので、発振出力のON/OFFが可能です。

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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/**************************************************************
    PIC18F DDS chip control
        DDS: AD9834 (Analog Devices Co.)
        PIC: PIC18F14K50
             16MHz Internal Clock Oscillator
2013.05.02  origin www.henteko.org
***************************************************************/
#include <p18f14k50.h>
#include <delays.h>
#include <EEP.h>
#include <stdio.h>
#include "lcd.h"

#pragma config FOSC = IRC
#pragma config WDTEN = OFF
#pragma config FCMEN = OFF
#pragma config PWRTEN = ON
#pragma config BOREN = ON
#pragma config BORV = 30
#pragma config LVP = OFF
#pragma config MCLRE = OFF
#pragma config HFOFST = ON
#pragma config PLLEN = OFF
#pragma config CPUDIV = NOCLKDIV

#define DDS_FSYNC    LATCbits.LATC5    // AD9834 sync
#define DDS_SCLK    LATAbits.LATA5    // AD9834 clock
#define DDS_SDATA    LATAbits.LATA4    // AD9834 data
#define MAX_FREQ    33000000    // max frequency
#define MIN_FREQ    -33000000    // min frequency
#define DEF_FREQ    10000000    // default frequency
#define EEPROM        0x0000        // EEPROM address
unsigned char tmp[17];            // lcd output srting
unsigned long freq;            // VFO frequency
long offset;                // IF frequency
unsigned long step;            // frequency up down step
unsigned char diff;            // frequency diff mode
unsigned char renc_now, renc_old;    // value of rotary encoder
unsigned char mode;            // 0:normal 1:IF setup mode
unsigned char cflag;            // frequency change flag

/***************************************************************
    ms step delay 
***************************************************************/
void delay_ms(unsigned int t)
{
    for(; t > 0; t--)        // 16MHz clock
        Delay1KTCYx(4);        // 1ms: 0.001 / ((1 / (16000000 / 4)) * 1000) = 12 
}

/***************************************************************
    us step delay 
***************************************************************/
void delay_us(unsigned int t)
{
    for(; t > 0; t--) {        // 16MHz clock
        Delay1TCY();        // 1 / (16000000 / 4) = 0.25us ...  0.25 * 4 = 1us
        Delay1TCY();        // Delay1TCY() = nop()
        Delay1TCY();
        Delay1TCY();
    }
}

/***************************************************************
    EEPROM write routine (long) 
***************************************************************/
void eeprom_write_long(unsigned int adr, long data)
{
    unsigned char i;

    for(i = 0; i < 4; i++) {
        Write_b_eep(EEPROM + adr * 4 + i, (unsigned char)((data >> (i * 8)) & 0x000000ff));
        Busy_eep();
    }
}

/***************************************************************
    EEPROM read routine (long)
***************************************************************/
long eeprom_read_long(unsigned int adr)
{
    long d;

    d = (unsigned long)Read_b_eep(EEPROM + adr * 4);
    d |= (unsigned long)Read_b_eep(EEPROM + adr * 4 + 1) << 8;
    d |= (unsigned long)Read_b_eep(EEPROM + adr * 4 + 2) << 16;
    d |= (unsigned long)Read_b_eep(EEPROM + adr * 4 + 3) << 24;

    return d;
}

/***************************************************************
    serial data send (MSB first)
***************************************************************/
void dds_dataset(unsigned long data, unsigned int bt)
{
    unsigned int i;

    DDS_FSYNC = 0;

    for(i = bt; i > 0 ; i--) {
        DDS_SDATA = (data >> (i - 1)) & 0x00000001;
        DDS_SCLK = 0;
        DDS_SCLK = 1;    
    }
    DDS_FSYNC = 1;
}

/***************************************************************
    AD9834 DDS control (frequency to serial code)
***************************************************************/
void freq2serial(unsigned long f)
{
    unsigned long msb, lsb;
    unsigned long data;

    data = f << 2;
    msb = (data >> 14) + 0x4000;
    lsb = (data & 0x00003fff) + 0x4000;

    dds_dataset(0x2000, 16);
    dds_dataset(lsb, 16);
    dds_dataset(msb, 16);
}

/***************************************************************
    interrupt (pin change interrupt)
***************************************************************/
#pragma interrupt isr
#pragma code isrcode = 0x8
void isr_direct(void)
{
    _asm
    goto isr
    _endasm
}
#pragma code
void isr(void)
{
    if(INTCONbits.RABIF) {
        if(PORTBbits.RB5 == 1) {
            if(PORTBbits.RB4 == 0)
                renc_now = 0;
            else 
                renc_now = 1;
        } else {
            if(PORTBbits.RB4 == 1)
                renc_now = 2;
            else
                renc_now = 3;
        }
        if((renc_now + 3 + 1) % 3 == renc_old) {
            diff = 0;
        }
        if((renc_now + 3 - 1) % 3 == renc_old) {
            diff = 1;
        }
        renc_old = renc_now; 

        INTCONbits.RABIF = 0;
    }
    cflag = 1;
}

/***************************************************************
    long to comma seperater lcd write
***************************************************************/
void long2comma(long data)
{
    int i;
    char buf[10];

    sprintf(buf, "%9ld", data);
    for(i = 0; i < 9; i++) {
        lcd_putch(buf[i]);
        if(i == 2 || i == 5) {
            if(buf[i] != 0x20 && buf[i] != '-')
                lcd_putch('.');
            else
                lcd_putch(' ');
        }
    }
}

/***************************************************************
    lcd write routine
***************************************************************/
void lcd_write()
{
    INTCONbits.GIE = 0;
    lcd_gotopos(0, 0);
    if(mode) { 
        sprintf(tmp, "IF ");
        lcd_putstr(tmp);
        long2comma(offset);
    } else {
        sprintf(tmp, "VFO");
        lcd_putstr(tmp);
        long2comma(freq);
    }
    sprintf(tmp, "Hz");
    lcd_putstr(tmp);

    if(!mode) {
        lcd_gotopos(0, 1);
        if(((freq + offset) > 0) && ((freq + offset) < MAX_FREQ))        
            sprintf(tmp, "DDS");
        else
            sprintf(tmp, "ERR");
        lcd_putstr(tmp);
        long2comma(freq + offset);
        sprintf(tmp, "Hz");
        lcd_putstr(tmp);
    }
    INTCONbits.GIE = 1;
}

/***************************************************************
    main
***************************************************************/
void main(void)
{
    unsigned char sw0_state, sw1_state;
    unsigned int tim0, tim1;
    unsigned int pos;        // cursor position

    OSCCON = 0b11111111;    // 16MHz internal clock

    TRISA = 0b00000000;        // RA4,5 AD9834 serial control
    TRISB = 0b11110000;        // RB4,5 rotary encoder input RB6,7 sw input
    TRISC = 0b00000000;        // LCD and RC5 AD9834 serial control
    ADCON0bits.ADON = 0;    // ad converter off
    UCONbits.USBEN = 0;        // USB disable
    ANSELHbits.ANS10 = 0;    // RB4 digital input
    ANSELHbits.ANS11 = 0;    // RB5 digital input
    ANSELbits.ANS3 = 0;        // RA4 digital input
    ANSELbits.ANS6 = 0;        // RC2 digital input
    INTCONbits.RABIE = 1;    // PortA,B pin change interrupt enable
    INTCON2bits.RABPU = 0;    // PortA,B pull-up enable
    INTCON2bits.RABIP = 0;    // PortA,B interruput priority low
    WPUB = 0b11110000;        // RB4,5,6,7 pull-up
    IOCBbits.IOCB4 = 1;        // RB4 pin change interrupt enable
    IOCBbits.IOCB5 = 1;        // RB5 pin change interrupt enable

    lcd_init();
    lcd_cls();

    sw0_state = sw1_state = 0;
    freq = eeprom_read_long(0);
    offset = eeprom_read_long(1);
    pos = 9;
    mode = 0;
    step = 1000;
    diff = 0;
    tim0 = tim1 = 0;

    if(freq <= 0 || freq >= MAX_FREQ) {
        freq = DEF_FREQ;
        offset = 0;
    }
    if(((freq + offset) > 0) && ((freq + offset) < MAX_FREQ)) {        
        PORTCbits.RC2 = 1;
        freq2serial(freq + offset);
    } else {
        PORTCbits.RC2 = 0;
        freq2serial(0);
    }    

    lcd_putcmd(0x0e);
    lcd_write();

    INTCONbits.GIE = 1;        // general interrupt enable
    cflag = 0;

    while(1){
        
        // step down or press for long time to EEPROM memory write
        if(PORTBbits.RB6 == 0) {
            sw0_state = 1;
            delay_ms(10);
            tim0++;
        }
        if(sw0_state && PORTBbits.RB6 == 1) {
            sw0_state = 0;
            tim0 = 0;
            if(step == 10) {
                step = 1;
                pos = 13;
            } else if(step == 100) {
                step = 10;
                pos = 12;
            } else if(step == 1000) {
                step = 100;
                pos = 11;
            } else if(step == 10000) {
                step = 1000;
                pos = 9;
            } else if (step == 100000) {
                step = 10000;
                pos = 8;
            }
        }
        
        // step up or press for long time to IF setup mode
        if(PORTBbits.RB7 == 0) {
            sw1_state = 1;
            delay_ms(10);
            tim1++;
        }
        if(sw1_state && PORTBbits.RB7 == 1) {
            sw1_state = 0;
            tim1 = 0;
            if(step == 1) {
                step = 10;
                pos = 12;
            } else if(step == 10) {
                step = 100;
                pos = 11;
            } else if(step == 100) {
                step = 1000;
                pos = 9;
            } else if(step == 1000) {
                step = 10000;
                pos = 8;
            } else if(step == 10000) {
                step = 100000;
                pos = 7;
            }
        }

        // frequency and offset write to EEPROM
        if(tim0 > 100) {
            sw0_state = 0;
            tim0 = 0;
            lcd_gotopos(0, 1);
            if(mode == 1) {
                freq = DEF_FREQ;
                offset = 0;
                sprintf(tmp, "Memory Clear!!!!");
            } else 
                sprintf(tmp, "Write to EEPROM!");
            lcd_putstr(tmp);
            eeprom_write_long(0, freq);
            eeprom_write_long(1, offset);
            delay_ms(1000);
            lcd_write();
        }        

        // mode change (mode0: VFO mode, mode1: IF setup mode)
        if(tim1 > 100) {
            sw1_state = 0;
            tim1 = 0;

            if(!mode) {
                mode = 1;
                dds_dataset(0x0040, 16);    // ad9834 sleep
                PORTCbits.RC2 = 0;
                lcd_gotopos(0, 1);
                sprintf(tmp, "IF Setup Mode   ");
                lcd_putstr(tmp);
                lcd_write();
            } else {
                mode = 0;
                lcd_gotopos(0, 1);
                sprintf(tmp, "VFO Mode        ");
                lcd_putstr(tmp);
                if(((freq + offset) > 0) && ((freq + offset) < MAX_FREQ)) {        
                    PORTCbits.RC2 = 1;
                    freq2serial(freq + offset);
                } else
                    PORTCbits.RC2 = 0;
                lcd_write();
            }
            while(PORTAbits.RA5 == 0);
        }

        // frequency changed
        if(cflag) {
            if(!mode) {
                if(diff) {
                    if((freq + step) <= MAX_FREQ)
                        freq += step;
                } else {
                    if((freq - step) >= step)
                        freq -= step;
                }
                if(((freq + offset) > 0) && ((freq + offset) < MAX_FREQ)) {        
                    freq2serial(freq + offset);
                    PORTCbits.RC2 = 1;
                } else {
                    PORTCbits.RC2 = 0;
                }
            } else {
                if(diff) {
                    if((offset + (long)step) <= MAX_FREQ)
                        offset += step;
                } else {
                    if((offset - (long)step) >= MIN_FREQ)
                        offset -= step;
                }
            }
            lcd_write();
            cflag = 0;
        }
        lcd_gotopos(pos, 0);
    }
}

▲ページ Top へ...