JH7UBCブログ

アマチュア無線 電子工作 家庭菜園など趣味のブログです

PIC16F1705 DAC テスト

2019-05-07 23:01:15 | PIC16F1705

 PIC16F1705は、8bit(0~255)のDAコンバータ(DAC)を内蔵しています。

 そのDACを利用して、1000Hzの三角波を作り、内蔵OPアンプ1(ボルテージフォロア接続)に接続して、出力してみます。

 正確なクロックを得るために外部クロックとし、20MHzのクリスタルオシレータの信号をRA5(CLKIN)に入れます。

 TMR2割り込みを利用して、10us(100KHz)ごとに割り込みを発生させ、割り込みごとにDACのデータ(DACR)をインクリメント、ディクリメントして、三角波を発生させます。

 DACの構造です。(PIC16F1705の説明書より)

 

  DACの設定は、DAC1CON0レジスタで行います。bit7(DAC1EN)=1として、DACを使えるようにします。bit3:2=DAC1PSS=00として、VSOURCE+=VDD(5V)とします。bit0=DAC1NSS=0として、VSOURCE-=Vssとします。DAC1OUT1とDACOUT2は、0として、ピンには出力しません。DAC1_Outputは、OPA1に接続します。

 つまり、DAC1CON0=0b10000000 とします。

 次に、OPA1の設定です。OPアンプの使い方(ボルテージフォロア)は、当ブログのこちらを参照してください。

 OPA1の正入力は、DAC_Outputとしますので、OPA1CH=10としますので、

 OPA1CON = 0b10010010 とします。

 プログラムです。

---------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
 
// CONFIG1
#pragma config FOSC = ECH      //外部クロックHigh Power mode(4?20MHz)
#pragma config WDTE = OFF       //ウォッチドックタイマー無効
#pragma config PWRTE = ON       //パワーアップタイマーを有効にする
#pragma config MCLRE = OFF      //MCLRピンをRA3として使用する
#pragma config CP = OFF         //プログラムメモリを保護しない
#pragma config BOREN = ON       //ブラウンアウトリセットを有効にする
#pragma config CLKOUTEN = OFF   //クロック出力を無効とし、RA4ピンとして使用する
#pragma config IESO = OFF       //内部・外部クロックの切り替えでの起動を行わない
#pragma config FCMEN = OFF      //外部クロックを監視しない
 
// CONFIG2
#pragma config WRT = OFF        //フラッシュメモリを保護しない
#pragma config PPS1WAY = OFF    //ロック解除シーケンスで何度でもPPSLOCKをセット/クリアできる
#pragma config ZCDDIS = ON      //ゼロクロス検出回路無効
#pragma config PLLEN = OFF      //×4PLLを動作させさない
#pragma config STVREN = ON      //スタックオーバーフローリセットを行う
#pragma config BORV = HI        //ブラウンアウトリセット電圧を高(2.7V)に設定
#pragma config LPBOR = OFF      //低消費電力ブラウンアウトリセット無効
#pragma config LVP = OFF        //低電圧プログラミングを行わない
 
#define _XTAL_FREQ 20000000

unsigned char i = 0;
unsigned char j = 0;
 
void __interrupt() isr(){
    TMR2IF = 0;         //TMR2IFクリア
    DAC1CON1 = j;
    if(i <= 50){
        j++;
    }else{
        j--;
    }
    i++;
    if(i == 100){
        i = 0;
        j = 0;
    }
}
 
void main() {
    ANSELA = 0b00000000 ;     // AN0-AN3は使用しない
    ANSELC = 0b00000000 ;     // AN4-AN6は使用しない
    TRISA  = 0b00100000 ;     // RA5は入力、他は出力
    TRISC  = 0b00000000 ;     // PORTCは全て出力
    PORTA  = 0b00000000 ;     // PORTA初期化
    PORTC  = 0b00000000 ;     // PORTC初期化
   
    /* OPアンプ1設定 */
    OPA1CON = 0b10010010;     // OPA1EN=1,OPA1UG=1,OPA1CH=10=DAC
   
    /* DAC設定 */
    DAC1CON0 = 0b10000000;    // DAC1EN=1,DAC1OE1=0,DAC1OE2=0,DACPSS=00=Vdd
   
   
    TMR2IE = 1;               //TMR2割り込み許可
    PEIE = 1;                 //周辺割り込み許可
    GIE = 1;                  //全割り込み許可
   
    /* PR2の計算
     * PR2=(Fosc/(Fout×4×TMR2 Prescaler Value))-1
     *    =((20000000/(100000×4×1))-1
     *    =49
     */
 
    PR2 = 49;                 //TMR2出力周波数100KHz
    T2CON = 0b00000100;       //prescaller 1:1,postscaler1:1, TMR2 start
   
    while(1){
       
    }
}

---------------------------------------------------------------

 TMR2から10usごとに割り込みがかかり、その都度jの値をインクリメントします。jが50を超えたらディクリメントします。iは割り込みの度にインクリメントされi=100になったら、i=0,j=0とします。これで三角波ができます。

 DACの出力電圧の最高値は、5.0×(50/256)=0.98Vです。

 実際に発生した波形です。

 一部を拡大してみます。ピークは、0.97Vです。

 階段状になると思ったのですが、かなりギザギザしています。

 周波数を測定してみました。

 1周期は、10us×100=1000us=1msですから、1000Hzです。実測値も1000Hzです。

 工夫すれば、いろいろな波形を作れそうです。


PIC16F1705 PWM テスト2

2019-05-01 10:32:00 | PIC16F1705

 PIC16F1705にはPWM出力用に、CCPモジュール2個(CCP1(PWM1),CCP2(PWM2))と専用のPWMモジュール2個(PWM3,PWM4)持っています。

 今回は、PWMモジュールを使ってみます。このモジュールは、下の図のような構造を持っています。

 CCPモジュールとの違いは、Duty Cycle registerの名称がPWMxDCHとなり、Duty Cycle設定用のデータの下位2bitが専用のレジスタPWMxDCLに格納されることです。

 (PIC16F1705の説明書より)

 今回のテストでは、PWM3をRA0にPWM4をRA1に出力します。

 PWM3,PWM4ともタイマーは、TMR2を使い、PWM周期は1ms(1000Hz)とし、PWM3にはデューティ比30%,PWM4にはデューティ比70%の信号を出力してみます。

 プログラムです。(mainプログラムのみ示します)

 クロックは、内臓クロック8MHzです。

-------------------------------------------------------

void main() {
    OSCCON = 0b01110010;    // 内部クロック8MHz
    ANSELA = 0b00000000;    // AN0-AN1を使わない
    ANSELC = 0b00000000;    // AN4-AN6を使わない
    TRISA  = 0b00000000;    // RORTAは全て出力
    TRISC  = 0b00000000;    // PORTCは全て出力
    PORTA  = 0b00000000;    // PORTAクリア
    PORTC  = 0b00000000;    // PORTCクリア
       
    /*
     * RA0にPWM3を出力する
     * RA1にPWM4を出力する
     */
    RA0PPS = 0b00001110;    //RA0=PWM3OUT
    RA1PPS = 0b00001111;    //RA1=PWM4OUT
   
    PWM3CON = 0b10000000;   //PWM3 enable,active high
    PWM4CON = 0b10000000;   //PWM4 enable,active high
    CCPTMRS = 0b00000000;   //PWM3,PWM4にTimer2を割り当てる
   
    PR2 = 124;              //Timer2の周期を設定 1ms(1000Hz)
    PWM3DCH = 37;           //PWM3のデューティ比 30%
    PWM3DCL = 0;
    PWM4DCH = 87;           //PWM4のデューティ比70%
    PWM4DCL = 0;
    T2CON = 0b00000110;     //prescale 1:16 Timer2 start
   
    while(1){
       
    }
}

-------------------------------------------------------

 出力波形です。青がPWM3、赤がPWM4です。

 まとめると、PIC16F1705は、4つのPWM出力ができますが、使えるタイマーは、TMR2,TMR4,TMR6(いずれも8bitタイマー)です。

 PWM出力は、RA3を除く任意のポートに設定できます。目的、用途に応じてポート、タイマー、周期、デューティ比を使い分けます。


PIC16F1705 PWM テスト1

2019-04-30 12:14:09 | PIC16F1705

 PIC16F1705は、CCP(Capture/Compare/PWM)モジュール2個とPWMモジュール2個、合わせて4つのPWM出力を持っています。

 まず、CCPモジュールを使ってみます。

 CCP1(PWM1)をRC0に、CCP2(PWM2)をRC1に出力してみます。

 CCPモジュールでのPWM発生のしくみです。

 (PIC16F1705の説明書より)

 PIC12F1822やPIC16F1827などと同じしくみです。上の図では、TMR2を利用した例です。

 今回は、CCP1(PWM1)では、TMR4を使用し、CCP2(PWM2)では、TMR6を使用します。

 クロックは、内部クロック8MHzとし、CCP1(PWM1)の周期(周波数)は、1ms(1000Hz)、CCP2(PWM2)の周期(周波数)は、2ms(500Hz)で、ともにデューティ比は50%の信号を出力します。

 PWMの周期とデューティ比の値の決め方は、下の図のとおりです。(PIC16F1827の説明ですが、式は同じです)

 上の式からPRxの値は、クロック周波数をFosc、出力周波数をFoutとすると

 PRx=(Fosc/(Fout×4×TMRxPrescale Value))-1となります。

 プログラムです。

------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
 
// CONFIG1
#pragma config FOSC = INTOSC    //内部クロックを使う
#pragma config WDTE = OFF       //ウォッチドックタイマー無効
#pragma config PWRTE = ON       //パワーアップタイマーを有効にする
#pragma config MCLRE = OFF      //MCLRピンをRA3として使用する
#pragma config CP = OFF         //プログラムメモリを保護しない
#pragma config BOREN = ON       //ブラウンアウトリセットを有効にする
#pragma config CLKOUTEN = OFF   //クロック出力を無効とし、RA4ピンとして使用する
#pragma config IESO = OFF       //内部・外部クロックの切り替えでの起動を行わない
#pragma config FCMEN = OFF      //外部クロックを監視しない
 
// CONFIG2
#pragma config WRT = OFF        //フラッシュメモリを保護しない
#pragma config PPS1WAY = OFF    //ロック解除シーケンスで何度でもPPSLOCKをセット/クリアできる
#pragma config ZCDDIS = ON      //ゼロクロス検出回路無効
#pragma config PLLEN = OFF      //PLLを動作させない
#pragma config STVREN = ON      //スタックオーバーフローリセットを行う
#pragma config BORV = HI        //ブラウンアウトリセット電圧を高(2.7V)に設定
#pragma config LPBOR = OFF      //低消費電力ブラウンアウトリセット無効
#pragma config LVP = OFF        //低電圧プログラミングを行わない
#define _XTAL_FREQ 8000000     //クロック8MHz
 
void main() {
    OSCCON = 0b01110010;    // 内部クロック8MHz
    ANSELA = 0b00000000;    // AN0-AN1を使わない
    ANSELC = 0b00000000;    // AN4-AN6を使わない
    TRISA  = 0b00000000;    // RORTAは全て出力
    TRISC  = 0b00000000;    // PORTCは全て出力
    PORTA  = 0b00000000;    // PORTAクリア
    PORTC  = 0b00000000;    // PORTCクリア
   
    /* PWMに関する設定
     * CCP1(PWM1)をRC0に出力する
     * CCP1(PWM1)の周波数は、1000Hz(周期は1ms)
     * CCP2(PWM2)をRC1に出力する
     * CCP2(PWM2)の周波数は、500Hz(周期は2ms)
     * デューティ比は、50%とする
     */
 
    /* RC0にCCP1(PWM1)を出力する */
    RC0PPS = 0b00001100;    //RC0=CCP1(PWM1)
    /* RC1にCCP2(PWM2)を出力する */
    RC1PPS = 0b00001101;    //RC1=CCP2(PWM2)
    /* CCP1,CCP2はPWM mode */
    CCP1CON = 0b00001100;   //CCP1=PWM mode
    CCP2CON = 0b00001100;   //CCP2=PWM mode
    /* Timerの割り当て */
    CCPTMRS = 0b00001001;   //PWM1にTMR4,PWM2にTMR6を割り当てる
   
    /* PWM1の周期とデューティ比の設定 */
    PR4 = 124;             //PWM1の周期1000Hz(1ms)
    CCPR1L = 62;           //PWM1のデューティ比 50%
    T4CON = 0b00000110;     //prescale 1:16 Timer4 start
   
    /* PWM2の周期とデューティ比の設定 */
    PR6 = 249;              //PWM2の周期500Hz(2ms)
    CCPR2L = 125;           //PWM2のデューティ比 50%
    T6CON = 0b00000110;     //prescale 1:16 Timer6 start
   
    while(1){
       
    }
}
 
