2020年3月15日日曜日

Attiny85 VFO

 Dijispark(Attiny85)を使ったVFO。Arduino IDE1.8.12でDijispark開発環境を作って進めたが、次の様な問題が発生。
1)Bootloaderにより起動されるので、アプリケーション立上りに約6秒かかる。
2)アプリケーションで使える領域が、約6kB。(B00tloader2kB占有のため)
3)DijisparkのP1(LED点灯回路)が、周辺ポートレベルを吊り上げる為、ポートが正常動作しない。





解決策

1)Bootloaderなしとし、ArduinoIDEで開発する為のAttiny85環境整備を行った。
2)DijisparkのP1回路に繋がっている抵抗(102)を外した。

VFO仕様

1)周波数の自動メモリー保存
2)Auto STEP(10Hz,100Hz,1kHz,10kHz)のロータリー式での切替
3)RIT機能
4)BFO
5)S-Meter
6)モノバンド
7)帯域外への送信保護
8)自動モード切替

回路図


スケッチ

スイッチ入力は、アナログ信号をラダー回路によってレベル判断を行っている。アナログ信号のPB5は、リセットを兼ねているのでしきい値を設けている。スイッチ入力の判断は、マクロにして見通しを良くした。
機械式エンコーダの場合、加速・定常域・減速が安定しておらず、かつ、連続回転の為のノブ持ち返しがある。その為、速度判定2s、ノブ持ち返し保持時間6sに設定している。(エンコーダの種類、回し方などで動作が大きく変わる)
スケッチは、ダウンロードサイトのAttiny85フォルダからダウンロードできる。

///////////////////////////////////////////////////////////////////////////////////
//    si5351a VFO Atiny85
//
//                                                        2020/3/12
//                                                        JA2GQP   
///////////////////////////////////////////////////////////////////////////////////

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

#include "src/si5351a21.h"                   
#include <Rotary.h>
#include <LiquidCrystal_I2C.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_A,ENC_B);
LiquidCrystal_I2C lcd(0x27, 16, 2);

////////////////////////////////
// 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();
  lcd.backlight();
  lcd.createChar(0, bar5);
  lcd.createChar(1, bar1); 

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

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

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

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

void eep_wdata(){
  eep_write4(Vfo_Dat,Eep_Freq);
}
 
//----------  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;
    }
  }
}



2020年3月7日土曜日

2 stage RQA0009/AFT05HS004NT1 AMP



終段にRQA0009またはAFT05HS004NT1 が使える2stage AMPである。先回紹介の1段APMを2段にしたもので、ドライブレベルが-20dBm ~-10dBm位でHFから50MHz 帯まで4W以上の出力が得られる。今回も専用PCBを作ったが、前段をパスさせれば1段AMPとしても使う事が出来る。






回路図

    

デバイスの発熱

集積度が高くなった分、放熱が悪い。FETのタブをPCBに半田付けしているが、基板エッジに近いので、銅板などをタブに半田付けすれば放熱が簡単に済みそうだ。




 

2019年12月29日日曜日

RQA0009モジュール

中華ハンディ機(UV-5Rなど)に使われているRQA0009を使ってRFアンプを作った。このRQA0009は、Aliexpressで50円/個で入手できるので、ローコストのアンプが作れる。製作したアンプは、広帯域1段構造である。PCBは、市販のRFモジュールを模造し、配線部は片側とした。市販のRFモジュールとコンパチブルにしたかったが、寸法25x50、信号配置オリジナルとなった。回路は、お世話になっているJA2NKD/1 OMの回路を使用した。




回路図


測定結果

条件:アイドリング時のドレイン電流を150mA・電源電圧 13.8V・SGからCWモードで10mW~20mWを出力。
結果、HF~50MHz帯まで5W出力を確認。

問題点

今回製作したPCBは、裏側面ベタグランドプレーン構造でスルーホールによる放熱を試みたが、チップが高温で連続した出力を得るのは困難であった。チップ面の放熱が必要である。

チップ面の放熱対策

    
PCB部品面にアルミ板で放熱させる構造とした。チップの上側にある穴にスタッドを取り付け、アルミ板を固定。チップ面に皿ネジを当て、アルミ板に放熱させる様にした。

皿ネジの頭が、チップ面に接触させる構造。チップ面に放熱シートを挟み、放熱個効果を高めた。熱電対などの測定環境がないが、アルミ板に熱が伝わっており、放熱効果が確認できた。

    













   


    

