2019年4月20日土曜日

si5351 oled 3BAND VFO

si5351用に作ったPCB使用の、3BAND VFO(7MHz、14MHz、18MHz)である。スケッチは、LZ2WSG, KN34PCベースだが、機能追加などもあり、殆ど原型を留めてない。 VFOの機能は、自動メモリー書込み、自動モード切替、Sメータ、送信時の周波数範囲外保護(但し、受信範囲制限なし)など。oledはI2Cの128x64を使ったが、128X32用も別スケッチを用意した。




128x32を実装した様子である。oled128x64との違いは、コールサイン表示有無である。この表示を見る限り、LZ2WSG, KN34PCと違いは、モード表示のみである。
   














回路図

回路図である。モード切替用に2本、バンド切り替え用に3本のI/O使用。













スケッチ

スケッチは、JA2GQP's Download siteのsi5351 VFOフォルダからダウンロード可能。ベースにしたスケッチは、BITX用のもので逆ヘテロダインになっている。今回作ったものは、アッパーヘテロダインで目標周波数+IF周波数である。

//////////////////////////////////////////////////////////////////////
//  si5351a oled(128x64) VFO program ver.1.0
//    Copyright(C)2019.JA2GQP.All rights reserved.
//
//                                                2019/4/19
//                                                  JA2GQP
//--------------------------------------------------------------------
//  Function
//    1.STEP(10k,1k,50)
//    2.Automatic memory
//    3.Protection Operation At The Time Of Transmission
//    4.3 bands
//    5.Automatic mode switching
//    6.S-Meter
//--------------------------------------------------------------------
// Reference sketch
//    24.12.2018, Arduino IDE v1.8.8, LZ2WSG, KN34PC
//    Si5351 VFO CLK0, 20m, OLED 0.91" 128x32 display, S-meter, RX/TX, 50Hz/1000Hz
//////////////////////////////////////////////////////////////////////

#include "src/Rotary.h"               // http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
#include "src/si5351.h"               // https://github.com/etherkit/Si5351Arduino, v2.1.0
#include "src/SSD1306AsciiAvrI2c.h"   // https://github.com/greiman/SSD1306Ascii
#include <EEPROM.h>


////////////////////////////////
// Set Device
////////////////////////////////
Rotary r = Rotary(2, 3);
Si5351 si5351(0x60);                 // Si5351 I2C address
SSD1306AsciiAvrI2c oled;

////////////////////////////////
// I/O Port
////////////////////////////////
const byte SW_BAND = A0;              // Band SW
const byte SW_STEP = A1;              // STEP SW
const byte SW_TX = A2;                // TX/RX
const byte AD_SM = A7;                // S-meter AD

////////////////////////////////
// EEPROM Memory Address
////////////////////////////////
const byte  Eep_Init = 0x00;          // Eep Init(1byte*1)
const byte  Eep_Band = 0x01;          // Band(1byte*1)
const byte  Eep_Freq = 0x10;          // Frequency(4byte*8)
const byte  Eep_Step = 0x30;          // STEP(4byte*8)

////////////////////////////////
// frquency data
////////////////////////////////
const unsigned long FRQ_TBL[3][4] = {
 // DEF     LOW(cw)  MID(ssb)  HI
  7050000 ,7000000 ,7045000 ,7200000,
  14090000,14000000,14100000,14350000,
  18110000,18068000,18110000,18168000
  };
//---- data offset -----
const byte DEF_F = 0;
const byte LOW_F = 1;
const byte MID_F = 2;
const byte HI_F  = 3;
//---- IF offset -------
const unsigned long RX_IF = 10700000;
const unsigned long TX_IF = 0;

////////////////////////////////
// Encorder STEP
////////////////////////////////
const int STEP_50 = 50;               // STEP 50Hz
const int STEP_1k = 1000;             //      1k
const int STEP_10k = 10000;           //      10k

////////////////////////////////
// etc
////////////////////////////////
const byte  Max_Band = 3;             // Max Channel(8ch)
const byte  Def_Band = 0;             // Default Band(0)
const byte  Int_End = 72;             // Initial end code
const char Call[9] = "JA2GQP";        // Display Call sign

