AVR-DDSでPLL-VFOの実験

この仕組みを利用した短波ラジオを「PLL-VFOを使った短波受信機の製作」で実際に作成しました。(2009-07-22)
AVR-DDSの出力を正弦波にしたものを「AVR-DDSでPLL-VFOの実験その2」で追試しています。(2009-01-26)

 短波ラジオやエアバンドラジオを作るたびに、周波数カウンタを内蔵しようと試していますが、局発が不安定になったり、周波数の変動が大きくて役に立たなかったり・・と、実現できていません。
 ・・で、考えたのがPLL。PLLでVFOを作れば周波数は安定するし、周波数カウンタなくても周波数が直読できるハズ。実際にVFOとして使用しませんが、基礎的な実験としてPLLでVFOを試してみます。

 3線式シリアル通信で設定するPLL専用ICです。PICやAVRが使えるようになったので、利用しようと調達しました。・・・・が、これらのデータシート見るとVHF以上で使うもののようです。エアバンドラジオで使えると思いますが、最初はブレッドボードでお気軽にテストできる短波用のPLL-VFOを作ろうと思います。

PLL-IC

 書籍も調達しました。(手前の発振回路の設計と応用は、以前から持ってましたが、あまり読んでいません。^^;)

PLLや発振回路の書籍

 書籍には、基本的なPLLのブロック図として、以下のようなものがあげられています。

PLL回路ブロック図

 PLL専用ICは、位相比較器と2個の分周器(基準周波数用とループバック制御用)を内蔵したものが多いようです。PLLで周波数を可変するには、この2個の分周器の分周比を変化させるのが一般的です。
 今回は、専用ICを使用せずにスクラッチで作ります。位相比較器は、PLL専用ロジックICの4046Bを使用します。分周器には、プログラマブル分周ICを使えればいいのですが、PLL専用ICに機能吸収されたためか、現在では多くのICが廃品種で、入手が困難です。
 ネットでいろいろと情報を探すと、「DDS-VFO」というものがたくさんヒットします。マイコンでDDS専用チップを制御して直接、局発周波数を発振させています。今回の目標である短波は、5MHz~10MHz程度までなので専用DDSチップを使えば簡単に発振できそうですが、専用DDSチップが高価なので「お気軽に実験」というわけにはいきません。
 以前、AVR-DDSを製作したので、この仕組みを使ってやってみようと思います。AVR-DDSは、低周波までしか発振できませんが、正確で安定した周波数を出力します。この出力をPLLの基準周波数として使用してみます。つまり、基準周波数発振器と基準周波数用の分周器をAVR-DDSで代用します。ループバック分周器は、固定分周比でいいので汎用ロジックICの74HC4040を使用します。


基準周波数が変動する場合のループフィルタの設計が良くわかりません。適当にロックする範囲をカットアンドトライで決定することにします。

 ブロック図は以下になります。

PLL回路ブロック図

 VCOは、ブレッドボード上に組んだハートレー型LC発振回路で、単体の発振周波数範囲は、約4~15MHzとなりました。周波数の可変ステップを1kHzとしたいので、ループバック分周器の分周比を1/1024に固定し、位相比較器で基準周波数の1024倍になるようにVCOをループバック制御します。結果として、AVR-DDSは、1Hz単位で4000~14000Hzの出力が必要です。

 ブレッドボードで実験しながら作成した回路図が以下になりました。

AVR-DDSをリファレンスとしたPLL-VFO回路図
AVR-DDSをリファレンスとしたPLL-VFO回路図

 VCOは、最初にコルピッツ発振回路を変形したクラップ発振回路でやってみたのですが、発振周波数範囲が、8~12MHzと狭く目標に達しませんでした。別途、書籍を参考にハートレー回路を試したところ10MHz以上の発振帯域が得られたのでこれを使用します。ただ、発振する波形に多少、歪があるようで受信機のVFOとして使用できるかどうかは微妙です。

 ぐちゃぐちゃしていますが、こんな感じでやってみました。AVRは、DDSのサンプリング周波数を稼ぐために20MHzのクロックで動作させます。発振周波数の可変はロータリーエンコーダを使用し、スイッチで1Hz単位か10Hz単位を切り替えるようにしてあります。

秋月電子で購入したロータリーエンコーダーは、クリックがあるため1Hz単位の調整が困難です。このため、QRPな自作の日記さんを参考にして、クリックを無効化しました。

 左側の液晶パネルがAVRに接続したもので、上の行がAVR-DDSの出力する基準周波数、下の行が、基準周波数にループバック分周比の1024を乗算した周波数です。右の液晶は周波数カウンタでVCOの出力を測定しています。PLLで制御されているのでVCOの周波数は安定しています。AVR-DDSの計算する出力とのずれは、AVR-DDSの実際の出力周波数が3Hz程度、低いことが原因かと思われます。(AVR-DDSの出力を周波数カウンタで測定すると9978Hzでした。)

 オシロスコープで見た波形です。左のCH1がAVR-DDSの基準周波数波形。CH2がループバック分周器からの出力で完全に同期が取れてロックした状態です。右はVCOの出力波形です。PLLの基準周波数としてかなりの安定度が必要ですが、AVR-DDSの出力にローパスフィルタを使用していないので位相歪も出力されています。ただ、位相比較器の後段のループフィルタの特性なのかVCOの出力波形を見る限りは、影響は少ないように見えます。

