周波数測定プログラム作成
arduino 24MHZ(25MHz)動作確認を兼ねて、基準クロック発生器のプログラムを改変し、周波数測定プログラムを作成しました。
周波数測定プログラムには、いくつか便利なライブラリーが公開されていますが、ノーマル状態でないと正常に動作しませんし、高精度と書かれているものでもそれなり程度なので、高クロックでの動作確認を兼ねて、周波数測定プログラムを作成しました。
手抜きで、システムクロックを25MHz決め打ちで作っていますが、F_CPUによってパラメータ(定数)を切り替えるようにするといろいろなシステムでも動作可能となります。システムクロックの誤差も補正できるので、測定できる周波数の上限はシステムクロックの1/2.3程度(24MHzで10.5MHz、16MHzで7MHz程度)ですが、….
F_CPUによって以下の表の黄色の部分の定数に差し替えます。
TIMER2_OCR2A、ratio_vctの場所、conversion(補正値)は、10000000倍した整数にします。補正値には必要に応じてシステムクロックの補正分も盛り込みます。
F_CPU | TCCR2B(CS2) | TCNT2 | → ratio | ||||
25000000 | 1024 | 218 | 55.7398687214612 | 217 | 56 | 0.99992064629751 | 100.007936 |
24999899 | 1024 | 217 | 55.9953299706135 | 217 | 56 | 0.999916606618099 | 100.008340033694 |
24000000 | 1024 | 220 | 53.0260180995475 | 216 | 54 | 1.00006400409626 | 99.9936 |
24000000 | 1024 | 216 | 54.0034562211982 | 243 | 50 | 0.960553278688525 | 104.106666666667 |
20000000 | 1024 | 243 | 40.0230532786885 | 216 | 45 | 1.00006400409626 | 99.9936 |
20000000 | 1024 | 216 | 45.0028801843318 | 251 | 39 | 0.993653337403337 | 100.63872 |
16000000 | 1024 | 251 | 31.0019841269841 | 251 | 31 | 1.00006400409626 | 99.9936 |
16000000 | 1024 | 255 | 30.517578125 | 251 | 31 | 1.00006400409626 | 99.9936 |
8000000 | 256 | 251 | 62.0039682539683 | 251 | 62 | 1.00006400409626 | 99.9936 |
8000000 | 1024 | 216 | 18.0011520737327 | 216 | 18 | 1.00006400409626 | 99.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*/ }
最後に、基準クロック発生器とツーショット