合同会社ひめぽーと は、愛媛の店舗、生産者様のホームページ作成・制作・管理、ネットショップ作成・制作・管理、パソコン関連サポートなど、バックアップ致します。

アーカイブ: ブログ

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)作成 その2


GPS周波数標準器 完成!

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)作成

1/10分周回路も追加して、1MHZも出力。

Arduinoを16MHz化して5MHZを計測することで、初期より周波数変動への応答性を向上(途中で24MHZ化しましたが、10MHz直接計測には波形整形にLS14を使用していたことでマージンが確保できず、LS14をHC14など高速なものにするか直接クロックを入力すればOKなんだが....手抜きで断念。)

割り込み処理ルーチンをリアルタイム性の低い部分を切り離し、縮小することで、GPSモジュールとのソフトウェアシリアルでのエラー発生を低減

最終的なスケッチは、以下。

電源ON後15分後以降、GPSから1ppsを得られていれば、周波数補正開始、電源ON後、約50分で実用レベル(±0.007Hz以下)に安定します。

/*
   GPS base 10MHz with MCP4725
   カウンタ変数をULL化
   CPUを8MHzから16MHzに、それに伴いダイレクト計測周波数も2.5MHzから5MHzへ
  ・割り込み処理ルーチンを最低限の処理にする。
  ・周波数測定方法を100s間隔で周波数を監視しながら、最適な場所で補正をかけることで
  環境温度などによる周波数偏移への対応速度を早くしつつ周波数の確度も向上させる。
  ->温度に顕著な変動の兆候がなければ、周波数測定を継続する。
  流用の都合でLCD接続(Encoder共通)が8線接続となっているが、新規作成の場合は
  EncoderをSPI接続のものに変更して、4線接続とすることで信号ラインの不足を補える。
*/
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac; // constructor
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <TinyGPS++.h>
TinyGPSPlus gps;
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(6, 7); // RX, TX
#define rs 2
#define enable 3
#define rw 9
#define d0 A3
#define d1 10
#define d2 A2
#define d3 11
#define d4 A1
#define d5 12
#define d6 A0
#define d7 13
LiquidCrystal lcd(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
#define enter_sw A6   // front SW 10以下 ON 2000以上 OFF
#define en_en 4       // EAW - Absolute Contacting Encoder LCD DATA LINE共通
#define ocxo 5;       //OCXO 入力ピン
#define gps1ppm 8;    //GPS 1Hz 入力ピン
const uint8_t debug = 0; // 1: debug code output
uint32_t debug_f = 0 ;
const uint8_t ratio = 2; // 分周比
/* 精度設定 */
uint8_t accuracy = 0; /* 単位の周波数測定のカウンター */
const uint8_t accuracy_max = 20; /* 計測時間とDAC精度を考えると10で十分と思うが... */
#define target 10000000 /* 設定周波数 */
const uint64_t count_ref = target * (100 / ratio);
/* VOCXO Voltage */
const int dacvoltmax = 4095;
const int dacvolt_norm = 2048;
int dacvolt = dacvolt_norm; /* VOCXO Voltage = 2.0V 初期値:2048 */
int dacvolt_d = 0; /* 値はダミー */
volatile uint16_t intcarry;
volatile uint16_t carry;    /* 割り込みルーチン内で割り込み追い越しを配慮した溢れ回数を保存*/
volatile uint32_t start_cnt;
volatile uint32_t end_cnt;
volatile uint16_t gps_loop;
uint64_t  freq1000;
uint8_t ignore;
uint64_t count[accuracy_max] = {};
uint32_t count_chk;
volatile uint8_t f_check; //周波数処理確認フラグ 1:処理が必要な状態、0:処理完了 2:追い越しが発生!
int val;
uint8_t enter;
int val_old;
uint8_t encode_old = 0;
char gps_loop_msg3[4] = "***";
char freq_msg15[16] = " ********.**Hz ";
const uint8_t encode[256] =                    //もし255なら読み取りミスで無視する。
{
  56, 40, 55, 24, 255, 39, 52, 8, 57, 255,
  255, 23, 255, 36, 13, 120, 255, 41, 54, 255,
  255, 255, 53, 7, 255, 255, 255, 20, 19, 125,
  18, 104, 105, 255, 255, 25, 106, 38, 255, 255,
  58, 255, 255, 255, 255, 37, 14, 119, 118, 255,
  255, 255, 107, 255, 255, 4, 255, 3, 255, 109,
  108, 2, 1, 88, 255, 89, 255, 255, 255, 255,
  51, 9, 10, 90, 255, 22, 11, 255, 12, 255,
  255, 42, 43, 255, 255, 255, 255, 255, 255, 255,
  255, 21, 255, 126, 127, 103, 255, 102, 255, 255,
  255, 255, 255, 255, 255, 91, 255, 255, 255, 255,
  255, 116, 117, 255, 255, 115, 255, 255, 255, 93,
  94, 92, 255, 114, 95, 113, 0, 72, 71, 255,
  68, 73, 255, 255, 29, 255, 70, 255, 69, 255,
  255, 35, 34, 121, 255, 122, 255, 74, 255, 255,
  30, 6, 255, 123, 255, 255, 255, 124, 17, 255,
  255, 255, 67, 26, 255, 27, 28, 255, 59, 255,
  255, 255, 255, 255, 15, 255, 255, 255, 255, 255,
  255, 255, 255, 5, 255, 255, 255, 110, 255, 111,
  16, 87, 84, 255, 45, 86, 85, 255, 50, 255,
  255, 255, 46, 255, 255, 255, 33, 255, 83, 255,
  44, 75, 255, 255, 31, 255, 255, 255, 255, 255,
  255, 255, 32, 100, 61, 101, 66, 255, 62, 255,
  49, 99, 60, 255, 47, 255, 255, 255, 48, 77,
  82, 78, 65, 76, 63, 255, 64, 98, 81, 79,
  80, 97, 96, 112
};
uint8_t read_Enter() { // Enter SW
  uint8_t value;
  int enter_val = analogRead(enter_sw);
  if (enter_val < 100) value = 1;
  else value = 0;
  return value;
}
uint8_t read_Encoder() { // Encoder(8bits)の値を読む
  uint8_t value;
  pinMode(d0, INPUT);
  pinMode(d1, INPUT);
  pinMode(d2, INPUT);
  pinMode(d3, INPUT);
  pinMode(d4, INPUT);
  pinMode(d5, INPUT);
  pinMode(d6, INPUT);
  pinMode(d7, INPUT);
  //   digitalWrite(en_en, LOW);
  PORTD &= ~_BV(4);
  value = digitalRead(d7) * 128 + digitalRead(d6) * 64 + digitalRead(d5) * 32 + digitalRead(d4) * 16;
  value = value + digitalRead(d3) * 8 + digitalRead(d2) * 4 + digitalRead(d1) * 2 + digitalRead(d0);
  //value = (PINB & _BV(5)) * 128 + (PINC & _BV(0)) * 64 + (PINB & _BV(4)) * 32 + (PINC & _BV(1)) * 16;
  //value = value + (PINB & _BV(3)) * 8 + (PINC & _BV(2)) * 4 + (PINB & _BV(2)) * 2 + (PINC & _BV(3));
  //    digitalWrite(en_en, HIGH);
  PORTD |= _BV(4);
  pinMode(d0, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT);
  value = encode[value - 1];
  if (value == 255) value = encode_old; else encode_old = value;
  return value;
}
ISR(TIMER1_CAPT_vect)
{
  /*
    WDT Reset
    if(カウンターフラグ無効)
      WDT起動
      ICR1に保存された値を保存
      カウンターフラグ有効に
    else
      ICR1に保存された値を保存
      溢れ割り込み追い越しをチェック
      桁溢れ回数を保存(intcarry -> carry + 1?)
      周波数処理確認フラグ += 1
    if(周波数処理確認フラグ > 1)
      発生回数・オーバーフロー回数を初期化
      カウンターフラグ無効
  */

  wdt_reset();
  gps_loop += 1;

  if (ignore) {
    if (start_cnt > 0) {
      /* 最初の1回はICR1値が不安定なので無視 */
      ignore = 0;
      /* GPS 1ppm 監視 WDT start */
      {
        MCUSR = 0;
        WDTCSR |= 0b00011000; //WDCE WDE set
        WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
      }
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < 9000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
    }
    intcarry = 0;
    gps_loop = 0;

  } else {
    if (gps_loop == 100) {
      end_cnt = (word)ICR1;
      /* キャり追い越し補正する */
      if ((end_cnt < 9000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
        intcarry += 1;
        TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      }
      carry = intcarry;
      f_check += 1;
      gps_loop = 0;
      intcarry = 0;
    }
  }
  if (f_check > 1) {
    ignore = 1;
    f_check = 0;
  }
}
ISR(TIMER1_OVF_vect) {
  /* オーバーフロー回数を加算 */
  if (!ignore)intcarry += 1;
}
/*
  WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset();
*/
ISR(WDT_vect) {
  wdt_reset();
  /*
      カウンターフラグを無効にする
      発生回数・オーバーフロー回数を初期化
  */
  start_cnt = 0;
  gps_loop = 0;
  ignore = 1;
  f_check = 0;
  {
    MCUSR = 0;
    WDTCSR |= 0b00011000; /* WDCE WDE set */
    WDTCSR =  0b00000000; /* status clear */
  }
}
void setup()
{
  pinMode(en_en, OUTPUT);
  digitalWrite(en_en, HIGH);
  delay(1);

  lcd.begin(20, 2);
  lcd.setCursor(0, 0);
  lcd.print("OCXO + MCP4725 10MHz");
  lcd.setCursor(0, 1);
  lcd.print(" 2018.12 by Drg.Jr. ");

  Serial.begin(115200);
  Serial.println("Hello World!");
  Serial.println("OCXO + MCP4725 10MHz"); Serial.println("");

  // pulse gen setup init clock
  // initialize the MCP4725
  dac.begin(0x60);
  dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する

  delay(5000);

  gpsSerial.begin(9600); // ソフトウェアシリアルの初期化
  gpsSerial.print("\r\n");
  gpsSerial.print("\r\n");
  /*
    u-blox 6
    Receiver Description
    Including Protocol Specification を参考
  */
  gpsSerial.print("$PUBX,41,1,0007,0003,4800,0*13\r\n");   // GPSモジュールのbpsを変更
  gpsSerial.begin(4800);                                   // ソフトウェアシリアルの再初期化
  gpsSerial.print("\r\n");
  gpsSerial.print("\r\n");


  //  lcd.clear();

  intcarry = 0;
  start_cnt = 0;
  end_cnt = 0;
  freq1000 = 0;
  ignore = 1;
  gps_loop = 0;
  for (int i = 0 ; i <= accuracy_max ; i ++) {
    count[i] = 0;
  }

  /*カウンターセットアップ*/
  cli();
  TCCR1A = 0;   /* TCCR1A タイマモード */
  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */
  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */
  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */
  sei(); /* 全体の割り込み許可 */
}
void loop()
{
  char msg20[21];
  char msg4[5];
  byte val;
  byte enter;

  /* GPS module Accsess */
  while (gpsSerial.available()) {
    char read_gps = gpsSerial.read();
    gps.encode(read_gps);
    if ((gps.satellites.isUpdated()) && (gps.time.isUpdated())) {

      val = read_Encoder();
      enter = read_Enter();

      char sa[3];
      byte jst = gps.time.hour() + 9;
      if (jst >= 24) jst = jst - 24;
      if (enter == 0) {
        sprintf(msg20, "GpsSat.%s   %2d:%02d:%02d",
                dtostrf(gps.satellites.value(), 2, 0, sa),
                jst, gps.time.minute(), gps.time.second() );
      } else {
        sprintf(msg20, "GpsSat.%s %4d/%02d/%02d",
                dtostrf(gps.satellites.value(), 2, 0, sa),
                gps.date.year(), gps.date.month(), gps.date.day() );
      }
      lcd.setCursor(0, 1);
      if (val != val_old) {
        lcd.print("                    ");
        lcd.setCursor(0, 1);
        switch (int(val / 16)) {
          case 1:
            lcd.print("Latitude : ");
            lcd.print(gps.location.lat(), 6); // Latitude in degrees (double)
            break;
          case 2:
            lcd.print("Longitude : ");
            lcd.print(gps.location.lng(), 6); // Longitude in degrees (double)
            break;
          case 3:
            lcd.print("Speed (m/s)");
            lcd.print(gps.speed.mps()); // Speed in meters per second (double)
            break;
          case 4:
            lcd.print("Speed (km/s)");
            lcd.print(gps.speed.kmph()); // Speed in kilometers per hour (double)
            break;
          case 5:
            lcd.print("Course (deg)");
            lcd.print(gps.course.deg()); // Course in degrees (double)
            break;
          case 6:
            lcd.print("Altitude (m)");
            lcd.print(gps.altitude.meters()); // Altitude in meters (double)
            break;
          case 7:
            lcd.print("Altitude (km)");
            lcd.print(gps.altitude.kilometers()); // Altitude in kilometers (double)
            break;
          case 8:
            lcd.print("HDOP : ");
            lcd.print(gps.hdop.value()); // Horizontal Dim. of Precision (100ths-i32)
        }
        val_old = val;
      } else {
        lcd.print(msg20);
      }

    }
  }

  /*----------Freq Counter & VOCXO Control----------*/
  if (f_check == 1) {
    count_chk = ((carry * 65536) + end_cnt) - start_cnt;
    start_cnt = end_cnt ;
    f_check = 0;

    /* 起動15分後、OCXOの周波数が安定したら周波数補正開始 */
    /* 異常値を無視する */
    if (((millis() > 900000) || (debug)) && (abs(count_chk - count_ref) < 1500)) {

      byte a0, a1;
      if (accuracy == 0) {
        a0 = 0;
        a1 = accuracy_max - 1;
      } else {
        a0 = accuracy;
        a1 = accuracy - 1;
      }

      count[a0] = count_chk;
      accuracy += 1;
      if (accuracy > accuracy_max) accuracy = 0;

      byte n = 0;
      uint64_t count_sum = 0;
      for (int i = 0 ; i < accuracy_max ; i ++) {
        if ( count[i] != 0 ) {
          n += 1;
          count_sum = count_sum + count[i];
        }
      }
      if (n > 0) {
        freq1000 = count_sum;
      } else {
        freq1000 = 0;
      }

      int count_dif = (freq1000 - (count_ref * n)) / ratio;
      int sum_dif = count_sum - count_ref * n;

      if (abs(count_dif) < 1) {
        dacvolt_d = 1;
      }
      else {
        dacvolt_d = abs(count_dif) * 2.4;
      }
      if ((sum_dif < 0) && (count[a0] < (count_ref - 0))) dacvolt += dacvolt_d;
      if ((sum_dif > 0) && (count[a0] > (count_ref + 0))) dacvolt -= dacvolt_d;
      if (dacvolt > dacvoltmax) dacvolt = dacvoltmax; else if (dacvolt < 0)dacvolt = 0;

      char s8[9];
      char acc = '-';
      if ((n / ratio) >= 10) {
        uint16_t freq1000_surplus = (freq1000 % (n * (100 / ratio))) * 1000 / (n * (100 / ratio));
        sprintf(freq_msg15, " %s.%03dHz", dtostrf(uint32_t(freq1000 / (n * (100 / ratio))), 8, 0, s8), freq1000_surplus);
        if (abs(count_dif) < 8) acc = 'o'; else acc = 'x';
      } else {
        uint16_t freq1000_surplus = (freq1000 % (n * (100 / ratio))) * 100 / (n * (100 / ratio));
        sprintf(freq_msg15, " %s.%02dHz ", dtostrf(uint32_t(freq1000 / (n * (100 / ratio))), 8, 0, s8), freq1000_surplus);
      }

      dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する

      lcd.setCursor(0, 0);
      lcd.print(acc);
      lcd.print(freq_msg15);

      if (dacvolt > dacvoltmax) {
        dacvolt = dacvoltmax;
        /* エラー表示を入れる */
        lcd.print( " NG ");
      } else if (dacvolt < 0) {
        dacvolt = 0;
        /* エラー表示を入れる */
        lcd.print( " NG ");
      } else if (abs(dacvolt - dacvolt_norm) > 500) {
        /* 警告表示を入れる */
        lcd.print( " CHK");
      } else lcd.print( "    ");

      if (debug>0 ) {
        Serial.print(msg20 ); Serial.print("," );
        Serial.print(long(freq1000 / 100) ); Serial.print(int(freq1000 % 100) ); Serial.print("," );
        Serial.print(long(count_sum / 10000) ); Serial.print(int(count_sum % 10000) ); Serial.print("," );
        Serial.print(uint32_t (count_chk) ); Serial.print("," );
        Serial.print(uint32_t (count_ref) ); Serial.print("," );
        Serial.print(n ); Serial.print("," );
        Serial.print(acc ); Serial.print("," );
        Serial.print(sum_dif ); Serial.print("," );
        Serial.print(count_dif ); Serial.print("," );
        Serial.print(dacvolt_d ); Serial.print("," );
        Serial.print(dacvolt ); Serial.print("," );
        Serial.println(gps.failedChecksum() );
      }
    } else {
      char s8[9];
      freq1000 = count_chk;
      uint16_t freq1000_surplus = (freq1000 % (100 / ratio)) * 100 / (100 / ratio);
      sprintf(freq_msg15, " %s.%02dHz", dtostrf(uint32_t(freq1000 / (100 / ratio)), 8, 0, s8), freq1000_surplus);
      lcd.setCursor(0, 0);
      lcd.print("Wait! ");
      lcd.print(freq_msg15);

    }

  }
}

注意

MCP4725 DACモジュール使用にあたりGND共通で使用する場合は、モジュールボードにある出力側のGNDは未接続にします。接続した場合、ループが形成されて動作不良が発生します。

WINGONEER MCP4725 DACモジュールI2CインタフェースDA変換

arduino 24MHZ(25MHz)動作確認用に周波数測定プログラム作成


周波数測定プログラム作成

arduino 24MHZ(25MHz)動作確認を兼ねて、基準クロック発生器のプログラムを改変し、周波数測定プログラムを作成しました。

周波数測定プログラムには、いくつか便利なライブラリーが公開されていますが、ノーマル状態でないと正常に動作しませんし、高精度と書かれているものでもそれなり程度なので、高クロックでの動作確認を兼ねて、周波数測定プログラムを作成しました。

手抜きで、システムクロックを25MHz決め打ちで作っていますが、F_CPUによってパラメータ(定数)を切り替えるようにするといろいろなシステムでも動作可能となります。システムクロックの誤差も補正できるので、測定できる周波数の上限はシステムクロックの1/2.3程度(24MHzで10.5MHz、16MHzで7MHz程度)ですが、….

arduino 24MHZ(25MHz)動作確認用に周波数測定

F_CPUによって以下の表の黄色の部分の定数に差し替えます。

TIMER2_OCR2A、ratio_vctの場所、conversion(補正値)は、10000000倍した整数にします。補正値には必要に応じてシステムクロックの補正分も盛り込みます。

F_CPUTCCR2B(CS2)TCNT2→ ratio    
        
25000000102421855.7398687214612217560.99992064629751100.007936
24999899102421755.9953299706135217560.999916606618099100.008340033694
24000000102422053.0260180995475216541.0000640040962699.9936
24000000102421654.0034562211982243500.960553278688525104.106666666667
20000000102424340.0230532786885216451.0000640040962699.9936
20000000102421645.0028801843318251390.993653337403337100.63872
16000000102425131.0019841269841251311.0000640040962699.9936
16000000102425530.517578125251311.0000640040962699.9936
800000025625162.0039682539683251621.0000640040962699.9936
8000000102421618.0011520737327216181.0000640040962699.9936

プログラム本体は、以下。

#define FAST_MODE と #define ALONE_MODE のコメントアウトするかどうかで、1Sごとの通常測定と高精度測定、外部1pps入力と自前クロック(単独モード) での測定と切替ができます。

  • 計測対象クロックは、5に入力。
  • 外部1ppsは、8に入力。
  • 単独モードの場合は、11と8を結線
  • T0はシステム、T1は周波数測定、T2は自前クロックに使用しています。
  • LCDコントラストは、簡易ソフトウェアPWM
/*
   周波数カウンター
   カウンタ変数をULL化
   CPUを8MHzから24MHzに、それに伴いダイレクト計測周波数も2.5MHzから10MHzへ
   (※現状、テスト評価用の25MHzの設定。ALONE部分はクロックに合わせて変更が必要)
  ・割り込み処理ルーチンを最低限の処理にする。
  ・周波数測定方法を1000sを最長に100s間隔で周波数を表示
  最終的に1000sでの平均周波数を表示。
   +----------------------------+
   |Frequency/stability/accuracy|
   +----------------------------+
*/
//#include <Wire.h>
#include <LiquidCrystal.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#define rs 10
#define enable 9
#define rw 7
#define d4 6
#define d5 4
#define d6 3
#define d7 2
#define v0 12 //固定 ソフトウェアPWMの関係。変更する場合はPWMの部分も修正
#define bl 13
LiquidCrystal lcd(rs, rw, enable, d4, d5, d6, d7);
const int lcd_cont = 20; //LCDコントラスト ソフトウェアPWM(%)
#define ocxo 5       //OCXO 入力ピン
#define gps1ppm 8    //GPS 1Hz 入力ピン
#define FAST_MODE     //高精度測定時は、コメントアウト
#define ALONE_MODE    //GPS 1pps使用時は、コメントアウト。単独モード。 11と8を結線
const uint8_t debug = 0; // 1: debug code output
uint32_t debug_f = 0 ;
#define TIMER2_OCR2A_VAL  217                               // F_CPUによりratioと組み合わせ決定:224
#define TIMER2_TCCR2B_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // F_CPUによりratioと組み合わせ決定:1024
#ifdef ALONE_MODE
#define TIMER2_TCCR2A_VAL (1<<COM2A0) | (1<<WGM21) // 比較一致でOC2Aピン トグル(交互)出力&CTC動作
const uint32_t conversion = 9999166;   // 補正値(1/10000000)、計算値+OSC誤差の吸収
const uint8_t ratio = 1;
const uint16_t ratio_vct = 56 * ratio; // 分周比を考慮したカウント単位,F_CPUにより決定
const uint8_t accuracy_max = 5; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
volatile uint16_t carry_chk = 3000; /* キャリー追い越しチェック幅 -65535 */
#else
#define TIMER2_TCCR2A_VAL 0 // OC2Aピン 切断&標準動作
const uint32_t conversion = 10000000; // 補正値(1/10000000)
volatile uint16_t carry_chk = 6000; /* キャリー追い越しチェック幅 -65535 */
#ifdef FAST_MODE
const uint8_t ratio = 1; // 分周比を考慮したカウント単位
const uint8_t ratio_vct = ratio;
const uint8_t accuracy_max = 1; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
#else
const uint8_t ratio = 100; // 分周比を考慮したカウント単位
const uint8_t ratio_vct = ratio;
const uint8_t accuracy_max = 10; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
#endif
#endif
/* 精度設定 */
uint8_t accuracy = 0; /* 単位の周波数測定のカウンター */
volatile uint16_t intcarry;
volatile uint16_t carry;    /* 割り込みルーチン内で割り込み追い越しを配慮した溢れ回数を保存*/
volatile uint32_t start_cnt;
volatile uint32_t end_cnt;
volatile uint16_t gps_loop;
uint64_t  freq1000;
uint8_t ignore;
uint32_t count[accuracy_max] = {};
uint64_t count_chk;
volatile uint8_t f_check; //周波数処理確認フラグ 1:処理が必要な状態、0:処理完了 >2:処理追い越しが発生!
void timer_start(void)
{
  TCCR2A = TIMER2_TCCR2A_VAL;
  OCR2A = TIMER2_OCR2A_VAL;
  GTCCR = (1 << PSRASY);
  TCCR2B = TIMER2_TCCR2B_VAL;
  //  TIMSK2 = (1<<OCIE2A);
  DDRB |= (1 << PB3);
  //  PORTB &= ~_BV(PB3);
}
ISR(TIMER1_CAPT_vect) {
  /*
    WDT Reset
    if(カウンターフラグ無効)
      WDT起動
      ICR1に保存された値を保存
      カウンターフラグ有効に
    else
      ICR1に保存された値を保存
      溢れ割り込み追い越しをチェック
      桁溢れ回数を保存(intcarry -> carry + 1?)
      周波数処理確認フラグ += 1
    if(周波数処理確認フラグ > 1)
      発生回数・オーバーフロー回数を初期化
      カウンターフラグ無効
  */

  wdt_reset();
  ++ gps_loop;

  if (ignore > 0) {
    /* 最初はICR1値が不安定なので無視 */
    -- ignore ;
    /* GPS 1ppm 監視 WDT start */
    {
      MCUSR = 0;
      WDTCSR |= 0b00011000; //WDCE WDE set
      WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < carry_chk) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
    }
    intcarry = 0;
    gps_loop = 0;

  } else {
    if (gps_loop == ratio_vct) {
      end_cnt = (word)ICR1;
      /* キャり追い越し補正する */
      if ((end_cnt < carry_chk) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
        ++ intcarry;
        TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      }
      carry = intcarry;
      ++ f_check;
      gps_loop = 0;
      intcarry = 0;
    }
  }
  if (f_check > 1) {
    ignore = 2;
    f_check = 0;
  }
}
ISR(TIMER1_OVF_vect) {
  /* オーバーフロー回数を加算 */
  if (!ignore) ++ intcarry;
}
/*
  WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset();
*/
ISR(WDT_vect) {
  wdt_reset();
  /*
      カウンターフラグを無効にする
      発生回数・オーバーフロー回数を初期化
  */
  start_cnt = 0;
  gps_loop = 0;
  ignore = 2;
  f_check = 0;
  {
    MCUSR = 0;
    WDTCSR |= 0b00011000; /* WDCE WDE set */
    WDTCSR =  0b00000000; /* status clear */
  }
}
void setup() {
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("FreqCounter 10MHz");
  lcd.setCursor(0, 1);
  lcd.print(" 2018.12 by Drg.Jr. ");
  pinMode(v0, OUTPUT);
  //  digitalWrite(v0, LOW);     // LCD コントラスト
  //  PORTB &= ~_BV(4); //LOW    // SW PWMの為、PINコントロールを高速化
  //  PORTB |= _BV(4); //HIGH

  pinMode(bl, OUTPUT);
  digitalWrite(bl, HIGH);    // LCD バックライト ON

  if (debug) {
    Serial.begin(115200);
    Serial.println("Hello World!");
    Serial.println("");
  }


  //  lcd.clear();


  intcarry = 0;
  start_cnt = 0;
  end_cnt = 0;
  freq1000 = 0;
  ignore = 2;
  gps_loop = 0;
  f_check = 0;

  /*カウンターセットアップ*/
  timer_start(); //Stand alone用 基準クロック発生PB3/pin11

  cli();
  TCCR1A = 0;   /* TCCR1A タイマモード */
  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */
  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */
  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */
  sei(); /* 全体の割り込み許可 */
}
void loop() {
  /*----------Freq Counter & VOCXO Control----------*/
  if (f_check == 1) {

    count_chk = ((carry * 65536) + end_cnt) - start_cnt;
    start_cnt = end_cnt ;

    f_check = 0;

    if (!ignore) {
      count_chk = count_chk * conversion / 10000000; // 既知誤差の補正
      count[accuracy] = count_chk;

      if (accuracy < (accuracy_max - 1))accuracy += 1; else accuracy = 0;

      byte n = 0;
      uint64_t count_sum = 0;
      for (int i = 0 ; i < accuracy_max ; ++i) {
        if (count[i] > 0) {
          ++n;
          count_sum = count_sum + count[i];
        }
      }
      if (n > 0) {
        freq1000 = count_sum;
      } else freq1000 = 0;

      char msg15[15];
      char s8[9];
      if ((ratio * n) >= 1000) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 1000 / (ratio * n);
        sprintf(msg15, " %s.%03dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else if ((ratio * n) >= 100) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 100 / (ratio * n);
        sprintf(msg15, " %s.%02dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else if ((ratio * n) >= 10) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 10 / (ratio * n);
        sprintf(msg15, " %s.%01dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else {
        sprintf(msg15, " %sHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8));
      }

      lcd.setCursor(0, 2);
      lcd.print("                    ");
      lcd.setCursor(0, 2);
      lcd.print(msg15);

      if (debug) {
        char msg4[4];
        sprintf(msg4, "%04d", uint16_t(freq1000 % 10000));
        Serial.print(uint32_t(freq1000 ));  Serial.print("," );
        Serial.print(msg4 );  Serial.print("," );
        Serial.print(long(count_chk ) ); Serial.print("," );
        Serial.print(n ); Serial.print("," );
        Serial.print(accuracy ); Serial.println("" );
      }

    } else {
      count[accuracy_max] = {};
      accuracy = 0;
    }

  }
  //--------------------------------------------
  /*
      LCDコントラスト調整
      高速化の為に直接レジスタを操作
      LCDコントラストの制御ピンを変更する場合は注意
  */
  int pwb_time = (micros() % 1000) / 10;
  if (pwb_time > lcd_cont) PORTB &= ~_BV(4); /*LOW*/ else PORTB |= _BV(4); /*HIGH*/
}

最後に、基準クロック発生器とツーショット

基準クロック発生器 と オーバークロック動作確認中のArduino 25MHz

Arduinoを24MHzで動かす ~覚書~


秋月電子のATmega168/328マイコンボードキットを24MHzで動かす方法

まずは、16MHzでArduino化します。

方法は、いろいろな方が説明されているので、そちらをじっくり読めばできると思います。

実は、私は組立後ロクに確認もしないで行ったので、ブートローダーの書き込みは完了するのにスケッチが書き込めなくハマってしまいました。

原因は、ハンダ忘れが2か所も….汗

まぁ、うまくいかないときこそ、基本に立ち返って、ですね。

Arduino化が正常に完了したら、いよいよ24MHzで動作させましょう!

インターネット上に公開されている情報からは、ほぼ30MHzまでは動作するようなので、事前チェックは不要かもしれませんが、まず、16MHzの状態でシリアルコンソール74880bpsで通信できるスケッチを書き込んでおいて、24MHzにして、シリアルコンソールを11520bpsにして期待値どおり表示が出ることを確認します。

Arduino 1.8.5にArduinor Diecimila 24MHz版を追加する。

boards.txtを編集します。

Arduino 1.8.5をインストールした場所(例えばc:\Program Files (x86)\Arduino\hardware\arduino\avr)にもありますが、編集するのは、「ユーザーフォルダー\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.23」にあるファイルです。インストールした場所にあるのはインストール時のオリジナルで、こちらを編集しても反映されません。

反映されない場合は、一度ボードマネージャを立ち上げると反映されると思います。Updateは上書きされる可能性があるので、少なくとも修正部分は控えておくと良いと思います。

「Arduino Duemilanove or Diecimila w/ ATmega328P」の定義を丸ごとコピーして、以下のように編集して差し込みます。

編集の肝は、f_cpu と upload.speed

##############################################################

diecimila24.name=Arduino Diecimila 24

diecimila24.upload.tool=avrdude
diecimila24.upload.protocol=arduino

diecimila24.bootloader.tool=avrdude
diecimila24.bootloader.low_fuses=0xFF
diecimila24.bootloader.unlock_bits=0x3F
diecimila24.bootloader.lock_bits=0x0F

diecimila24.build.f_cpu=24000000L
diecimila24.build.board=AVR_DUEMILANOVE24
diecimila24.build.core=arduino
diecimila24.build.variant=standard

## Arduino Diecimila 24 w/ ATmega328P
## ———————————————-
diecimila24.menu.cpu.atmega328=ATmega328P

diecimila24.menu.cpu.atmega328.upload.maximum_size=30720
diecimila24.menu.cpu.atmega328.upload.maximum_data_size=2048
diecimila24.menu.cpu.atmega328.upload.speed=86400

diecimila24.menu.cpu.atmega328.bootloader.high_fuses=0xDA
diecimila24.menu.cpu.atmega328.bootloader.extended_fuses=0xFD
diecimila24.menu.cpu.atmega328.bootloader.file=atmega/ATmegaBOOT_168_atmega328.hex

diecimila24.menu.cpu.atmega328.build.mcu=atmega328p

## Arduino Diecimila 24 w/ ATmega168
## ———————————————
diecimila24.menu.cpu.atmega168=ATmega168

diecimila24.menu.cpu.atmega168.upload.maximum_size=14336
diecimila24.menu.cpu.atmega168.upload.maximum_data_size=1024
diecimila24.menu.cpu.atmega168.upload.speed=28800
diecimila24.menu.cpu.atmega168.bootloader.high_fuses=0xdd
diecimila24.menu.cpu.atmega168.bootloader.extended_fuses=0xF8
diecimila24.menu.cpu.atmega168.bootloader.file=atmega/ATmegaBOOT_168_diecimila.hex

diecimila24.menu.cpu.atmega168.build.mcu=atmega168

 

これで、シリアルコンソールや基本的な時間関数などは、正常に動作するようになり、特に24MHz化を気にする必要はなくなります。

ただ、ライブラリーで、CPUクロックf_cpuを考慮していない場合は注意が必要です。

16MHz版に対して1.5倍のSpeedとなりました。

先の記事の10HHz 標準クロックの周波数測定も、これを使えば、直接10MHzを扱えるので同じ精度が4倍のスピードで完了して環境温度等による周波数変動に対する追従特性の大幅な向上が可能となります。

問題点

今回実験に使用したボードは、たまたまリセット時間が長いのか?もしくは168Pだからか?スケッチの書き込みも問題ない様ですが、Arduinoのブートローダーの仕組み上、CPUが高速になるとブートローダーが待ちきれず書き込み済みのスケッチに動作を移すことがあるようです。

先の記事の作成に使用したのと同じ怪しいArduino Pro mini 8MHzで24MHz動作を確認したところまさにこの現象が発生しました。AVR自体は32MHzでもちゃんと動作しているのに20MHz以上のクロックではスケッチが書き込めない事象で、以下の暫定対策で24MHzでも書き込みできることを確認しました。

高速CPUに対応できるブートローダーへ変更が正しい対応と思いますが、暫定回避策としてDTRとリセットの間のコンデンサーの容量を大きくするなど物理t的にリセットパルスを少し長くする(電源オフ時に残留電荷で壊す可能性もありますし、どのくらいか難しいですが...)対策でもなんとかなりそう。。。。最悪、タイミングが難しいが人間リセットも。。。汗。(RXでLEDが光るようになっていればこれが光ったのをみてResetを解除すればよいらしい。)

実際にやってみましたが、コンデンサーを取り付ける方法は有効ですが、壊す可能性もあり、手動リセットはオーバークロックの場合は、タイミングが難しくちょっと無理っぽいので、Arduino側のReset入力のコンデンサーをショート(もしくはパス)して、USB-Serial変換ボード側でDTRをトリガーで負パルスを発生するようにNE555(100uF、100kVR)でワンショット、出力を少しなまらして(TrのBaseとGND間に200Ωと33uF)、Trでひっくり返してArduinoのDTR(Reset)へという専用書き込み器(ジャンパーで通常モードと切替)を作成しました。クロック・個体種別によってパルス幅は調整が必要かもしれませんが、標準のリセットのタイミングより少し遅らせる(なまらせた部分)ことが重要なポイントとなりました。

今のところ100%成功しています。AVRの本来の低消費電力に反しますが、目的によっては、24MHz(簡単な確認では32MHzでも問題ない模様で十分なマージンがあると思って良さそう。)で動作させることもできる準備が出来ました。

クロックアップArduino用書き込みTool

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)作成


GPS 1ppsから10MHzの標準周波数を発生する装置を作成

故障したGPSモジュール(購入時からの部品取り付け不良)を再生して作ったGPSモジュールからダイレクトPULL方式による標準器を作ってしまったので、暫く作りかけで放置状態でした「REF SIGNAL GENERATOR / GPS」が完成しました。まだ、ちょこちょこプログラムは修正するかもしれませんが、ひとまず完成ということでお披露目。

こちらは、現在、周波数カウンターの基準クロックとして活躍してくれている、GPSモジュールにPLL ICを直結してダイレクトに10MHzを発生する標準器モドキです。独立したオシレーターを持たないので、衛星が3個以上安定して補足できないと使えず、瞬間はジッターが多く、短時間での精度が必要な用途には使えません。原理は簡単!GPSモジュール(u-blox 6)から比較的ジッターの少ない2MHzを出して、IC NB3N502(14 MHz to 190 MHz PLL
Clock Multiplier)で5倍して、10MHzを得ています。ケースは100均で購入。室外に設置したGPSアンテナを含めて5000円に満たない簡単標準10MHz発振器です。ジッターが大きく、ここのパルスの精度が必要なオーディオADC/DACの基準クロックなどには使えませんが、周波数カウンターの基準クロックに使う分には十分な性能と思います。

今回、作成したのは、VOCXOをGPS 1PPSクロックで補正して、正確な10MHzを出力する装置です。衛星をロストしても長時間高い精度の周波数でジッターの少ない標準クロックを安定して供給できます。

高い精度といっても諸事情で、±0.01Hz以下程度の性能になっていますが、個人で使用するには何に使うの?ってくらいの十分な性能と思います。

標準周波数発生装置 REF SIGNAL GENERATOR / GPS の概要

主な部品

VOCXO:OFC MC862X4-004W

GPSモジュール:u-blox 6

コントローラ:Arduino Pro mini /8MHz..

DAC:MCP4725

ケース:映像関係のジャンクから流用。ロジックICもこのジャンクから取り外したものを使用

主な機能

標準周波数:10MHz 4ch、

      1pps 1ch、

      1MHz 1ch(1MHz予定、現状はコネクタのみ)

表示:20 x 2 バックライトあり、

   Power&1pps 表示LED

SW: PushするとLCDの時間表示が、年月日表示になる。

ロータリーエンコーダ: 通常、LCD2行目の表示は、補足中の衛星数と時間表示ですが、これを回すと一時的にGPSモジュールから得られる緯度経度などいくつかのデータを表示する。まぁ。ロータリーエンコーダがあるのに使わないのはさみしいので遊びでつけたおまけ程度(^-^;

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)通常表示

回路概要

ArduinoのT1タイマーの標準タイマー機能と捕獲割り込み、溢れ割り込みを使った周波数カウンタで、GPS1ppsを捕獲信号としてICP1にVOCXOのクロックをT1に入力してこれをカウントして、目的周波数差分を計測、差分補正分のVOCXOの制御電圧をDACで発生させて、周波数を補正しています。

使用したArduino pro miniは、Arduinoについて何も知らない時に購入した8MHz版でしたので、10MHzを4分周した2.5MHzを時間軸を長くすることで測定精度を確保しています。

VOCXOは、本来5V駆動品と思いますが、3.3Vでも安定動作するようなので、レギュレータを抱き合わせにして、DACの基準電圧を兼ねた4Vで稼働させています。レギュレータがVOCXOと同じ温度に保たれることで、安定したDACの基準電圧を得ています。(5Vで駆動した方が環境温度に対する耐性が高いかもしれませんが、流用電源の関係で安定化すると4Vとなってしまいました。

30分程度で実用レベル(±0.1Hz程度)にはなりますが、残念ながら、完全安定状態(0.004Hz程度まで安定)になるまでの時間は、4分周して周波数測定している関係もあり、2時間程度とちょっと時間がかかります。(現時点のスケッチでは….汗)

こまかい部分は、Arduinoのスケッチを掲載するんでそちらを参考頂ければわかると思いますので回路図は省略。

本当は、作成中につまらないミスで最初に作成した回路図と最終回路で差分があり、修正版回路図を作成してない為(-_-;)

その分、スケッチには不要なHW情報も若干記載

Arduino スケッチ(プログラム)

Arduinoのスケッチを作成するにあたり、いろいろ役に立ちそうな事があったので、応用できそうな部分など、ボチボチ説明できればと思います。

まぁ、拙いソースなのであとで自分が応用する為のメモみたいなものですが、.....

で、いきなりスケッチ(ちょっと長いですが)

/*
   GPS base 10MHz with MCP4725
   +----------------------------+
   |Frequency/stability/accuracy|
   | GPS Status                 |
   +----------------------------+
*/
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac; // constructor
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <TinyGPS++.h>
TinyGPSPlus gps;
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(6, 7); // RX, TX
#define rs 2
#define enable 3
#define rw 9
#define d0 A3
#define d1 10
#define d2 A2
#define d3 11
#define d4 A1
#define d5 12
#define d6 A0
#define d7 13
LiquidCrystal lcd(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
#define enter_sw A6 // front SW 10以下 ON 2000以上 OFF
#define en_en 4 // EAW - Absolute Contacting Encoder LCD DATA LINE共通
#define ocxo25mhz 5; //OCXO 10MhHzの4分周2.5mhz 
#define gps1ppm 8;    //GPS 1Hz
byte debug = 1; // 1: debug code output
uint32_t debug_f = 0 ;
uint32_t sc = 0;
uint32_t ec = 0;
/* 精度設定 */
const int stability = 50; /* 前回測定より約0.5Hz以内の変動になれば、補正開始 */
int accuracy = 40; /* 40で決め打ちしている箇所あり。修正は要注意! */
const int accuracy_max = 1200; /* 400の整数倍1600まで可能だが...時間とDAC精度を考えると800で十分 */
/* VOCXO Voltage */
const int dacvoltmax = 4095;
const int dacvolt_norm = 2037;
int dacvolt = 2048; /* VOCXO Voltage = 2.0V 初期値:2048 */
int dacvolt_d = 10; /* 値はダミー */
volatile unsigned int intcarry;
volatile unsigned int start_cnt;
volatile unsigned int end_cnt;
volatile unsigned long  freq100_old;
volatile unsigned long  freq100;
volatile byte ignore;
unsigned int gps_loop;
volatile unsigned long count;
volatile unsigned long count_old;
int val;
byte enter;
int val_old;
byte enter_old = 2;
byte encode_old = 0;
unsigned long millis_old;
char gps_loop_msg3[4] = "***";
char freq_msg15[16] = " ********.**Hz ";
const byte encode[256] =                    //もし255なら読み取りミスで無視する。
{
  56, 40, 55, 24, 255, 39, 52, 8, 57, 255,
  255, 23, 255, 36, 13, 120, 255, 41, 54, 255,
  255, 255, 53, 7, 255, 255, 255, 20, 19, 125,
  18, 104, 105, 255, 255, 25, 106, 38, 255, 255,
  58, 255, 255, 255, 255, 37, 14, 119, 118, 255,
  255, 255, 107, 255, 255, 4, 255, 3, 255, 109,
  108, 2, 1, 88, 255, 89, 255, 255, 255, 255,
  51, 9, 10, 90, 255, 22, 11, 255, 12, 255,
  255, 42, 43, 255, 255, 255, 255, 255, 255, 255,
  255, 21, 255, 126, 127, 103, 255, 102, 255, 255,
  255, 255, 255, 255, 255, 91, 255, 255, 255, 255,
  255, 116, 117, 255, 255, 115, 255, 255, 255, 93,
  94, 92, 255, 114, 95, 113, 0, 72, 71, 255,
  68, 73, 255, 255, 29, 255, 70, 255, 69, 255,
  255, 35, 34, 121, 255, 122, 255, 74, 255, 255,
  30, 6, 255, 123, 255, 255, 255, 124, 17, 255,
  255, 255, 67, 26, 255, 27, 28, 255, 59, 255,
  255, 255, 255, 255, 15, 255, 255, 255, 255, 255,
  255, 255, 255, 5, 255, 255, 255, 110, 255, 111,
  16, 87, 84, 255, 45, 86, 85, 255, 50, 255,
  255, 255, 46, 255, 255, 255, 33, 255, 83, 255,
  44, 75, 255, 255, 31, 255, 255, 255, 255, 255,
  255, 255, 32, 100, 61, 101, 66, 255, 62, 255,
  49, 99, 60, 255, 47, 255, 255, 255, 48, 77,
  82, 78, 65, 76, 63, 255, 64, 98, 81, 79,
  80, 97, 96, 112
};
byte read_Enter() { // Enter SW
  byte value;
  int enter_val = analogRead(enter_sw);
  if (enter_val < 100) value = 1;
  else value = 0;
  return value;
}
byte read_Encoder() { // Encoder(8bits)の値を読む
  byte value;
  pinMode(d0, INPUT);
  pinMode(d1, INPUT);
  pinMode(d2, INPUT);
  pinMode(d3, INPUT);
  pinMode(d4, INPUT);
  pinMode(d5, INPUT);
  pinMode(d6, INPUT);
  pinMode(d7, INPUT);
  //   digitalWrite(en_en, LOW);
  PORTD &= ~_BV(4);
  value = digitalRead(d7) * 128 + digitalRead(d6) * 64 + digitalRead(d5) * 32 + digitalRead(d4) * 16;
  value = value + digitalRead(d3) * 8 + digitalRead(d2) * 4 + digitalRead(d1) * 2 + digitalRead(d0);
  //value = (PINB & _BV(5)) * 128 + (PINC & _BV(0)) * 64 + (PINB & _BV(4)) * 32 + (PINC & _BV(1)) * 16;
  //value = value + (PINB & _BV(3)) * 8 + (PINC & _BV(2)) * 4 + (PINB & _BV(2)) * 2 + (PINC & _BV(3));
  //    digitalWrite(en_en, HIGH);
  PORTD |= _BV(4);
  pinMode(d0, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT);
  value = encode[value - 1];
  if (value == 255) value = encode_old; else encode_old = value;
  return value;
}
ISR(TIMER1_CAPT_vect)
{
  /*
    WDT再起動(WDT Reset)
    if(カウンターフラグ無効)
    ICR1に保存された値を保存
    カウンターフラグ有効に
    if(発生回数が規定値)
    なら周波数を算出・表示
    発生回数・オーバーフロー回数を初期化
    カウンターフラグ無効
  */
  wdt_reset();
  gps_loop += 1;

  if (ignore) {
    if (start_cnt > 0) {
      /* 最初の1回はICR1値が不安定なので無視 */
      ignore = 0;
      /* GPS 1ppm 監視 WDT start */
      {
        MCUSR = 0;
        WDTCSR |= 0b00011000; //WDCE WDE set
        WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
      }
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < 6000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      if (debug) debug_f = 2;
    }
    intcarry = 0;
    gps_loop = 0;

  } else if (gps_loop == accuracy) {
    count_old = count;
    freq100_old = freq100;
    end_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if (debug) {
      debug_f = intcarry;
      sc = start_cnt;
      ec = end_cnt;
    }
    if ((end_cnt < 6000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
      intcarry += 1;
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      if (debug) debug_f = 1;
    }
    count = ((intcarry * 65536) + end_cnt) - start_cnt;

    if ((accuracy >= 400)) {
      freq100 = count / (gps_loop / 400);
    } else {
      freq100 = count * 10; //  40で決め打ち
    }

    /* 異常値を無視する */
      if (abs(freq100 - 1000000000) > 1500) {
      count = count_old;
      freq100 = freq100_old;
      if (debug) debug_f = 0XFFFF;
      }
      

    char s8[9];
    int freq100_surplus = freq100 % 100;
    sprintf(freq_msg15, " %s.%02dHz ", dtostrf((freq100 / 100), 8, 0, s8), freq100_surplus);
    start_cnt = end_cnt ;
    gps_loop = 0;
    intcarry = 0;

    /* 起動15分後、OCXOの周波数が安定したら周波数補正開始 */
    if ((abs(count - count_old) < stability) && (!ignore) && ((millis() > 900000) || (debug))) {
      unsigned long count_ref;
      if ((accuracy >= 400)) {
        count_ref = 1000000000 * (accuracy / 400);
        dacvolt_d = abs(count - count_ref) * 800 / accuracy ;
        if (abs(count - count_ref) <= 0) {
          accuracy = accuracy + ((accuracy < accuracy_max) * 400) ;
        } else {
          if (abs(count - count_ref) >= 50) {
            accuracy = 40;
          } else if (abs(count - count_ref) >= 5) {
//            accuracy = accuracy - (accuracy > 400) * 400;
            accuracy = 400;
          }
        }
        if (count < (count_ref - 1)) dacvolt += dacvolt_d;
        if (count > (count_ref + 1)) dacvolt -= dacvolt_d;
      } else {
        count_ref = 100000000;
        dacvolt_d = abs(count - count_ref) * 3.3 ;
        if (abs(count - count_ref) <= 5) {
//          accuracy = 400 + (count == count_ref) * 400; /* OCXOが十分調整されてる場合のみ*/
          accuracy = 400;
        }
      if (count < count_ref) dacvolt += dacvolt_d;
      if (count > count_ref) dacvolt -= dacvolt_d;
     }    }  } } ISR(TIMER1_OVF_vect) {  /* オーバーフロー回数を加算 */  if (!ignore)intcarry += 1; } /*   WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset(); */ ISR(WDT_vect) {  wdt_reset();  /*       カウンターフラグを無効にする       発生回数・オーバーフロー回数を初期化   */  start_cnt = 0;  gps_loop = 0;  ignore = 1;  {    MCUSR = 0;    WDTCSR |= 0b00011000; /* WDCE WDE set */    WDTCSR =  0b00000000; /* status clear */  } } void setup() {  pinMode(en_en, OUTPUT);  digitalWrite(en_en, HIGH);  delay(1);  lcd.begin(20, 2);  lcd.setCursor(0, 0);  lcd.print(" OCXO + MCP4725 10MHz");  lcd.setCursor(0, 1);  lcd.print(" 2018.11 by Drg.Jr. ");  Serial.begin(115200);  Serial.println("Hello World!");  Serial.println("OCXO + MCP4725 10MHz"); Serial.println("");  // pulse gen setup init clock  // initialize the MCP4725  dac.begin(0x60);  delay(5000);  gpsSerial.begin(9600); // ソフトウェアシリアルの初期化  gpsSerial.print("\r\n");  gpsSerial.print("\r\n");  /*     u-blox 6     Receiver Description     Including Protocol Specification を参考   */  gpsSerial.print("$PUBX,41,1,0007,0003,4800,0*13\r\n");   // GPSモジュールのbpsを変更  gpsSerial.begin(4800);                                   // ソフトウェアシリアルの初期化  gpsSerial.print("\r\n");  gpsSerial.print("\r\n");  lcd.clear();  intcarry = 0;  start_cnt = 0;  end_cnt = 0;  freq100_old = 0;  freq100 = 0;  ignore = 1;  gps_loop = 0;  millis_old = millis();  /*カウンターセットアップ*/  cli();  TCCR1A = 0;   /* TCCR1A タイマモード */  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */  sei(); /* 全体の割り込み許可 */ } void loop() {  char msg20[21];  char msg4[5];  byte val;  byte enter;  dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する  if ((millis() % 10) == 0) {    char acc;    if (!ignore) {      if (accuracy == accuracy_max) acc = 'o';      else if (accuracy >= 400) acc = '+'; else acc = '-';    } else {      acc = 'x';    }    sprintf(msg4, "%04d", dacvolt);    lcd.setCursor(0, 0);    lcd.print(acc);    lcd.print(freq_msg15);    lcd.print(msg4);    val = read_Encoder();    enter = read_Enter();    if (freq100 != freq100_old) {      if (dacvolt > dacvoltmax) {        dacvolt = dacvoltmax;        /* エラー表示を入れる */      }      if (dacvolt < 0) {        dacvolt = 0;        /* エラー表示を入れる */      }      if (abs(dacvolt - dacvolt_norm) > 500) {        /* 警告表示を入れる */      }    }  }  /* GPS module Accsess */  {    while (gpsSerial.available()) {      char read_gps = gpsSerial.read();      gps.encode(read_gps);      if ((gps.satellites.isUpdated()) && (gps.time.isUpdated())) {        char sa[3];        byte jst = gps.time.hour() + 9;        if (jst >= 24) jst = jst - 24;        if (enter == 0) {          sprintf(msg20, "GpsSat.%s   %2d:%02d:%02d",                  dtostrf(gps.satellites.value(), 2, 0, sa),                  jst, gps.time.minute(), gps.time.second() );        } else {          sprintf(msg20, "GpsSat.%s %4d/%02d/%02d",                  dtostrf(gps.satellites.value(), 2, 0, sa),                  gps.date.year(), gps.date.month(), gps.date.day() );        }        lcd.setCursor(0, 1);        if (val != val_old) {          lcd.print("                    ");          lcd.setCursor(0, 1);          switch (int(val / 16)) {            case 1:              lcd.print("Latitude : ");              lcd.print(gps.location.lat(), 6); // Latitude in degrees (double)              break;            case 2:              lcd.print("Longitude : ");              lcd.print(gps.location.lng(), 6); // Longitude in degrees (double)              break;            case 3:              lcd.print("Speed (m/s)");              lcd.print(gps.speed.mps()); // Speed in meters per second (double)              break;            case 4:              lcd.print("Speed (km/s)");              lcd.print(gps.speed.kmph()); // Speed in kilometers per hour (double)              break;            case 5:              lcd.print("Course (deg)");              lcd.print(gps.course.deg()); // Course in degrees (double)              break;            case 6:              lcd.print("Altitude (m)");              lcd.print(gps.altitude.meters()); // Altitude in meters (double)              break;            case 7:              lcd.print("Altitude (km)");              lcd.print(gps.altitude.kilometers()); // Altitude in kilometers (double)              break;            case 8:              lcd.print("HDOP : ");              lcd.print(gps.hdop.value()); // Horizontal Dim. of Precision (100ths-i32)          }          val_old = val;        } else {          lcd.print(msg20);        }      }      //      if (gpsSerial.overflow()) Serial.println("SoftwareSerial overflow!");    }    if ((debug) && ((millis() - millis_old) >= 1000) && (gps_loop == 1)) {      millis_old = millis();      Serial.print(msg20 ); Serial.print("," );      Serial.print(freq100 ); Serial.print("," );      Serial.print(count ); Serial.print("," );      Serial.print(accuracy ); Serial.print("," );      Serial.print(dacvolt ); Serial.print("," );      Serial.print(gps.charsProcessed() ); Serial.print("," );      Serial.println(gps.failedChecksum() );      if (debug_f && (debug > 1)) {        if (debug_f < 3 )Serial.println(" Overflow Flag clear" );        Serial.print(sc ); Serial.print("," ); Serial.println(ec );        debug_f = 0;        sc = 0;        ec = 0;      }    }  }  //  delay(0); }

ハードウェアの改善点

Ardurnoは、16MHz版で2分周の5MHz入力、可能であれば、24MHzまでオーバークロックして10MHzを生で計測が理想と思います。但し、20MHzあたりから、GPSモジュールの感度に大きく影響してくるようで電源周り、電磁波の回り込みなど配慮が必要)GPSモジュールとのコミュニケーションはSoftwareSirialを使用することになるので、新規に作成するなら、最低16MHz版を使用すべきと思います。8MHz版だと4800bpsまで落としてやっと何とか情報を引き出せる状態となっています。

DACの基準電源は、DACの最低動作電圧2.7Vまで下げた方が少しでも細かい制御が可能になり、精度をもう一桁上げられるかもしれません。現状は0.003Hz/1LSB程度)

今回はDACに12bitsのMCP4725を使用していますが、より細かい制御が期待できる秋月電子通商などでも扱っているMCP3425(16Bit ADC I2C 基準電圧内蔵)が良いかもしれません。基準電源も内蔵してい点でも扱いやすくなります。次回作成の機会があれば、使いたいと思います。

回路を簡素化して、コストも下げたい場合は、スイッチサイエンスが取り扱っているA-Star 328PB Micro – 5V/20MHzw

を使うのも良いかと思います。2個の16ビットカウンタと第2のUSARTが追加されているので、DACの代わりにPWMを使い、GPSモジュールとの通信もより簡単になると思います。

 

(次回へ続く)….予定 (^-^;

Si5351Aクロックジェネレータで、パルスジェネレータの作成


アンリツ PULSE GENERATOR MG418Aを入手しました。

アンリツ PULSE GENERATOR MG418A

 

アナログ式の古~いパルスジェネレータで、ジャンク扱いで入手したものですが、しっかり動作します。

以前に苦労したRISE Timeも当たり前に5nS以下で、急峻なわりに綺麗な波形を発生できます。

若干、自励発振周波数はスペックに達しない感じですが、しっかりした波形を出してくれますので、オシロスコープの校正などにも使えます。ただ、残念ながらアナログ式で正確な発振周波数を設定するのは難しく、どうしても若干の揺らぎがあります。

そこで、外部トリガーモードで動作させて、デジタル生成したベースクロックを入力するようにしました。

Si5351Aを利用して、パルスジェネレータを作成

目標は、1kHz~220MHzのパルスジェネレータでしたが、最終的に1.5k~200MHzのパルスジェネレータとなりました。

まぁ、これでもメーカ保障スペックを大幅に拡張できたので、良しとします。

内心は、どこかのサイト(ストロベリーリナックスでした)で1kHzで発振した記事があり、方法がわかれば、1kHzは実現したいと思いはあります。

Si5351Aクロックジェネレータで、パルスジェネレータの作成

 

Si5351Aクロックジェネレータで、シールドボード使いパルスジェネレータの作成

ハードウェア編

主なパーツは以下となります。

HiLetgo 1602 LCD キーパッド シールド ボード ブルー バックライトを付き Arduinoに対応 Duemilanoveロボット [並行輸入品]

新品価格
¥680から
(2018/10/23 20:48時点)

WINGONEER Arduino UNO R3シールドボードDIY用プロトタイプPCB

新品価格
¥599から
(2018/10/23 20:44時点)

HiLetgo Si5351Aクロックジェネレータブレークアウトボード8KHz~160MHz I2Cコントローラクロックジェネレータ

新品価格
¥1,000から
(2018/10/23 20:46時点)

keyestudio UNO R3 開発ボード USBケーブル UNO R3 Arduino互換

新品価格
¥1,100から
(2018/10/23 20:50時点)

Superbat SMAメスコネクタ SMAジャック PCB実装型 基板部品 信号増幅器・LNAボードモジュール・アンプ対応 自作 5個入

新品価格
¥880から
(2018/10/23 20:51時点)

カモン SMA(オス)←→BNC(メス)変換アダプタ【BNC-SMA】

新品価格
¥210から
(2018/10/23 20:52時点)

はじめて、まとも?に作成したArduino利用のToolとなります。

ソフトウェア編

プログラム作成は、Arduino 1.8.5で作成しました。便利すぎて癖になりそう。

さて、

今一つ、温度で水晶発振器の周波数が変動する為、期待したほど精度が得られず、簡単なHeaterの設置を考えましたが、Arduinoからの電源駆動を考えるとあまり消費電力を増やしたくないので、以下の機能をプログラムで実現することで凡そ目的を果たすものに仕上げました。

  • 水晶発振器の元々の誤差を補正できる機能
  • 水晶発振器の温度を測定(温度センサーLM61CIZを追加)して、発振周波数を補正する機能
  • こまかい周波数設定ができる機能

プログラムの中身は、私はプログラマーでないのでかなり拙い力ずくとなっています。

/**************************************************************************/
//v.0.1 10.10.2018 Frequency generator 1.5k...200MHz . JrDrg
//v.0.1c 10.14.2018 + OSC calibration
//v.0.2c 10.15.2018 PLL clock Improvement + new Algorithm
//v.0.3 10.16.2018 ch2 10MHz fix
/**************************************************************************/

 

#include <Wire.h>
#include <Adafruit_SI5351.h>
#include <LiquidCrystal.h>

 

Adafruit_SI5351 clockgen = Adafruit_SI5351();

 

/* Key setup */
int lcd_key = 0;
int adc_key_in = 0;
int adc_key_in2 = 0;
#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5

 

int read_LCD_buttons()
{
// take measures to chattering
adc_key_in = analogRead(0);
delay(40);
adc_key_in2 = analogRead(0);
if (abs(adc_key_in - adc_key_in2) > 2) return btnNONE;
// For V1.1 us this threshold
if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
// For V1.1 us this threshold
if (adc_key_in < 50) return btnRIGHT;
if (adc_key_in < 250) return btnUP;
if (adc_key_in < 350) return btnDOWN;
if (adc_key_in < 550) return btnLEFT;
if (adc_key_in < 850) return btnSELECT;
return btnNONE;
}

 

//◆ Siral Debug 0:off 1:on 1<:manual check
byte debug = 0;

 

//◆ Normal: oscT=0
float oscT = 1524.1 ; //Tweak base OSC frequency e.f.1479.7,1475.8 1484.3
int oscT_temp = 29.12; // ↑temperature
int32_t osc = 25000000; //base OSC frequency
float C_by_T =-10.5; // ΔOSC/temperature

 

//◆Ref Volt
float Vref = 4.88;   //Power Voltage 5.0V/AC Adapter

 

//◆ Initial Output Clock Freq
volatile int32_t outF = 25000000 ;// initial frequency in Hz
float outF2 = 10000000-0.0 ;//ch2: Fix frequency in Hz
uint32_t stpx = 10; //initi step size 10Hz

 

float oscf;
int SI5351_m;
int SI5351_Fm;
uint32_t SI5351_n;
uint32_t SI5351_d;
uint32_t SI5351_div;
uint32_t SI5351_mn;
uint32_t SI5351_md;
uint32_t SI5351_R_DIV; // 1,2,4,8,16,31,64,128
boolean changed_f = 1;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

 

int SI5351_m2;
uint32_t SI5351_n2;
uint32_t SI5351_d2;
uint32_t SI5351_div2;
uint32_t SI5351_mn2;
uint32_t SI5351_md2;

 

// Temp Cal init
int anlg1;
float temp;
float temp_cal;
float temp_cal_bk;
float oscf_bk;

 

/**************************************/
/* Calc Pram & Displays the frequency */
/**************************************/
void count_frequency()
{

 

if(debug) Serial.println("===== in ====== ");

 

uint32_t f;
uint32_t calF;

calF = outF;

 

SI5351_R_DIV = 1;
if ((outF < 1120000) && (9000 <= outF)){
if (outF <=35000){
SI5351_R_DIV = 128;
}else{
SI5351_R_DIV = 32;
}
calF=calF*SI5351_R_DIV;
}

 

f = calF;
SI5351_mn = 0;
SI5351_md = 1;

if (150000000 < calF) {
SI5351_d = 1048575;
SI5351_div = 6;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((90000000 <= calF) && (calF<= 150000000)){
SI5351_d = 1048575;
SI5351_div = 6;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((45000000 <= calF) && (calF< 90000000)){
SI5351_d = 1048575;
SI5351_div = 10;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((36000001 < calF) && (calF< 45000000)){
SI5351_d = 1048575;
SI5351_div = 20;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((18000000 <= calF) && (calF<= 36000001)){
if ((calF == 25000000) && (oscT == 0)){
SI5351_m = 32;
SI5351_n = 0;
SI5351_d = 1;
SI5351_div = 32;
SI5351_mn = 0;
SI5351_md = 1;
}else{
SI5351_d = 1000000;
SI5351_div = 25;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
}
if ((9000000 <= calF) && (calF< 18000000)){
SI5351_d = 500000;
SI5351_div = 50;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((4500000 <= calF) && (calF< 9000000)){
SI5351_d = 250000;
SI5351_div = 100;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((2250000 <= calF) && (calF< 4500000)){
SI5351_d = 125000;
SI5351_div = 200;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((1120000 <= calF) && (calF< 2250000)){
SI5351_d = 62500;
SI5351_div = 400;
f=SI5351_div * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if ((1500<=calF) && (calF < 9000)){
SI5351_R_DIV = 64;
if (calF < 3000) SI5351_R_DIV = 128;
f = calF*SI5351_R_DIV;
SI5351_mn = 1100;
SI5351_d = 1048575;
SI5351_div = 900;
f=(SI5351_div + SI5351_mn) * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}
if (calF < 1500){
SI5351_R_DIV = 128;
f = calF*SI5351_R_DIV;
SI5351_mn = 1150;
SI5351_md = 1;
SI5351_d = 1048575;
SI5351_div = 900;
f=(SI5351_div + SI5351_mn) * f;
SI5351_m = f/oscf;
SI5351_n = (f % osc)/(oscf/SI5351_d);
}

 

// ch2 10MHz
SI5351_d2 = 500000;
SI5351_div2 = 50;
f=SI5351_div2 * outF2;
SI5351_m2 = f/oscf;
SI5351_n2 = (f % osc)/(oscf/SI5351_d2);
SI5351_mn2 = 0;
SI5351_md2 = 1;

 

if(debug){
Serial.println(" 10M fix :");
Serial.print("SI5351_m2: ");
Serial.println(SI5351_m2);
Serial.print("SI5351_n2: ");
Serial.println(SI5351_n2);
Serial.print(" f: ");
Serial.println(f);
Serial.println("");
}

 

/**************************************/
/* Displays the frequency */
/**************************************/
lcd.clear();
lcd.setCursor(2,0);

char msg[17];
char s[17];
sprintf(msg, "%s [Hz]", dtostrf(outF, 9, 0, s));
lcd.print(msg);

 

display_stpx();
}

 

/**************************************/
/* Display the frequency change step */
/**************************************/
void display_stpx()
{
lcd.setCursor(0, 1);
lcd.print("Stp:");
switch (stpx)
{
case 10:
lcd.print(" 10");
break;
case 100:
lcd.print(" 100");
break;
case 1000:
lcd.print(" 1k");
break;
case 10000:
lcd.print(" 10k");
break;
case 100000:
lcd.print("100k");
break;
case 1000000:
lcd.print(" 1M");
break;
case 10000000:
lcd.print(" 10M");
break;
}
lcd.print("Hz ");

}

void setup(void)
{
if(debug){
Serial.begin(115200);
Serial.println("******************");
Serial.println("* 1.5k-200MHz PG *");
Serial.print("* Debug mode :");
Serial.print(debug);
Serial.println(" *");
Serial.println("******************");
Serial.println("");
}

oscf = osc + oscT;
osc = oscf;

oscf_bk=oscf;

lcd.begin(16, 2);
Wire.begin();

// Startting Tittle
lcd.setCursor(0,0);
lcd.print(" 1.5k-200MHz PG");
lcd.setCursor(0,1);
lcd.print(" 10.2018 JrDrg");

/* Initialise the sensor */
if (clockgen.begin() != ERROR_NONE)
{
lcd.setCursor(0,1);
lcd.print(" clockgen error");

while(1);
}

/* Enable the clocks */
clockgen.enableOutputs(true);

// temp calibration
anlg1 = analogRead(1) ;
delay(3000); //3seconds
anlg1 = 0;
for (int i=1; i <=100;i++){
anlg1 =anlg1 + analogRead(1) ;
delay(5);
}
anlg1 = anlg1/100 ;
temp = ((Vref * anlg1)/1024)*100-60 ;
temp_cal = (temp-oscT_temp) * C_by_T ;
temp_cal_bk = temp_cal;
oscf = oscf_bk + temp_cal;
osc = oscf;
}

void loop(void)
{

/**************************************/
/* Change the frequency */
/* btnUP Increment */
/* btnDOWN Decrement */
/* btnRIGHT Increment Tweak */
/* btnLEFT Decrement Tweak */
/**************************************/

lcd_key = read_LCD_buttons(); // read the buttons

switch (lcd_key) // depending on which button was pushed, we perform an action
{
case btnUP:
{
outF += stpx;
changed_f = 1;
break;
}
case btnDOWN:
{
outF -= stpx;
changed_f = 1;
break;
}
case btnRIGHT:
{
if (abs(temp_cal-temp_cal_bk)<20) outF += 1; else{ delay(2000); }
changed_f = 1;
break;
}
case btnLEFT:
{
if (abs(temp_cal-temp_cal_bk)<20) outF -= 1; else{ delay(2000); }
changed_f = 1;
break;
}
}

// btnSELECT frequency step Renge

if (lcd_key == btnSELECT)
{
switch (stpx)
{
case 10:
stpx = 100;
break;
case 100:
stpx = 1000;
break;
case 1000:
stpx = 10000;
break;
case 10000:
stpx = 100000;
break;
case 100000:
stpx = 1000000;
break;
case 1000000:
stpx = 10000000;
break;
case 10000000:
stpx = 10;
break;
}
display_stpx();
}

if(changed_f && !(debug > 1))
{
// check over renge
if(outF > 205000000)
outF =1470;
if(outF < 1470)
outF = 205000000;
if (outF < 0)
outF = 205000000;

//◆ Calc SI5351 setup param
count_frequency();

//◆ Disable the clocks
clockgen.enableOutputs(false);

// ◆Set up the PLL with 'fractional mode'
// This sets PLL_A or PLL_B to be 25MHz * (m + n/d)
// m (the integer multipler) can range from 15 to 90
// n (the numerator) can range from 0 to 1,048,575
// d (the denominator) can range from 1 to 1,048,575
// clockgen.setupPLLInt(SI5351_PLL_A, SI5351_m);
clockgen.setupPLL(SI5351_PLL_A, SI5351_m, SI5351_n, SI5351_d);
clockgen.setupPLL(SI5351_PLL_B, SI5351_m2, SI5351_n2, SI5351_d2);

// ◆Set up the clock divider
// For the output use 0, 1 or 2
// For the PLL input, use either SI5351_PLL_A or SI5351_PLL_B
// The final frequency is equal to the PLL / (div + n/d)
// div can range from 4 to 900
// n can range from 0 to 1,048,575
// d can range from 1 to 1,048,575
// clockgen.setupMultisynthInt(0, SI5351_PLL_A, SI5351_MULTISYNTH_DIV_4);
clockgen.setupMultisynth(0, SI5351_PLL_A, SI5351_div, SI5351_mn, SI5351_md);
clockgen.setupMultisynth(2, SI5351_PLL_B, SI5351_div2, SI5351_mn2, SI5351_md2);

// ◆Additional R Divider
// output is the clock output #
// The R divider can be any of the following:
// SI5351_R_DIV_1
// SI5351_R_DIV_2
// SI5351_R_DIV_4
// SI5351_R_DIV_8
// SI5351_R_DIV_16
// SI5351_R_DIV_32
// SI5351_R_DIV_64
// SI5351_R_DIV_128

clockgen.setupRdiv(2, SI5351_R_DIV_1);

switch (SI5351_R_DIV)
{
case 1:
clockgen.setupRdiv(0, SI5351_R_DIV_1);
break;
case 2:
clockgen.setupRdiv(0, SI5351_R_DIV_2);
break;
case 4:
clockgen.setupRdiv(0, SI5351_R_DIV_4);
break;
case 8:
clockgen.setupRdiv(0, SI5351_R_DIV_8);
break;
case 16:
clockgen.setupRdiv(0, SI5351_R_DIV_16);
break;
case 32:
clockgen.setupRdiv(0, SI5351_R_DIV_32);
break;
case 64:
clockgen.setupRdiv(0, SI5351_R_DIV_64);
break;
case 128:
clockgen.setupRdiv(0, SI5351_R_DIV_128);
break;
}

//◆ Enable the clocks
clockgen.enableOutputs(true);

changed_f = 0;
temp_cal_bk = temp_cal;

if(debug){
Serial.print(" outF ");
Serial.println( outF);
Serial.print(" SI5351_m ");
Serial.println( SI5351_m);
Serial.print(" SI5351_n ");
Serial.println( SI5351_n);
Serial.print(" SI5351_d ");
Serial.println(SI5351_d);
Serial.print(" SI5351_div ");
Serial.println(SI5351_div);
Serial.print(" SI5351_mn ");
Serial.println(SI5351_mn);
Serial.print(" SI5351_md ");
Serial.println(SI5351_md);
Serial.print(" SI5351_R_DIV ");
Serial.println(SI5351_R_DIV);
Serial.println("================ ");
}

}
//loop delay 300mseconds
delay(250);

// display temp
anlg1 = analogRead(1) ;
anlg1= 0;
for (int i=1; i <=100;i++){
anlg1=anlg1 + analogRead(1) ;
delay(5);
}
anlg1 = anlg1/100;
temp = ((Vref * anlg1)/1024)*100-60 ;

if (abs(temp_cal-temp_cal_bk)>20){
lcd.setCursor(11, 1);
lcd.print(" ");
delay(80);
}
char msgT[4];
char sT[4];
sprintf(msgT, "%s0", dtostrf(temp, 2, 2, sT));
lcd.setCursor(11, 1);
lcd.print(msgT);

// Tweak base OSC frequency by temp.
temp_cal = (temp-oscT_temp) * C_by_T ;
oscf = oscf_bk + temp_cal;
osc = oscf;

if(debug){
Serial.print(temp);
Serial.print(" ℃ ");
Serial.println(oscf);
}

}

// END

 

完成して動作確認中。

SI5351 Frequency generator 1.5k...200MHz

プログラムについて

ソースの最初の方に以下の設定箇所がありますので、使用個体に応じて設定が必要になります。

気長に丁寧に調整することで、±10×10-6以下の精度に設定できると思います。

oscT:水晶発振器の誤差(購入品は60ppm程度でした)

oscT_temp:上記の確認温度

osc:水晶発振器の規定周波数(通常は25000000)

C_by_T:温度勾配 周波数ずれ/℃(購入品は簡単な実測で-10.5Hz/℃、厳密には完全な線形でないはずですが、ここでは線形として処理)

Vref:リファレンス電圧。通常はArduinoの電圧※

 

その他、使用した「LCD キーパッド シールド ボード」スイッチがチャタリング誤動作がひどく複数回読みと電圧偏移で判断するように細工しています。

調整のコツは、

  1. まずは、基準となる周波数カウンター(できれば、0.1Hzまで計測できるもの)を用意して、25MHzの周波数が測れるようにする。
  2. [debug=1]にしてシリアル表示を有効にする。
  3. [oscT=0]にする。
  4. 起動して、シリアルコンソールに表示される温度が一定になるのを待つ。その温度を[oscT_temp]に設定して、再度起動。
  5. その時の周波数カウンターの値と25MHzとの差分を[oscT]に設定する。
  6. 再起動して、周波数を確認。目的の精度になるまで、[oscT]を微調整。なかなかぴったりとはならないので妥協が必要かも(-_-;)

注意:温度が変わると設定のやり直しなので、十分温度が安定してから作業する。

※内部の基準電源1.1Vを利用することで自動設定も可能と思いますが、現状は定数で設定しています。

操作方法と表示について

操作キーは、

Enter:周波数切替サイズ 10Hz/100Hz/1kHz/10kHz/100kHz/1MHz/10MHz

Right : 通常時+1、温度測定で20Hz以上周波数ずれが予想される場合は、補正周波数設定

Left : 通常時-1、同上

UP:周波数up

Down:周波数down

Si5351Aを利用したパルスジェネレータの表示

表示は、見ての通り、1行目は周波数、2行目は、周波数切替サイズとオシレータの温度

オシレータの温度表示は、周波数ずれが20Hz以上予想される場合は点滅

最後に

ICへの設定値が仕様を逸脱している部分が多々ありますが、現状は動作に支障はないようなので良しとしています。暇になったら厳密に仕様の範囲で動かせることも検討できればと思っています。

十分使えるものにできたと思いますが、連続的に周波数を変更できない(周波数変更の瞬間に乱れるので、一度発振を停止して設定後再発振するようにしています。)ので、折角温度補正機能を付けたのですが、リニアには適用できません。周波数を変更などすると補正された周波数が適用されます。

スイープ発振を実現された事例もあるようなので、そのようにSI5351を設定することで、温度補正をリニアに適用することも可能になるかもしれません。

補正データもプログラム上の定数になっているので、個体によってプログラムの変更になってしまいます。プログラムに初期設定機能を追加して、プログラム上から校正ができるとより汎用的になろうかと思います。折角AVRにはEEPROMなどが搭載されていますから….

その他、温度測定結果がばらついてしまいます。温度センサーの電源の取り回し、Arduino周りのノイズ対策(測定時はCPUをスリープさせるとか)などハード的プログラム的にも修正の余地はあると思います。現状は高速性を要求しないプログラムなので、多数回読込み平均でバラツキを平均化して真値に近い値を確認するようにしています。

ジャンク IWATSU 岩通通信機 SC-7203 universal counter の 修理


IWATSU 岩通 ユニバーサルカウンタ SC-7203 を入手

1.3GHzまで測定できるアマチュアが使用するには十分な周波数カウンターです。

「悪い状態ではないが、正しく動作するものかどうか不明」という商品でしたが、出品写真から明らかに故障品でした。

デジタル表示が正常にされていません。

表示機能そのものの故障の可能性もありますが、おそらく、初期リセットが正常に走らない状態と推測した上で、入手しました。

IWATSU 岩通通信機 SC-7203 universal counterの開腹

電源を入れるたびに表示が変わり、岩通のカウンターの見慣れたLEDの表示(全点灯から数字が表示される)がされません。やはりリセットが走っていません。

リセット回路(電源電圧監視IC TL7700)はバックアップ電池(NiCd)の近くにあるので液漏れで悲惨な状態も想定して、開腹してみました。

IWATSU 岩通 ユニバーサルカウンタ SC-7203 中身 TL7700交換後

写真は、TL7700交換後ですが、部品面は綺麗で、TL7700の足が黒ずんでいる程度でしたが、基板裏側には、腐食跡とNiCd電池を交換した形跡がありました。

リセットIC(電源電圧監視IC) TL7700をM51957Bに交換

TL7700は現役のICなので入手も可能ですが、手元にM51957B 電圧検出システムリセットICがありましたので、これに乗せ換えることにしました。

検出電圧やPin配置が異なりますが、機能ピンは同じですし、SOPですのでDIP変換に変換基板を使用しますので、変換基板上でPin配置を変更しました。NCを利用して、パターンカットは1か所だけで、あとはすず線で取り付けピン兼用で配線しました。(詳細は写真を参考にしてください。)

TL7700を電圧検出システムリセットIC M51957BFPCFORに交換

検出電圧が変わりましたので、抵抗値も変更します。(写真右したので亀の子 抵抗(^-^;)

無事起動するようになったところで、周波数表示の校正しましたが、規定時間の電源起動後1時間を過ぎても周波数表示が安定しません。

内部の基準クロック10MHzの温度補償がまともに働かなくなっているようです。

SC-7203をSC-7204相当にアップグレードする

SC-7203には高精度基準クロック搭載モデル SC-7204があります。

筐体は兼用ですし、基板自体も共用でパターンは存在しています。

パターン確認中に気が付いたのですが、TCXO(TC314A)への電源供給が、TC314Aの横にあるR196経由で供給されています。これカーボン抵抗でしょうから、温度によって値が変動します。ということは、「温度によってTCXOへ供給される電圧が変わる=周波数が変わる」となります。なぜかわざわざ電源パターンをカットしてまでこの接続に変更されています。確認はしていませんが、この引き回しを変更するだけで改善が期待できそうに思われます。

実際に

TCXO(TC314A)を簡易的に熱遮断

簡易的にTCXOを熱遮断してみましたが、周波数の変動傾向は変わりませんでしたので、変動要因は横のカーボン抵抗がかなり疑わしいと思います。

今回は、SC-7204相当まで性能を引き上げる為に手持ちのOCXO(OFC MC852X4-004W)を載せました。

TC314Aの上にあるコネクターパターンに接続します。R196経由でTCXOにつながる電源ラインがコネクターパターンにあるので、これをGNDに落とせば、TCXOは実装のままで行けるかと思いましたが、このラインはパワーオンスタンバイの機能があるようで、GNDに落とすと起動しません。なので、TCXOは仕方なく取り外しました。(何か使い道あるかなぁ?….一度単体で温度補償の確認をしてみようか….)

このコネクタへは、上の写真の左から、「AC・AC・NC(GND)・CLK・STB?(R196に接続)・GND」となっており、OCXOの電源はACを整流安定化(DC 12V程度)して使用することになります。OCXOは温度が安定するまで電流が流れますので電圧安定化回路(三端子電源)のヒートシンクが少々心もとないですが、とりあえず我慢してもらう事します。(実装スペースも厳しいし、適当なヒートシンクが手持ちにない為)

ちなみに

  • 筐体の内側はEMI対策で導電性塗料が吹き付けられていますので、ショートさせないように注意が必要。
  • 起動を遅らせたい場合はSTBをGNDレベルに落とすと起動しません。

さて、こまかい事はパスして….(^-^;

 

TCXO(TC314A)をOCXOに変更

こんな感じになりました。(線長に余裕がなく少々ぶちゃいく。ACラインはツイストにしてNC/GNDでしっかりシールドして、もう少し引き回しに余裕を持たす方が良いでしょう。長期を考えると配線にストレスは故障の要因になりますから)

SC-7204相当にアップグレードしたIWATSU SC-7203 UNIVERSAL COUNTER

しっかり、GPSで校正して、下1桁±2以下となりました。電源起動後約15分でほぼ安定状態に入りその後の変動は見られません。流石!!OCXO

HEWLETT PACKARD 54600A OSCILLOSCOPE  100MHz 2 CHANEL のセルフ校正


修理したHP 54600A オシロスコープのセルフ校正

H5460xxにはセルフ校正機能があり、比較的簡単に校正できます。

但し、内蔵セルフ・テストの実行で、DACの出力レベルのチェックで問題がないことが、大前提となります。

もし、DACのセルフチェックで問題がある場合は、ほぼ修理は不可と思いますし、リアにあるDAC出力を使用するので自己校正はできません。

まぁ、気休めに各電圧をチェックして、5.1V電圧を調整してみるくらいですが、調整できる程度なら、ほぼDAC回路とは関係ないでしょう。

自己校正に必要な機材について

BNC同軸ケーブル 1本

BNC同軸ケーブル 2本

23cm程度で同じ長さの物が必要です。

実際には、特性が同じで長さは50cmとかでも大丈夫なようですので、最初の1本と共用できます。

BNC T型アダプタ m-f-f 3個

BNC 50Ω 終端

パルス発生器

100kHz,1Vp-p,Risetime<5nS

実際には、周波数や電圧は画面表示のようにシビアでないの通常の簡易的なパルスジェネレーターでも問題ないと思いますが、問題は立ち上がり時間が5nS以下という条件です。これは、結構高価な機器でも限られるスペックと思われます。(普通は、せいぜい15nS~)

HEWLETT PACKARD 54600A OSCILLOSCOPE  自己校正に必要な機材

H5460xx、自己校正の為のパルスジェネレータを作成

ということで、自己校正用の立ち上がりが5nS以下と急峻な矩形波発振器を作成します。

いろいろ実験した結果、以下の回路図になりました。

LM7171とLMC555を使ったRisetime<5nSの矩形波発生器LMC555の出力のプルアップ抵抗を小さくするほど、立ち上がり時間は短くなります。

また、LM7171の出力の可変容量コンデンサを調整することで、オーバーシュートを発生させて立ち上がり時間を短くできます。

この回路は検討段階の作成経過の影響でLMC555を負電圧で動作させていますが、VDD/VEEが5V以下で丁度良い出力電圧がえられるので、正負電源で動作させることで、カップリングコンデンサを排除して、LM7171の入力抵抗も削除することでより急峻な波形を得られる可能性があります。試していませんが(-_-;)

H5460xxには自己校正の為の自作パルスジェネレータ

H54601Aの自己校正用の自作パルスジェネレータの立ち上がり波形

電源電圧がVDD=5V、VEE=-5Vで目的の波形を得ることができました。

LMC555は、電源電圧により、周波数が変わってしまいますが、今回は周波数はシビアではないので問題ないでしょう。

但し、変動は良くないと思いますので、暖機運転後の使用となります。電源ON直後は、周波数の変動があります。

H54601Aの自己校正に使った自作パルスジェネレータ

最終的には、LMC555でクロック発生回路を内蔵しましたが、LM7171に外部クロックを通すことでも校正用の急峻な立ち上がり波形を得ることもできます。上記は、その時の写真です。

 

立ち上がり時間の短い波形を作れてより汎用的なものとして、Si5351Aを使ったクロックモジュールを使う方法もあります。

ArduinoなどI2Cを持ったマイコンボードと接続することで任意の周波数を得ることができます。

HiLetgo Si5351Aクロックジェネレータブレークアウトボード8KHz~160MHz I2Cコントローラクロックジェネレータ

新品価格
¥1,000から
(2018/10/5 09:55時点)

 

駆動電圧を変えれるようにすることで、1nSという急峻な波形をその特徴を生かしたままで、出力インピーダンス50Ω&必要な電圧レベルが得られます。(スペック的には….気が向いたら確認したいと思います。)

Superbat SMAメスコネクタ SMAジャック PCB実装型 基板部品 信号増幅器・LNAボードモジュール・アンプ対応 自作 5個入

新品価格
¥880から
(2018/10/5 10:14時点)

 

Superbat 2本入 SMAオス⇔BNCメス 変換ケーブル(RG316 15CM)SMAP-BNCJ ハンディ機用 無線機CB・アマチュア無線など用 トランシーバー用 低損失

新品価格
¥920から
(2018/10/5 10:17時点)

これらを使うことで、オシロスコープとの接続も容易に実現できます。

HiLetgo・ 3個セット Mini USB Nano V3.0 ATmega328P CH340G 5V 16M マイクロコントローラーボード Arduinoと互換

新品価格
¥1,350から
(2018/10/5 11:30時点)

Arduino上で簡単なUSB-I2C変換プログラムを用意して、設定はPC経由で行うことで、比較的容易に目的のパルスジェネレータができると思います。

さらには、ArduinoのPWM出力を使って、Si5351Aを使ったクロックモジュールに供給する電圧をコントロールすることで、出力電圧の可変化など、後々も遊べそうです。

無事、HEWLETT PACKARD 54600A  のセルフ校正 完了!

HP 54600A 自己校正マニュアル その1

HP 54600A 自己校正マニュアル その2

NvRAMの電池消耗で校正値を失ったHP54600Aですが、無事に自己校正が出来ました。

自己校正の肝となるDAC出力も正確な値でしたし、リードアウトの数値も周波数カウンター、デジタルマルチメーターなどの値と差異はないようなので、安心して我が家の主力測定器として使用できます。

バンザーイ!

HP 54600A 修理しました


HEWLETT PACKARD 54600A OSCILLOSCOPE  100MHz 2 CHANEL 故障品を入手

このオシロスコープは、サンプル周波数は低い(20MSa/s)ですが、れっきとしたデジタルオシロスコープです。

小型軽量で、繰り返し波形については従来のアナログオシロスコープとほぼ同じ取扱いができる扱いやすい秀逸の測定器です。

入手したのは、

54600A Setup memories failed checksum test - defaults loaded

「Setup memories failed checksum test – defaults loaded」を表示して動作しない物です。画面も明らかに上下潰れています。

nvRAMから読み込んだセットアップデータが不正となっているようです。

リアパネルのCALIBRATIONスイッチをUNPROTECTED(上側にする)とこのエラー状態でも起動して、自己校正が正常に完了できれば、動作する可能性がありますが、2003年頃の測定器なので、製造から15年程度経過しており、nvRAMに内蔵されているリチウム電池の寿命(10年)を超えており、nvRAMの交換が適切な対応となりそうです。

使用されているnvRAMは、DS1220ABで、現在も入手は可能ですが、デッドストック(電池の消耗も進んでいる可能性もあり)で価格も高いですから、選ぶなら、互換性があり、価格も安めのM48Z02-70PC1で、Digi-Keyなどで手配可能な模様です。

DS1220AB NvRAM修理

この測定器は、うまく修理できれば長く自分で使いたいですし、修理にお金はかけたくないので、ダメもとでNvRAMの内蔵電池を取り除いて、代わりに外付けでリチウムボタン電池を取り付ける方法を選択しました。

ミニルーターで、丁寧に内蔵電池を掘り出します。

電池自体に穴を開けないように、できるだけ余分に削らないように焦らず丁寧に掘り出します。

電極は、電池に圧着溶接されているので、注意が必要です。

特にマイナス側は、NvRAM側で切れてしまうと新しい電池の取り付けが難しくなると思われます。プラス側(奥側)はNvRAMがわ一面が電極となるのでちぎれても大丈夫です。

NvRAM DS1220AB電池取り出し

HP 54600A NvRAM 修理

これで、無事エラーなく起動するようになりました。

但し、校正データがなくなっているので、測定器としては、校正が必要となります。

画面が垂直方法に縮んでいるので、これを修理

エラー表示なく起動するようにはなりましたが、やっぱり画面が垂直方向に縮んでいます。

54600Aの回路図は公開されておらず、54601A(4ch)の回路図を参考に考えていましたが、画像表示部分は全く別の回路となっているようで、54600Aでは、uPC1379Cで垂直信号を作っていました。

HP 54600A CRT垂直同期信号 uPC1379C

2か所疑わしいコンデンサーがあります。

特にICの参考回路の1000uF/25Vのところに1000uF/16Vが使用されており、リップルの大きい場所に耐圧が低い電解コンデンサは極端に寿命が短くなる可能性が大なので疑わしいです。

高圧回路に注意して基板を取り外し、10uF/16V(参考回路図上は22uF/16V)と1000uF/16Vをそれぞれ、10uF/16V,1000uF/35Vに交換しました。(以下の写真の赤囲い)

取り外したコンデンサを確認したところ、簡易測定結果からは、10uF/16Vは異常なしでしたが、1000uFは20%以上の容量低下がみられ明らかに劣化していました。

HP 54600A 垂直同期回路修理

無事、正常に表示されるようになりました。

自己校正に失敗!1chが表示されない事象発生

なんとチャネル1の波形が表示されない事が頻繁に発生していることが判明

HP 54600A UB1202AM周り回路図

信号を追いかけた結果、

回路図上PA300と記載されているAT&T  UB1202AMの動作が不安定なようで、外部信号の動きに関係なく出力信号が出なくなる状態が観測されました。

ほぼ、UB1202AMの個体不良で確定。

HP 54600A UB1202AM周り

このICは、プログラムゲイン プリアンプで、一般に入手は困難で、同機種のジャンクからの付け替えしかありません。

同機種のジャンクといっても知る人は知る人気機種なので、余程のジャンクでないと価格的に手出しは難しいので、今回は、ダメ元の荒療治を試してみました。

昔、壊れたGAの復活策として電子レンジでチンがありましたが(本当に直るかどうかは、かなり?????)、

それに匹敵するくらい怪しい方法で、完全に破壊する可能性が高い技術者らしからぬ方法ですが、ヒートガンでIC内部の温度がハンダ融解温度あたりまで温めてみました。

方法は、電源を落とした状態で、温度の目安としてハンダをICの上に置き、このハンダが完全に溶けるまでヒートガンでICを温めます。

結果…..

ーーーー

一時的修復かもしれませんが、正常に表示されるようになりました。

電源ON/OFF試験、Auto-Store機能で波形を記録しながらの長時間観測でも異常は見つかっていません。とりあえずは修理できた模様です。

とはいえ、荒療治なので、いつ再発するかわからないので、格安のジャンク探索は継続と考えています。

修理完了 HP 54600A

 

NF DF-194A VARIABLE PHASE DIGITAL FUNCTION SYNTHESIZER ジャンク入手


NF DF-194A VARIABLE PHASE DIGITAL FUNCTION SYNTHESIZERを入手

もちろんジャンクで入手。

情報の少ない本器ですが、ざっくり以下の仕様です。

主仕様:

周波数範囲:0.1mHz-420kHz( 正弦波/方形波)、0.1mHz-21kHz(三角波/ランプ波)

出力電圧:30Vp-p/10Vrms

出力インピーダンス:50Ω/BNC

可変位相出力:-360.0~+360.0deg

その他:トリガ/ゲート/バースト、周波数掃引、6桁表示、GP-IB

ざっくり、機能を絞った(目的を限定した)ファンクションジェネレーターです。

一般的なファンクションジェネレーターとの違いは、変調機能がない、代わりにフェーズ(位相)をずらした信号が出せます。

NF DF-194A を修理

ジャンクの内容は、

通電いたしますが、ボタンを押しても数字の変化がありません。
ファンが回転しません。

左右の取っ手と下部の脚欠品、表示画面に汚れがあり、数字が読み取りにくい

でした。入手後の確認結果も同様でした。

観察してみると、FANには電気が供給されているが回らない。(FANが唸っている)

この手の機種は、最初に全点灯して、表示関係のデモンストレーション(表示チェック)をするので、どうも電源投入直後の全点灯の状態に見える。

早速ばらしてみると、FANはACファン(一度交換されたと思われる接続となっていました)で、軸ずれしていて加熱した跡が見られます(シールが熱で浮き上がり反っている)。簡単に割れてしまいました。

NF DF-194Aで使用されていたPXJ43B1 ACファン

壊れたACファンは、単純にACが接続されただけのようでしたので、とりあえず接続を外してみました。

見事に起動しました。

どうやら、壊れたACファンの影響で、AC電源が期待通りの供給をされておらず、DC側も異常になり動作していなかったようです。

手持ちにはACファンなんてあるわけもなく、PC用の8cm DCファンを取り付けることにしました。

NF DF-194A DCファン化

内部のデジタル側に供給されている+15V(端子)から、3端子レギュレータで12Vを作りました。高回転タイプのファンなので、そのままでは少々煩いので、22Ω程度の抵抗を直列に入れて10Vで動作させました。GNDをシャーシに落としていますが、元々多点接地の構造なので問題ないと思います。

NF DF-194A でDCファンを動かすために追加した12Vレギュレータ回路

NF DF-194A 最終調整

表示器の汚れは、アクリルパネルの内側が薬品?で変質しており、アクリルパネル表面の簡単な研磨では改善できそうにないので、諦めました。

いろいろ設定を変えながら触ってみましたが、表示が正面以外からは読み取りにくいこと以外に不具合はないようです。

1点ありました。バックアップ用密閉電池が蒸発して機能していないようです。電源を入れるたびに初期値設定となりますが今は別に困らないのでとりあえず放置元々設定を記憶するようにはできてなく、保存した設定の保持用でした。電池機能はかなり低下しているようですが、保存した設定は保持されるようなので、とりあえず問題なしでした。

出力レベルは、3桁以上デジタルマルチテスターの測定値と一致しているので、特に触らないことにしました。

(正しく校正する為のAC電圧標準器を持ち合わしていないですしね。)

オフセット電圧もほぼ表示通りで問題なし。

周波数もほぼ正しいのですが、こちらは校正済みの周波数カウンタがあるので、合せ込みにトライしてみました。

電源の裏側の温度が高くなる部分に裸の水晶発振子で構成されていました。電源の発熱でオーブン的な効果を期待しているのでしょうか、十分あったまった後は、安定した周波数となっていて、表示(6桁)精度には簡単に調整できました。(追記:温度補償されているようで、温度によって周波数変動は殆どないようです。)

 

NF DF-194A 周波数を合わせ込みました。

NF DF-194A(下)、上のIWATSU UC-8152はGPSを使って校正済み

最後に

アナログ回路基板の写真を撮り忘れましたが、流石に評価用計測器らしく金属箔抵抗など高精度の部品を惜しみなく使っています。

だから、20年経過してもこの精度・性能ってことなんでしょう。

最高周波数は、低めですが、波形が綺麗で、精度も高いファンクションジェネレーターを格安で用意できました。

WaveSpectraで見てみたところ、歪み率0.01%程度(PCノイズが大きいので実力はもっとあると思います)で不要な高調波も元々のPCノイズに埋もれている感じでオーディオ評価用としても十分に使えそうです。

 

どうも最近は、エラー表示・起動不可・電源入らずなど、そんなジャンクばかりを追っかけている……

頭を抱えるようなジャンクには遭遇していないです(本能が囁くんです、こいつはやばい奴だからやめておけってね)が、本能より物欲が勝って手を出してしまう事もあるので、そのうち痛い目も見るのでしょう

追記

元々ついていたACファンを調べたら、吸い込み(吸気)方向に取り付けられていました。

確かに電源を冷やしたいのなら、直接電源回路に外気を吹き付けるのが良いとは思いますが、この筐体は吸排気口が殆どなく、ファンを吸気方向に取り付けると電源は冷えそうに感じますが、吸い込んだ空気が出ていく口がほとんどないので、内部に熱が籠りやすくなり、全体的に温度が上がるようになります。

電源は十分に冷やして、筐体内部は均等に温まることを想定しているのでしょうか…..

私はラック搭載の予定はないですが、元々ラック搭載を想定されている装置は、フロント吸気(測定器の場合、ほとんどが側面吸気)、リア排気が一般的です。これが逆だと他の装置が吐き出した熱気を吸い込んで冷やすはずが温めるという結果になる最悪のケースもあります。ラックを煙突のように想定して上下で空気の流れを作って冷却するような比較的大きい規模のラック搭載なら大丈夫でしょうけど…..

ということで、今回の修理では、DCファンは排気方向に取り付けています。ファンから遠い側の側面のスリットから吸い込んで、内部回路⇒電源を通って排気となります。これで電源部分もそれなりに冷えているようで、長時間通電しても触れる範囲で動作にも異常は見られないので大丈夫と思われますが、さらに期待した流れとなるよう装置内部にスリットの追加なども様子見しながら検討できればと思います。

同型機の他のオークション出品写真を調べると、ダストフィルタがついており、吸気となっているようで、そういう設計という事なんでしょう(-_-;)

どうせラックには搭載しないので、リア吸気でも問題はないので、設計通りの吸気方向にも取り付けてみて冷却具合を確認してみたいと思います。

追伸 2 その後

バックアップ用のNiCd電池を暫定で、スーパーキャパシタに変えておいたのですが、容量不足と充電電流が微小なので、バックアップ電圧が中途半端に不足する状況が頻発して、Power-ONの診断開始に時間がかかる事態となったので、まじめにもともとついていたNiCd電池(3.6V 50mAh)相当のNiMH 3.6V 80mAhに交換しました。

後、冷却ファンですが、メーカの設計通りの吸気に変更しました。予想通り、電源周りの冷却はそこそこですが、装置全体の温度が上がっています。暖機後はこのほうが動作が安定するかもしれません。元々は吸気フィルターがあるようなので、埃っぽい我が家でもあり、以前に使用していた台所換気専用のフィルターをカットして取り付けました。

NF DF-194A VARIABLE PHASE DIGITAL FUNCTION SYNTHESIZER 吸気FANフィルター

GPSで調整した周波数カウンターでクロックを合わせ込んでいたつもりでしたが、GPS基準クロックの試験をしていて、出来心でオシロスコープを使ったGPS同期クロックとの位相比較をした結果、(手持ちの周波数カウンターの測定限界を超えた)微妙ですが周波数のずれが判明しました。見つけてしまった以上気になるので、近いうちに再調整したいと思います。(そもそもの内蔵クロックの安定度の問題もあるので、ぴったり合したところで、あまり意味はないですが…..精神衛生上良くないので)

COMPAQ PC CQ1140jp (APU AMD E-450)オーバークロック


COMPAQ PC CQ1140jp (QF212AA-AAAD)にXubuntuをインストールしてみた

暫く放置状態だったこのAMD Fusion APU E-450デュアルコア・プロセッサー 搭載のデスクトップパソコンを立ち上げてみました。

相当前にWindows10なら、どのくらい動くか確認で入れただけなので未認証。とりあえずはWindows Updateも勝手にして動いているようですが、未認証は未認証、以前にUbuntuで使用した時より随分動画再生が軽くスムーズ(綺麗)なのをざっくり確認後、いくつかの派生UbuntuのLiveDVDで動作確認した後、一番良い印象だったXubuntuをインストールしました。

Windows10で実用的な動画性能だったので、ハード的には動画再生能力に問題はないようですが、やっぱり比較的軽量なXubuntuでもギリギリで再生している感じで余裕がありません。

動画の再生は、Amazonプライム動画をFirefoxで再生して確認しています。

AMD Fusion APU E-450をオーバークロックする

Windows10がノーマル状態で余裕をもって動画再生できているわけだから、もっとまとうな手段で対策もできそうなものですが、すでにHWアクセラレータも使っているはずの状態なので、E-450をオーバークロックしてしまいました。

『AmdMsrTweaker』をインストールします。

johkra/amdmsrtweaker-lnxにある64 bit executableを取ってきて、展開します。

展開したファイルを、パスの通ったフォルダに「amdmsrt」にリネームして移します。

当然、実行属性を付けます。

# sudo chmod +x amdmsrt

cpuidをインストールします。

# sudo apt update

# sudo apt install cpuid

これで準備は整いました。

実行してみましょう。

# sudo modprobe msr
# sudo modprobe cpuid
# sudo amdmsr

これで、現在のAPUの設定と設定可能な範囲が表示されます。

どうやら、機能としては3.3GHzまでオーバークロックできるようです。

実際にオーバークロックしてみます。core電圧も設定できますが、既に設定できる最大値ですので、電圧指定の「@1.35」は省略可能です。

# sudo amdmsrt P0=26.4@1.35

「26.4」は「27」でも設定できる値に丸められるのでざっくりで指定できます。max「33」

Windows10で動作させた時ほどの余裕はありませんが、少し安心して動画再生できる感じになりました。

ログイン時に自動的にオーバークロックさせる

ログインのたびに、コマンドをたたくのも面倒ですので、ログイン時に自動的にオーバークロックするようにしました。

適当な名前(ここでは、amd_overclock.shとします)で、オーバークロック シェルスクリプトを作成します。

#! /bin/sh
# modprobe msr
# modprobe cpuid
# amdmsr P0=26.4

管理者権限で実行しないといけませんので、

「セッションと起動」で、「自動開始アプリケーション」に登録する際に

sudo /usr/local/bin/smd_overclock.sh

と登録しますが、このままでは、パスワード入力が発生して都合が良くありません。

そこで、「smd_overclock.sh」の時だけ、sudoをノーパスワードに設定します。

# sudo visudo

以下を追記します。<username>は自分のuser名

<username> ALL=(ALL) NOPASSWD: /usr/local/bin/smd_overclock.sh

これで、再起動しても初めからオーバークロック状態で起動します。

再起動後、コンソールターミナルで、

# sudo amdmsr

これで現状の設定が表示されれば、成功です。

 

 

 

終わり