Si5351Aクロックジェネレータで、パルスジェネレータの作成

掲載日: / 更新日:
Si5351Aクロックジェネレータで、パルスジェネレータの作成

アンリツ PULSE GENERATOR MG418Aを入手しました。

アンリツ PULSE GENERATOR MG418A

 

アナログ式の古~いパルスジェネレータで、ジャンク扱いで入手したものですが、しっかり動作します。

以前に苦労したRISE Timeも当たり前に5nS以下で、急峻なわりに綺麗な波形を発生できます。

若干、自励発振周波数はスペックに達しない感じですが、しっかりした波形を出してくれますので、オシロスコープの校正などにも使えます。ただ、残念ながらアナログ式で正確な発振周波数を設定するのは難しく、どうしても若干の揺らぎがあります。

そこで、外部トリガーモードで動作させて、デジタル生成したベースクロックを入力するようにしました。

Si5351Aを利用して、パルスジェネレータを作成

目標は、1kHz~220MHzのパルスジェネレータでしたが、最終的に1.5k~200MHzのパルスジェネレータとなりました。

まぁ、これでもメーカ保障スペックを大幅に拡張できたので、良しとします。

内心は、どこかのサイト(ストロベリーリナックスでした)で1kHzで発振した記事があり、方法がわかれば、1kHzは実現したいと思いはあります。

Si5351Aクロックジェネレータで、パルスジェネレータの作成

 

Si5351Aクロックジェネレータで、シールドボード使いパルスジェネレータの作成

ハードウェア編

主なパーツは以下となります。

HiLetgo 1602 LCD キーパッド シールド ボード ブルー バックライトを付き Arduinoに対応 Duemilanoveロボット [並行輸入品]

新品価格
¥680から
(2018/10/23 20:48時点)

WINGONEER Arduino UNO R3シールドボードDIY用プロトタイプPCB

新品価格
¥599から
(2018/10/23 20:44時点)

HiLetgo Si5351Aクロックジェネレータブレークアウトボード8KHz~160MHz I2Cコントローラクロックジェネレータ

新品価格
¥1,000から
(2018/10/23 20:46時点)

keyestudio UNO R3 開発ボード USBケーブル UNO R3 Arduino互換

新品価格
¥1,100から
(2018/10/23 20:50時点)

Superbat SMAメスコネクタ SMAジャック PCB実装型 基板部品 信号増幅器・LNAボードモジュール・アンプ対応 自作 5個入

新品価格
¥880から
(2018/10/23 20:51時点)

カモン SMA(オス)←→BNC(メス)変換アダプタ【BNC-SMA】

新品価格
¥210から
(2018/10/23 20:52時点)

はじめて、まとも?に作成したArduino利用のToolとなります。

ソフトウェア編

プログラム作成は、Arduino 1.8.5で作成しました。便利すぎて癖になりそう。

さて、

今一つ、温度で水晶発振器の周波数が変動する為、期待したほど精度が得られず、簡単なHeaterの設置を考えましたが、Arduinoからの電源駆動を考えるとあまり消費電力を増やしたくないので、以下の機能をプログラムで実現することで凡そ目的を果たすものに仕上げました。

  • 水晶発振器の元々の誤差を補正できる機能
  • 水晶発振器の温度を測定(温度センサーLM61CIZを追加)して、発振周波数を補正する機能
  • こまかい周波数設定ができる機能

プログラムの中身は、私はプログラマーでないのでかなり拙い力ずくとなっています。

/**************************************************************************/
//v.0.1 10.10.2018 Frequency generator 1.5k...200MHz . JrDrg
//v.0.1c 10.14.2018 + OSC calibration
//v.0.2c 10.15.2018 PLL clock Improvement + new Algorithm
//v.0.3 10.16.2018 ch2 10MHz fix
/**************************************************************************/

 


#include <Wire.h>
#include <Adafruit_SI5351.h>
#include <LiquidCrystal.h>

 

Adafruit_SI5351 clockgen = Adafruit_SI5351();

 

/* Key setup */
int lcd_key = 0;
int adc_key_in = 0;
int adc_key_in2 = 0;
#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5

 