////////////////////////////////
// Memory Assign
////////////////////////////////
unsigned long Vfo_Dat;                // VFO Data
unsigned long Enc_Step;               // STEP
char Enc_Dir = 0;                     // -1 DIR_CCW, 0 DIR_NONE, 1 DIR_CCW
unsigned int Val_Smeter = 0;
unsigned int Val_Smeterb = 1;

long Time_Passd;                      // int to hold the arduino miilis since startup
byte Flg_eepWT = 0;                   // EEP Write Flag

byte Byt_Band = 0;
byte Flg_Mode;
byte Flg_Over;
byte Flg_Vfo = 0;
byte Flg_Tx = 0;
byte Disp_over = 1;
byte Disp_freq = 1;
byte Disp_Tx = 0;

//----------  setup  ----------------------------------------------------

void setup() {
  pinMode(SW_STEP, INPUT_PULLUP);
  pinMode(SW_TX, INPUT_PULLUP);       
  pinMode(SW_BAND, INPUT_PULLUP);       
  pinMode(9, OUTPUT);                 // Band1       
  pinMode(10, OUTPUT);                //     2       
  pinMode(11, OUTPUT);                //     3         
  pinMode(12, OUTPUT);                // LOW=none, HOGH=CW
  pinMode(13, OUTPUT);                // LOW=USB,  HIGH=LSB       

  attachInterrupt(0, rotary_encoder, CHANGE);
  attachInterrupt(1, rotary_encoder, CHANGE);

  si5351.init(SI5351_CRYSTAL_LOAD_8PF,0,0);    // crystal 25.000 MHz, correction 0
  si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_4MA);//Drive lebel 4mA set
  oled.begin(&Adafruit128x64, 0x3C);

  if(EEPROM.read(Eep_Init) != Int_End){    // Eep initialaz
    delay(10);
    eep_init();
  }
  eep_rdata();

  mode_disp();
  step_disp();
  call_disp();
}

//----------  main  ----------------------------------------------------

void loop() {
  if(digitalRead(SW_TX) == HIGH){           // receive?
    Flg_Tx = 0;
    si5351.output_enable(SI5351_CLK0, 1);   // VFO enable

    if (Disp_freq == 1) {
      Disp_freq = 0;
      Vfo_Dat += Enc_Dir * Enc_Step;
      Enc_Dir = 0;
      band_check();                        // range check

      si5351_set_freq(Vfo_Dat + RX_IF);
      freq_disp(Vfo_Dat);                  // frequency display
      Flg_Vfo = 1;
      mode_disp();

      Time_Passd = millis();
      Flg_eepWT = 1;
    }

    if (digitalRead(SW_STEP) == LOW) {      // increment events
      enc_step();
      step_disp();
      while (digitalRead(SW_STEP) == LOW);
    }

    if (digitalRead(SW_BAND) == LOW) {
      band_set();
    }
    Disp_Tx = 1;
  }
  else{                                     // send
    Flg_Tx = 1;
    Disp_over = 1;

    if(Flg_Over == 0){                      // Within transmittable range?
      si5351.output_enable(SI5351_CLK0, 1); // VFO enable
      if(Flg_Vfo == 1){
        si5351_set_freq(Vfo_Dat + TX_IF);
        Flg_Vfo = 0;
      }
    }
    else{                                   // Out of range
      si5351.output_enable(SI5351_CLK0, 0); // VFO disable
      if(Disp_over == 1){
        fover_disp();                       // Display of over range
        Disp_over = 0;
        Disp_freq = 1;
      }
    }
    if(Disp_Tx == 1){
      tx_disp();
      Disp_Tx = 0;     
    }
    Disp_freq = 1;
  }

  Val_Smeter = analogRead(AD_SM);
  if ((abs(Val_Smeter - Val_Smeterb)) > 3){ // if needed draw S-meter
    sm_disp();
    Val_Smeterb = Val_Smeter;
  }

  if(Flg_eepWT == 1){                       // EEPROM auto Write
    if(Time_Passd+2000 < millis()){
      eep_wdata(Byt_Band);
      Flg_eepWT = 0;
    }
  } 
}

//----------  Band set  ------------------------------------------------