---------------------------------------------------------
 周波数を測定しました。
 CCP1(PWM1)の出力周波数です。
 
 CCP2(PWM2)の出力です。
 
 内部クロックの精度が上がったのでしょう、けっこう正確な周波数を発生しています。
 
 出力波形です。(青がCCP1(PWM1)、赤がCCP2(PWM2))
 
 ほぼ同時にTMR4とTMR6をスタートさせたので、位相がそろっています。
 次は、PWM専用モジュールを使ってみます。

PIC16F1705 ADコンバータ テスト

2019-04-25 11:43:08 | PIC16F1705

 PIC16F1705は、8チャンネルのADコンバータを使うことができます。内部のADコンバータは1つなのですが、下の図のようにADCON0レジスタのCHS<4:0>によってチャンネルを選択します。

   PIC16F1705の説明書から転載

 

 今回のテストでは、AN0とAN1を使い、VREF+はFVR×2=2.048Vとして、AD変換値をI2C LCD AQM0802Aに2つの値を表示してみます。

ブレッドボードです。電源は乾電池2本(3V)です。

 プログラムです。

 AD変換の手順については、PIC12F1822と同じなので、私のホームページ「JH7UBCホームページ」のこの記事を見てください。

 ADコンバータの変換結果(0~1023)は、AQM0802Aの1行目にAN0(CH0)を2行目にAN1(CH1)を0.5秒ごとに表示します。

  AN0とAN1の選択は、ADCON0レジスタのbit2 CHS0で行います。CHS0=0でAN0、CHS0=1でAN1が選択されます。

-----------------------------------------------------------

/*
 * File:   main.c
 * Author: JH7UBC Keiji Hata
 * PIC16F1705 ADC test
 * Created on 2019/04/23
 */
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
 
// CONFIG1
#pragma config FOSC = INTOSC    //内部クロックを使う
#pragma config WDTE = OFF       //ウォッチドックタイマー無効
#pragma config PWRTE = ON       //パワーアップタイマーを有効にする
#pragma config MCLRE = OFF      //MCLRピンをRA3として使用する
#pragma config CP = OFF         //プログラムメモリを保護しない
#pragma config BOREN = ON       //ブラウンアウトリセットを有効にする
#pragma config CLKOUTEN = OFF   //クロック出力を無効とし、RA4ピンとして使用する
#pragma config IESO = OFF       //内部・外部クロックの切り替えでの起動を行わない
#pragma config FCMEN = OFF      //外部クロックを監視しない
 
// CONFIG2
#pragma config WRT = OFF        //フラッシュメモリを保護しない
#pragma config PPS1WAY = OFF    //ロック解除シーケンスで何度でもPPSLOCKをセット/クリアできる
#pragma config ZCDDIS = ON      //ゼロクロス検出回路無効
#pragma config PLLEN = ON       //×4PLLを動作させる
#pragma config STVREN = ON      //スタックオーバーフローリセットを行う
#pragma config BORV = HI        //ブラウンアウトリセット電圧を高(2.7V)に設定
#pragma config LPBOR = OFF      //低消費電力ブラウンアウトリセット無効
#pragma config LVP = OFF        //低電圧プログラミングを行わない
#define _XTAL_FREQ 32000000     //クロック32MHz
 
/* LCD関係定義 */
#define LCD_addr 0x7C   //3E+0
#define LCD_LINE1 0x80
#define LCD_LINE2 0xC0
 
/* I2C 初期化*/
void I2C_init(){
    SSP1CON1 = 0x28;        //SSPEN = 1,I2C Master Mode
    SSP1STATbits.SMP = 1;   //標準速度モード(100KHz)
    SSP1ADD = 0x4F;         //Fosc/(4*Clock)-1  Clock=100kHz,Fosc=32MHz
}
 
/* スタートコンディション */
void I2C_start(){
    SSP1CON2bits.SEN = 1;
    while(SSP1CON2bits.SEN);
}
 
/* ストップコンディション */
void I2C_stop(){
    SSP1IF = 0;
    SSP1CON2bits.PEN = 1;
    while(SSP1CON2bits.PEN);
    SSP1IF = 0;
}
 
/* I2Cに1byte送信 */
void I2C_write(unsigned char dat){
    SSP1IF = 0;
    SSP1BUF = dat;
    while(!SSP1IF);
}
 
/* write command */
void LCD_cmd(unsigned char cmd){
    I2C_start();          //start condition
    I2C_write(LCD_addr);  //send slave address
    I2C_write(0x00);      //send control byte
    I2C_write(cmd);       //send command
    I2C_stop();           //stop condition
}
 
/* write charactor */
void LCD_char(unsigned char dat){
    I2C_start();          //start condition
    I2C_write(LCD_addr);  //send slave address
    I2C_write(0x40);      //send control byte
    I2C_write(dat);       //send data
    I2C_stop();           //stop condition
}
 
/* LCD initialize */
void LCD_init(){
    __delay_ms(40); //40ms wait
    LCD_cmd(0x38);  //8bit,2line
    LCD_cmd(0x39);  //IS=1 : extention mode set
    LCD_cmd(0x14);  //Internal OSC Frequency
    LCD_cmd(0x70);  //Contrast set
    LCD_cmd(0x56);  //Power/ICON/Contrast Control
    LCD_cmd(0x6C);  //Follower control
    __delay_ms(200);//200ms wait
    LCD_cmd(0x38);  //IS=0 : extention mode cancel
    LCD_cmd(0x0C);  //Display ON
    LCD_cmd(0x01);  //Clear Display
    __delay_ms(2);  //wait more than 1.08ms
}
 
/* Clear Display */
void LCD_clear(){
    LCD_cmd(0x01);
    __delay_ms(1);
    __delay_us(80);
}
 
/* Return Home `*/
void LCD_home(){
    LCD_cmd(0x02);
    __delay_ms(1);
    __delay_us(80);
}
 
/* Locate cursor x,y */
void LCD_cursor(unsigned char x,unsigned char y){
    if (y == 0){
        LCD_cmd(LCD_LINE1+x);
    }
    if (y == 1){
        LCD_cmd(LCD_LINE2+x);
    }
}
 
/* write 1 charactor to LCD  */
void putch(unsigned char ch){
    LCD_char(ch);
}
 
/* ADconvert & display*/
void AD_convert(unsigned char channel){
    ADCON0bits.CHS0 = channel;   //ADC チャンネルセレクト
    __delay_us(20);         // 20us待つ
    GO = 1;                 // ADC スタート
    while(GO);              // AD変換終了まで待つ
    unsigned int val = (ADRESH << 8)| ADRESL;   //AD変換結果をvalに入れる
    LCD_cursor(3,channel);
    printf("%5d",val);      // AD変換結果を表示
}
 
void main() {
    OSCCON = 0b01110000;    // 内部クロック8MHz ×4=32MHz
    ANSELA = 0b00000011;    // AN0-AN1を使う。他はデジタル
    ANSELC = 0b00000000;    // AN4-AN6を使わない
    TRISA  = 0b00000011;    // RA0,RA1は入力、他は出力
    TRISC  = 0b00000011;    // RC0,RC1は入力、他は出力
    PORTA  = 0b00000000;    // PORTAクリア
    PORTC  = 0b00000000;    // PORTCクリア
   
    /* ADコンバータ設定*/
    ADCON0 = 0b00000001;    // bit0=1 ADCを有効にする
    ADCON1 = 0b10100011;    // bit7(ADFM)=1(右詰め),bit<6:4>=010 Fosc/32=1.0us
                            // bit<1:0>=00 VREF+=FVR
    FVRCON = 0b10000010;    // bit7(FVRON)=1,bit<1:0>=10 ADFVR×2=2.048V
   
    /* I2Cピン設定 */
    RC0PPS = 0x10;   //RC0をSCLに割り当てる(出力)
    SSPDATPPS = 0x11;//SSPDATにRC1を割り当てる(入力)
    RC1PPS = 0x11;   //RC1をSDAに割り当てる(出力)
   
    I2C_init();     // I2C初期化
    LCD_init();     // LCD初期化
   
    /* 表示初期設定*/
    LCD_home();
    printf("CH0");
    LCD_cursor(0,1);
    printf("CH1");
   
    while(1){ 
        AD_convert(0);  //channel0をAD変換・表示
        AD_convert(1);  //channel1をAD変換・表示
        __delay_ms(500);
    }
}
 

PIC16F1705 Si5351A VFO

2019-04-22 18:36:10 | PIC16F1705

 PIC16F1705で3チャンネルクロックジェネレータSi5351Aをコントロールして、VFOを作ってみます。

 表示は、I2C LCD AQM0802Aを使い、ロータリーエンコーダで周波数を変えます。

 周波数ステップは、STEPボタンを押すたびに1MHz,100KHz,10KHz,1KHzと変化し、もう一度押すと1MHzに戻ります。

 回路図です。電源は乾電池(3V)です。I2Cのプルアップ抵抗は、AQM0802Aモジュールに内蔵されているので、つけていません。ロータリーエンコーダを接続するRC4,RC5とSTEPボタンを接続するRC3は、PIC側でウィークプルアップしています。

