2020年10月24日土曜日

si4735 oled改造

PU2CLRのAll in one OLED表示タイプを改造した。H/Wは公開済みのベースボードを使い、スケッチを書いた。実用性を高めるため、SSB時に使うBFO操作を見直した。基本的には、SSB時のSTEPと云った概念に仕上げた。AMのSTEPは、1K、5K、10kの3段階。SSBのSTEPは、50Hz、1kの2段階にした。メモリ機能は、周波数、BFO、ボリュームとし、モード、バンドは除いた。

回路図

公開済みのベースボード回路を一部変更した。
・BFOのSW8は、未使用。
・音質改善のため、AF AMP入力部を変更。

BFO

STEPをFMなし、AM3段階、SSB2段階とした。SSB時のBFO設定は、STEPと云った概念としたので操作上は存在しない。ただ、S/W上BFOが存在しているが、何も意識することはない筈だ。

スケッチ

JA2GQP's Download site2のsi4735フォルダから必要なファイルがダウンロード可能。

/////////////////////////////////////////////////////////////////////
//  si4735 DSP Radio program(PU2CLR all in one OLE based) Ver1,01
//
//                                              2020/10/22
//                                                JA2GQP        
//-------------------------------------------------------------------
//  Ver1.01 Eep initialaz(STEP SW)
/////////////////////////////////////////////////////////////////////

#include <SI4735.h>
#include "src/SSD1306AsciiAvrI2c.h"   // https://github.com/greiman/SSD1306Ascii
#include "src/Rotary.h"
#include <EEPROM.h>

#include "src/patch_init.h" // SSB patch for whole SSBRX initialization string

const uint16_t size_content = sizeof ssb_patch_content; // see ssb_patch_content in patch_full.h or patch_init.h

#define FM_BAND_TYPE 0
#define MW_BAND_TYPE 1
#define SW_BAND_TYPE 2
#define LW_BAND_TYPE 3

// OLED Diaplay constants
#define I2C_ADDRESS 0x3C
#define RST_PIN -1 // Define proper RST_PIN if required.

#define RESET_PIN 7

// Enconder PINs
#define ENCODER_PIN_A     2
#define ENCODER_PIN_B     3

// Buttons controllers
#define STEP_SWITCH       8       // STEP(1, 5 or 10 KHz)
#define BFO_SWITCH        9       // BFO(BFO or VFO)
#define VOL_DOWN          10      // Volume Down
#define VOL_UP            11      //        Up
#define BAND_BUTTON_DOWN  12      // Band Down
#define BAND_BUTTON_UP    14      //      Up
#define AGC_SWITCH        15      // AGC ON/OF
#define BANDWIDTH_BUTTON  16      // banddwith(1.2, 2.2, 3.0, 4.0, 0.5, 1.0 KHz)
#define MODE_SWITCH       17      // MODE (Am/LSB/USB)

#define MIN_ELAPSED_TIME 100
#define MIN_ELAPSED_RSSI_TIME 150

#define DEFAULT_VOLUME 40 // change it for your favorite sound volume

#define FM 0
#define LSB 1
#define USB 2
#define AM 3
#define LW 4

#define SSB 1
////////////////////////////////
//  default value
////////////////////////////////
//#define DEF_AM          1431          // Gifu radio
//#define DEF_FM          7630          // FM pipi
#define DEF_VOL           40            // volume
#define DEF_B_IDX         0             // Band Index

////////////////////////////////
//  etc
////////////////////////////////
#define SIG_TIME        200
#define Int_End         73            // EEPROM init end

////////////////////////////////
// EEPROM Memory Address
////////////////////////////////
#define Eep_Top        0x00          // Eep Init(1byte)
#define Eep_End        Eep_Top       // Eep Init(1byte)
#define Eep_VOL        0x02          // Volume
#define Eep_Mode       0x04          // Mode(1byte)
#define Eep_B_IDX      0x06          // Band index
#define Eep_Bfo        0x08          // bfo data
//#define Eep_FM          0x02          // FM frequency(2byte)
//#define Eep_AM          0x04          // MF frequency(2byte)
#define Eep_Freq       0x10          // Frequency data

const char *bandModeDesc[] = {"FM ", "LSB", "USB", "AM "};
uint8_t currentMode = FM;

bool bfoOn = false;
bool disableAgc = true;
bool ssbLoaded = false;
bool fmStereo = true;

int currentBFO = 0;
byte Flg_eepWT = 0;
long elapsedRSSI = millis();
long elapsedButton = millis();
long Time_Passd;                      // int to hold the arduino miilis since startup

// Encoder control variables
volatile int encoderCount = 0;

// Some variables to check the SI4735 status
uint16_t currentFrequency;
uint16_t previousFrequency;
uint8_t currentStep = 1;
//uint8_t currentBFOStep = 25;
uint8_t currentBFOStep = 100;

uint8_t bwIdxSSB = 2;
const char *bandwitdthSSB[] = {"1.2", "2.2", "3.0", "4.0", "0.5", "1.0"};

uint8_t bwIdxAM = 0;
const char *bandwitdthAM[] = {"6", "4", "3", "2", "1", "1.8", "2.5"};

/*
   Band data structure
*/
typedef struct
{
  uint8_t bandType;     // Band type (FM, MW or SW)
  uint16_t minimumFreq; // Minimum frequency of the band
  uint16_t maximumFreq; // maximum frequency of the band
  uint16_t currentFreq; // Default frequency or current frequency
  uint16_t currentStep; // Defeult step (increment and decrement)
} Band;

////////////////////////////////
//   Band table
////////////////////////////////
Band band[] = {
  {FM_BAND_TYPE, 7600, 9500, 7630, 10},
//  {LW_BAND_TYPE, 100, 510, 300, 1},
  {MW_BAND_TYPE, 522, 1602, 1431, 9},
  {SW_BAND_TYPE, 1800, 3500, 1900, 1}, // 160 meters
  {SW_BAND_TYPE, 3500, 4500, 3535, 1}, // 80 meters
//  {SW_BAND_TYPE, 4500, 5500, 4850, 5},
//  {SW_BAND_TYPE, 5600, 6300, 6000, 5},
  {SW_BAND_TYPE, 6800, 7800, 7100, 1}, // 40 meters
//  {SW_BAND_TYPE, 9200, 10000, 9600, 5},
//  {SW_BAND_TYPE, 10000, 11000, 10100, 1}, // 30 meters
  {SW_BAND_TYPE, 10000, 11000, 10700, 1}, // 30 meters
//  {SW_BAND_TYPE, 11200, 12500, 11940, 5},
//  {SW_BAND_TYPE, 13400, 13900, 13600, 5},
  {SW_BAND_TYPE, 14000, 14500, 14200, 1}, // 20 meters
//  {SW_BAND_TYPE, 15000, 15900, 15300, 5},
//  {SW_BAND_TYPE, 17200, 17900, 17600, 5},
  {SW_BAND_TYPE, 18000, 18300, 18100, 1},  // 17 meters
  {SW_BAND_TYPE, 21000, 21900, 21200, 1},  // 15 mters
  {SW_BAND_TYPE, 24890, 26200, 24940, 1},  // 12 meters
  {SW_BAND_TYPE, 26200, 27900, 27500, 1},  // CB band (11 meters)
  {SW_BAND_TYPE, 28000, 30000, 28400, 1},
}; 

const int lastBand = (sizeof band / sizeof(Band)) - 1;
byte bandIdx = 0;

uint8_t rssi = 0;
uint8_t stereo = 1;
uint8_t volume = 0;

////////////////////////////////
// Devices class declarations
////////////////////////////////
Rotary encoder = Rotary(ENCODER_PIN_A, ENCODER_PIN_B);
SI4735 si4735;
SSD1306AsciiAvrI2c oled;

//----------  Setup  ---------------------------------

