2023年8月31日木曜日

oled85 VFO

ATtiny85でoledを使ったVFOを作った。oled ssd1306ライブラリに今迄SSD1306Ascii.hを使っていたが、ATtiny85では使えない事が判った。そこで、Tiny4kOLED.h使うことにした。しかし、Tiny4kOLED.hを使うとフォントが異なる為表示が変わってしまうので、フォントも移植する事にした。フォント移植により、表示の違いはない。ATtiny85に移植した所、表示が遅い事が判った。ATtiny85割込みでattachInterruptを使うと、表示が遅くなる事が判った。


回路図

 

ATtiny85のpin1は、RESETとAnalog DIを兼ねている。pin1はレベルLoになるとリセット動作するので、抵抗分圧でレベルhiの時、DI動作させている。





ATtiny85パラメータ

チップ設定は、この様にした。










Tiny4koled.hインストール


ライブラリを/srcに移したかったが、エラーが出たので諦めた。Tiny4koled.zipを同梱したので必要であればインストール。

スケッチ

///////////////////////////////////////////////////////////////////////
//  si5351a oled(128x32) ATtiny85 VFO program ver.1.0
//    Copyright(C)2023.JA2GQP.All rights reserved.
//
//                                                2023/8/27
//                                                  JA2GQP
//--------------------------------------------------------------------
//  Function
//    1.STEP(10k,1k,100,10)
//    2.EEPROM memorry save/reload
//    3.Protection Operation At The Time Of Transmission
//    4.S-Meter
//    5.mono band
//////////////////////////////////////////////////////////////////////

#include "src/Rotary.h"              
#include "src/si5351a21.h"
#include <Tiny4kOLED.h>
#include "font/Tlcdnums14x24.h"
#include "font/Tlabels.h"
#include "font/Tpixels.h"
#include <EEPROM.h>

////////////////////////////////
// Set Device
////////////////////////////////
#define SW1 (val < 545) && ( val >= 525)  // SW1(1.70V)
#define SW2 (val < 710) && ( val >= 690)  // SW2(2.23V)
#define SW3 (val < 790) && ( val >= 770)  // SW3(2.50V)
#define SW4 (val < 840) && ( val >= 820)  // SW4(2.65V))

#define SW_TX   SW1
#define SW_STEP SW2
#define SW_MODE SW3
#define SW_RIT  SW4
#define ENC_A   PB1
#define ENC_B   PB3

////////////////////////////////
// Set Device
////////////////////////////////
Rotary r = Rotary(ENC_B,ENC_A);

////////////////////////////////
// EEPROM Memory Address
////////////////////////////////
const byte  Eep_Init = 0x00;        // Eep Init(1byte*1)
const byte  Eep_Band = 0x01;        // Band(1byte*1)
const byte  Eep_Mode = 0x02;        // Mode(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] = {
 // DEF     LOW       HI
  7050000 ,7000000 ,7200000
  };

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

////////////////////////////////
// I/O Port
////////////////////////////////
const byte AD_IO = A0;                // analog DI
const byte AD_SM = A2;                // S-meter

//---- IF Frequency & Bfo data -----
const unsigned long IF_FREQ = 10700000L;  

const unsigned long BFO_TBL[3] = {
//   CW       LSB      USB
  10700600,10701500,10698500
  };
const byte CW  = 0;               // CW
const byte LSB = 1;               // LSB
const byte USB = 2;               // USB


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

////////////////////////////////
// Memory Assign
////////////////////////////////
unsigned long Vfo_Dat;                // VFO Data
unsigned long Enc_Step;               // STEP
int Rit_Dat;                          // RIT Data
unsigned int Val_Smeter = 0;
unsigned int Val_Smeterb = 1;

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

byte Byt_Mode = LSB;
byte Flg_Over;
byte Flg_Tx = 0;
byte Flg_Rit = 0;                     // RIT Flag
long Lng_Wk;                        // Long Work
int  Int_Wk;                        // Int Work
unsigned int val;

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

void setup() {
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);          

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

  oled.begin();
  oled.clear();
  oled.on();

  //----------  EEPROM initialyze
  if(EEPROM.read(Eep_Init) != Int_End){    
    delay(10);
    eep_init();
  }
  val = analogRead(AD_IO);
  delay(10);
  if(SW_STEP){
    delay(10);
    eep_init();
    fover_disp();
    do{
      val = analogRead(AD_IO);
      delay(10);
    }
    while (SW_STEP);
  }

  eep_rdata();
  step_disp(Enc_Step);
  mode_disp(Flg_Tx,Byt_Mode);
}

//----------  main(Loop)  -----------------------------------------------

void loop() {

////////////////////////
//        reseive
////////////////////////

  val = analogRead(AD_IO);
  delay(10);
  if(!SW_TX){
    Flg_Tx = 0;
    si5351a_enable(0x00);                  // si5351 output enable

    //----------  RIT proc
    if(Flg_Rit == 1){
      si5351aSetFrequency(Vfo_Dat + IF_FREQ + Rit_Dat);
      rit_disp(Rit_Dat);
    }          
    //---------- normal proc(not RIT)
    else{
      band_check();                        // range check
      si5351aSetFrequency(Vfo_Dat + IF_FREQ);
      freq_disp(Vfo_Dat);                  // frequency display
    }

    //----------  STEP SW check
    val = analogRead(AD_IO);
    delay(10);
    if(SW_STEP){
        enc_step();
        step_disp(Enc_Step);
        do{
          val = analogRead(AD_IO);
          delay(10);
        }
        while(SW_STEP);
    }

    //----------  MODE SW check
    val = analogRead(AD_IO);
    delay(10);
    if(SW_MODE){
      mode_set();
      mode_disp(Flg_Tx,Byt_Mode);
      do{
        val = analogRead(AD_IO);
        delay(10);
      }
      while(SW_MODE);
    }

    //---------- RIT SW check
    val = analogRead(AD_IO);
    delay(10);
    if(SW_RIT){    
      if(Flg_Rit == 0)
        Flg_Rit = 1;
      else{
        Flg_Rit = 0;
        Rit_Dat = 0;
      }
      do{
        val = analogRead(AD_IO);
        delay(10);
      }
      while(SW_RIT);
    }
  }

////////////////////////
//        trancemit
////////////////////////

  else{
    Flg_Tx = 1;
    if(Flg_Over == 1){                      // frequency limit over?
      fover_disp();
      si5351a_enable(0x05);                 // VFO and BFO off
    }                                    
    else{
      freq_disp(Vfo_Dat);                   // frequency display
      si5351aSetFrequency(Vfo_Dat + IF_FREQ);
    }
  }

////////////////////////
//        common
////////////////////////

  mode_disp(Flg_Tx,Byt_Mode);
  si5351aSetFrequency2(BFO_TBL[Byt_Mode]);  // Bfo data out  

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

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

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

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

  if(Flg_Tx == 0){
    if (result) {
      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 = millis();
    }
  }
}

//----------  mode set  -----------------------------------------------

void mode_set() {
  if (Byt_Mode == 2)
    Byt_Mode = 0;                        
  else
    Byt_Mode++;
}

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

void band_check(){
  if((Vfo_Dat >= FRQ_TBL[LOW_F])
      && (Vfo_Dat <= FRQ_TBL[HI_F]))
    Flg_Over = 0;
  else
    Flg_Over = 1;
}

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

void eep_init(){
  int i;

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

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

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

void eep_rdata(){
  Vfo_Dat = eep_read4(Eep_Freq);
  Enc_Step = eep_read4(Eep_Step);
  Byt_Mode = EEPROM.read(Eep_Mode);
}

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

void eep_wdata(){
  eep_write4(Vfo_Dat,Eep_Freq);
  eep_write4(Enc_Step,Eep_Step);
  EEPROM.write(Eep_Mode,Byt_Mode);       // mode LSB
}  
   
//----------  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;
}

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