ブレッドボードです。周波数の初期値は1MHzで、STEPの初期値は1MHzです。

 50MHzを発生さ、自作の周波数カウンタで周波数を測定しています。

 Si5351Aの負荷容量は、8pFとしていますが、若干低い周波数で発振しています。

 

 プログラムです。

  周波数の初期値は、1MHzとしてあります。このVFOをアマチュア無線の無線機等に利用する場合は、周波数は希望の周波数に設定し、STEPも目的に合わせて、設定してください。

 このプログラムで、発生可能な周波数は、500KHz~150MHzです。(100MHz以上では、AQM0802Aでは表示できません)

 私の自作周波数カウンタでは、約70MHzまでしかカウントできませんでしたが、無線機で聞いてみると100MHz以上もちゃんと発振しているようです。

 なお、今回は、xc8 v2.00でコンパイルしてみました。

 xc8 v1.45の場合は、割り込みルーチンを void interrupt isr() と記述してください。

--------------------------------------------------

/*
 * File:   main.c
 * Author: JH7UBC Keiji Hata
 * PIC16F1705 Si5351A VFO
 * Created on 2019/04/22
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
 
// CONFIG1
#pragma config FOSC = INTOSC    //内部クロックを使う
#pragma config WDTE = OFF       //ウォッチドックタイマー無効
#pragma config PWRTE = ON       //パワーアップタイマーを有効にする
#pragma config MCLRE = OFF      //MCLRピンをRA3として使用する
#pragma config CP = OFF         //プログラムメモリを保護しない
#pragma config BOREN = ON       //ブラウンアウトリセットを有効にする
#pragma config CLKOUTEN = OFF   //クロック出力を無効とし、RA4ピンとして使用する
#pragma config IESO = OFF       //内部・外部クロックの切り替えでの起動を行わない
#pragma config FCMEN = OFF      //外部クロックを監視しない
 
// CONFIG2
#pragma config WRT = OFF        //フラッシュメモリを保護しない
#pragma config PPS1WAY = OFF    //ロック解除シーケンスで何度でもPPSLOCKをセット/クリアできる
#pragma config ZCDDIS = ON      //ゼロクロス検出回路無効
#pragma config PLLEN = ON       //×4PLLを動作させる
#pragma config STVREN = ON      //スタックオーバーフローリセットを行う
#pragma config BORV = HI        //ブラウンアウトリセット電圧を高(2.7V)に設定
#pragma config LPBOR = OFF      //低消費電力ブラウンアウトリセット無効
#pragma config LVP = OFF        //低電圧プログラミングを行わない
#define _XTAL_FREQ 32000000     //クロック32MHz
 
/* ロータリーエンコーダ関係定義*/
#define ECA RC5 //エンコーダA
#define ECB RC4 //エンコーダB
volatile unsigned char curDat;
volatile unsigned char befDat;
volatile signed char count= 0;
 
/* LCD関係定義 */
#define LCD_addr 0x7C   //3E+0
#define LCD_LINE1 0x80
#define LCD_LINE2 0xC0
 
/* Si5351A関係定義*/
#define Si5351_ADDR 0xC0    //60<<1
#define MSNA_ADDR 26
#define MS0_ADDR 42
#define CLK0_CTRL 16
#define CLK1_CTRL 17
#define CLK2_CTRL 18
#define OUTPUT_CTRL 3
#define PLL_RESET 177
#define XTAL_LC 183
unsigned long Freq = 1000000;    //周波数初期値 1MHz
unsigned long Freq_old;         //周波数の前の値
const unsigned long XtalFreq = 25000000; //水晶発振器の周波数
unsigned long divider;
unsigned long PllFreq;
unsigned int mult;
unsigned long l;
float f;
unsigned long num;
const unsigned long denom = 1048575;
unsigned long P1;
unsigned long P2;
unsigned long P3;
 
/* STEP関係設定 */
unsigned long Step = 1000000;       //STEP初期値1MHz
#define STEP_SW RC3
 
/* I2C initialize */
void I2C_init(){
    SSP1CON1 = 0x28;        //SSPEN = 1,I2C Master Mode
    SSP1STATbits.SMP = 1;   //標準速度モード(100KHz)
    SSP1ADD = 0x4F;         //Fosc/(4*Clock)-1  Clock=100kHz,Fosc=32MHz
}
 
