DDSをリファレンスにしたPLL-VFOの実験

[公開:any]

[電子工作/アマチュア無線]
[電子工作/PIC]
[AD9833,DDS,PIC,PIC18F14K50]

origin 2016-10-16



 50MHzのSSBトランシーバーを作る予定でVFOを実験中です。前回製作した7MHzトランシーバーで作り直したDDS-ICのAD9833を使用したVFOが余っているので、これを使うことを考えました。ただ、50MHzトランシーバーに必要なVFOは10MHz付近のIFを採用すると40MHz付近の周波数を出力する必要があります。AD9833の出力周波数は最大でも12.5MHzまでなのでそのままでは使用できません。
 最初はミキサーICのTA7358かNE612を使用して水晶発振回路とDDSとのプリミックスVFOを考えたのですが、DDSの出力を5〜10MHzとした場合に必要となるクリスタル(30〜35MHz)が手持ちになかったので、別途後日実験する予定です。
 次に考えたのは、PLLを使用したVFOです。40MHz付近を発振するVCOをプリスケーラーで1/10の4MHz付近として、この周波数とAD9833で作成したリファレンスを位相比較してVCOを制御してやれば、周波数変動の少ない正確な周波数のVFOができそうです。ということでブレッドボードで実験してみました。

A9833 PLL-VFO

 DDSは、周波数変更の最小ステップを10Hzとしています。プリスケーラーで1/10としているので、実際の周波数ステップは10倍の100Hz単位となります。当然、DDSを1Hzステップとすれば10Hz、0.1Hzステップとすれば1Hz単位で周波数を可変することが理論的には可能ですが、ループフィルターの設計が面倒になります。(・・と思います。←このあたりの理論はあまりよくわかっていません。)

 実験した回路図です。VCOは、信号純度を無視すれば39MHz〜44MHz程度までは可変可能です。位相比較器は74HC4046を使用しました。最初は4046Bで実験したのですが、4MHzの位相比較はうまく動作しませんでした。DDSからの出力はそのままでは-18dBmと小さいので位相比較器の入力には弱すぎます。2SK241の広帯域アンプで増幅しました。

DDS PLL-VFO

 プリスケーラーは、計算が簡単になるように10進カウンタの74HC4017を使用しました。ロジックICは、HCタイプで最大30MHz程度までの動作速度と記憶していたので、ちょっと厳しいかな・・・と思っていましたが、手持ちの東芝製のTC74HC4017のデータシートを見てみると5Vで79MHzまで動作すると書いてあり問題ありませんでした。VCOから42.480MHzの信号を入れると1/10の4.248MHzの信号が出力されます。


 IFは、8MHz帯か9MHz帯を使用する予定です。DDSのプログラムを変更して4.0〜4.3MHzまで出力するようにしました。ループフィルタは、適当に決めた状態でも問題なくPLLがロックしました。スペアナでスパンを10KHzと5MHzで見てみましたが、とりあえず問題はなさそうです。受信機で聞いてみると微妙にノイズっぽいものが聞こえますが、ブレッドボードでの環境が影響している可能性もあります。


 AD9833は価格の安いDDS-ICなので、高い周波数までの出力はできませんが、PLLのリファレンスとして使用するか、またはミキサーICを使用したプリミックスVFOとしてなら十分に利用可能と思われます。

 過去のアナログ・デバイセズDDS-ICの制御プログラムと大きな違いはありませんが、参考までに載せておきます。使いまわしなので、余分なコードがあります。開発はMPLAB X IDEv3.10を使用し、Cコンパイラは、XC8version1.21を使用しています。

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
#include <xc.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 DEBUG

#define _XTAL_FREQ 16000000

#define DDS_FSYNC    LATAbits.LATA5  // AD9834 sync
#define DDS_SCLK    LATAbits.LATA4    // AD9834 clock
#define DDS_SDATA    LATCbits.LATC5    // AD9834 data
#define MAX_FREQ    4300000    // max frequency
#define MIN_FREQ    4000000    // min frequency
#define DEF_FREQ    4248000    // default frequency
#define FIX_FREQ    8000000    // IF frequency
#define EEPROM    0x0000    // EEPROM address

unsigned long freq;    // VFO frequency
unsigned long step;    // frequency up down step
unsigned char renc_now, renc_old;    // value of rotary encoder
unsigned char cflag;    // frequency change flag
unsigned char diff;

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

void eeprom_write_long(unsigned int adr, long data) {
    unsigned char i;

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

long eeprom_read_long(unsigned int adr) {
    long d;

    d = (unsigned long)eeprom_read(EEPROM + adr * 4);
    d |= (unsigned long)eeprom_read(EEPROM + adr * 4 + 1) << 8;
    d |= (unsigned long)eeprom_read(EEPROM + adr * 4 + 2) << 16;
    d |= (unsigned long)eeprom_read(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;
}

/***************************************************************
    AD9833 DDS control (frequency to serial code)
***************************************************************/
void freq2serial(unsigned long f) {
    unsigned long msb, lsb;
    unsigned long data;
    char buf[10];

    data = (double)(f / 0.093132257);
    msb = (data >> 14) + 0x4000;
    lsb = (data & 0x00003fff) + 0x4000;

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

/***************************************************************
    pin change interrupt  for rotary encoder 
***************************************************************/
void interrupt isr(void) {
    if(INTCONbits.RABIF) {
        if(PORTBbits.RB6 == 1) {
            if(PORTBbits.RB7 == 0)
                renc_now = 0;
            else
                renc_now = 1;
        } else {
            if(PORTBbits.RB7 == 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;
}

/***************************************************************
    lcd write routine
***************************************************************/
void lcd_update() {
    char buf[10];
    
    lcd_gotopos(0, 0);
    sprintf(buf, "%6ld00", freq / 10);
    lcd_puts(buf);
    lcd_gotopos(0, 1);
    sprintf(buf, "step%4d", step * 10);
    lcd_puts(buf);
}

/***************************************************************
    main
***************************************************************/
void main(void) {
    unsigned char sw0_state, sw1_state;
    unsigned int tim0, tim1;
    unsigned long ftmp;

    OSCCON = 0b11110111;    // 16MHz internal clock

    TRISA = 0b00000000;    // RA4,5 AD9834 serial control
    TRISB = 0b11110000;    // RB6,7 rotary encoder input and RB4,5 input
    TRISC = 0b00000000;    // LCD and RC5 AD9834 serial control
    ADCON0bits.ADON = 0;    // ad converter off
    ANSEL = 0;
    ANSELH = 0;
    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 = 0b11100000;    // RB5,6,7 pull-up
    IOCBbits.IOCB6 = 1;    // RB6 pin change interrupt enable
    IOCBbits.IOCB7 = 1;    // RB7 pin change interrupt enable

    sw0_state = sw1_state = 0;
    freq = DEF_FREQ;
    mode = 0;
    step = 10;
    diff = 0;
    tim0 = tim1 = 0;

    lcd_init();
    lcd_update();

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

    delay_ms(500);
    freq2serial(freq);

    while(1) {

        // step down or press for long time to EEPROM memory write
        if(PORTBbits.RB5 == 0) {
            sw0_state = 1;
            delay_ms(10);
            tim0++;
        }
        if(sw0_state && PORTBbits.RB5 == 1) {
            sw0_state = 0;
            tim0 = 0;
            if(mode == 0) {
                   PORTCbits.RC2 = 1;
                mode = 1;
                step = 1000;
                lcd_update();
            } else {
                PORTCbits.RC2 = 0;
                mode = 0;
                step = 10;
                lcd_update();
            }
        }

        // frequency and offset write to EEPROM
        if(tim0 > 100) {
            sw0_state = 0;
            tim0 = 0;
            // write eeprom
            delay_ms(1000);
            lcd_update();
        }

        // frequency changed
        if(cflag) {
            if(diff) {
                if((freq + step) <= MAX_FREQ)
                    freq += step;
            } else {
                if((freq - step) >= MIN_FREQ)
                    freq -= step;
            }
            freq2serial(freq);
            lcd_update();
            cflag = 0;
        }
    }
}

プリミックスVFOのテスト


 ミキサーICのNE612ANとTA7358APを使用したプリミックスVFOもテストしてみました。両ICとも水晶発振回路が構成できるので、35MHz帯のXtalで発信させて、DDSからの4MHz帯の信号と混合させて39MHz帯の出力を得ます。回路図は以下のとおりです。

premix VFO

 ブレッドボードで実験しました。

NE612 premix VFOTA7358 premix VFO

 VFOの出力をスペアナで見てみました。左がNE612で右がTA7358です。アッテネータをいくつ入れたか忘れたので出力レベルは適当に見てください。スプリアスの多いこの状況では実際に使用するのは厳しいと思います。基本波から近い(4MHz程度)スプリアスは普通のフィルターでは落としきれません。

premix VFOpremix VFO

 ミキサーICを使ったプリミックスVFOは簡単なんですが、使用する周波数をよく考える必要がありそうです。例えばDDSの周波数の2倍程度までの水晶発振信号との混合ならフィルターワークでなんとかなるかもしれません。


▲ページ Top へ...