void setup()
{
  byte ee;
  byte ssbm = 0;
  
  // Encoder pins
  pinMode(ENCODER_PIN_A, INPUT_PULLUP);
  pinMode(ENCODER_PIN_B, INPUT_PULLUP);

  pinMode(BANDWIDTH_BUTTON, INPUT_PULLUP);
  pinMode(BAND_BUTTON_UP, INPUT_PULLUP);
  pinMode(BAND_BUTTON_DOWN, INPUT_PULLUP);
  pinMode(VOL_UP, INPUT_PULLUP);
  pinMode(VOL_DOWN, INPUT_PULLUP);
  pinMode(BFO_SWITCH, INPUT_PULLUP);
  pinMode(AGC_SWITCH, INPUT_PULLUP);
  pinMode(STEP_SWITCH, INPUT_PULLUP);
  pinMode(MODE_SWITCH, INPUT_PULLUP);

  oled.begin(&Adafruit128x64, 0x3C);

  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), rotaryEncoder, CHANGE); // Encoder interrupt
  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), rotaryEncoder, CHANGE);

  si4735.getDeviceI2CAddress(RESET_PIN); // Looks for the I2C buss address and set it.  Returns 0 if error
  si4735.setup(RESET_PIN, FM_BAND_TYPE);
    
  delay(300);  

//  EEPROM.get(Eep_End, ee);
  if(digitalRead(STEP_SWITCH) == LOW){                  // Eep initialaz
    oled.setFont(font5x7);
    oled.setCursor(18, 4);
    oled.print("Initialization");
    while(digitalRead(STEP_SWITCH) == LOW)
      ;
    eep_init();
  }
  eep_rdata();
  
  useBand();  // Set up the radio for the current band (see index table variable bandIdx )
  previousFrequency = currentFrequency;

  si4735.setVolume(volume);
  showStatus();
}

//----------  Encorder procedure(INT)  ---------------

void rotaryEncoder()
{ // rotary encoder events
  uint8_t encoderStatus = encoder.process();
  if (encoderStatus)
  {
    if (encoderStatus == DIR_CW)
    {
      encoderCount = 1;
    }
    else
    {
      encoderCount = -1;
    }
    Time_Passd = millis();
    Flg_eepWT = 1;
  }
}

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

void eep_rdata(){
  EEPROM.get(Eep_VOL, volume);
  EEPROM.get(Eep_B_IDX,bandIdx);
  EEPROM.get(Eep_Mode,currentMode);
  EEPROM.get(Eep_Freq, currentFrequency);
  band[bandIdx].currentFreq = currentFrequency;
  EEPROM.get(Eep_Bfo, currentBFO);
  si4735.setSSBBfo(currentBFO);
}

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

void eep_wdata(){
  EEPROM.put(Eep_Mode, currentMode);
  EEPROM.put(Eep_B_IDX, bandIdx);
  EEPROM.put(Eep_Bfo, currentBFO);
  EEPROM.put(Eep_Freq, currentFrequency);
}  

//----------  EEPROM Initialization ----------------------------------

void eep_init(){
  for(int i=0;i<64;i++)
    EEPROM.put(Eep_Top + i, 0);

  EEPROM.put(Eep_Mode, FM);
  EEPROM.put(Eep_B_IDX, 0);
  EEPROM.put(Eep_VOL, 40);
  EEPROM.put(Eep_Bfo, 0);
  EEPROM.put(Eep_Freq, 7630);
  EEPROM.put(Eep_End, Int_End);
}

//----------  Volume lebel display  ------------------

void showVolume()
{
  static byte v_old=0;
  byte v_new;

  v_new = si4735.getCurrentVolume();
  
  if(v_old != v_new)
  {
    oled.setFont(font5x7);
    oled.setCursor(96, 6);
    oled.print("VOL  ");
    oled.setCursor(115, 6);
    oled.print(v_new);
    v_old = v_new;
  }
}

//----------  Frequency display  ---------------------

void showFrequency()
{
  unsigned long fr;
  unsigned long sf_rx;
 
  if(currentMode == FM)                                      // FM
    sf_rx = (float)currentFrequency * 10000.0;
  else if(currentMode == AM)                                 // AM
    sf_rx = (float)currentFrequency * 1000.0;
  else if((currentMode == LSB) || (currentMode == USB)){      // SSB
    sf_rx = ((float)currentFrequency * 1000.0) + currentBFO;
  }
  else
    sf_rx = (float)currentFrequency * 1000.0;

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

//----------  Status display  ------------------------

void showStatus()
{
  showFrequency();
  oled.setFont(font5x7);

  if(!bfoOn){
      oled.setCursor(109, 4);
      oled.print("   ");
      if ( currentMode != FM){
        oled.setCursor(109, 4);
        oled.print(currentStep);
        oled.print("k");
      }
  }
  else if(bfoOn){
      oled.setCursor(109, 4);
      oled.print("   ");
      oled.setCursor(109, 4);
      oled.print(currentBFOStep);
  }

  if (currentMode == LSB || currentMode == USB)
  {
      oled.setCursor(2, 6);
      oled.print("BW   ");
      oled.setCursor(14, 6);
      oled.print(String(bandwitdthSSB[bwIdxSSB]));
  }
  else if (currentMode == AM)
  {
      oled.setCursor(2, 6);
      oled.print("BW   ");
      oled.setCursor(14, 6);
      oled.print(String(bandwitdthAM[bwIdxAM]));
  }

  else if ( currentMode == FM) {
    oled.setCursor(2, 6);
    oled.print((si4735.getCurrentPilot()) ? "STE  " : "MON  ");
  }

  // Show AGC Information
  si4735.getAutomaticGainControl();
  oled.setCursor(50, 6);
  oled.print((si4735.isAgcEnabled()) ? "AGCon " : "AGCoff");

  showRSSI();
  showVolume();
}

//----------  RSSI(signal lebel)----------------------

void showRSSI(){
  char a = 0;
  unsigned char m = 0;
  float rssi_new;
  static float rssi_old = 100.0;
  static unsigned char mode_old = 255;
  unsigned char mode_new;
  String  bandMode;
         
/////////////////////////
//  MODE display
/////////////////////////
  if (currentFrequency < 520 )
    bandMode = "LW ";
  else
    bandMode = bandModeDesc[currentMode];

  oled.setFont(font5x7);
  mode_new = currentMode;
  if(mode_new != mode_old){
    oled.setCursor(2, 4);
    oled.print(bandMode);
    mode_old = mode_new;
  }

/////////////////////////
//  s-meter display
/////////////////////////
  rssi_new = si4735.getCurrentRSSI();
  if(rssi_new != rssi_old){
    a = (rssi_new - 15) / 3.0;           // 9 characters for S = 1,3,5,7,8,9,+10,+20,+30
    if(a < 0)
      a = 0;
    else if(a > 9)
      a = 9;
    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
    rssi_old = rssi_new;
  }
}

//----------  BAND up  -------------------------------

void bandUp()
{
  // save the current frequency for the band
  band[bandIdx].currentFreq = currentFrequency;
  band[bandIdx].currentStep = currentStep;

  if (bandIdx < lastBand)
  {
    bandIdx++;
  }
  else
  {
    bandIdx = 0;
  }
  useBand();
}

//----------  BAND down  -----------------------------

void bandDown()
{
  // save the current frequency for the band
  band[bandIdx].currentFreq = currentFrequency;
  band[bandIdx].currentStep = currentStep;
  if (bandIdx > 0)
  {
    bandIdx--;
  }
  else
  {
    bandIdx = lastBand;
  }
  useBand();
}

//----------  LOAD SSB  ------------------------------

void loadSSB()
{
  si4735.reset();
  si4735.queryLibraryId(); // Is it really necessary here?  Just powerDown() maigh work!
  si4735.patchPowerUp();
  delay(50);
  // si4735.setI2CFastMode(); // Recommended
  si4735.setI2CFastModeCustom(500000); // It is a test and may crash.
  si4735.downloadPatch(ssb_patch_content, size_content);
  si4735.setI2CStandardMode(); // goes back to default (100KHz)

  // delay(50);
  // Parameters
  // AUDIOBW - SSB Audio bandwidth; 0 = 1.2KHz (default); 1=2.2KHz; 2=3KHz; 3=4KHz; 4=500Hz; 5=1KHz;
  // SBCUTFLT SSB - side band cutoff filter for band passand low pass filter ( 0 or 1)
  // AVC_DIVIDER  - set 0 for SSB mode; set 3 for SYNC mode.
  // AVCEN - SSB Automatic Volume Control (AVC) enable; 0=disable; 1=enable (default).
  // SMUTESEL - SSB Soft-mute Based on RSSI or SNR (0 or 1).
  // DSP_AFCDIS - DSP AFC Disable or enable; 0=SYNC MODE, AFC enable; 1=SSB MODE, AFC disable.
  si4735.setSSBConfig(bwIdxSSB, 1, 0, 1, 0, 1);
  delay(25);
  ssbLoaded = true;
}


//----------  Use BAND  ------------------------------

void useBand()
{
  if (band[bandIdx].bandType == FM_BAND_TYPE)
  {
    currentMode = FM;
    si4735.setTuneFrequencyAntennaCapacitor(0);
    si4735.setFM(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep);
    bfoOn = ssbLoaded = false;

  }
  else
  {
    if (band[bandIdx].bandType == MW_BAND_TYPE || band[bandIdx].bandType == LW_BAND_TYPE)
      si4735.setTuneFrequencyAntennaCapacitor(0);
    else
      si4735.setTuneFrequencyAntennaCapacitor(1);

    if (ssbLoaded)
    {
      si4735.setSSB(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep, currentMode);
      si4735.setSSBAutomaticVolumeControl(1);
      si4735.setSsbSoftMuteMaxAttenuation(0); // Disable Soft Mute for SSB
    }
    else
    {
      currentMode = AM;
      si4735.setAM(band[bandIdx].minimumFreq, band[bandIdx].maximumFreq, band[bandIdx].currentFreq, band[bandIdx].currentStep);
      si4735.setAutomaticGainControl(1, 0);
      si4735.setAmSoftMuteMaxAttenuation(0); // // Disable Soft Mute for AM
      bfoOn = false;
    }

  }
  delay(100);
  currentFrequency = band[bandIdx].currentFreq;
  currentStep = band[bandIdx].currentStep;
  showStatus();
}

//-------------------- main loop ----------------------------------------------

void loop()
{
  char bfo_disp=0;
  // Check if the encoder has moved.
  if (encoderCount != 0)
  {
    if (bfoOn)
    {
      currentBFO = (encoderCount == 1) ? (currentBFO + currentBFOStep) : (currentBFO - currentBFOStep);
      si4735.setSSBBfo(currentBFO);
        if(currentBFO >= 1000){
          currentBFO = currentBFO - 1000;
          si4735.frequencyUp();
          currentFrequency = si4735.getFrequency();
        }
        else if(currentBFO < 0){
          currentBFO = currentBFO + 1000;
          si4735.frequencyDown();
          currentFrequency = si4735.getFrequency();
        }
      showFrequency();
    }
    else
    {
      if (encoderCount == 1)
        si4735.frequencyUp();
      else
        si4735.frequencyDown();

      // Show the current frequency only if it has changed
      currentFrequency = si4735.getFrequency();
    }
    encoderCount = 0;
  }

/////////////////////////
//  BANDWIDTH switch
/////////////////////////

    if (digitalRead(BANDWIDTH_BUTTON) == LOW)
    {
      if (currentMode == LSB || currentMode == USB)
      {
        bwIdxSSB++;
        if (bwIdxSSB > 5)
          bwIdxSSB = 0;
        si4735.setSSBAudioBandwidth(bwIdxSSB);
        // If audio bandwidth selected is about 2 kHz or below, it is recommended to set Sideband Cutoff Filter to 0.
        if (bwIdxSSB == 0 || bwIdxSSB == 4 || bwIdxSSB == 5)
          si4735.setSBBSidebandCutoffFilter(0);
        else
          si4735.setSBBSidebandCutoffFilter(1);
      }
      else if (currentMode == AM)
      {
        bwIdxAM++;
        if (bwIdxAM > 6)
          bwIdxAM = 0;
        si4735.setBandwidth(bwIdxAM, 1);
      }
      showStatus();
      while(digitalRead(BANDWIDTH_BUTTON) == LOW)
        ;
    }

/////////////////////////
//  BAND UP switch
/////////////////////////

    else if (digitalRead(BAND_BUTTON_UP) == LOW){
      bandUp();
      while(digitalRead(BAND_BUTTON_UP) == LOW)
        ;
      eep_wdata();
    }

/////////////////////////
//  BAND DOWN switch
/////////////////////////

    else if (digitalRead(BAND_BUTTON_DOWN) == LOW){
      bandDown();
      while(digitalRead(BAND_BUTTON_DOWN) == LOW)
        ;
      eep_wdata();
    }

/////////////////////////
//  VOLUME DOWN switch
/////////////////////////

    else if (digitalRead(VOL_UP) == LOW)
    {
      si4735.volumeUp();
      volume = si4735.getVolume();
      showVolume();
      while(digitalRead(VOL_UP) == LOW)
        ;
      EEPROM.put(Eep_VOL, volume);
    }

/////////////////////////
//  VOLUME UP switch
/////////////////////////

    else if (digitalRead(VOL_DOWN) == LOW)
    {
      si4735.volumeDown();
      volume = si4735.getVolume();
      showVolume();
      while(digitalRead(VOL_DOWN) == LOW)
        ;
      EEPROM.put(Eep_VOL, volume);
    }

/////////////////////////
//  AGC switch
/////////////////////////

    else if (digitalRead(AGC_SWITCH) == LOW)
    {
      disableAgc = !disableAgc;
      // siwtch on/off ACG; AGC Index = 0. It means Minimum attenuation (max gain)
      si4735.setAutomaticGainControl(disableAgc, 1);
      showStatus();
      while(digitalRead(AGC_SWITCH) == LOW)
        ;
    }

/////////////////////////
//  STEP switch
/////////////////////////
    
    else if (digitalRead(STEP_SWITCH) == LOW)
    {
      if ( currentMode == FM) {
        fmStereo = !fmStereo;
        if ( fmStereo )
          si4735.setFmStereoOn();
        else
          si4735.setFmStereoOff(); // It is not working so far.
      } else {

        if ((bfoOn) && (currentBFOStep == 50)){   // SSM mode
          currentStep = 1;
          bfoOn = false;
          si4735.setFrequencyStep(currentStep);
          band[bandIdx].currentStep = currentStep;
          showStatus();
        }

        else{                                     // AM mode
          if (currentStep == 1)
            if(currentMode == LSB || currentMode == USB){
              bfoOn = !bfoOn;
              currentBFOStep = 50;
            }
            else  
              currentStep = 5;
          else if (currentStep == 5)
            currentStep = 10;
          else
            currentStep = 1;

          si4735.setFrequencyStep(currentStep);
          band[bandIdx].currentStep = currentStep;
          showStatus();
        }
      }
      while(digitalRead(STEP_SWITCH) == LOW)
        ;
    }

/////////////////////////
//  MODE switch
/////////////////////////

    else if (digitalRead(MODE_SWITCH) == LOW)
    {
      if (currentMode != FM ) {
        if (currentMode == AM)
        {
          // If you were in AM mode, it is necessary to load SSB patch (avery time)
         loadSSB();
         currentMode = LSB;
        }
        else if (currentMode == LSB)
        {
          currentMode = USB;
        }
        else if (currentMode == USB)
        {
          currentMode = AM;
          ssbLoaded = false;
          bfoOn = false;
        }
        // Nothing to do if you are in FM mode
        band[bandIdx].currentFreq = currentFrequency;
        band[bandIdx].currentStep = currentStep;
        useBand();
      }
      while(digitalRead(MODE_SWITCH) == LOW)
        ;
      EEPROM.put(Eep_Mode, currentMode);
    }

  // Show the current frequency only if it has changed
  if (currentFrequency != previousFrequency)
  {
    previousFrequency = currentFrequency;
    showFrequency();
  }

  // Show RSSI status only if this condition has changed
  if ((millis() - elapsedRSSI) > MIN_ELAPSED_RSSI_TIME * 9)
  {
    si4735.getCurrentReceivedSignalQuality();
    int aux = si4735.getCurrentRSSI();
    if (rssi != aux)
    {
      rssi = aux;
      showRSSI();
    }
    elapsedRSSI = millis();
  }

  delay(10);

  if((Time_Passd+2000 < millis()) && Flg_eepWT == 1){
    eep_wdata();
    Flg_eepWT = 0;
  }
}


EEPROM初期化

STEP SWを押しながら電源onするとEEPROMが初期化される。

使用感

実際にSSBを聞いてみて、BFOがバンドが替わると、ズレる。また、保存したBFOも再電源投入後ズレる。(既知のバグと思っている。)今回、バンドをメモリ保存して無いが無くても不自由感は無かった。




     



2020年6月18日木曜日

esp8266 VFO

回路全体
esp8266(WeMos mini D1の模造品)を使ったモノバンドVFOである。画面は、SPI 1.3" IPS240x240と組み合わせた。ともに、Aliexpressで格安に入手する事が出来る。このVFOの特徴は、アナログタイプのSメータとデジタル風表示である。
       

画面表示例
esp8266にDIOが少ない為、モノバンドとした。メモリーが十分有るので、PCF8474などをI2Cに接続する事で、マルチバンド化も容易であろう。
s-meterは、イメージデータをimage2cppでモノクロデータにして加工した。カラー表示を行うので有れば、同様なツールとしてlcd-image-converterがある。これらのツールを使えば、簡単にデータ化できる。
周波数表示に使ったフォントは、デジタル風ものを選んだが、等幅フォントでない。この為、1文字毎にロケーションを指定している。
VFOの機能は、オートモード、RIT、自動メモリー保存、バンド範囲チェックなど 際立った機能は無いが実用的に仕上がった。




回路図


DIOにTX/RXまで使っており、拡張性が無いように見えるかも知れないが、必要な機能が達成できれば、それで良い。








mini D1設定

CPUパラメータ設定


esp8266は曲者

開発にArduino IDEを使ったが、esp8266(WeMos mini D1)は個性的な印象だ。DIOすら制限があり、この割付がベストだと思っている。esp8266には、産業用コントローラなどで使っているウォッチドグタイマー装備されおり、トラブル時の対策を悩ませる一員となった。


スケッチ

必要なファイルは、JA2GQP's Download siteのesp8266フォルダからダウンロード可能。
//////////////////////////////////////////////////////////////////////
//  si5351a IPS(240x240) VFO program ver.1.0
//    Copyright(C)2020.JA2GQP.All rights reserved.
//
//                                                2020/6/18
//                                                  JA2GQP
//--------------------------------------------------------------------
//  Function
//    1.STEP(1k,100,10)
//    2.Automatic memory
//    3.Protection Operation At The Time Of Transmission
//    4.Automatic mode switching
//    5.S-Meter/Power meter
//////////////////////////////////////////////////////////////////////

#include <Adafruit_GFX.h> 
#include <Fonts/Org_01.h>
#include <EEPROM.h>
#include "src/Arduino_ST7789.h"
#include "src/Rotary.h"             
#include "src/si5351.h"             
#include <wire.h>
#include "src/smeter.h"
#include <Ticker.h>

//---------- define value ----------------------------------------------

#define TFT_DC    D8                // DC
#define TFT_RST   D6                // RES
#define TFT_MOSI  D7                // SDA
#define TFT_SCLK  D5                // SCL
#define SW_TX     D0                // D0
#define SW_RIT    RX                // RX
#define SW_STEP   TX                // TX

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

////////////////////////////////
// frquency data
////////////////////////////////
const unsigned long FRQ_TBL[4] = {
 // DEF     LOW(cw)  MID(ssb)  HI
  7050000 ,7000000 ,7045000 ,7200000
  };

//---- data offset ------------
#define DEF_F  0
#define LOW_F  1
#define MID_F  2
#define HI_F   3

//---- IF offset --------------
#define IF    10700000
#define LSB   10701500
#define USB   10698500
#define CW    10700700

//---- ETC frequency mode -----
#define FT8_1 7041000
#define FT8_2 7074000
#define AM    7195000

////////////////////////////////
// etc
////////////////////////////////
#define Int_End   73                // Initial end code
#define LW_RIT    -5000             // RIT Lower Limit
#define HI_RIT    5000              //     Upper Limit

////////////////////////////////
// Set Device
////////////////////////////////
Rotary r = Rotary(D3,D4);
void ICACHE_RAM_ATTR enc_proc();
Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST);
Si5351 si5351(0x60);
Ticker ticker1;

//---------- Variable setting ------------------------------------------

unsigned long Vfo_Dat = 0;          // VFO data
unsigned long Bfo_Dat = 0;          // BFO data
unsigned int  Enc_Step = 1000;      // STEP

int  Rit_Dat = 0;                   // RIT Data
long Lng_Wk = 0;                    // Long Work
unsigned long Time_Passd = 0;       // Time Pass(ms)

int  Int_Wk = 0;                    // Integer Work
byte Flg_Tx = 0;                    // TX Flag
byte Flg_Mode = 0;                  // Mode Flag
byte Flg_Over = 0;                  // Over Flag
byte Flg_Rit = 0;                   // RIT Flag
byte Flg_enc = 0;                   // EEP Write Flag
byte Disp_Freq = 1;
byte Disp_Rit = 1;

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

void setup(){
  pinMode(SW_TX,INPUT);       
  pinMode(SW_STEP,INPUT);       
  pinMode(SW_RIT,INPUT);       

  EEPROM.begin(128);

  r.begin(0);
  attachInterrupt(digitalPinToInterrupt(D3), enc_proc, CHANGE);
  attachInterrupt(digitalPinToInterrupt(D4), enc_proc, CHANGE);
  ticker1.attach_ms(2000, save_time);

  tft.init(240, 240);   // initialize a ST7789 chip, 240x240 pixels
  tft.setFont(&Org_01);
  tft.fillScreen(BLACK);
  delay(500);

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_4MA);//Drive lebel 4mA set
  si5351.drive_strength(SI5351_CLK2,SI5351_DRIVE_4MA);//Drive lebel 4mA set

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

  si5351_freq(Vfo_Dat);

  band_check();
  mode_disp();
  step_disp();
  dot_disp();
}

//----------- main loop ------------------------------------------------

void loop(){
  meter_disp(analogRead(A0));                 // s-meter,Power meter   

///////////////////
//  Receive
///////////////////

  if(digitalRead(SW_TX) == LOW){          // RX?(TX is HIGH)
    if(digitalRead(SW_STEP) == LOW){      // STEP?
      step_proc();
    }
    if(digitalRead(SW_RIT) == LOW){       // RIT?
      rit_disp(Rit_Dat);
      rit_proc();
    }

    if(Flg_Mode == 3)
      si5351.output_enable(SI5351_CLK2, 0); // VFO disable
    else
      si5351.output_enable(SI5351_CLK2, 1); // VFO enable
    si5351_bfo(Bfo_Dat);

    si5351.output_enable(SI5351_CLK0, 1);   // VFO enable
    freq_disp(Vfo_Dat); 

    if(Flg_Rit == 1)
      si5351_freq(Vfo_Dat+IF+Rit_Dat);      // Frequency(RIT) data out
    else
      si5351_freq(Vfo_Dat+IF);

    Flg_Tx = 0;
    band_check();
    mode_disp();
    yield();
  }

///////////////////
//  Transmit
///////////////////

  else{                                     // TX
    if(Flg_Over == 0){
      si5351.output_enable(SI5351_CLK0, 1); // VFO enable
    }
    else{
      si5351.output_enable(SI5351_CLK0, 0); // VFO disable
    }
    si5351_freq(Vfo_Dat+IF);                // Frequency data out
    Flg_Tx = 1;
    yield();
  }

///////////////////
//  Common
///////////////////
 
  tx_disp();

  if(Flg_Rit == 1){
    if(Disp_Rit == 1){
      rit_disp(Rit_Dat);
      Disp_Rit = 0;
    }
  }
}

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

void save_time(){
noInterrupts();
  if(Flg_enc == 1){
    eep_wdata();
    Flg_enc = 0;
  }
interrupts();
}

//----------  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);
      EEPROM.commit();
    }
    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(){
  Vfo_Dat = eep_read4(Eep_Freq);
  Enc_Step = eep_read4(Eep_Step);
}

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

void eep_wdata(){
  eep_write4(Vfo_Dat,Eep_Freq);
  eep_write4(Enc_Step,Eep_Step);
}
 
//----------  EEPROM Initialization ------------------------------------

void eep_init(){
  int i;

  for (i=0;i<128;i++){                       // 0 clear(128byte)
    EEPROM.write(i, 0);
    EEPROM.commit();
  }
  eep_write4(FRQ_TBL[DEF_F],Eep_Freq);
  eep_write4(1000,Eep_Step);                // Step(1kHz)
  EEPROM.write(Eep_Init,Int_End);           // Init end set(73)
  EEPROM.commit();
  }

