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