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;
}      



    



35 件のコメント:

  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.

      削除
  3. hello dear friend!
    i m Mikele 9a3xz and i have one problem,when i compile sketch si5351_oled128x64 tell me:
    no matching function for call to 'SSD1306AsciiAvrI2c::setFont(void (&)())'

    maybe you know what is my problem ???
    Thanks in advance ,73 de 9a3xz,Mikele

    返信削除
    返信
    1. Hi Mikele.
       
      Download the file from si5351a VFO folder on JA2GQP's Download site. Unzip the zip file and copy the src folder to the same folder as the sketch. The library refers to the src folder where the sketches are stored in the Arduino IDE.

      削除
  4. THANKS for quick answer,everything now is OK.Only one question ,on outputs d9,d10 and d11 for band1,band2 and band3 can i conecting the transistors like 2n3904 or something like this ?
    thanks.

    返信削除
    返信
    1. Hi Mikele.
      There is no problem even if the level is converted by connecting a transistor to the port.

      削除
  5. yes ,thanks ,maybe also 74hc238 for drive relays for bpf s or lpf s.

    返信削除
    返信
    1. OK. As another method, it may be interesting to use PCF8574 as I2C I / F. You can find it on my blog as a VFO for AFPSN. Look if you are interested.

      削除
  6. thanks so much for information,i will try with simple 74hc238.all the best from 9a3xz,Mikele...your projects and ja2nkd is so beautiful,many thanks one again dear friend.

    返信削除
  7. Ok, i finished, all working good, only one question, for changing Usb/Lsb on pin SCK on arduino board, i must conecting +5V or ground? Thanks in advance.

    返信削除
    返信
    1. This VFO does not have a built-in BFO. SCK / D13 is used as LSB / USB switching output port. This signal is used to switch TRV LSB / USB BFO. Depending on the TRV's BFO circuit, a level converter or relay circuit may be required. If you don't need LSB / USB control, you can leave it unconnected.

      削除
  8. yes yes ,i think for bfo circuit,for switching d13 usb/lsb goes to ground or +5v ?

    返信削除
  9. sorry in my bad english, i wanted to say if i can change the usb to lsb externally with the switch but i don't think it can.73 de 9a3xz Mikele

    返信削除
    返信
    1. This VFO is made with the concept of the main controller. LSB / USB and CW mode change automatically at the set frequency. These modes send the switching signal for TRX. Not an input signal.
      If you switch the mode with a manual switch like many VFOs, you need to change the sketch. Again, this VFO cannot be switched manually. For VFO with BFO, si5351_oled_BFO.zip is available on the download site. The mode of the VFO with BFO is also automatically switched.

      削除
  10. many thanks for information.dds works good !73 de 9a3xz

    返信削除
    返信
    1. I intend to make it easy to operate with as few switches as possible. 73.

      削除
  11.  大変に参考になる記事を楽しく拝見させていただいております。

    OLEDを使ってみたくなり、このプログラムを見つけて試して見ました。

    コンパイル時、oled.setFont(pixels)とoled.setFont(labels)でエラーとなりました。
    調べてみると、ダウンロードしたZipファイルを解凍したFontのフォルダーに、この2つの
    フォントが入っていませんでした。
    このフォントはどちらかで別途に入手されたのでしょうか?
     以上 よろしくお願い致します。

    返信削除
  12. 先にフォントの件で質問した者です。
    解決致しました。
    ブログの記事をコピーして、個別にライブラリーをDlしていたのを
    ダウンロードサイトから改めて作り直したところ、上手く行きました。
    お騒がせして申し訳ありませんでした。

    返信削除
  13. nekochan59さん
    このoledライブラリは、メモリー節約のために専用フォントを作ってます。
    その為、エラーが起きたと思います。正常に動作した様なので、安心しました。

    返信削除
  14. このVFOを作りました。快適に動作しております。
    ちょっと気になったことなのですが、少し前の製作品から、Si5351の出力設定を
    4mAとして、最大にしていないのは何か理由があるのでしょうか?

    si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_4MA);
    //Drive lebel 4mA set

     初期の作品では、出力レベルの選択機能もあった様に思いましたが・・・。

     以上 よろしくお願いします。

    返信削除
  15. nekochan59さん

    コメント有難うございます。
    このVFOのsi5351ライブラリは、メモリに余裕が有ったのでJA2GQPオリジナルを使ってません。その為、少し前のVFOと異なってます。出力レベル4mAは特に意図した値では有りません。2mA、4mA、6mA、8mAの値を設定することで、出力レベルが変わります。
    (最小値2mAから最大値8mA)
    setupで設定している4mAを変更したいのであれば、数値のみ書替えればOKです。
    例えば最大値にする場合
    si5351.drive_strength(SI5351_CLK0、SI5351_DRIVE_8MA;
    となります。

    返信削除
  16. Do you have any library that runs arduino nano and runs a 1602 lcd screen instead of an oled screen? Than you.
    hdyasa45@hotmail.com

    返信削除
  17. Hi TA3IHD.
    Unfortunately, there are no plans to port it for the OLED version of 1602.
    For the 1602 library, I think you should use the 1602 I2C version.
    It can be easily ported by referring to the Attiny85 VFO PCB version.

    返信削除
  18. #include "src/Rotary.h" or #include "Rotary.h"
    #include "src/si5351.h" or #include "si5351.h"
    #include "src/SSD1306AsciiAvrI2c.h" or #include "SSD1306AsciiAvrI2c.h"
    maaf saiya bertanya pak

    返信削除
  19. Unknown

    The library is included in the zip file. The library is a src folder. If you unzip the zip file, you will not get an error without modifying the sketch.

    返信削除
  20. Não funciona o link , gostaria de montar um ft73 py7pq

    返信削除
  21. PY7PQ OM
    What is FT73?
    Is it a protocol for digital communication?
    I can't reply because I don't know the specifications of the FT73.

    返信削除
  22. Hello JA2GQP. i have problem with si5351_oled_BFO version because switching USB LSB - on pin D13 between 0V and 5V does not switch USB LSB. is there a bug in the software? I tested on two arduinos. the same effect. no reaction

    返信削除
  23. This VFO automatically switches modes.
    LSB below 10MHz. USB above 10MHz.
    As written in the sketch, D8 to D13 are set as output ports.
    (The mode does not change even if D13 is set to LOW or HIGH.)
    I think it's easy to switch LSB/USB on a specific frequency or manually.

    返信削除
  24. ah ok. i see. i have monoband vfo 7mz. how about cw mode?

    返信削除