//---------- over display ------------------------------------------------

void over_disp() {
  static byte Flg_Overb = 0;

  if((Flg_Over == 1) && (Flg_Overb == 0)){
    tft.fillRect(0,208,240,32,BLACK);
    tft.setTextSize(3);
    tft.setCursor(0, 232);
    tft.setTextColor(MAGENTA);
    tft.print("OUT OF RANGE");
    Flg_Overb = Flg_Over;
  }
  if((Flg_Overb == 1) && (Flg_Over == 0)){
    tft.fillRect(0,208,240,32,BLACK);
    Flg_Overb = Flg_Over; 
  }
}

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

void tx_disp() {
  static byte Flg_Txb = 0;

  if((Flg_Tx == 1) && (Flg_Txb == 0)){
    tft.fillRect(0,208,240,32,BLACK);
    tft.setTextSize(3);
    tft.setCursor(0, 232);

    if(Flg_Over == 1){
      tft.setTextColor(MAGENTA);
      tft.print("OUT OF RANGE");
    }
    else{
      tft.setTextColor(YELLOW);
      tft.print("SENDING");
    }
 
    Flg_Txb = Flg_Tx;
  }

  if((Flg_Txb == 1) && (Flg_Tx == 0)){
    tft.fillRect(0,208,240,32,BLACK);
    if(Flg_Rit == 1)
      rit_disp(Rit_Dat);
    Flg_Txb = Flg_Tx; 
  }
}

//---------- Frequency display -----------------------------------------