/* Start condition */
void I2C_start(){
    SSP1CON2bits.SEN = 1;
    while(SSP1CON2bits.SEN);
}
 
/* Stop condition */
void I2C_stop(){
    SSP1IF = 0;
    SSP1CON2bits.PEN = 1;
    while(SSP1CON2bits.PEN);
    SSP1IF = 0;
}
 
/* Send 1byte to I2C */
void I2C_write(unsigned char dat){
    SSP1IF = 0;
    SSP1BUF = dat;
    while(!SSP1IF);
}
 
/* Write command */
void LCD_cmd(unsigned char cmd){
    I2C_start();          //start condition
    I2C_write(LCD_addr);  //send slave address
    I2C_write(0x00);      //send control byte
    I2C_write(cmd);       //send command
    I2C_stop();           //stop condition
}
 
/* Write charactor */
void LCD_char(unsigned char dat){
    I2C_start();          //start condition
    I2C_write(LCD_addr);  //send slave address
    I2C_write(0x40);      //send control byte
    I2C_write(dat);       //send data
    I2C_stop();           //stop condition
}
 
/* LCD initialize */
void LCD_init(){
    __delay_ms(40); //40ms wait
    LCD_cmd(0x38);  //8bit,2line
    LCD_cmd(0x39);  //IS=1 : extention mode set
    LCD_cmd(0x14);  //Internal OSC Frequency
    LCD_cmd(0x70);  //Contrast set
    LCD_cmd(0x56);  //Power/ICON/Contrast Control
    LCD_cmd(0x6C);  //Follower control
    __delay_ms(200);//200ms wait
    LCD_cmd(0x38);  //IS=0 : extention mode cancel
    LCD_cmd(0x0C);  //Display ON
    LCD_cmd(0x01);  //Clear Display
    __delay_ms(1);  //wait more than 1.08ms
    __delay_us(80);
}
 
/* Return Home */
void LCD_home(){
    LCD_cmd(0x02);
    __delay_ms(1);
    __delay_us(80);
}
 
/* Locate cursor x,y */
void LCD_cursor(unsigned char x,unsigned char y){
    if (y == 0){
        LCD_cmd(LCD_LINE1+x);
    }
    if (y == 1){
        LCD_cmd(LCD_LINE2+x);
    }
}
 
/* write 1 charactor to LCD */
void putch(unsigned char ch){
    LCD_char(ch);
}
 
/* Si5351A関係関数*/
/* Send data to Si5351A register*/
void Si5351_write(unsigned char Reg , unsigned char Data)
{
    I2C_start();
    I2C_write(Si5351_ADDR);
    I2C_write(Reg);
    I2C_write(Data);
    I2C_stop();
}
/* Si5351A initialize */
void Si5351_init(){
    Si5351_write(OUTPUT_CTRL,0xFF); //Disable all output
    Si5351_write(CLK0_CTRL,0x80);   //CLOCK0 power down
    Si5351_write(CLK1_CTRL,0x80);   //CLOCK1 power down   
    Si5351_write(CLK2_CTRL,0x80);   //CLOCK2 power down   
    Si5351_write(XTAL_LC,0x80);     //Crystal Load Capasitance=8pF
    Si5351_write(PLL_RESET,0xA0); //Reset PLLA and PLLB
    Si5351_write(CLK0_CTRL,0b01001111); //CLOCK0 Power up 8mA
    Si5351_write(OUTPUT_CTRL,0xFE); //Enable CLOCK0
}
/* Send parameter to register */
void Parameter_write(unsigned char REG_ADDR,unsigned long Pa1,unsigned long Pa2,unsigned long Pa3)
{
    Si5351_write(REG_ADDR + 0,(Pa3 & 0x0000FF00) >> 8);
    Si5351_write(REG_ADDR + 1,(Pa3 & 0x000000FF));
    Si5351_write(REG_ADDR + 2,(Pa1 & 0x00030000) >> 16);
    Si5351_write(REG_ADDR + 3,(Pa1 & 0x0000FF00) >> 8);
    Si5351_write(REG_ADDR + 4,(Pa1 & 0x000000FF));
    Si5351_write(REG_ADDR + 5,((Pa3 & 0x000F0000) >> 12) | ((Pa2 & 0X000F0000) >> 16));
    Si5351_write(REG_ADDR + 6,(Pa2 & 0x0000FF00) >> 8);
    Si5351_write(REG_ADDR + 7,(Pa2 & 0x000000FF));
}
 
