PIC-DDSの基礎実験

[公開:any]

[電子工作/PIC]
[電子工作/実験]
[PIC,PIC18F,PIC18F2550,DDS]

origin 2009-03-05


 「AVR-DDSでPLL-VFOの実験」と「AVR-DDSでPLL-VFOの実験その2」でAVRを使ったDDS(Direct Digital Synthesizer)を使用しました。一般的に、DDSで歪みの少ない波形を出力するには、サンプリング周波数を高くする必要があります。AVR-DDSでは、AVRはクロック20MHzで動作させていますが、これが仕様上限でこれ以上のクロックは、規格外となります。
 最近のPIC・・・特に、PIC18Fは、内部PLLで40MHzとか48MHzという高速クロックで動作します。単純に早いクロックのワンチップマイコンを使えば、サンプリング周波数を稼いできれいな波形の出力が得られるのではないかと考え、簡単な実験をしてみました。

 早速、秋月電子からPIC18Fを調達しました。18ピンのPIC18F1320と28ピンのPIC18F2550です。今回の実験では、R-2Rラダー抵抗のDAC出力にI/Oポートが8ポートと必要となります。LCDや入力を考えるとPIC18F2550を使用するしかなさそうです。

PIC18F

 実験に使った回路です。PIC18F2550は、28ピンもあるのに連続した論理8ビットが取れるのはPORTBしかありません。(PIC18F1320は、連続8ビットが2系統とれます。しかし、物理的なピンアサインは連続していません。)

 PIC-DDSから正弦波を出力し、LPF(ローパスフィルタ)は、カットオフ周波数30KHz程度のCRフィルタとオペアンプを使ったアクティブフィルタを試してみました。

PIC-DDS回路図

 クロックは、水晶発振器から12MHzを供給して内部PLLで48MHzとしています。サンプリング周期の生成は、AVRと同じようにTimer1とCCPモジュールののコンペアマッチ割り込みを使用しました。

 AVRの2倍以上のクロックなので高速な割り込み間隔を期待したのですが、PIC16と同じようにタイマーに供給されるクロックは、CPUクロックの1/4となるようです。(・・データシートがわかりにくい・・というか英語が苦手^^;)ということで、CPUは48MHzとなりますが、タイマーのクロックは12MHzとなり、残念ながらAVRよりは遅い周期しか得られません。

 サンプリング周波数100KHzとして、DDSから10000KHzと12345KHzを出力して周波数カウンタで測定してみると、ピッタリの周波数が得られます。この周波数の正確さはDDSならではです。

PIC-DDSテスト発振1PIC-DDSテスト発振2

 同じくサンプリング周波数100KHzで20KHzを出力したときの波形です。上のCH1がアクティブフィルタ出力、下のCH2がCRフィルタ出力です。さすがにCRフィルタでは波形の歪が見た目でもはっきりとわかります。一方、アクティブフィルタは、見た目は歪みの少ない正弦波に見えます。

PIC-DDS発振波形1

 サンプリング周波数を200KHzで20KHzを出力したときの波形です。下のCRフィルタ出力波形もある程度は、歪が少なくなっています。

PIC-DDS発振波形2

 PICによるDDSもサンプリング周波数によって出力波形の品質が左右されます。できるだけ高速なサンプリング周波数を実現したいのですが、割り込み周期を短くすると当然、他の処理に使える時間が短くなります。今回は、メインルーチンでLED点滅の処理をさせただけですが、サンプリング周波数が100KHzを超えると、メインルーチンのループ間隔が長くなり、目に見えてLEDの点滅速度が遅くなります。実際に使用するときは、メインルーチンの処理内容(時間)にもよりますが、サンプリング周波数100KHz以下が実用範囲と思われます。

 DDSは、その仕組から量子化したテーブル位置を計算するときに割り切れるときは、位相雑音が小さくなり、反対に割り切れずに小数点以下が切り捨てられる状況だと位相雑音が大きくなります。
 サンプリング周波数100KHzで10KHz(10000Hz)を出力したときのオシロスコープでのFFTの結果です。歪みの少ない波形が出力されています。

PIC-DDSスペクトラム1

 同じくサンプリング周波数100KHzで10.1KHz(10100Hz)を出力したときのFFTの結果です。信号の裾野が広がり、位相雑音が増えているのがわかります。この位相雑音は、フィルタで取り除くことは困難です。量子化の精密化等が必要です。

PIC-DDSスペクトラム2

 一応は、PICのみで数Hz〜20KHz程度までのDDS低周波発振器を作れそうです。オペアンプを使ったアクティブフィルタの性能次第では、もう少し高い周波数までいけそうな感じです。

 プログラムは、MPLAB 8.20 とMPLAB C18 Student Editionを使用しました。コーディングは、「電子工作の実験室」さんを参考にAVR-DDSのものをコンバートしました。(別途、mikroCでも試してみましたが、割り込みの書き方が違うだけでほぼ同じソースでいけます。)
 ロータリーエンコーダを使って周波数の可変までやりたかったのですが、・・なんと!!、ピン変化割り込みがPORTBしか対応していないようでAVRのソースそのままを使いまわすことが出来ませんでした。入手性や製品ラインナップを考えると圧倒的にPICが良さそうなんですが、こういうところがちょっと残念です。

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
#include <p18cxxx.h>
#include <delays.h>
#include <timers.h>   

#pragma config FOSC = HSPLL_HS
#pragma config WDT = OFF
#pragma config PLLDIV = 3
#pragma config CPUDIV = OSC1_PLL2
#pragma config USBDIV = 2
#pragma config FCMEN = OFF
#pragma config PWRT = ON
#pragma config BOR = OFF
#pragma config BORV = 2
#pragma config LVP = OFF
#pragma config VREGEN = ON
#pragma config MCLRE = OFF
#pragma config PBADEN = OFF

#define SF        100000     // Sampling Frequency
#define ACCU    65536   // Accumulator

const unsigned char sinewave[] = // sine wave 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,
};

unsigned int delta;
unsigned char pos;
unsigned int phe;

#pragma interrupt isr save = PROD

#pragma code isrcode = 0x000008
void isr_direct(void)
{ 
    _asm  
        goto isr  
    _endasm
}

#pragma code
void isr(void)
{
    PIR1bits.CCP1IF = 0;
    phe += delta;
    pos = phe >> 8;
    PORTB = sinewave[pos];
}

void main (void)
{
    unsigned long freq;

    TRISB = 0;
    TRISC = 0;
    LATB = 0x00;

    T1CON = 0b11000001;
    CCP1CON = 0b00001011;
    PIE1bits.CCP1IE = 1;

    CCPR1L = 0x77;        // 50khz=ef 80khz=95 100khz=77 160khz=4a 200khz=3b
    CCPR1H = 0x00;

    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;

    freq = 20000;
    delta = freq * ACCU / SF;

    while(1) {
        Delay10KTCYx(100);
        LATC ^= 0xff;
    }
}

▲ページ Top へ...