void freq_disp(long freq){
  static char buf[10]="";
  static char bufb[10]="";

  long_asc(buf,freq);
  tft.setFont(&Org_01);
  tft.setTextSize(6);
  for(int i=0;i<8;i++){
    if(buf[i]!=bufb[i]){
      switch(i){
        case 0:
          tft.fillRect(132+72,155,35,46,BLACK);
          tft.setCursor(135+72, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 1:       
          tft.fillRect(132+36,155,35,46,BLACK);
          tft.setCursor(135+36, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 2:
          tft.fillRect(132,155,35,46,BLACK);
          tft.setCursor(135, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 3:
          tft.fillRect(0+72,155,35,46,BLACK);
          tft.setCursor(0+72, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 4:
          tft.fillRect(0+36,155,35,46,BLACK);
          tft.setCursor(0+36, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 5:
          tft.fillRect(0,155,35,46,BLACK);
          tft.setCursor(0, 195);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 6:
          tft.fillRect(0+36,110,35,46,BLACK);
          tft.setCursor(0+36, 150);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
        case 7:
          tft.fillRect(0,110,35,46,BLACK);
          tft.setCursor(0, 150);
          tft.setTextColor(GREEN);
          tft.print(buf[i]);
          bufb[i] = buf[i];
          break;
      }
    }
  }
}

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

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

  if((Vfo_Dat == FT8_1) || (Vfo_Dat == FT8_2)){
    Flg_Mode = 2;
    Bfo_Dat = USB;
    Flg_Over = 0;
  }

  if(Vfo_Dat == AM){
    Flg_Mode = 3;
    Flg_Over = 0;
  }
}

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

void mode_disp() {
  static byte Flg_Modeb = 73;

  if(Flg_Mode != Flg_Modeb){
    tft.setFont(&Org_01);
    tft.setTextSize(3);

    if (Flg_Mode == 0){                         // 0=CW
      tft.fillRect(185,138,52,15,BLACK);
      tft.setCursor(185, 150);
      tft.setTextColor(CYAN);
      tft.print("CW ");
    }
    else if(Flg_Mode == 1){                     // 1=LSB
      tft.fillRect(185,138,52,15,BLACK);
      tft.setCursor(185, 150);
      tft.setTextColor(CYAN);
      tft.print("LSB");
    }
    else if(Flg_Mode == 2){                     // 2=USB
      tft.fillRect(185,138,52,15,BLACK);
      tft.setCursor(185, 150);
      tft.setTextColor(CYAN);
      tft.print("USB");
    }
    else if(Flg_Mode == 3){                     // 3=AM
      tft.fillRect(185,138,52,15,BLACK);
      tft.setCursor(185, 150);
      tft.setTextColor(CYAN);
      tft.print("AM ");
    }

    Flg_Modeb = Flg_Mode;
  }
}

//----------- Dot display ----------------------------------------------

void dot_disp(){
  tft.setFont(&Org_01);
  tft.setTextSize(6);
  tft.setCursor(115, 195);
  tft.setTextColor(GREEN);
  tft.print(".");
}

//----------  long to ascii  -------------------------------------------
 
char *long_asc(char *str,long n){
  int  i = 0;                       // Write the number
  char *p = str;
  unsigned long  u = abs(n);

  do{
    *p++ = "0123456789"[u % 10];
    u = u / 10;
    i++;
    }
  while( 0 != u )
    ;
   *p = '\0';
}

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

void si5351_freq(unsigned long freq){
  si5351.set_freq(freq * SI5351_FREQ_MULT, SI5351_CLK0);
}

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

void si5351_bfo(unsigned long bfo){
    si5351.set_freq(bfo * SI5351_FREQ_MULT, SI5351_CLK2);
}

//----------- s-meter --------------------------------------------------

void meter_disp(uint16_t signalLevel){
  static int a1b,a2b = 0;
  const int hMeter = 120;                                   // horizontal center
  const int vMeter = 120;                                   // vertical center
  const int rMeter = 120;                                   // needle length

//  signalLevel = map(signalLevel, 0, 127, 0, 1023);
  float smeterValue = (signalLevel) * 330 / 1024;           // convert the signal
  smeterValue = smeterValue - 34;                           // shifts needle to zero position
  tft.drawBitmap(0, 0, myBitmap, 240,88, WHITE);            // draws background
  int a1 = (hMeter + (sin(smeterValue / 35.0) * rMeter));   // meter needle horizontal coordinate
  int a2 = (vMeter - (cos(smeterValue / 35.0) * rMeter));   // meter needle vertical coordinate

  if((a1 != a1b) || (a2 != a2b)){
    tft.drawLine(a1b, a2b, hMeter, vMeter, BLACK);          // clear needle
    a1b = a1;
    a2b = a2;
    tft.drawLine(a1, a2, hMeter, vMeter, WHITE);            // draws needle
    delay(1);
  }
}

//----------  rit proc  ------------------------------------------------

void rit_proc(){
  if(Flg_Rit == 0){
    Flg_Rit = 1;
//    eep_wdata();
  }
  else{
    Flg_Rit = 0;
    Rit_Dat = 0;
    Disp_Freq = 1;
    tft.fillRect(0,208,240,32,BLACK);
  }
  while(digitalRead(SW_RIT) == LOW)
    yield();
}

//----------  RIT Display  ---------------------------------------------

void rit_disp(int rit_data) {
  unsigned int ri,rit;

  tft.fillRect(42,208,154,32,BLACK);
  tft.setFont(&Org_01);
  tft.setTextSize(3);
  tft.setCursor(0, 232);
  tft.setTextColor(YELLOW);
  tft.print("RIT ");

  if (rit_data > 0)
    tft.print('+');

  tft.print(rit_data);
}

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

void enc_proc() {                     // rotary encoder events
unsigned char result = r.process();

  if(Flg_Tx == 0){
    if(result) { 
      Disp_Freq = 1;
      Disp_Rit = 1;

      if(result == DIR_CW){
        Lng_Wk = Vfo_Dat + Enc_Step;
        Int_Wk = Rit_Dat + Enc_Step;
      }
      else{
        Lng_Wk = Vfo_Dat - Enc_Step;
        Int_Wk = Rit_Dat - Enc_Step;
      }

      if(Flg_Rit == 1)
        Rit_Dat = Int_Wk;
      else{
        Vfo_Dat = Lng_Wk;
        Rit_Dat = 0;
      }
      Rit_Dat = constrain(Rit_Dat,LW_RIT,HI_RIT);  // RIT range check
      Flg_enc = 1;
    }
  }
}

//----------  step proc ------------------------------------------------

void step_proc(){
  if(Enc_Step == 1000)                       // Step = 10Hz ?
    Enc_Step = 10;                       //   Yes,1khz set
    else
      Enc_Step = Enc_Step * 10;            // Step down 1 digit

  step_disp();
  while(digitalRead(SW_STEP) == LOW)
    yield();
}

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

void step_disp(){
  tft.fillRect(185,113,52,15,BLACK);

  tft.setFont(&Org_01);
  tft.setTextSize(3);
  tft.setCursor(190, 125);
  tft.setTextColor(CYAN);

  switch(Enc_Step){
    case 10:
      tft.println("10 ");
      break;
    case 100:
      tft.println("100");
      break;
    case 1000:
      tft.println("1k ");
      break;
  }
}
   
   

2020年5月30日土曜日

si4735評価ボード

操作面
si4735を使った例は多数あるが、自作であるゆえ標準仕様がない。そこで、スケッチの動作確認を目的に、oledを使った評価ボードを作った。このPCBは、I2C oledの他、秋月AQM1602XA-RN-GBWも搭載可能。



部品面
ICは、変換基板をっ使ってDIPにしている。CPUは、安価な中華製Arduino  Pro mini(3.3V 8MHz)用にPCBとした。AF AMPは、中華製8002a(LM4872相当)を使っているが、ミュート回路は省いた。電源on時にポップ音があるが、評価ボードなので問題ないと思っている。


oledを実装した操作面
左側から電源SW、タクトSW8個、スイッチ付ロータリーエンコーダ。左上にoled(I2C)である。秋月の16文字x2行を使った方がバランスが良い。





部品面
ICは、DIP(変換基板使用)で設計した。Arduino Pro miniは、安価な中華製でA4,A5ピンが中央寄りに配置されているものを使っている。アンテナ回路は、外部アンテナ専用とし、簡略化した。








回路図



スケッチ
All Band(All Mode)のoled版PU2CLRのライブラリに含まれている物を使っている。
パターン引き回し上、I/Oポート変更とスイッチ処理の変更を行っている。これ以外に周波数設定を変更したが、不満があるが実用に耐える。

スイッチのチャタリング防止(例えば、Band Up処理)は、タイマーを使わずに確実な動作にした。修正したスケッチは、JA2GQP's Download siteのsi4735フォルダがらダウンロード可能。

一例
/////////////////////////
//  BAND UP switch
/////////////////////////

    else if (digitalRead(BAND_BUTTON_UP) == LOW){
      bandUp();
      while(digitalRead(BAND_BUTTON_UP) == LOW)
        ;
    }

動作試験

7MHz SSBとFM放送を受信したが、実用レベルと思う。

2020年5月3日日曜日

Attiny85 VFO PCB版

Attiny85 VFO用にPCBを作った。モノバンド用に使いやすくする為、余分なスイッチは無い。今回、電源電圧を3.3Vで動作出来るように、LCDも見直した。si5351aモジュールは、Adafurit製もしくは秋月電子製も動作可能。





回路図

設定

デバイスの設定
       

ヒューズビット

スケッチ

電源電圧3.3Vに対応させるため、LCDをAQM1602XA-RN-GBWに変更した。それに伴い変更している。ファイルはJA2GQP's Download siteのattiny85フォルダからダウンロード可能。

1)インクルードファイル(si5351a21.h)
////////////////////////////////////////////////////////////////////////
// Author: Hans Summers, 2015
// Website: http://www.hanssummers.com
//
// A very very simple Si5351a demonstration
// using the Si5351a module kit http://www.hanssummers.com/synth
// Please also refer to SiLabs AN619 which describes all the registers to use
//----------------------------------------------------------------------
// Modifications: JA2GQP,2017/5/20
//     1)Output is CLK0 and CLK2.
//     2)Arduino and stm32duino Operable. 
////////////////////////////////////////////////////////////////////////

#include <Wire.h>

#define OUTE_CTRL   3
#define CLK0_CTRL   16               // Register definitions
#define CLK1_CTRL   17
#define CLK2_CTRL   18
#define MSNA_ADDR   26
#define MSNB_ADDR   34
#define MS0_ADDR    42
#define MS1_ADDR    50
#define MS2_ADDR    58
#define PLL_RESET   177
#define XTAL_LOAD_C 183

#define R_DIV_1      0b00000000     // R-division ratio definitions
#define R_DIV_2      0b00010000
#define R_DIV_4      0b00100000
#define R_DIV_8      0b00110000
#define R_DIV_16     0b01000000
#define R_DIV_32     0b01010000
#define R_DIV_64     0b01100000
#define R_DIV_128    0b01110000

#define Si5351A_ADDR  0x60

#define CLK_SRC_PLL_A 0b00000000
#define CLK_SRC_PLL_B 0b00100000

#define XTAL_FREQ     25000000    // Crystal frequency for Hans' board

//------------- momory define ------------

uint32_t xtalFreq = XTAL_FREQ;    // 2017/9/29

////////////////////////////////////////////////////////////////////////
// I2C write
////////////////////////////////////////////////////////////////////////

void Si5351_write(byte Reg , byte Data){
  Wire.beginTransmission(Si5351A_ADDR);
  Wire.write(Reg);
  Wire.write(Data);
  Wire.endTransmission();
}

////////////////////////////////////////////////////////////////////////
// Set up specified PLL with mult, num and denom
// mult is 15..90
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
///////////////////////////////////////////////////////////////////////

void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom){
  uint32_t P1;                            // PLL config register P1
  uint32_t P2;                            // PLL config register P2
  uint32_t P3;                            // PLL config register P3

  P1 = (uint32_t)(128 * ((float)num / (float)denom));
  P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
  P2 = (uint32_t)(128 * ((float)num / (float)denom));
  P2 = (uint32_t)(128 * num - denom * P2);
  P3 = denom;

  Si5351_write(pll + 0, (P3 & 0x0000FF00) >> 8);
  Si5351_write(pll + 1, (P3 & 0x000000FF));
  Si5351_write(pll + 2, (P1 & 0x00030000) >> 16);
  Si5351_write(pll + 3, (P1 & 0x0000FF00) >> 8);
  Si5351_write(pll + 4, (P1 & 0x000000FF));
  Si5351_write(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
  Si5351_write(pll + 6, (P2 & 0x0000FF00) >> 8);
  Si5351_write(pll + 7, (P2 & 0x000000FF));
}

////////////////////////////////////////////////////////////////////////
// Set up MultiSynth with integer divider and R divider
// R divider is the bit value which is OR'ed onto the appropriate
// register, it is a #define in si5351a.h
////////////////////////////////////////////////////////////////////////

void setupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv){
  uint32_t P1;                          // Synth config register P1
  uint32_t P2;                          // Synth config register P2
  uint32_t P3;                          // Synth config register P3

  P1 = 128 * divider - 512;
  P2 = 0;                               // P2 = 0, P3 = 1 forces an integer value for the divider
  P3 = 1;

  Si5351_write(synth + 0, (P3 & 0x0000FF00) >> 8);
  Si5351_write(synth + 1, (P3 & 0x000000FF));
  Si5351_write(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv);
  Si5351_write(synth + 3, (P1 & 0x0000FF00) >> 8);
  Si5351_write(synth + 4, (P1 & 0x000000FF));
  Si5351_write(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
  Si5351_write(synth + 6, (P2 & 0x0000FF00) >> 8);
  Si5351_write(synth + 7, (P2 & 0x000000FF));
}

////////////////////////////////////////////////////////////////////////
// output enable/disable control
// Example: si5351a_enable(0x01);
// CLK0 output disable
////////////////////////////////////////////////////////////////////////

void si5351a_enable(byte oeb){
  Si5351_write(OUTE_CTRL,oeb);                  // AN619 Register 3
}

////////////////////////////////////////////////////////////////////////
// Set CLK0 output ON and to the specified frequency
// Frequency is in the range 1MHz to 150MHz
// Example: si5351aSetFrequency(10000000);
// will set output CLK0 to 10MHz
//
// This example sets up PLL A
// and MultiSynth 0
// and produces the output on CLK0
////////////////////////////////////////////////////////////////////////

void si5351aSetFrequency(uint32_t frequency){
  uint32_t pllFreq;
//  uint32_t xtalFreq = XTAL_FREQ;        // 2017/9/29
  uint32_t l;
  float f;
  uint8_t mult;
  uint32_t num;
  uint32_t denom;
  uint32_t divider;

  divider = 900000000 / frequency;        // Calculate the division ratio. 900,000,000 is the maximum internal
                                          // PLL frequency: 900MHz
  if (divider % 2) divider--;             // Ensure an even integer
                                          //division ratio

  pllFreq = divider * frequency;          // Calculate the pllFrequency:
                                          //the divider * desired output frequency

  mult = pllFreq / xtalFreq;              // Determine the multiplier to
                                          //get to the required pllFrequency
  l = pllFreq % xtalFreq;                 // It has three parts:
  f = l;                                  // mult is an integer that must be in the range 15..90
  f *= 1048575;                           // num and denom are the fractional parts, the numerator and denominator
  f /= xtalFreq;                          // each is 20 bits (range 0..1048575)
  num = f;                                // the actual multiplier is mult + num / denom
  denom = 1048575;                        // For simplicity we set the denominator to the maximum 1048575

                                          // Set up PLL A with the calculated  multiplication ratio
  setupPLL(MSNA_ADDR, mult, num, denom);
                                          // Set up MultiSynth divider 0, with the calculated divider.
                                          // The final R division stage can divide by a power of two, from 1..128.
                                          // reprented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file)
                                          // If you want to output frequencies below 1MHz, you have to use the
                                          // final R division stage
  setupMultisynth(MS0_ADDR, divider, R_DIV_1);
                                          // Reset the PLL. This causes a glitch in the output. For small changes to
                                          // the parameters, you don't need to reset the PLL, and there is no glitch
//  Si5351_write(PLL_RESET, 0x20);
                                          // Finally switch on the CLK0 output (0x4F)
                                          // and set the MultiSynth0 input to be PLL A
  Si5351_write(CLK0_CTRL, 0x4F | CLK_SRC_PLL_A);    // Strength 8mA
}

////////////////////////////////////////////////////////////////////////
// Set CLK1 output ON and to the specified frequency
// Frequency is in the range 1MHz to 150MHz
// Example: si5351aSetFrequency2(10000000);
// will set output CLK0 to 10MHz
//
// This example sets up PLL B
// and MultiSynth 1
// and produces the output on CLK1
////////////////////////////////////////////////////////////////////////

void si5351aSetFrequency2(uint32_t frequency){
  uint32_t pllFreq;
//  uint32_t xtalFreq = XTAL_FREQ;        // 2017/9/29
  uint32_t l;
  float f;
  uint8_t mult;
  uint32_t num;
  uint32_t denom;
  uint32_t divider;

  divider = 900000000 / frequency;        // Calculate the division ratio. 900,000,000 is the maximum internal
                                          // PLL frequency: 900MHz
  if (divider % 2) divider--;             // Ensure an even integer
                                          //division ratio

  pllFreq = divider * frequency;          // Calculate the pllFrequency:
                                          //the divider * desired output frequency

  mult = pllFreq / xtalFreq;              // Determine the multiplier to
                                          //get to the required pllFrequency
  l = pllFreq % xtalFreq;                 // It has three parts:
  f = l;                                  // mult is an integer that must be in the range 15..90
  f *= 1048575;                           // num and denom are the fractional parts, the numerator and denominator
  f /= xtalFreq;                          // each is 20 bits (range 0..1048575)
  num = f;                                // the actual multiplier is mult + num / denom
  denom = 1048575;                        // For simplicity we set the denominator to the maximum 1048575

                                          // Set up PLL B with the calculated  multiplication ratio
  setupPLL(MSNB_ADDR, mult, num, denom);
                                          // Set up MultiSynth divider 0, with the calculated divider.
                                          // The final R division stage can divide by a power of two, from 1..128.
                                          // reprented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file)
                                          // If you want to output frequencies below 1MHz, you have to use the
                                          // final R division stage
  setupMultisynth(MS2_ADDR, divider, R_DIV_1);
                                          // Reset the PLL. This causes a glitch in the output. For small changes to
                                          // the parameters, you don't need to reset the PLL, and there is no glitch
//  Si5351_write(PLL_RESET, 0x80);
                                          // Finally switch on the CLK1 output
                                          // and set the MultiSynth0 input to be PLL B
  Si5351_write(CLK2_CTRL, 0x6F | CLK_SRC_PLL_B);    // Strength 8mA
}


2)VFOスケッチ(st7032_vfo)
///////////////////////////////////////////////////////////////////////////////////
//    si5351a VFO Atiny85
//
//                                                        2020/3/23
//                                                        JA2GQP   
//---------------------------------------------------------------------------------
//    fuse BIT
//                low = f1 , high = DF , extend = ff
//
///////////////////////////////////////////////////////////////////////////////////

//---------- Library include --------------------------------

#include "src/si5351a21.h"                   
#include "src/Rotary.h"
#include "src/ST7032.h"
#include <EEPROM.h>

//---------- Define value -----------------------------------

#define SW1 (val < 682) && ( val >= 472)  // SW1
#define SW2 (val < 767) && ( val >= 642)  // SW2
#define SW3 (val < 818) && ( val >= 727)  // SW3

#define SW_TX   SW1
#define SW_RIT  SW2
#define ENC_A   PB1
#define ENC_B   PB3
             
Rotary r=Rotary(ENC_B,ENC_A);
ST7032 lcd;

////////////////////////////////
// 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[4] = {
 // DEF     LOW(cw)  MID(ssb)  HI
  7050000 ,7000000 ,7045000 ,7200000
  };

//---- data offset -----
const byte DEF_F = 0;
const byte LOW_F = 1;
const byte MID_F = 2;
const byte HI_F  = 3;

//---- Bfo data -----
const unsigned long IF_FREQ = 10700000L; 
const unsigned long CW =      10700600L; 
const unsigned long LSB =     10701500L;
const unsigned long USB =     10698500L;

////////////////////////////////
// etc
////////////////////////////////
const byte  Int_End = 73;           // Initial end code
const int  LW_RIT = -5000;          // RIT Lower Limit
const int  HI_RIT = 5000;           // RIT Upper Limit

byte bar5[8]={B00000,               // s-meter barcode data
B11011,
B11011,
B11011,
B11011,
B11011,
B11011,
B00000};

byte bar1[8]={B10000,
B11000,
B11100,
B11110,
B11110,
B11100,
B11000,
B10000};

unsigned long previousMillis;

//---------- Variable setting -------------------------------

unsigned long Vfo_Dat;
unsigned long Vfo_Datb = 0;
unsigned long Bfo_Dat;
unsigned long Bfo_Datb = 0;
unsigned long Enc_Step = 10;

int Rit_Dat;                        // RIT Data
int Rit_Datb = 0;                   //          old
long Lng_Wk;                        // Long Work
int  Int_Wk;                        // Int Work
char Lcd_Dat[12] = "           ";   // Lcd Display Buffer
byte Flg_Tx = 0;                    // TX Flag
byte Flg_Mode = 0;                  // Mode Flag
byte Flg_Over = 0;
byte Flg_Rit = 0;                   // RIT Flag
byte Flg_Rdisp = 0;                 // RIT Display Flag
byte Flg_Disp = 0;                  // Display Flag

long Enc_Delay = 0;                 // Encorder Delay(ms)
long Time_Passd;                    // Time Pass(ms)
byte Flg_eepWT = 0;                 // EEP Write Flag

long Enc_Velocityb;
long Enc_Velocity;                  // Encorder velocity(ms)

//----------  Initialization  Program  ----------------------

void setup() {
  unsigned int val;

  pinMode(ENC_A,INPUT_PULLUP);      // PB3
  pinMode(ENC_B,INPUT_PULLUP);      // PB4

  lcd.begin(16, 2);
  lcd.setContrast(40);            // at 3.3V
//  lcd.setContrast(10);              // at 5V

  lcd.createChar(0, bar5);
  lcd.createChar(1, bar1); 

  GIMSK |= (1 << PCIE);             // Enable pin change interrupt
  PCMSK |= (1 << PCINT1) | (1 << PCINT3);
  sei();                            // INT Enable

  val = analogRead(A0);             // EEPROM init check
  delay(1);
  if(SW_RIT)                        // init?
    EEPROM.write(Eep_Init,0x00);     
  do{
    val = analogRead(A0);         
    delay(1);
    if(SW_RIT){
      lcd.setCursor(0, 0);
      lcd.print("EEP Initialize");
    }
  }
  while(SW_RIT);

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

  eep_rdata();
  delay(10);

  band_check();
  mode_disp();
}

//----------  Main program  ---------------------------------

void loop() {
  unsigned int val;

  val = analogRead(A0);
  delay(1);
  if(SW_TX)                         // SW_TX
    Flg_Tx = 1;
  else
    Flg_Tx = 0;

  Fnc_TRdsp();                      // T/R Display

//////////////////////////
//  RX
//////////////////////////
  if(Flg_Tx == 0){                  // RX
    display_smeter(analogRead(A2)); // s-meter Display
    Fnc_Stp();
    si5351a_enable(0x00);
     
    val = analogRead(A0);
    delay(1);
    if(SW_RIT)                      // SW_RIT
      Fnc_Rit();                    // RIT Flag set

    if(((Vfo_Dat != Vfo_Datb) && (Flg_Rit == 0)) || (Flg_Disp == 1)){
      si5351aSetFrequency(Vfo_Dat + IF_FREQ);
      band_check();
      mode_disp();
      Fnc_Fdsp(Vfo_Dat);
      Vfo_Datb = Vfo_Dat;
      Flg_Disp = 0;
    }

    if(((Rit_Dat != Rit_Datb) && (Flg_Rit == 1)) || (Flg_Rdisp == 1)){
      si5351aSetFrequency(Vfo_Dat + IF_FREQ + Rit_Dat);
      rit_disp(Rit_Dat);
      Rit_Datb = Rit_Dat;
      Flg_Rdisp = 0;
    }

    si5351aSetFrequency2(Bfo_Dat);  // Bfo data out
  }

//////////////////////////
//  TX
//////////////////////////
  else{                           
    mode_disp();

    if(Flg_Over == 0){
      si5351a_enable(0x00);
      si5351aSetFrequency(Vfo_Dat + IF_FREQ);
    }
    else{                           // Frequency over
      si5351a_enable(0x07);
    }

    if(Flg_Rit == 1)
      Flg_Rdisp = 1;
    else
      Flg_Disp = 1;
  }

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

//----------  Function Rit  ---------

void Fnc_Rit(){
  unsigned int val;

  if(Flg_Rit == 0){
    Rit_Dat = 0;
    Flg_Rit = 1;
    lcd.setCursor(11,0);
    lcd.print("+0   ");
  }
  else{
    mode_disp();
    si5351aSetFrequency(Vfo_Dat + IF_FREQ);
    Flg_Rit = 0;
  }

  do{
    val = analogRead(A0);
    delay(1);
  }
  while(SW_RIT);
}

//----------  RIT Display  ---------------------------------------------

void rit_disp(int rit_data) {
  unsigned int ri,rit;

  lcd.setCursor(11,0);

  if (rit_data >= 0){
    lcd.print("+    ");
    lcd.setCursor(12,0);
    lcd.print(rit_data,DEC);
  }
  else{
    lcd.print("     ");
    lcd.setCursor(11,0);
    lcd.print(rit_data,DEC);
  }
}

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

void mode_disp() {
  lcd.setCursor(11,0);
  if((Flg_Tx == 1) && (Flg_Over == 1))
    lcd.print(" Over");             // frequency over
  else if (Flg_Mode == 0)
    lcd.print("   CW");             // CW
  else if(Flg_Mode == 1)
    lcd.print("  LSB");             // LSB
  else if(Flg_Mode == 2)
    lcd.print("  USB");             // USB
}

//----------  LCD Bar Display PROC.  ------------------------

void display_smeter(int strength){
// range is 0 to 1024 of adc
//meter run from position 3 to 14 (12 )
  int scale = (strength*15)/1024;
  lcd.setCursor(0, 1);
  lcd.print("S");
  int smeter=(scale*11)/15;
  if(smeter<10)
    lcd.print(smeter);
  else
    lcd.print("9");
  lcd.print(">");

  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 1000) {
    previousMillis = currentMillis;
    //clear all after a small interval
    lcd.setCursor(3, 1);
    lcd.print("            ");
    lcd.noCursor();
  }

  // write it and show for 500 milli sec
  for (int i = 3; i<scale; i++){
    lcd.setCursor(i, 1);
    lcd.write(byte(0));
    if(smeter>9)
      lcd.print("+");
  }
}


//----------  Encorder procedure(INT)  ----------------------

ISR(PCINT0_vect) {
  unsigned char result = r.process();

  if(Flg_Tx == 0){
    if(result) { 
      Enc_Velocity = millis() - Enc_Velocityb;

      if(result == DIR_CW){
        Lng_Wk = Vfo_Dat + Enc_Step;
        Int_Wk = Rit_Dat + Enc_Step;
      }
      else{
        Lng_Wk = Vfo_Dat - Enc_Step;
        Int_Wk = Rit_Dat - Enc_Step;
      }

      if(Flg_Rit == 1)
        Rit_Dat = Int_Wk;
      else{
        Vfo_Dat = Lng_Wk;
        Rit_Dat = 0;
      }

      Rit_Dat = constrain(Rit_Dat,LW_RIT,HI_RIT);  // RIT range check

      Flg_eepWT = 1;
      Time_Passd = Enc_Velocityb =millis();
    }
  }
}

//----------  Function Send/receive Display  ------

void Fnc_TRdsp(){
  lcd.setCursor(0,0);
  if(Flg_Tx == 1)
    lcd.print("T");
  else
    lcd.print("R");
}

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

void Fnc_Fdsp(long f_disp){
  Fnc_Dot_Edit(Lcd_Dat,f_disp);
  lcd.setCursor(1,0);
  lcd.print("          ");
  lcd.setCursor(1,0);
  lcd.print(Lcd_Dat);
}

//----------  Function String Dot Edit  --------
 
char *Fnc_Dot_Edit(char *str,long n){
  int  i = 0;                       // Write the number
  char *p = str;
  unsigned long  u = abs(n);

  do{
    *p++ = "0123456789"[u % 10];
    u = u / 10;
    i++;
    if((0 != u) && (0 == (i % 3)))
      *p++ = '.';
    }
  while( 0 != u )
    ;
  if ( n < 0 )
     *p++ = '-';
   *p = '\0';
   Fnc_Revr( str );
   return str;
}

//----------  Function String Reverse  ---------------------------------

void Fnc_Revr(char *str){
  int i,n;
  char c;

  n=strlen(str);
  for(i = 0;i < n / 2;i++){
    c=str[i];
    str[i]=str[n - i - 1];
    str[n - i - 1]=c;
    }
}

//----------  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(){
  Vfo_Dat = eep_read4(Eep_Freq);
  delay(10);
}

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

void eep_wdata(){
  eep_write4(Vfo_Dat,Eep_Freq);
  delay(10);
}
 
//----------  EEPROM Initialization ------------------------------------

void eep_init(){
  int i;

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

  eep_write4(FRQ_TBL[DEF_F],Eep_Freq);
  EEPROM.write(Eep_Init,Int_End);   // Init end set(73)
}

//----------  Function Encorder STEP  ----------------------------------

void Fnc_Stp(){
  if(Time_Passd+2000 < millis()){
    Enc_Step = 10;
  }

  else{
    if(Enc_Delay+6000 < millis()){
      if(Enc_Velocity < 100)        // chenge STEP?
        Enc_Step = Enc_Step * 10;
      if(Enc_Step > 10000)          // 10kHz over?
          Enc_Step = 10;            // STEP = 10Hz
      Enc_Delay = millis();
    }
  }

}

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

void band_check(){
  if((Vfo_Dat >= FRQ_TBL[LOW_F]) && (Vfo_Dat <= FRQ_TBL[MID_F])){
    Flg_Mode = 0;                   // CW(0=CW, 1=LSB, 2=USB) 
    Bfo_Dat = CW;
    Flg_Over = 0;
  }

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

  else{
    Flg_Over = 1;
    if(Vfo_Dat >= 10000000){        // greate than 10MHz                     
      Flg_Mode = 2;                 // USB(0=CW, 1=LSB, 2=USB)
      Bfo_Dat = USB;
    }
    else{
      Flg_Mode = 1;                 // LSB(0=CW, 1=LSB, 2=USB)
      Bfo_Dat = LSB;
    }
  }
}

実装写真

Adafruit純正または中華製モジュール

秋月電子製モジュール