int read_LCD_buttons()
{
// take measures to chattering
adc_key_in = analogRead(0);
delay(40);
adc_key_in2 = analogRead(0);
if (abs(adc_key_in - adc_key_in2) > 2) return btnNONE;
// For V1.1 us this threshold
if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
// For V1.1 us this threshold
if (adc_key_in < 50) return btnRIGHT;
if (adc_key_in < 250) return btnUP;
if (adc_key_in < 350) return btnDOWN;
if (adc_key_in < 550) return btnLEFT;
if (adc_key_in < 850) return btnSELECT;
return btnNONE;
}

 

//◆ Siral Debug 0:off 1:on 1<:manual check
byte debug = 0;

 

//◆ Normal: oscT=0
float oscT = 1524.1 ; //Tweak base OSC frequency e.f.1479.7,1475.8 1484.3
int oscT_temp = 29.12; // ↑temperature
int32_t osc = 25000000; //base OSC frequency
float C_by_T =-10.5; // ΔOSC/temperature

 

//◆Ref Volt
float Vref = 4.88;   //Power Voltage 5.0V/AC Adapter

 

//◆ Initial Output Clock Freq
volatile int32_t outF = 25000000 ;// initial frequency in Hz
float outF2 = 10000000-0.0 ;//ch2: Fix frequency in Hz
uint32_t stpx = 10; //initi step size 10Hz

 

float oscf;
int SI5351_m;
int SI5351_Fm;
uint32_t SI5351_n;
uint32_t SI5351_d;
uint32_t SI5351_div;
uint32_t SI5351_mn;
uint32_t SI5351_md;
uint32_t SI5351_R_DIV; // 1,2,4,8,16,31,64,128
boolean changed_f = 1;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

 

int SI5351_m2;
uint32_t SI5351_n2;
uint32_t SI5351_d2;
uint32_t SI5351_div2;
uint32_t SI5351_mn2;
uint32_t SI5351_md2;

 

// Temp Cal init
int anlg1;
float temp;
float temp_cal;
float temp_cal_bk;
float oscf_bk;

 