/* Set frequency */
void VFO_set(unsigned long frequency){
    /* Set PLLA */
    divider = 900000000 / frequency;
    divider >>= 1;  //dividerは整数かつ偶数
    divider <<= 1;
    PllFreq = divider * frequency;
    mult = PllFreq / XtalFreq;
    l = PllFreq % XtalFreq;
    f = l;
    f *= denom;
    f /= XtalFreq;
    num = f;
    P1 = (unsigned long)(128 * ((float)num /(float)denom));
    P1 = (unsigned long)(128 * (unsigned long)mult + P1 - 512);
    P2 = (unsigned long)(128 * ((float)num / (float)denom));
    P2 = (unsigned long)(128 * num -denom * P2);
    P3 = denom;
    Parameter_write(MSNA_ADDR,P1,P2,P3);
 
    /* Set MS0 */
    P1 = 128 * divider - 512;
    P2 = 0;
    P3 = 1;
    Parameter_write(MS0_ADDR,P1,P2,P3);
}
 
/* Display Frequency */
void Freq_disp(unsigned long frequency){
    LCD_home();
    printf("%8ld",frequency);
}
 
/* Display Step */
void Step_disp(unsigned int stp){
    LCD_cursor(4,1);
    switch(stp){
        case 1000000:
            printf("  1M");  
            break;
        case 100000:
            printf("100K");
            break;
        case 10000:
            printf(" 10K");
            break;
        default:
            printf("  1K");
            break;
    }
}
 
/* Change Step */
void Step_change(){
    __delay_ms(5);
    if(Step == 1000){
        Step = 1000000;
    }else{
    Step = Step/10;
    }
    Step_disp(Step);
    while(!STEP_SW){
    __delay_ms(5);
    }
}
 
/* Interrupt Service Routine */
void __interrupt isr(){
    IOCIF = 0;   //割り込みフラッグクリア
    IOCCF4 = 0;
    IOCCF5 = 0;
    __delay_ms(2);
    curDat = ECA + (ECB<<1);
    if (befDat != curDat){
        unsigned char d = ((befDat<<1)^curDat) & 3; //回転方向判定
        if(d < 2){
            count++;
        }else{
            count--;
        }
        befDat = curDat;
    }
    if(count >= 4){
        Freq += Step;
        count = 0;
    }else if(count <= -4){
        Freq -= Step;
        count = 0;
    }
}
 
/* Main program */
void main() {
    OSCCON = 0b01110000 ;     // 内部クロック8MHz ×4=32MHz
    ANSELA = 0b00000000 ;     // AN0-AN3を使わない
    ANSELC = 0b00000000 ;     // AN4-AN6を使わない
    TRISA  = 0b00000000 ;     // PORATは全て出力
    TRISC  = 0b00111011 ;     // RC0,RC1,RC3RC4,RC5は入力、RC2は出力
    PORTA  = 0b00000000 ;     // PORTAクリア
    PORTC  = 0b00000000 ;     // PORTCクリア
    /* I2Cピン設定 */
    RC0PPS = 0x10;   //RC3をSCLに割り当てる(出力)
    SSPDATPPS = 0x11;//SSPDATにRC4を割り当てる(入力)
    RC1PPS = 0x11;   //RC4をSDAに割り当てる(出力)
   
    /* ロータリーエンコーダ関係設定 */
    OPTION_REGbits.nWPUEN=0;  // ウィークプルアップ許可
    WPUC = 0b00111000;        // RC3,RC4,RC5をプルアップ   
    /* IOC割り込み設定 */
    IOCCN = 0b00110000;     //RC4,RC5立下り割り込み設定
    IOCCP = 0b00110000;     //RC4,RC5立ち上がり割り込み設定
    IOCIE = 1;              //IOC割り込み許可
    PEIE = 1;               //周辺割り込み許可
    GIE = 1;                //全割り込み許可
   
    I2C_init();
    LCD_init();
    Si5351_init();
       
    /* Roatry Encoder 初期値 */
    befDat = ECA + (ECB<<1);
   
    Freq_disp(Freq);
    VFO_set(Freq);
    Freq_old = Freq;
    LCD_cursor(0,1);
    printf("STEP");
    Step_disp(Step);
   
    while(1){
        if(STEP_SW == 0){
            Step_change();
        }
       
        if(Freq != Freq_old){
        VFO_set(Freq);
        Freq_disp(Freq);
        }
        Freq_old = Freq;
    }
}