void band_set(){
  if(Byt_Band == Max_Band-1)
    Byt_Band = 0;
  else
    Byt_Band++;
  EEPROM.write(Eep_Band,Byt_Band);

  while(digitalRead(SW_BAND) == LOW);
  eep_rdata();
  band_check();                             // range check
  si5351_set_freq(Vfo_Dat + RX_IF);
  freq_disp(Vfo_Dat);                       // frequency display
  step_disp();
  mode_disp();
}

//----------  Write EEPROM 4byte  --------------------------------------

void eep_write4(long value,int address){
  address += 3;
  for(int i = 0;i < 4;i++){
    byte toSave = value & 0xFF;
    if(EEPROM.read(address) != toSave){
      EEPROM.write(address,toSave);
      }
    value = value >> 8;
    address--;
  }
}

//----------  Read EEPROM 4byte  ---------------------------------------

long eep_read4(int address){
  long value = 0;

  for(int i = 0;i < 4;i++){
    value = value | EEPROM.read(address);
    if( i < 3){
      value = value << 8;
      address++;
    }
  }
  return value;
}

//----------  EEPROM Dat Read  -----------------------------------------

void eep_rdata(){
  Byt_Band = EEPROM.read(Eep_Band);
  if(Byt_Band == 0){
    digitalWrite(9,HIGH);
    digitalWrite(10,LOW);
    digitalWrite(11,LOW);
  }
  else if(Byt_Band == 1){
    digitalWrite(9,LOW);
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
  }
  else if(Byt_Band == 2){
    digitalWrite(9,LOW);
    digitalWrite(10,LOW);
    digitalWrite(11,HIGH);
  }
  Vfo_Dat = eep_read4(Eep_Freq+Byt_Band*4);
  Enc_Step = eep_read4(Eep_Step+Byt_Band*4);
}

//----------  EEPROM Dat Write  ----------------------------------------

void eep_wdata(byte band){
  EEPROM.write(Eep_Band,band);
  eep_write4(Vfo_Dat,Eep_Freq+band*4);
  eep_write4(Enc_Step,Eep_Step+band*4);
}
 
//----------  EEPROM Initialization ------------------------------------

void eep_init(){
  int i;

  for (i=0;i<128;i++)                       // 0 clear(128byte)
    EEPROM.write(i, 0);

  for(i=0;i<Max_Band;i++){
    eep_write4(FRQ_TBL[i][DEF_F],Eep_Freq+i*4);
    eep_write4(STEP_1k,Eep_Step+i*4);       // Step(1kHz)
  }
  EEPROM.write(Eep_Band,Def_Band);          // Default Band(0)
  EEPROM.write(Eep_Init,Int_End);           // Init end set(73)
}

//----------  Rotaly Encorder  -----------------------------------------

void rotary_encoder() {                     // rotary encoder events
  uint8_t result = r.process();

  if(Flg_Tx == 0){
    if (result) {
      Disp_freq = 1;
      if (result == DIR_CW)
        Enc_Dir = 1;
      else
        Enc_Dir = -1;
    }
  }
}

//----------  si5351 PLL Output  ---------------------------------------

void si5351_set_freq(uint32_t frequency) {
  si5351.set_freq(frequency * SI5351_FREQ_MULT, SI5351_CLK0);
}

//----------  Encorede STEP  -------------------------------------------

void enc_step() {
  if (Enc_Step == STEP_50) {
    Enc_Step = STEP_1k;                     // 1000 Hz, round to XX.XXX.000
    float tmp = round (Vfo_Dat / (STEP_1k * 1.000));
    Vfo_Dat = (uint32_t)(tmp * STEP_1k);
    Disp_freq = 1;
  }
  else if(Enc_Step == STEP_1k)
    Enc_Step = STEP_10k;                    // 10k
  else 
    Enc_Step = STEP_50;                     // 50 Hz
}

//----------  STEP Display  --------------------------------------------

void step_disp() {
  oled.setCursor(109, 4);
  oled.setFont(labels);
  if (Enc_Step == STEP_50)
    oled.print("6");                        // 50Hz
  else if(Enc_Step == STEP_1k)
    oled.print("8");                        // 1k
  else
    oled.print("9");                        // 10k
}

//---------- Frequency renge over --------------------------------------

void fover_disp() {
  oled.setFont(lcdnums14x24);
  oled.setCursor(1, 0);
  oled.print("--.---.--");
}

//---------- callsign display ------------------------------------------

void call_disp() {
  oled.setFont(Arial_bold_14);
  oled.setCursor(56, 6);
  oled.print(Call);
}

//----------  Frequency Display  ---------------------------------------

void freq_disp(uint32_t sf_rx) {
  uint16_t fr;

  oled.setFont(lcdnums14x24);
  oled.setCursor(1, 0);
  fr = sf_rx / 1000000;
  if (fr < 10)
    oled.print(':');                        // ':' is changed to ' ' in lcdnums14x24.h
  oled.print(fr);
  oled.print('.');
  fr = (sf_rx % 1000000) / 1000;
  if (fr < 100)
    oled.print('0');
  if (fr < 10)
    oled.print('0');
  oled.print(fr);
  oled.print('.');
  fr = (sf_rx % 1000) / 10;
  if (fr < 10)
    oled.print('0');
  oled.print(fr);
}

//---------- TX display ------------------------------------------------

void tx_disp() {
  oled.setCursor(2, 4);
  oled.setFont(labels);
  oled.print("1");                          // "1" is "TX" in labels.h
}

//---------- Mode display ----------------------------------------------

void mode_disp() {
  oled.setCursor(2, 4);
  oled.setFont(labels);
  if (Flg_Mode == 0){
    oled.print("2");                        // "2" is "CW" in labels.h
    digitalWrite(12,HIGH);
    digitalWrite(13,LOW);
  }
  else if(Flg_Mode == 1){
    oled.print("3");                        // "3" is "LSB"
    digitalWrite(12,LOW);
    digitalWrite(13,HIGH);
  }
  else if(Flg_Mode == 2){
    oled.print("4");                        // "4" is "USB"
    digitalWrite(12,LOW);
    digitalWrite(13,LOW);
  }
}

//----------  S-Meter Display  -----------------------------------------

void sm_disp() {
  uint8_t a = 0;
  uint8_t m = 0;

  a = (Val_Smeter + 3) / 113;  // 1024 / 9 characters for S = 1,3,5,7,8,9,+10,+20,+30
  oled.setFont(pixels);
  oled.setCursor(25, 4);
  for (m = 0; m < a; m++)
    if (m < 6)
      oled.print('7');                      // '5' - hollow rectangle, 6px
    else
      oled.print('8');                      // '6' - filled rectangle, 6px
  for (m = a; m < 9; m++)
    oled.print('.');                        // '.' 1px
}

//----------  band chek  -----------------------------------------------

void band_check(){
  if((Vfo_Dat >= FRQ_TBL[Byt_Band][LOW_F])
      && (Vfo_Dat <= FRQ_TBL[Byt_Band][MID_F])){
    Flg_Mode = 0;                             // CW(0=CW, 1=LSB, 2=USB) 
    Flg_Over = 0;
  }
  else if((Vfo_Dat >= FRQ_TBL[Byt_Band][MID_F])
      && (Vfo_Dat <= FRQ_TBL[Byt_Band][HI_F])){
    if(Vfo_Dat >= 10000000)                   // greate than 10MHz                     
      Flg_Mode = 2;                           // USB(0=CW, 1=LSB, 2=USB)
    else
      Flg_Mode = 1;                           // LSB(0=CW, 1=LSB, 2=USB)
    Flg_Over = 0;
  }
  else
    Flg_Over = 1;
}      



    



5 件のコメント:

  1. hello i am using your code on my homemade transceiver and i need to enable the bfo on clk2 can you help me please ? thanks in advance

    返信削除
    返信
    1. I made this project a simple VFO. Uploaded the BFO added sketch to the download site. I think that it works without problems, but please correct and use if there is a problem. The version with BFO vs. BFO added is si5351_oled_BFO.zip in the si5351 VFO folder.

      削除
    2. このコメントは投稿者によって削除されました。

      削除
  2. I am a big fan of qrp i have finished my transceiver using your sketch and now i am trying to define a cw key of arduino that enables the clk1 to output signal for cw operation, and later i want to include a voltmeter in the sketch. Thanks again for your help 73!

    返信削除
    返信
    1. I added this BFO because I thought it was better to have it. The clk1 output is not used because it causes isolation degradation. clk1 will not be used in the future. There are no plans to add voltmeters, clocks, etc. If you want to meet your specifications, please develop your own.

      削除