AD9834を使ったスイープジェネレータの実験その2

[公開:any]

[電子工作/実験]
[電子工作/AVR]
[AD9834,AVR,ATmega168,DDS]

origin 2010-04-03


DDSを使ったスイープジェネレーターの製作で完結しました。


 アナログ・デバイセズ社のDDS-IC AD9834を使ったスイープジェネレータの実験の続編です。前回はAD9834をコントロールするマイコンとしてPIC18Fを使用しましたが、技量不足でシリアル通信に十分な速度が出ずに納得するスイープ時間が得られませんでした。今回は、より高速なAVRを使用してAD9834を制御してみます。


 AVRは、ATmega168を最高クロックの20MHzで使用します。PIC18Fは、外部12MHzを内部PLL4倍で48MHz動作でしたが、最小インストラクション時間は、クロックの1/4となり、12MHzの速度しか出ません。AVRは、供給クロックがそのまま最小インストラクション時間となるため、単純に考えても1.6倍は高速です。
 ほぼ同じソースプログラムで実験します。PIC18Fでは、周波数設定に必要な48ビットを転送するのに456μsもかかりましたが、AVRでは、約150μsと期待を上回る速度が得られました。下のキャプチャは、スイープ動作させた時のものですが、今度は、周波数設定間の処理時間(周波数から28ビットのレジスタ値を算出し、14ビットのLSBとMSBの分解)が目立ちます。100μsのインターバルを挿入して279μsなので計算処理に179μsもかかっています。


 周波数レジスタの28ビットを求めるには、周波数を最小周波数ステップで除算しています。最小周波数ステップは、 AD9834のクロックは50MHzなので

50*10^6 ÷ 2^28 =0.18626・・・・

と中途半端な数字です。この浮動小数点計算に時間が消費されていると思われます。
 FRMSで使用されている秋月のDDSが中途半端な高速クロックで動作していることはWeb等から情報を得ていました。このクロックモジュールは、単品でも秋月電子で扱っています。今回、秋月のWebサイトで、この中途半端な周波数は2の26乗ということが分かりました。・・・ということは、つまり2の乗数で周波数設定するときに計算が単純になるということです。
 AD9834は2の28乗なのでこのクロックモジュールを使用すると

2^26 ÷ 2^28 = 0.25

と切りのいい数字になります。0.25での除算は、4倍の乗算と同じなので2進数であれば2ビットの左シフトで計算できます。浮動小数点計算より処理時間が大幅に削れそうです。

 ということで、秋月電子から67.10886MHzのクロックモジュールを調達して試してみました。


 28ビットの周波数レジスタを求めるプログラムソースも、

    data = (double)(freq / 0.186264514);

から

    data = freq << 2;

と単純になります。計測すると100μsのインターバルを挿入しても125μsと大幅に高速化することができました。実質計算時間は25μsと約7倍も早くなった計算です。この部分については、試してはいませんが、PIC18Fでも同じように高速化できると思います。


 スイープのインターバルを短くすると今度は、周波数設定のシリアル通信150μsが長く感じます。AD9834のシリアルは、最小25nsのクロックで動作するので単純計算でも48ビットのデータ転送に1.2μsしかかからないことになります。つまり、まだまだ時間がかかりすぎています。
 C言語のままではこれ以上の高速化は難しいので、シリアル制御部分のみをアセンブラで外部関数として作成して試してみました。・・結果、約42μsと処理を高速化することができました。


 また、スイープ時のインターバルをタイマー1の割り込みで処理することで正確で安定したスイープができるようにしました。周波数から28ビットのレジスタ値を計算する25μsとデータ転送の42μsを考慮して最小インターバルを100μsとしました。1秒間に10000回の周波数変更ができることになります。100KHzを1Hz単位でスイープすると10秒かかる計算になります。前回の実験より6倍程度高速化できました。

 実験の回路図です。回路図ではATmega88Pとなっていますが、8KのフラッシュをオーバーしたのでATmega168に変更しています。


 スペアナでAMラジオ用のクリスタルフィルターを測定してみました。上の直線は、スルーでスイープしたものです。なお、当然ながらスペアナのスイープとは同期していないので肝心な測定時間は大して短縮できません。^^;
 

 DDSのクロックが上がったので最高出力周波数を約1/3の22MHzとしました。20MHz出力時のスプリアスです。ブレッドボードの環境のせいか、DDSクロックのフィードスルーがあります。このクロックのフィードスルーと出力信号のイメージがスプリアスとしてたくさん出ています。ブレッドボード外部にローパスフィルタを接続すればバッサリとカットできることは確認しました。


 今回もあまり参考にならないソースプログラムです。基本的に前回のPIC18Fと同じです。単純な信号発生器として使用するシングルモードとスイープジェネレータとして使用するモードを切り替えとしました。スイープのためのインターフェースを多少変更しましたが実験レベルでの使用を想定したものなので、行き当たりばったりで適当な作りとなっています。

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
/**********************************************************************
    AVR DDS−IC control
     DDS device : AD9834 (Anlog Devices)
**********************************************************************/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <math.h>

#include "lcd.h"

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

#define MAX_FREQ    30000000        // max frequency
#define MIN_FREQ    1                // min frequency
#define DEF_FREQ    1000000            // default frequency

FILE *fp;                            // File descriptor for LCD
unsigned long freq;                    // frequency
unsigned long diff;                    // frequency step
unsigned char mode;
unsigned char swpmode;
unsigned char renc_now, renc_old;     // value of rotary encoder
unsigned char sleep;
unsigned long fbegin, fend, fstep, fintr;
unsigned long swf;

/**********************************************************************
    milisecond order delay
**********************************************************************/
void delay_ms(unsigned int t) {    while(t--)  _delay_ms(1); }

/***************************************************************
    AD9834 DDS control (frequency to serial code)
***************************************************************/
void freq2serial(unsigned long freq)
{
    unsigned int msb, lsb;
    unsigned long data;
    
    data = freq << 2;
    msb = (data >> 14) + 0x4000;
    lsb = (data & 0x00003fff) + 0x4000;

    serial_send(0x2000);
    serial_send(lsb);
    serial_send(msb);
}

/**********************************************************************
    Pin change interrupt
    Frequency Up & Down by Rotary encorder input
**********************************************************************/
ISR(PCINT1_vect)
{
    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(mode == 1 && swpmode == 3) {
            if((freq > diff) && (freq - diff) >= 100) {
                freq -= diff;
            }
        } else {
            if(freq > diff)
                freq -= diff;
        }
    }
    if((renc_now + 3 - 1) % 3 == renc_old) {
        if((freq + diff) < MAX_FREQ)
            freq += diff;
    }
    renc_old = renc_now; 

    if(sleep == 0 && mode == 0)
        freq2serial(freq);
}
/**********************************************************************
    Timer1 Compare match interrupt
    Signal Sweeper
**********************************************************************/
ISR(TIMER1_COMPA_vect)
{
    if(swf <= fend) {
        freq2serial(swf);
        swf += fstep;
    } else {
        cbi(TIMSK1, OCIE1A);
        sleep = 0;
    }
}

/**********************************************************************
    main routine
**********************************************************************/
int main(void)
{
    unsigned char sw0_state, sw1_state, sw2_state, sw3_state;
    unsigned int tim;
     
     DDRB = 0b11111111;        // LCD
    DDRC = 0b00000000;        // sw input, routary encoder input 
    DDRD = 0b11111111;        // PD0-2 DDS serial output

    PORTC = 0b00111111;    // PC0-5 pull up

    PCICR = _BV(PCIE1);        // pin change interrupt enable
      PCMSK1 = _BV(PCINT12)|_BV(PCINT13);
    TCCR1B = 0b01001010;    // Timer1 プリスケーラ 1/8

    lcd_init();
    lcd_cls();
    fp = fdevopen(lcd_putch, NULL);        // Open File descriptor for LCD

    sw0_state = sw1_state = sw2_state = sw3_state = 0;
    freq = DEF_FREQ;        
    diff = 1000;    
    mode = 0;
    tim = 0;
    sleep = 0;
    fbegin = 355000;        // sweep default settiong
    fend = 555000;
    fstep = 10;
    fintr = 100;
    swpmode = 4;

    freq2serial(freq);

    sei();

    while(1) {
        if(bit_is_clear(PINC, PC0)) {
            sw0_state = 1;            
            delay_ms(15);
        }
        if(sw0_state && bit_is_set(PINC, PC0)) {
            sw0_state = 0;
            if(diff == 10) {
                diff = 1;
            } else if(diff == 100 && mode == 1 && swpmode != 3) {
                diff = 10;
            } else if(diff == 1000) {
                diff = 100;
            } else if(diff == 10000) {
                diff = 1000;
            } else if (diff == 100000) {
                diff = 10000;
            }
        }
        if(bit_is_clear(PINC, PC1)) {
            sw1_state = 1;            
            delay_ms(15);
        }
        if(sw1_state && bit_is_set(PINC, PC1)) {
            sw1_state = 0;
            if(diff == 1) {
                diff = 10;
            } else if(diff == 10) {
                diff = 100;
            } else if(diff == 100) {
                diff = 1000;
            } else if(diff == 1000) {
                diff = 10000;
            } else if(diff == 10000) {
                diff = 100000;
            }
        }
        if(bit_is_clear(PINC, PC2)) {
            sw2_state = 1;            
            delay_ms(15);
            tim++;
        }
        if(sw2_state && bit_is_set(PINC, PC2)) {
            sw2_state = 0;
            tim = 0;
            if(mode) {
                if(swpmode == 0) {
                    fbegin = freq;
                    swpmode = 1;
                    freq = fend;
                } else if(swpmode == 1) {
                    fend = freq;
                    swpmode = 2;
                    freq = fstep;
                    diff = 1000;
                } else if(swpmode == 2) {
                    fstep = freq;
                    swpmode = 3;
                    freq = fintr;
                    diff = 100;
                } else if(swpmode == 3) {
                    fintr = freq;
                    swpmode = 4;
                    freq = fbegin;
                    diff = 1000;
                } else if(swpmode == 4) {
                    swpmode = 0;                
                }
            }
        }
        if(bit_is_clear(PINC, PC3)) {
            sw3_state = 1;
            delay_ms(15);
        }
        if(sw3_state && bit_is_set(PINC, PC3)) {
            sw3_state = 0;
            if(!mode) {
                if(sleep) {
                    sleep = 0;
                    freq2serial(freq);
                } else {
                    sleep = 1;
                    serial_send(0x0040);
                }
            } else {
                if(sleep) {
                    sleep = 0;
                    cbi(TIMSK1, OCIE1A);
                } else {
                    if(swpmode == 4) {
                        sleep = 1;
                        swf = fbegin;
                        OCR1A = (int)(fintr * 2.5);
                        sbi(TIMSK1, OCIE1A);    // Timer1 compare match A interrupt
                    }
                }
            }
        }
        if(tim > 50) {
            sw2_state = 0;
            tim = 0;
            if(mode) {
                mode = 0;
                sleep = 1;
                serial_send(0x0040);
                lcd_goto(0, 0);
                fprintf(fp, "===Single Mode==");
            } else {
                mode = 1;
                sleep = 1;
                swpmode = 0;
                serial_send(0x0040);
                lcd_goto(0, 0);
                fprintf(fp, "===Sweep Mode===");
            }
            loop_until_bit_is_set(PINC, PC2);
        }

        if(mode) {
            if(swpmode == 0) {
                lcd_goto(0, 0);
                fprintf(fp, "SWP stp:%6ldHz", diff);
                lcd_goto(1, 0);
                fprintf(fp, "Start:%8ldHz", freq);
            } else if(swpmode == 1) {
                lcd_goto(0, 0);
                fprintf(fp, "SWP stp:%6ldHz", diff);
                lcd_goto(1, 0);
                fprintf(fp, "Stop :%8ldHz", freq);
            } else if(swpmode == 2) {
                lcd_goto(0, 0);
                fprintf(fp, "SWP stp:%6ldHz", diff);
                lcd_goto(1, 0);
                fprintf(fp, "Step :%8ldHz", freq);
            } else if(swpmode == 3) {
                lcd_goto(0, 0);
                fprintf(fp, "SWP stp:%6ldus", diff);
                lcd_goto(1, 0);
                fprintf(fp, "Intrv:%8ldus", freq);
            } else if(swpmode == 4) {
                lcd_goto(0, 0);
                fprintf(fp, "===Sweep Mode===");
                lcd_goto(1, 0);
                fprintf(fp, "Push to Start-SW");
            }
        } else {
            lcd_goto(0, 0);
            fprintf(fp, "SGL    *%6ldHz", diff);
            lcd_goto(1, 0);
            fprintf(fp, "Freq :%8ldHz", freq);
        }
    }
}

 シリアル通信部分のあやしいアセンブラも参考までに・・・・・。ループで回さずに単純にずらずらシリアルに書いたほうが早いかもしれません。(ステップ計算してみればわかりますが・・・)

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
#include<avr/io.h>

.global serial_send
.func serial_send

#define S_PORT _SFR_IO_ADDR(PORTD)
#define S_CLOCK PD1
#define S_FSYNC PD2
#define S_SDATA PD0
#define dataL r24
#define dataH r25
#define temp r18

serial_send:
    ldi        r22, 2
    mov        temp, dataH
    cbi     S_PORT, S_FSYNC
1:
    ldi        r23, 8
2:
    sbrc    temp, 7
    rjmp    3f
    cbi     S_PORT, S_SDATA
    rjmp    4f
3:
    sbi     S_PORT, S_SDATA
4:
    cbi     S_PORT, S_CLOCK
    sbi     S_PORT, S_CLOCK
    rol        temp
    dec        r23
    brne    2b
    mov     temp, dataL
    dec        r22
    brne    1b

    sbi     S_PORT, S_FSYNC
    ret

.endfunc

アセンブラを一部見直しました。処理内容に変更はありません。(2010-04-06)


▲ページ Top へ...