いっそのこと正弦波を出力し、ローパスフィルタをとおして位相誤差によるジッタを取り除いてコンパレータで方形波に変換したものが良いかもしれません。

 AVRのプログラムです。前回製作したAVR-DDSでは、DDS部分はサンプリング周波数を高く取るためにインラインアセンブラとしていました。今回はロータリーエンコーダや液晶表示をリアルタイムで処理するためにTimer1の割り込みでDDSを実現しています。
 割り込み中の処理(ただでさえ割り込みではステップが多くなる)のため、最大サンプリング周波数は160KHzとなりました。これでも計算より2~3Hz低い出力周波数です。(20MHzの水晶が正確だと仮定してですが)
 無理すれば、サンプリング周波数を200KHz程度まであげることが出来ますが、ロータリーエンコーダ入力によるピン変化割り込みが連続してかかると周波数が不安定(発振周波数が下がる)になります。
 たかが「方形波」となめていましたが、14000Hzとなると20MHzクロックのAVRでは、割り込みプログラミングでは、かなり厳しくなります。全部、アセンブラでも書き直しかけましたが、ロータリーエンコーダの処理が面倒で進んでいません。(AVRでは、はじめてのアセンブラ・・・・^^;)
 いっそのこと、PIC24Fなどの早いマイコンに浮気して、プログラミング技術不足をごまかそうかと思案中です。^^;

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
//----------------------------------------------------------------
//  AVR-Direct Digital Synthesiser
//               only Square wave
//
//                                              2009-01-20 www.henteko.org
//----------------------------------------------------------------
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "lcd.h"
#define cbi(addr,bit)     addr &= ~(1<<bit)
#define sbi(addr,bit)     addr |=  (1<<bit)
#define SF         160000     // Sampling Frequency
#define ACCU    65536   // Accumulator
FILE *fp;                    // for LCD
volatile unsigned int freq;    // DDS出力周波数[Hz]
unsigned int delta;            // 増分 
unsigned int phe;            // フェーズ
unsigned char diff;            // 周波数の増減量[Hz]
volatile unsigned char renc_now, renc_old; // エンコーダ値
void delay_ms(unsigned int count)
{while(count--) { _delay_ms(1); }}
//----------------------------------------------------------------
// Timer1 Compare match interrupt
//   サンプリング周期で割り込み。波形のポジションを計算して出力。
//   方形波のみなので、波形データを使用せず、任意の1bitのみに出力。
// ---------------------------------------------------------------
ISR(TIMER1_COMPA_vect)
{
    phe += delta;
    if(phe >> 15)        // >>8(/256) and >>7(/128)
        sbi(PORTD, PD7);
    else
        cbi(PORTD, PD7);
}
//----------------------------------------------------------------
// Pin changed interrupt
//   ロータリーエンコーダからの信号で割り込み。
//   右回りか左回りかを判別してDDS周波数を増減
//----------------------------------------------------------------
ISR(SIG_PIN_CHANGE1)
{
    if(bit_is_set(PINC, PC4)){
        if(bit_is_clear(PINC, PC5))
            renc_now = 0;
        else 
            renc_now = 1;
    } else {
        if(bit_is_set(PINC, PC5))
            renc_now = 2;
        else
            renc_now = 3;
    }
    if((renc_now + 3 + 1) % 3 == renc_old) {
        if(freq > 4000)
            freq -= diff;
    }
    if((renc_now + 3 - 1) % 3 == renc_old) {
        if(freq < 14000)
            freq += diff;
    }
    renc_old = renc_now; 
    delta = freq * ACCU / SF;    // 位相増分計算
}
//----------------------------------------------------------------
// Main routine
//   メインループでは、周波数増減切替スイッチ判定と液晶表示処理のみ
//   
//----------------------------------------------------------------
int main()
{
    unsigned char sw0_state;
    DDRB = 0b11111111;        // LCD Output
    DDRC = 0b00000000;        // sw & rotary encoder input
    DDRD = 0b11111111;        // DDS Output
    PORTD = 0b00000000;        // clear
    PORTC = 0b00000011;        // pc0,1 pull up
    lcd_init();
    lcd_cls();
    fp = fdevopen(lcd_putch, NULL);        // LCD出力ファイルディスクプリタ
    sw0_state = 0;
    PCICR = _BV(PCIE1);
    PCMSK1 = _BV(PCINT12)|_BV(PCINT13);
    TCCR1B = 0b01001001;    // Timer1 プリスケーラ 1/1
    TIMSK1 = _BV(OCIE1A);    // Timer1 compare match A interrupt
    OCR1A = 124;                // Clock 20MHz sampling 160kHz 20000000/160000=125(-1)  
    
    phe = 0;
    diff = 1;                // Frequency Up Down default 1Hz
    freq = 10000;
    delta = freq * ACCU / SF;    // 位相増分計算
    lcd_goto(0, 0);
    fprintf(fp, "%8d", freq);
    lcd_goto(1, 0);
    fprintf(fp, "%8ld", (unsigned long)freq * 1024l);
    sei();
    while(1) {
    
        if(bit_is_clear(PINC, PC1)) {
            sw0_state = 1;            
            delay_ms(10);
        }
        if(sw0_state && bit_is_set(PINC, PC1)) {
            sw0_state = 0;
            if(diff == 1) {
                diff = 10;        // 増減10Hz単位
                sbi(PORTD, PD0);
            } else {
                diff = 1;            // 増減1Hz単位
                cbi(PORTD, PD0);
            }
        }
        lcd_goto(0, 0);
        fprintf(fp, "%8d", freq);
        lcd_goto(1, 0);
        fprintf(fp, "%8ld", (unsigned long)freq * 1024l);
    }
}

5件のピンバック

コメントは現在停止中です。