2019年10月31日木曜日

UPAK変換PCB

最近、送信用に使えそうな入手可能なデバイスが少なくなっている。Aliexpressから安価で入手可能なデバイスRQA0009(UPAK形状で出力5W程)がある。UPAK形状であるので、放熱を配慮した実装が必要。そこで、UPAKをTO-220に変換する放熱可能なPCBを作る事にした。








RQA0009データシート





マウント


配線用リード線が出してないが、RAQ0009をマウントした例である。他のUPAKデバイスも実装可能である。













評価

現時点では、放熱を考慮したPCB設計をしたが、未評価である。



   


     

2019年9月22日日曜日

CA3028もどきPCB

Webを検索していると、面白い物を発見することが有る。JG1EAD OMのCA3028をディスクリートで作り、試験をしている記事を見つけた。トランシーバなどを自作する場合、廃版部品を使いたい場合もあり、代替え手段が有れば有難いことだ。そこで、CA3028もどきPCBを作る事にした。









回路図



PCB

リード部品で作る事を前提に設計した。抵抗は1/6W型を使用した。


                       

参考

W1FB OMのデザインノートに、CA3028を使った回路がある。試験したい回路が多数あり、非常に興味深い。


















2019年6月18日火曜日

SR-FRS FMトランシーバ

SR-FRS1WVを使った2m FMトランシーバ(2m FM 1W出力)。写真のシールドされた部分が、SR-FRSモジュールである。このモジュールは、中華の数社から販売されており、DRA818、SA818、SR-FRSなどのキーワードで探すことが出来る。SR-FRSを除き殆ど、12.5kHzまたは25kHzステップとなっており、国内では使いづらい。SR-FRSは、20kHzステップとして動作させられるので、音声FMに適している。




BK4811

SR-FRSの回路である。BK4811(DSP)とRF AMP,マイコンから構成されている。このBK4811は、70cmも対応しているが残念ながら、DSPにコマンドを与えない限り、70cmは動作しない様だ。










トランシーバ回路

周波数設定にロータリーエンコーダを使い、oled表示器を備えた回路図である。注意しなければいけない事は、SR-FRSの定格電圧が低いことである。電源電圧を4.5V以下で使うのが良いであろう。











PCB設計

PCBEを使ってPCB設計し、KicadのViewerでチェックを行った。マイク入力とRF出力が近接している為、RF回り込みを心配したが、全く問題なく動作した。









ハンディタイプ


ハンディタイプ トランシーバとして纏めた例である。リチュームイオン電池(3.7V)と充電器を内蔵。人間工学からメーカ製とは考え方の異なる配置とした。

















スケッチ

バンドプランによる運用周波数制限、スケルチoff、自動メモリー書込みの機能がある。
JA2GQP's Download siteのSR-FRSフォルダからダウンロード可能。Ver1.00での既知のバグは、1kHz台の表示が誤表示する事がある。誤表示は、電源offまたは運用周波数範囲外チェック後の希望周波数への移動などにより回避できる。この下1桁誤表示は、Ver1.01で確認試験を行っていたが、誤表示する事は無かった。下記の黄色部分は、Downloadサイトに反映されてアップロード済み。

//////////////////////////////////////////////////////////////////////
//  SR-FRS1W VHF TRX program ver.1.01
//    Copyright(C)2019.JA2GQP.All rights reserved.
//
//                                                2019/06/18
//                                                  JA2GQP
//--------------------------------------------------------------------
//  Function
//    1.encoeder STEP 20kHz
//    2.Squelch on/off(SQ SW)
//    3.Auto save memory(frequency)
//    4.Voltmeter
//////////////////////////////////////////////////////////////////////

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

////////////////////////////////
// Set Device
////////////////////////////////
Rotary r = Rotary(2, 3);
SSD1306AsciiAvrI2c oled;

////////////////////////////////
// I/O Port
////////////////////////////////
const byte SW_SQ = 4;                 // SQ SW

////////////////////////////////
// EEPROM Memory Address
////////////////////////////////
const byte  Eep_Init = 0x00;          // Eep Init(1byte*1)
const byte  Eep_Freq = 0x10;          // Frequency(4byte*8)
const byte  Int_End = 73;             // Initial end code