/**************************************/
/* Calc Pram & Displays the frequency */
/**************************************/
void count_frequency()
{

 

if(debug) Serial.println("===== in ====== ");

 

uint32_t f;
uint32_t calF;

calF = outF;

 

SI5351_R_DIV = 1;
if ((outF < 1120000) && (9000 <= outF)){
if (outF <=35000){
SI5351_R_DIV = 128;
}else{
SI5351_R_DIV = 32;
}
calF=calF*SI5351_R_DIV;
}

 

f = calF;
SI5351_mn = 0;
SI5351_md = 1;

if (150000000 < calF) {
SI5351_d = 1048575;
SI5351_div = 6;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((90000000 <= calF) && (calF<= 150000000)){
SI5351_d = 1048575;
SI5351_div = 6;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((45000000 <= calF) && (calF< 90000000)){
SI5351_d = 1048575;
SI5351_div = 10;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((36000001 < calF) && (calF< 45000000)){
SI5351_d = 1048575;
SI5351_div = 20;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((18000000 <= calF) && (calF<= 36000001)){
if ((calF == 25000000) && (oscT == 0)){
SI5351_m = 32;
SI5351_n = 0;
SI5351_d = 1;
SI5351_div = 32;
SI5351_mn = 0;
SI5351_md = 1;
}else{
SI5351_d = 1000000;
SI5351_div = 25;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
}
if ((9000000 <= calF) && (calF< 18000000)){
SI5351_d = 500000;
SI5351_div = 50;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((4500000 <= calF) && (calF< 9000000)){
SI5351_d = 250000;
SI5351_div = 100;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((2250000 <= calF) && (calF< 4500000)){
SI5351_d = 125000;
SI5351_div = 200;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((1120000 <= calF) && (calF< 2250000)){
SI5351_d = 62500;
SI5351_div = 400;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((1500<=calF) && (calF < 9000)){
SI5351_R_DIV = 64;
if (calF < 3000) SI5351_R_DIV = 128;
f = calF*SI5351_R_DIV;
SI5351_mn = 1100;
SI5351_d = 1048575;
SI5351_div = 900;
f=(SI5351_div + SI5351_mn) * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if (calF < 1500){
SI5351_R_DIV = 128;
f = calF*SI5351_R_DIV;
SI5351_mn = 1150;
SI5351_md = 1;
SI5351_d = 1048575;
SI5351_div = 900;
f=(SI5351_div + SI5351_mn) * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}

 

// ch2 10MHz
SI5351_d2 = 500000;
SI5351_div2 = 50;
f=SI5351_div2 * outF2;
SI5351_m2 = f/oscf;
SI5351_n2 = (f % osc)/(oscf/SI5351_d2);
SI5351_mn2 = 0;
SI5351_md2 = 1;

 


if(debug){
Serial.println(" 10M fix :");
Serial.print("SI5351_m2: ");
Serial.println(SI5351_m2);
Serial.print("SI5351_n2: ");
Serial.println(SI5351_n2);
Serial.print(" f: ");
Serial.println(f);
Serial.println("");
}

 

/**************************************/
/* Displays the frequency */
/**************************************/
lcd.clear();
lcd.setCursor(2,0);

char msg[17];
char s[17];
sprintf(msg, "%s [Hz]", dtostrf(outF, 9, 0, s));
lcd.print(msg);

 

display_stpx();
}

 

/**************************************/
/* Display the frequency change step */
/**************************************/
void display_stpx()
{
lcd.setCursor(0, 1);
lcd.print("Stp:");
switch (stpx)
{
case 10:
lcd.print(" 10");
break;
case 100:
lcd.print(" 100");
break;
case 1000:
lcd.print(" 1k");
break;
case 10000:
lcd.print(" 10k");
break;
case 100000:
lcd.print("100k");
break;
case 1000000:
lcd.print(" 1M");
break;
case 10000000:
lcd.print(" 10M");
break;
}
lcd.print("Hz ");

}

void setup(void)
{
if(debug){
Serial.begin(115200);
Serial.println("******************");
Serial.println("* 1.5k-200MHz PG *");
Serial.print("* Debug mode :");
Serial.print(debug);
Serial.println(" *");
Serial.println("******************");
Serial.println("");
}

oscf = osc + oscT;
osc = oscf;

oscf_bk=oscf;

lcd.begin(16, 2);
Wire.begin();

// Startting Tittle
lcd.setCursor(0,0);
lcd.print(" 1.5k-200MHz PG");
lcd.setCursor(0,1);
lcd.print(" 10.2018 JrDrg");

/* Initialise the sensor */
if (clockgen.begin() != ERROR_NONE)
{
lcd.setCursor(0,1);
lcd.print(" clockgen error");

while(1);
}

/* Enable the clocks */
clockgen.enableOutputs(true);

// temp calibration
anlg1 = analogRead(1) ;
delay(3000); //3seconds
anlg1 = 0;
for (int i=1; i <=100;i++){
anlg1 =anlg1 + analogRead(1) ;
delay(5);
}
anlg1 = anlg1/100 ;
temp = ((Vref * anlg1)/1024)*100-60 ;
temp_cal = (temp-oscT_temp) * C_by_T ;
temp_cal_bk = temp_cal;
oscf = oscf_bk + temp_cal;
osc = oscf;
}

void loop(void)
{

/**************************************/
/* Change the frequency */
/* btnUP Increment */
/* btnDOWN Decrement */
/* btnRIGHT Increment Tweak */
/* btnLEFT Decrement Tweak */
/**************************************/

lcd_key = read_LCD_buttons(); // read the buttons

switch (lcd_key) // depending on which button was pushed, we perform an action
{
case btnUP:
{
outF += stpx;
changed_f = 1;
break;
}
case btnDOWN:
{
outF -= stpx;
changed_f = 1;
break;
}
case btnRIGHT:
{
if (abs(temp_cal-temp_cal_bk)<20) outF += 1; else{ delay(2000); }
changed_f = 1;
break;
}
case btnLEFT:
{
if (abs(temp_cal-temp_cal_bk)<20) outF -= 1; else{ delay(2000); }
changed_f = 1;
break;
}
}

// btnSELECT frequency step Renge

if (lcd_key == btnSELECT)
{
switch (stpx)
{
case 10:
stpx = 100;
break;
case 100:
stpx = 1000;
break;
case 1000:
stpx = 10000;
break;
case 10000:
stpx = 100000;
break;
case 100000:
stpx = 1000000;
break;
case 1000000:
stpx = 10000000;
break;
case 10000000:
stpx = 10;
break;
}
display_stpx();
}

if(changed_f && !(debug > 1))
{
// check over renge
if(outF > 205000000)
outF =1470;
if(outF < 1470)
outF = 205000000;
if (outF < 0)
outF = 205000000;

//◆ Calc SI5351 setup param
count_frequency();

//◆ Disable the clocks
clockgen.enableOutputs(false);

// ◆Set up the PLL with 'fractional mode'
// This sets PLL_A or PLL_B to be 25MHz * (m + n/d)
// m (the integer multipler) can range from 15 to 90
// n (the numerator) can range from 0 to 1,048,575
// d (the denominator) can range from 1 to 1,048,575
// clockgen.setupPLLInt(SI5351_PLL_A, SI5351_m);
clockgen.setupPLL(SI5351_PLL_A, SI5351_m, SI5351_n, SI5351_d);
clockgen.setupPLL(SI5351_PLL_B, SI5351_m2, SI5351_n2, SI5351_d2);


// ◆Set up the clock divider
// For the output use 0, 1 or 2
// For the PLL input, use either SI5351_PLL_A or SI5351_PLL_B
// The final frequency is equal to the PLL / (div + n/d)
// div can range from 4 to 900
// n can range from 0 to 1,048,575
// d can range from 1 to 1,048,575
// clockgen.setupMultisynthInt(0, SI5351_PLL_A, SI5351_MULTISYNTH_DIV_4);
clockgen.setupMultisynth(0, SI5351_PLL_A, SI5351_div, SI5351_mn, SI5351_md);
clockgen.setupMultisynth(2, SI5351_PLL_B, SI5351_div2, SI5351_mn2, SI5351_md2);


// ◆Additional R Divider
// output is the clock output #
// The R divider can be any of the following:
// SI5351_R_DIV_1
// SI5351_R_DIV_2
// SI5351_R_DIV_4
// SI5351_R_DIV_8
// SI5351_R_DIV_16
// SI5351_R_DIV_32
// SI5351_R_DIV_64
// SI5351_R_DIV_128

clockgen.setupRdiv(2, SI5351_R_DIV_1);

switch (SI5351_R_DIV)
{
case 1:
clockgen.setupRdiv(0, SI5351_R_DIV_1);
break;
case 2:
clockgen.setupRdiv(0, SI5351_R_DIV_2);
break;
case 4:
clockgen.setupRdiv(0, SI5351_R_DIV_4);
break;
case 8:
clockgen.setupRdiv(0, SI5351_R_DIV_8);
break;
case 16:
clockgen.setupRdiv(0, SI5351_R_DIV_16);
break;
case 32:
clockgen.setupRdiv(0, SI5351_R_DIV_32);
break;
case 64:
clockgen.setupRdiv(0, SI5351_R_DIV_64);
break;
case 128:
clockgen.setupRdiv(0, SI5351_R_DIV_128);
break;
}

//◆ Enable the clocks
clockgen.enableOutputs(true);

changed_f = 0;
temp_cal_bk = temp_cal;

if(debug){
Serial.print(" outF ");
Serial.println( outF);
Serial.print(" SI5351_m ");
Serial.println( SI5351_m);
Serial.print(" SI5351_n ");
Serial.println( SI5351_n);
Serial.print(" SI5351_d ");
Serial.println(SI5351_d);
Serial.print(" SI5351_div ");
Serial.println(SI5351_div);
Serial.print(" SI5351_mn ");
Serial.println(SI5351_mn);
Serial.print(" SI5351_md ");
Serial.println(SI5351_md);
Serial.print(" SI5351_R_DIV ");
Serial.println(SI5351_R_DIV);
Serial.println("================ ");
}

}
//loop delay 300mseconds
delay(250);

// display temp
anlg1 = analogRead(1) ;
anlg1= 0;
for (int i=1; i <=100;i++){
anlg1=anlg1 + analogRead(1) ;
delay(5);
}
anlg1 = anlg1/100;
temp = ((Vref * anlg1)/1024)*100-60 ;

if (abs(temp_cal-temp_cal_bk)>20){
lcd.setCursor(11, 1);
lcd.print(" ");
delay(80);
}
char msgT[4];
char sT[4];
sprintf(msgT, "%s0", dtostrf(temp, 2, 2, sT));
lcd.setCursor(11, 1);
lcd.print(msgT);

// Tweak base OSC frequency by temp.
temp_cal = (temp-oscT_temp) * C_by_T ;
oscf = oscf_bk + temp_cal;
osc = oscf;

if(debug){
Serial.print(temp);
Serial.print(" ℃ ");
Serial.println(oscf);
}

}

// END

 

完成して動作確認中。

SI5351 Frequency generator 1.5k...200MHz

プログラムについて

ソースの最初の方に以下の設定箇所がありますので、使用個体に応じて設定が必要になります。

気長に丁寧に調整することで、±10×10-6以下の精度に設定できると思います。

oscT:水晶発振器の誤差(購入品は60ppm程度でした)

oscT_temp:上記の確認温度

osc:水晶発振器の規定周波数(通常は25000000)

C_by_T:温度勾配 周波数ずれ/℃(購入品は簡単な実測で-10.5Hz/℃、厳密には完全な線形でないはずですが、ここでは線形として処理)

Vref:リファレンス電圧。通常はArduinoの電圧※

 

その他、使用した「LCD キーパッド シールド ボード」スイッチがチャタリング誤動作がひどく複数回読みと電圧偏移で判断するように細工しています。

調整のコツは、

  1. まずは、基準となる周波数カウンター(できれば、0.1Hzまで計測できるもの)を用意して、25MHzの周波数が測れるようにする。
  2. [debug=1]にしてシリアル表示を有効にする。
  3. [oscT=0]にする。
  4. 起動して、シリアルコンソールに表示される温度が一定になるのを待つ。その温度を[oscT_temp]に設定して、再度起動。
  5. その時の周波数カウンターの値と25MHzとの差分を[oscT]に設定する。
  6. 再起動して、周波数を確認。目的の精度になるまで、[oscT]を微調整。なかなかぴったりとはならないので妥協が必要かも(-_-;)

注意:温度が変わると設定のやり直しなので、十分温度が安定してから作業する。

※内部の基準電源1.1Vを利用することで自動設定も可能と思いますが、現状は定数で設定しています。

操作方法と表示について

操作キーは、

Enter:周波数切替サイズ 10Hz/100Hz/1kHz/10kHz/100kHz/1MHz/10MHz

Right : 通常時+1、温度測定で20Hz以上周波数ずれが予想される場合は、補正周波数設定

Left : 通常時-1、同上

UP:周波数up

Down:周波数down

Si5351Aを利用したパルスジェネレータの表示

表示は、見ての通り、1行目は周波数、2行目は、周波数切替サイズとオシレータの温度

オシレータの温度表示は、周波数ずれが20Hz以上予想される場合は点滅

最後に

ICへの設定値が仕様を逸脱している部分が多々ありますが、現状は動作に支障はないようなので良しとしています。暇になったら厳密に仕様の範囲で動かせることも検討できればと思っています。

十分使えるものにできたと思いますが、連続的に周波数を変更できない(周波数変更の瞬間に乱れるので、一度発振を停止して設定後再発振するようにしています。)ので、折角温度補正機能を付けたのですが、リニアには適用できません。周波数を変更などすると補正された周波数が適用されます。

スイープ発振を実現された事例もあるようなので、そのようにSI5351を設定することで、温度補正をリニアに適用することも可能になるかもしれません。

補正データもプログラム上の定数になっているので、個体によってプログラムの変更になってしまいます。プログラムに初期設定機能を追加して、プログラム上から校正ができるとより汎用的になろうかと思います。折角AVRにはEEPROMなどが搭載されていますから….

その他、温度測定結果がばらついてしまいます。温度センサーの電源の取り回し、Arduino周りのノイズ対策(測定時はCPUをスリープさせるとか)などハード的プログラム的にも修正の余地はあると思います。現状は高速性を要求しないプログラムなので、多数回読込み平均でバラツキを平均化して真値に近い値を確認するようにしています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA