Update: Sep. 14, 2018

クロックジェネレータ IC Si5351A をPICマイコンで制御


秋月から販売されているSILICON LABS のクロックジェネレータIC Si5351AモジュールをPICマイコンで制御する実験を行いましたで紹介します。


 


Si5351AをPICマイコンで制御する

近頃、周波数源としてクロックジェネレータICが話題を呼んでいます。
SILICON LABS の Si5351A もそのひとつで、PLLを用いたプログラマブルなクロックICでありながら高C/Nを実現しており、無線機のVFOなどにDDSに代わるデバイスとして注目されています。

ところが巷間、VFOの自作としてWeb上で発表されいるものは、ほとんどがマイコンにArduinoを用いたもので、PICマイコンの情報はあまり検索されません。
Arduinoは、Web上で様々なライブラリが公開されており、Webからダウンロードしてスケッチと呼ばれるCライクなソースリスト上にインクルードすれば、ライブラリの中身は判らなくてもSi5351Aなどを簡単に使用することが出来るようです。

Arudinoに押されて、どうもPICの存在感が以前より薄れてきているようで、PICユーザーの私としては口惜しい思いをしています。
そこで、PICを用いて秋月から販売されているSi5351Aモジュールを制御する実験を行いましたので、ソースリストも含めて情報を公開します。


Si5351Aの制御

Si5351Aは、プログラマブルな分数分周器を用いてクリスタル(秋月のモジュールでは25MHz)の源発振をPLL方式で逓倍する逓倍段と、その後ろに同じくプログラマブルな分数分周器(Multisynth)を配置して所望の出力周波数を得る構造となっています。




従ってPLL部とMultisynth部の分周器でそれぞれ設定が行えるため、所望の出力周波数を得る分周器の設定の組合せは一意には決まりません。

PLLの逓倍周波数を fpll 、 クリスタルの源発振周波数を fxtal 、 所望の出力周波数を fout とすると

       fpll = fxtal * ( a + b / c )

       fout = fpll / ( d + e / f )

       *:Si5351Aの仕様書には d + e/f の部分も a, b, c を用いて記述されていますが、fpllを設定するa,b,cとは別ものですので、ここではあえて d, e, f で現しています

VFOのように任意の周波数を連続して可変するようなアプリケーションでは、Multisynth部を固定値とするとプログラムが簡単になります。

今回作成したプログラムでは、Multisynth の分周数 (d + e / f の部分)は可変せず、偶数整数の固定値とします。(つまり e = 0, f = 1,  d を偶数に設定)
そして周波数の可変はPLL側の逓倍数 a + b / c の値を変更することにより行っています。

Multisynthの分周数を分数分周とせず、偶数の整数分周とすることで、この部分で発生するフェーズジッタを減らすことが出来るのでスプリアスを低減できます。

a, b, c, d, e, f それぞれを決定後、さらにそれをパラメータ P1, P2, P3 へ変換し、レジスタへ書込むことにより周波数が設定されます。

      PLL用      P1 = 128 a + 128 * b / c - 512
      Multisynth用  P1 = 128 d + 128 * e / f - 512

      PLL用      P2 = 128 * b - c * 128 * b / c
      Multisynth用  P2 = 128 * e - f * 128 * e / f

      PLL用      P3 = c
      Multisynth用  P3 = f

P1は、18bit、P2とP3は20bitのパラメータです。レジスタは8bitなのでパラメータのビットを分割し、対応するレジスタへ書込みます。

以上が考え方の基本ですが、レジスタの設定等詳細はSi5351Aの仕様書並びに本ページの最後に掲載するソースリストを参照下さい。


回路図



マイコンに PIC16F1827 、内蔵クロック発振 16MHz で動作させます。
今回のプログラムでは単純に周波数 7MHz を設定しているだけですが、PICのプログラムメモリーを約2kワード消費します。



周波数の連続可変

Si5351Aの仕様書には、周波数設定をする場合は、先ず 出力ドライバ(CLK0)をDisableして出力を止めてから各分周器のレジスタ設定を行い、PLLをリセットしてから出力ドライバ(CLK0)をEnableにするよう指示されています。

これはVFOのようなアプリケーションでは、周波数を連続可変するときに一瞬発振が停止することになるのであまり宜しくありません。
そこで、出力ドライバ(CLK0)を止めずに分周器のレジスタ設定を行い、さらにPLLのリセットも行わずに周波数が可変できるかを実験してみました。



上の波形は、CLK0のDisableとPLLののリセットを行わずにSi5351Aの周波数設定を 7MHz → 50MHz へ可変したときのものです。(CH1黄色の波形)
CH2青の波形は、分周器のレジスタへ書込みを始めるタイミングを示します。(青の波形の立下りのタイミングでレジスタへの書込みを開始)
分周器のレジスタへ書込みが始まるとそれまで7MHzを出力していたものが、急に非常に高い周波数を出力するようになり、その後レジスタへの書込みが終了すると、正規の50MHzを出力していることが判ります。

このようにVFOとして使用する場合、所望の周波数以外が出力されることは宜しくありませんので、やはり仕様書どおりCLK0 Disable→PLLリセットの手順は必要なようです。
以下に仕様書どおりの手順で周波数を7MHz → 50MHzに可変した場合の波形を示します。
約 8mS の間、出力が停止しています。(青の波形の立下りのタイミングから50MHzが出力されるまでの時間)



1回転300パルスなどのロータリーエンコーダをグルグル回すようなVFOには、この出力停止期間は無視できないかも知れません。
この点が、DDSを用いたVFOとの決定的な差のようにも思いますが、Web上にはSi5351Aを用いた無線機の自作例が多数紹介されているので、それほど大きなウィークポイントでも無いのかも知れません。

それから、上の波形でPLLリセットからCLK0がEnableされて50MHzが出力される瞬間、細いパルスが1本出ているのが気にかかります。
出力立上りの瞬間、高い周波数のノイズを出しているようで、これも気にかかるところです。


消費電流

QRPトランシーバなど省電力の機器に使用する場合、消費電流も出来るだけ小さくしたいものです。

そこで、Si5351Aの電源の消費電流を測定してみました。
なお、消費電流は、出力ドライバCLK0のDrive Strength 設定 (レジスタ16 の [D1:D0] には依存しないようです。
下のグラフは、Drive Strength 8mAに設定して測定しています。




10MHz以下の周波数では約 15mA 消費します。
DDS の AD9834 などは 約10mA なので少し大きい感じですが、許容できる範囲かな、とは思います。


ソースリスト

今回のプログラムのソースリストを示します。(henteko.org さんのWebサイトを参考にさせて頂きました。)
コンパイラは XC8 を使用。
マイコンは、4kワ-ドROM の PIC16F1827 です。I2Cモジュールを2つ内蔵しているので、SSP1の方を使用しています。
出力周波数の設定は si5351_setfreq (unsigned long freq ) の引数に1ヘルツのケタまで周波数を渡せばOKです。
このプログラムでは単に7MHzを出力させているだけですが、ロータリーエンコーダなどを用いて連続的に周波数データを設定すればVFOになります。
設定可能周波数範囲は分周器のレジスタ設定値の制限から 500kHz ~ 150MHz です。
その他は、細かくコメントを記載してありますので、参考にして下さい。


/******************************************************************

             si5351 Ver 0.0

             si5351 テスト

             Author JR3TGS

          周波数設定範囲 500kHz - 150MHz

           I2Cモジュールは、SSP1を使用


             XC8 Ver 2.00

             PIC16F1827

            Sep. 14, 2018

********************************************************************/


#include <xc.h>
#include <stdio.h>

#define _XTAL_FREQ 16000000
#define XTAL_FREQ 25000000   // si5351 XTAL 25MHz

#pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FOSC = INTOSC, FCMEN = OFF
#pragma config MCLRE = ON, WDTE = OFF, CP = OFF, PWRTE = ON, CLKOUTEN = OFF
#pragma config PLLEN = OFF, WRT = OFF, STVREN = OFF, BORV = LO, LVP = OFF

void i2c_start();
void i2c_stop();
void i2c_write(unsigned char byte);
void si5351_write(unsigned char reg, unsigned char data);
void si5351_ini();
void si5351_setpll(unsigned char a, unsigned long b, unsigned long c);    // PLL逓倍比設定
void si5351_setmultisynth(unsigned int d);                                            // Multisynth分周比設定
void si5351_setfreq(unsigned long freq);                                               // si5351への周波数書込み


//***********************************************************************************

void main(void)
{

  TRISA = 0b00000000;
  TRISB = 0b00010010;     // SCL, SDA input (I2C(SSP1モジュール)使用するので入力に設定必要)
  PORTA = 0b00000000;
  PORTB = 0b00000000;
  ANSELA = 0b00000000;
  ANSELB = 0b00000000;
  OSCCON = 0b01111100;   // INTOSC 16MHz
  SSP1CON1 = 0b00101000; // I2C master mode, Clock 100kHz = Fosc / ( 4 * (SSPADD + 1 ))
  SSP1STAT = 0b00000000; // Slew rate control Enabled
  SSP1ADD = 0b00100111;  // SSPADD 39d, Clock = Fosc / ( 4 * (SSPADD + 1 )) = 100kHz


  si5351_ini();

  si5351_setfreq(7000000);

  while(1)
  {
    PORTAbits.RA0 = 1;
    __delay_ms(300);

    PORTAbits.RA0 = 0;
    __delay_ms(300);
  }

}



//*************************************************************************************

void si5351_ini()
{
  si5351_write(3, 0xFF);    // Reg. address 3d(Output Enable Control), All output disable
  si5351_write(16, 0x80);   // Reg. address 16d (CLK0 Control), CLK0 power down
  si5351_write(17, 0x80);   // Reg. address 17d (CLK0 Control), CLK1 power down
  si5351_write(18, 0x80);   // Reg. address 18d (CLK0 Control), CLK2 power down
  si5351_write(183, 0x80);  // Reg. address 183d (Crystal Internal Load Capacitance), 8pF
}


void si5351_setfreq(unsigned long freq)  // 周波数設定範囲 500kHz - 150MHz
{
/*
   PLLが900MHz付近の周波数でかつ出力する周波数の偶数の逓倍周波数を求め、Multisynth の分周比を偶数の固定値とする。
   出力周波数の可変はPLL側の分数分周比(逓倍比)を可変することにより行う。(Si5351のPLL最高周波数は900MHz)

     fout = fpll / (d + e / f) → Multisynthの分周比(d + e / f)を e=0, f=1, dを偶数値に設定する
     fpll = fxtal * (a + b / c)  PLLの逓倍比 a, b, c を可変することにより、foutを可変する


     fpll = fxtal * (a + b/c) → fpll = fxtal * a + fxtal * b/c
     この式から fxtalをa逓倍したもの(疎調)に fxtalを b/c逓倍(微調)したものを加算したものが fpll
     (c を大きくすると周波数可変の分解能が上がる)

     a は fpll を fxtalで除算、 fxtal * b/c の部分は剰余で求まる
     a = fpll / fxtal
     fxtal * b/c = fpll % fxtal → b = fpll % fxtal * c / fxtal
*/


  unsigned int div;
  unsigned long pllfreq;
  unsigned char a;
  unsigned long b;
  unsigned long c;


  div = 900000000 / freq;  // Multisynth分数分周器の分周比divを決定
  div = div >> 1;        // divは整数かつ偶数とする
  div = div << 1;        // (偶数とするとスプリアスを抑制できる)

  pllfreq = div * freq;     // 出力周波数の偶数倍となるPLL周波数決定

  a = pllfreq / XTAL_FREQ;

  c = 0xFFFFF;        //20-bit denominator フルビット (最大分解能に設定)

  b = pllfreq % XTAL_FREQ * ((float)c / XTAL_FREQ);



  si5351_write(3, 0xFF);   // Reg. address 3d(Output Enable Control), All output disable

  si5351_setpll(a, b, c);    // PLL 逓倍比設定
  si5351_setmultisynth(div); // Multisynth 分周比設定

  si5351_write(16, 0x4F);   // Reg. address 16d(CLK0 Control), CLK0 power up, integer mode, drive strength 8mA
  si5351_write(177, 0xA0); // Reg. address 177d (PLL Reset), PLL A and B reset
  si5351_write(3, 0xFE);   // Reg. address 3d(Output Enable Control), CLK0 output enable
}


void si5351_setpll(unsigned char a, unsigned long b, unsigned long c)  // PLL逓倍比設定
{
  unsigned long P1;
  unsigned long P2;
  unsigned long P3;

  P1 = 128 * a + (unsigned long)(128 * (float)b / c ) - 512;
  P2 = 128 * b - c * (unsigned long)(128 * (float)b / c);
  P3 = c;

  // PLL分数分周器の設定 (レジスタアドレス順に設定しないと設定値が反映されないことがあるので注意)
  si5351_write(26, (P3 & 0x0FF00) >> 8); // P2, P3 are 20-bit parameter
  si5351_write(27, (P3 & 0x000FF));
  si5351_write(28, (P1 & 0x30000) >> 16); // P1 is 18-bit parameter
  si5351_write(29, (P1 & 0x0FF00) >> 8);
  si5351_write(30, (P1 & 0x000FF));
  si5351_write(31, ((P3 & 0xF0000) >> 12) | ((P2 & 0xF0000) >> 16));
  si5351_write(32, (P2 & 0x0FF00) >> 8);
  si5351_write(33, (P2 & 0x000FF));
}


void si5351_setmultisynth(unsigned int d)  // Multisynth分周比設定
{
  unsigned long P1;
  unsigned long P2;
  unsigned long P3;

  P1 = 128 * (unsigned long)d - 512;   // e=0, f=1
  P2 = 0;
  P3 = 1;

  // Multisynth分数分周器の設定 (レジスタアドレス順に設定しないと設定値が反映されないことがあるので注意)
  si5351_write(42, (P3 & 0x0FF00) >> 8);  // P2, P3 are 20-bit parameter
  si5351_write(43, (P3 & 0x000FF));
  si5351_write(44, (P1 & 0x30000) >> 16); // P1 is 18-bit parameter
  si5351_write(45, (P1 & 0x0FF00) >> 8);
  si5351_write(46, (P1 & 0x000FF));
  si5351_write(47, ((P3 & 0xF0000) >> 12) | ((P2 & 0xF0000) >> 16));
  si5351_write(48, (P2 & 0x0FF00) >> 8);
  si5351_write(49, (P2 & 0x000FF));
}


void si5351_write(unsigned char reg, unsigned char data)  //レジスタへのデータ書込み
{
  i2c_start();
  i2c_write(0xC0);  // Si5351A slave address
  i2c_write(reg);   // Send register address
  i2c_write(data);  // send data
  i2c_stop();
}


void i2c_start()
{
  SSP1CON2bits.SEN = 1;
  while(SSP1CON2bits.SEN);
}


void i2c_stop()
{
  SSP1CON2bits.PEN = 1;
  while(SSP1CON2bits.PEN);
}


void i2c_write(unsigned char byte)
{
  PIR1bits.SSP1IF = 0;
  SSP1BUF = byte;
  while(!PIR1bits.SSP1IF);
  SSP1CON1bits.CKP = 1;
}