////////////////////////////////
// Constant Memory
////////////////////////////////
const char Call[9] = "JA2GQP";        // Display Call sign
const unsigned long DEF_FREQ =145000; // Default
const unsigned long BAND_L =144600;   // Low limit
const unsigned long BAND_H =145800;   // High limit

////////////////////////////////
// Variable Memory
////////////////////////////////
unsigned long frq;                    // Frequency data
unsigned long frqb;                   //           old data
const unsigned int STEP = 20;         // STEP 20kHz

String GBW = "0";                     // Bandwidth(0:Nallow 1:Wide)
unsigned long  TFV;                   // Transmit frequency
unsigned long  RFV;                   // Receive frequency
String RXCXCSS = "00";                // CICSS/CDCSS(00-121)
String SQ = "1";                      // Squelch level(0:Monitor mode)
String TXCXCSS = "00";                // CICSS/CDCSS(00-121)
String FLAG = "0";                    // Bit0(Busy),Bit1(Compression),Bit2(Power)

float v;                              // volt data
float vb = 0;                         //           old
byte Flg_freq = 0;
byte Flg_sq = 0;
byte Flg_eepWT = 0;                   // EEP Write Flag
long Time_Passd = 0;                  // int to hold the arduino miilis since startup

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

void setup(){
  pinMode(SW_SQ,INPUT_PULLUP);

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

  analogReference(INTERNAL);          // Internal 1.1V set
  oled.begin(&Adafruit128x64,0x3C);

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

  eep_rdata();
  frs_init();
  frs_set();

  volt_disp();
  freq_disp(frq);
  call_disp();
}

//---------- loop -------------------------------------------------------

void loop(){
  if(frq != frqb){
    frq = constrain(frq,BAND_L,BAND_H);
    frs_set();
    freq_disp(frq);
    frqb = frq;
    volt_disp();

    Time_Passd = millis();
    Flg_eepWT = 1;
  }

  if (digitalRead(SW_SQ) == LOW) {
    sq_set();
    frs_set();
  }

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

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

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

  if (result) {
    Flg_freq = 1;
    if (result == DIR_CW)
      frq = frq + STEP;
    else
      frq = frq - STEP;
  }
}

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

void sq_set(){
  if(Flg_sq == 0){
    SQ = "0";
    Flg_sq = 1;
  }
  else{
    SQ = "1";
    Flg_sq = 0;
  }
  while(digitalRead(SW_SQ) == LOW);
}

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

void eep_init(){
  int i;

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

  eep_write4(DEF_FREQ,Eep_Freq);
  EEPROM.write(Eep_Init,Int_End);           // Init end set(73)
}

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

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

void eep_wdata(){
  eep_write4(frq,Eep_Freq);
}

//---------- 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) {
  unsigned int wk;

  oled.setFont(lcdnums14x24);
  oled.setCursor(16, 2);
  wk = sf_rx / 1000;
  oled.print(wk);
  oled.print('.');
  wk = sf_rx % 1000;
  if (wk < 100)
    oled.print('0');
  if (wk < 10)
    oled.print('0');
  oled.print(wk/10*10);
}

//---------- SR-FRS Initialize ----------------------------------------

void frs_init(){
  Serial.println("AT+DMOCONNECT");    // Se connecter au module
  Serial.println("");
  Serial.println("");
  delay (500);
}

//---------- SR-FRS data set ------------------------------------------

void frs_set(){
  TFV = RFV = frq;

  Serial.print("AT+DMOSETGROUP=");    // Commencer un message
  Serial.print(GBW);
  Serial.print(',');
  Serial.print((float)TFV / 1000, 4);
  Serial.print(',');
  Serial.print((float)RFV / 1000, 4);
  Serial.print(',');
  Serial.print(RXCXCSS);
  Serial.print(',');
  Serial.print(SQ);
  Serial.print(',');
  Serial.print(TXCXCSS);
  Serial.print(',');
  Serial.println(FLAG);
  delay(20);
}

//---------- Volt Meter -----------------------------------------------

void volt_disp(){
  v = analogRead(A1) * 1.1;
  v = v / 1023;
  v = v / 2200;
  v = v * 12200;

  if(vb != v){
    oled.setFont(Arial_bold_14);
    oled.setCursor(1, 0);
    oled.print("        ");
    oled.setCursor(1, 0);
    oled.print(v,2);
    oled.print('V');
    vb = v;
  }
}




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