void enc_step() {
  if (Enc_Step == 10000)
    Enc_Step = 10;                        // 1000 Hz, round to XX.XXX.000
  else
    Enc_Step = Enc_Step * 10;
}


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

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

  a = (Val_Smeter + 3) / 113;  // 1024 / 9 characters
  oled.setFont(&Tpixels);
  oled.setCursor(25, 3);
  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
}

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

void step_disp(unsigned long stp) {
  oled.setCursor(109, 3);
  oled.setFont(&Tlabels);
  if (stp == 10)
    oled.print("5");                        // 10
  else if(stp == 100)
    oled.print("7");                        // 100
  else if(stp == 1000)
    oled.print("8");                        // 1k
  else
    oled.print("9");                        // 10k
}

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

void mode_disp(byte flg,byte mod) {
  oled.setCursor(2, 3);
  oled.setFont(&Tlabels);
  if(flg == 1)
    oled.print("1");                        // "1" is "TX" in labels.h
  else{
    if(mod == 0)
      oled.print("2");                      // "2" is "CW" in labels.h
    else if(mod == 1)
      oled.print("3");                      // "3" is "LSB"
    else if(mod == 2)
      oled.print("4");                      // "4" is "USB"
  }
}

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

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

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

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

  oled.setFont(&Tlcdnums14x24);
  oled.setCursor(1, 0);
  oled.print("::::");

  if (rit_data < 0)
    oled.print('-');
  else
    oled.print('+');

  rit = abs(rit_data);
   ri = rit / 1000;  
  oled.print(ri);
  oled.print('.');
  ri = (rit % 1000) / 10;
  if (ri < 10)
    oled.print('0');
  oled.print(ri);
}

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

void freq_disp(uint32_t sf_rx) {
  uint16_t fr;

  oled.setFont(&Tlcdnums14x24);
  oled.setCursor(1, 0);
  fr = sf_rx / 1000000;
  if (fr < 10)
    oled.print(':');                        // ':' is changed to ' '
  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);
}

mono BAND用で、96%のメモリー消費である。










ダウンロード

必要なファイルはdownload siteのattiny85フォルダにある、oled85VFO.zipを
ダウンロードすれば良い。

4 件のコメント:

  1. 素晴らしい作品の公開ありがとうございます。
    現在、本作品に取り組んでおります。
    ちょっとわからないことがあり、二点ほどご確認させください。

    1.
    まず、本作品を焼くときのFUSE設定ですが
    8MHz(Internal)の時に LFUSEですがデフォルトのL:62ではなくL:E2の設定でCKDIV8を無効にされた設定で焼かれたのでしょうか?
    (3.3V動作ですが16MHz(PLL)でL:F1も試しているところです)
    L:62だとちょっともっさりした動きでしたので確認させてください。

    2.
    周波数の表示についてですが、以前のsi5351a VFO Ver1.1では
    freq+ifshift として周波数表示がキャリア周波数のところに
    になっていたと認識していましたが最近のコードでは
    IF_FREQとなっていて、表示周波数に違和感がありました。
    本コードも、BFO_TBL[Byt_Mode]のオフセットにするようにすれば
    最近の無線機と同じようになるのでは?と思ったのですが
    これまでのコードから変更された理由は何かあるのでしょうか?

    このoled85VFOのコードは回路図もコードも洗練されているので
    モノバンドではいろいろシンプルに楽しめそうに思っております。

    よろしくお願いします。

    返信削除
  2. 7n4mis OM
    1.アセンブラーではないので、特にfuseといった概念は有りません。
    ただ、chip設定画面でパラメータを設定するのみです。この結果がfuseに反映されるはずです。パラメータ設定した内容は画面コピーの通りです。

    2.周波数セ帝に関して考え方の相違です。基本的な考え方は、キャリア周波数を表示すべきだと考えてます。例えばLSB/USB/CWとモードを変えた時、受信周波数が変わること自体不自然です。最近の無線機はいろんな設定ができ、キャリア周波数での表示も可能です。

    返信削除
  3. This VFO is neither DDS nor nano.
    If you want to customize it to your specifications, please do it yourself.
    As I wrote at the top of the blog, I don't customize it.

    返信削除