やりたいことに対して時間が足りなすぎる。

http://www.rigexpert.com/index?s=articles&f=aas

このページが面白いので、1つ1つ見ていきたいという気持ちがあります。まずは一番上の Analyzers based on diode detectors のうち、抵抗1本を検出に使うものです。

やってることは負荷インピーダンスを電流と負荷電圧から求め、さらに入力電圧との関係からリアクタンスの絶対値まで求めるものです。

計算

1 を 、2 を 、3 を という名前にします。

負荷 に対し、それぞれの電圧を としとき、負荷 にかかっている電圧 の絶対値(検波値) は

回路全体にかかる電圧

(1) の式を について解いて

(3) を (2) に代入する。

これを について解く

(3) の式のルートをとって を求めると

また、回路全体の電流

負荷 はそれぞれ

となり、抵抗成分とリアクタンス成分の絶対値が求まる (容量性か誘導性かはわからない)

  1. トップ
  2. tech
  3. アンテナアナライザの回路 シリーズ抵抗1本型

http://www.rigexpert.com/index?s=articles&f=aas

ブリッジタイプ

これはリターンロスブリッジを使う形のものです。

  • 1を
  • 2を
  • 3を
  • 4を

とすると

から電圧反射係数が求められます。 で入力電圧を求め、 で得た電圧反射係数を正規化するイメージ)

から負荷インピーダンスの絶対値がわかります。

ブリッジのイメージをつかむ

一番問題なのはブリッジに接続されている がどのようになるかです。負荷側の電位は50Ωと負荷インピーダンスとの分圧、もう片方は50Ωと50Ωの分圧で固定になっています (すなわち入力電圧の0.5倍が基準点)。

ここでもし負荷インピーダンスが50Ωであれば、負荷側の分圧も入力電圧の0.5倍になり、ブリッジ部分の電位差はゼロになります。ブリッジの平衡状態です。

負荷インピーダンスが25Ωになると、25/(25+50)=0.333... で電位差が -0.1666...、100Ωになると 100/(100+50)=0.666... で電位差が 0.1666... になります。つまりここの電位差は基準インピーダンス50Ωに対しての比になります。

もし入力電圧が1Vだとしたら、この電位差の2倍がすなわち電圧反射係数になります。(反射係数には複素数の場合、角度が出てくるが、位相は計っていないのでこれはわからず、この反射係数は絶対値です)

ブリッジ電位差が反射係数になっていることを確かめる

ブリッジ中の負荷インピーダンスを とし、それ以外のインピーダンス値を とすると、入力電圧が1Vのときのブリッジの電位差 E は以下のようになります。ここで、 をブリッジの50Ω/50Ωで分圧された側の電位、 を負荷側の電位としています。

電圧反射係数は

なので、1Vのとき、ブリッジの電位差は反射係数の半分になっています。また、 は入力の半分の電圧になりますから、ブリッジ電位差 で正規化することで反射係数そのものを求められます。

反射係数の大きさと、負荷インピーダンスがわかると、負荷の成分がわかる

反射係数の大きさと、負荷インピーダンスがわかると、まず負荷の抵抗成分を求められます。

反射係数の大きさ 、伝送路インピーダンス 、負荷インピーダンス の関係は以下の通りです。

は 50Ω なので 50 で置き換え、Z は R + jX に置き換えます。

絶対値同士の比なので分子分母とも絶対値にします

負荷インピーダンスの絶対値が既知ですから

(2) を (1) に代入します

難しいので maxima で解かせて

R が求められれば 、Z との関係から X の絶対値も求められます。

あれ? VSWR って簡単な比で求められなかったけ?

VSWR は以下のような式で求められますが、これは負荷インピーダンスが純抵抗な場合 (jX = 0) だけで、純抵抗でなければ反射係数を一旦求める必要があります。

この式を前提にインピーダンス比がSWRになる(インピーダンスがSWRに直接関連づく)と覚えていると、SWRと負荷インピーダンスの値から R が求められる理屈がわからなくなります。

インピーダンス比からSWRを求めた場合、純抵抗を想定してSWR値を求めたわけですから、このSWRと負荷インピーダンスからRを逆算すると、必ず jX の項は0になります。

別の説明をすると、たとえ同じ絶対値の負荷インピーダンスでも、リアクタンス成分でSWRは上がります。Z = 50 であっても、50 + 0j の場合と 0 + 50j の場合では違うよということで、前者のSWRは1ですが後者は無限です。

  1. トップ
  2. tech
  3. アンテナアナライザの回路 ブリッジ型

リターンロスブリッジを買ってみたので、手元にあるいろんなものを測ってみました。

測定方法

普通のリターンロスブリッジなので、DUT をオープンしてノーマライズ、DUT に測定対象を接続してリターンロスを読みます。

ダミーロードたち

SMA コネクタの小電力ダミーロードたちです

ちょっとだけ長いやつ (耐電力 2Wのもの)

短いやつ 1W か 0.5W

もうひとつ短いやつ

ダミーロードが悪いのかリターンロスブリッジが悪いのかよくわかりませんが、そこそこまでしか測れなそうです。実用上はまぁまぁ使える感じでしょうか…

アマチュア無線用の M コネクタ定格15Wダミーロードです

第一電波工業 ダイヤモンド ダミーロード DL50A -

4.0 / 5.0

DC-1GHz まで使える (1.15以下 (DC-800MHz) 1.20以下 (800-1000MHz) というスペックですが、1.15=23.13dB / 1.20=20.83dB なのでスペック通りの測定はできてない感じです。

ハンディ機用ホイップアンテナ

144MHz/430MHz 帯用のホイップアンテナです。1/4λなので人体アースしながら測っています。アースしないと全然違う帯域に同調していました。

モービル用マルチバンドホイップ

ベランダに設置してあるホイップです。最近調整してませんが… 2014年の10月あたりの記録はこれです [tech][ham] ベランダに設置した UHV-6 (モービルホイップ) の現状 | Wed, Oct 8. 2014 - 氾濫原

なぜかちょっとずつズレているようにみえます。

ちなみに DSA815 には VSWR モードがあって、実はリターンロスからVSWR換算を自動でやってくれます。これを使って 21MHz 帯を見てみました。

7MHz も見てみました。

アンテナアナライザーでも測ってみました。

アンテナアナライザーとTG付きのスペクトラムアナライザー

リターンロスブリッジを使えばTG付きのスペクトラムアナライザーはスカラー型のネットワークアナライザと似た機能を持つようになります。しかし位相は測れないので決してベクター型ネットワークアナライザの代わりはできません。

アンテナアナライザーはベクター型ネットワークアナライザのアンテナ特化版です。(ベクター型ではないアンテナアナライザーもありますが…) なので R + jX を分けて表示することができ、

  • アンテナが共振しているのに抵抗成分のミスマッチでVSWRが高い (トランスをつかえば解決)
  • アンテナが共振していなくてVSWRが高い (アンテナ自体の調整が必要)

という原因をわけて解析することができます。


50MHz 帯まではアンテナアナライザーがあるのでスペアナでリターンロスを測る機会はまずなさそうです。しかしそれ以上高い周波数ではスペアナ+リターンロスブリッジが活躍できそうです。

  1. トップ
  2. tech
  3. スペアナとリターンロスブリッジを使ってSWRを測ってみる

[tech] OCXO の VFC ピンでの可変範囲を GSP の 1PPS を使って調べた | Fri, Feb 26. 2016 - 氾濫原 でとりあえず可変範囲ぐらいはわかったので、半固定抵抗を使ってだいたい10MHzぐらいにあわせてみたいと思います。

でもって、回路やコードの構成を変えました。

MPU のクロック自体を OCXO の 10MHz とする

前回は mbed デフォルト (内蔵の12MHz CRクロックを逓倍した48MHz) でやっていました。別にこれでもいいのですが、せっかく高精度な 10MHz があるのに、これをクロックにしないのももったいないなという感じになりました。

それに、LPC1114 自体のクロック源をOCXOにすれば、内蔵カウンタでフリーランさせた値がそのまま周波数カウントになるので、GPIOを1ピン節約できます。

つまり 10MHz 5逓倍で 50MHz にして LPC1114 最大クロックで動かしてみたいと思います。OCXO の周波数カウントは5分周すれば元の10MHzになりそうです。

システムクロックとして外部クロック(水晶発振器)を使うには

用語ですが「システムオシレータ」と「システムクロック」に区別があって、システムオシレータは外部水晶発振子を使うことを想定した内蔵発振回路、システムクロックはシステムオシレータまたはBYPASSされた外部クロックを指しています。

まず OCXO は発振器なので、内蔵システムオシレータは使わないようにします。

ドキュメントを見るわけですが、システムオシレータコントロールレジスタ (SYSOSCCTRL) の BYPASS が日本語版だと間違っているので原文を見る必要があります。

Bypass enabled. PLL input (sys_osc_clk) is fed directly from the XTALIN pin bypassing the oscillator. Use this mode when using an external clock source instead of the crystal oscillator.

となっていて、XTALIN から直接クロックを入力する場合には、内蔵のシステムオシレータをバイパスするという意味になります。

まぁとにかく XTALIN には外部クロックを直接入れれば良いのですが (XTALOUT はフロート)、ここで罠があります。XTALIN は他のピンと違って 1.8V 以上入力してはならないことになっています。

5V クロックなら 5.6k / 2.4k、3.3V クロックなら 1.2k / 1k あたりの分圧が必要です。

ただ、どうも OCXO の出力そのままだとうまく分圧できなかったので、一旦バッファ (74HCU04しかなかったのでこれで…) で受けてからクロック入力に使いました。

外部クロック設定コード

ぶっちゃけかなりハマりました。デバッグするためには CLKOUT を有効にして、どの段階までちゃんと出力が出ているかを確認する必要があります。

レジスタ自体はデータシート通りに記述すれば良いのですが、関連レジスタがかなり多いのでたいへんです……

ちょっとハマったのは SYSPLLCTRL の MSEL が実際の分周数-1を設定する必要がありました。(データシートにももちろん書いてありますが…)

やってることは

  1. (mbed の初期化では IRC=12MHz を PLL で 48MHz にされています)
  2. PLL を設定しなおすので、メインクロックソースを IRC にしておく (12MHz動作に)
  3. 一旦 PLL 回路の電源を切る (ついでのシステムオシレータ回路も確実に切っておく)
  4. システムオシレータをバイパスする設定をする
  5. PLL 関連レジスタを適当に設定して 5 逓倍できるようにする
  6. PLL のクロックソースをシステムクロックに
  7. PLL 回路の電源を入れ、PLL のロックを待つ
  8. メインクロックを PLL 出力に変える (この時点で 50MHz 動作に)
  9. IRC の電源を切る (切らなくてもいいんですが)
  10. SystemCoreClock を50MHzに更新しておく

という感じです。

void set_system_clock_to_external_10mhz() {
#ifdef DEBUG_CLOCK
	// XXX: set CLKOUT to debug (dp24)
	LPC_IOCON->PIO0_1 = (0b001<<0/*FUNC=CLKOUT*/);
	LPC_SYSCON->CLKOUTCLKSEL = 0b11; /*0b00=IRC, 0b01=SysOsc, 0b10=WDT, 0b11=Main*/
	LPC_SYSCON->CLKOUTDIV = 1;
	LPC_SYSCON->CLKOUTUEN = 0;
	LPC_SYSCON->CLKOUTUEN = 1;
#endif

	// Power down config (irc=on, sysosc=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	// Ensure clock source to internal in temporary
	LPC_SYSCON->MAINCLKSEL = 0b00;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0);

	// Power down config (syspll=off, sysosc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<5/*SYSOSC_PD*/) |
		(1<<7/*SYSPLL_PD*/)
	);

	// Set System OSC = BYPASS (clock fed to XTALIN directly 1.8V / XTALOUT must be floating)
	LPC_SYSCON->SYSOSCCTRL = (1<<0/*BYPASS*/);

	// PLL Clock Source = System OSC
	LPC_SYSCON->SYSPLLCLKSEL = (0b01<<0/*SEL*/);

	// Update PLL Source
	LPC_SYSCON->SYSPLLCLKUEN = 0;
	LPC_SYSCON->SYSPLLCLKUEN = 1;
	while ( (LPC_SYSCON->SYSPLLCLKUEN & 0b1) == 0 ); 

	// Set PLL
	// M = F_clkout / F_clkin
	// FCCO = 2 * P * F_clkout (P = {1, 2, 4, 8}) (FCCO=156-320MHz)
	// F_clkout = 50MHz / M = 5 / P = 2 / FCCO = 200MHz
	LPC_SYSCON->SYSPLLCTRL = ( (5 - 1)<<0/*MSEL*/) | (0b01<<5/*PSEL*/);

	// Power down config (syspll=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<7/*SYSPLL_PD*/)
	);
	// Wait for PLL Lock
	while ( (LPC_SYSCON->SYSPLLSTAT & 0b1) == 0 );

	// Update Main Clock to PLL Output
	LPC_SYSCON->MAINCLKSEL = 0b11;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0); 

	// Power down config (irc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	SystemCoreClock = 50000000;
}


周波数カウント部分の変更

タイマーをタイマーとして使い、システムクロックをそのままカウントします。キャプチャピンはGPS1PPSをキャプチャするようにし、キャプチャのタイミングでキャプチャレジスタにタイマカウンタをコピー(ハードウェア動作)させて割込みを生成します。GPIO のピンチェンジ割込みは使いません。

レジスタの設定は問題ないのですが、割込み周りでだいぶハマりました。

  • mbed の InterruptManager で TIMER_32_0_IRQn の割込みを設定しようとしたがうまくいかなかった
  • NVIC_EnableIRQ しないとダメだった
  • extern "C" を忘れてて割込みが呼ばれずしばらくはまった

また、前回よりも精度を高めるため、10秒、100秒、1000秒のゲートでの誤差を表示するようにしました。dHz (デシヘルツ) cHz (センチヘルツ) mHz (ミリヘルツ) 単位で表示しているのが誤差の項目です。

±1カウントエラーがあるので、多少 +1/-1 がでることがあります。

1000秒単位での調整は面倒なので、10秒単位の調整しかしてませんが、このように手で雑に調整してもこのぐらいの精度が出ていました。

コード

LPC1114 はSRAMが4KBと少ないため、カウントされた値をそのまま持つのではなく、誤差を signed で持つようにしています。そもそもOCXOの可変範囲が±5Hz程度しかないので、それ以上の情報を持つ必要がありません。ということで int8_t の配列で履歴を持って、直近のn件を合計して見る形になっています。

これにより、例えばカウンタをそのまま持つには10Mは16bitに納まらないので32bitの配列にしなければなりませんが、誤差だけを保持すれば4分の1の容量ですみます。というか uint32_t だとLPC1114では1000個の履歴を持てません。

constexpr uint32_t CLOCK = 10000000;
constexpr uint16_t HISTORY = 1000;

int8_t errors[HISTORY];
uint16_t error_index = 0;
uint16_t error_count = 0;

volatile bool updated = 0;
Serial serial(USBTX, USBRX);

extern "C" void TIMER32_0_IRQHandler (void) {
	static uint32_t prev = 0;
	uint32_t count = LPC_TMR32B0->CR0;
	uint32_t pps_counter;

	if (prev < count) {
		pps_counter = count - prev;
	} else {
		// overflowed
		pps_counter = (0xffffff - prev) + count + 1;
	}
	prev = count;

	int16_t error = static_cast<int32_t>(CLOCK) - static_cast<int32_t>(pps_counter);
	if (abs(error) < 15) {
		error_index = (error_index + 1) % HISTORY;
		errors[error_index] = error;
		if (error_count < HISTORY) {
			error_count++;
		}
		updated = 1;
	}

	LPC_TMR32B0->IR = (1<<4/*CR0 Interrupt*/);
}

int main() {
	set_system_clock_to_external_10mhz();
	NVIC_EnableIRQ(TIMER_32_0_IRQn);

	// enable 32bit counter
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9/*CT32B0 32bit counter clock*/);

	// Capture pin dp14 (for gps 1pps)
	LPC_IOCON->PIO1_5         |= (0b010<<0/*FUNC=CT32B0_CAP0*/);

	// Match output (not used)
	//	LPC_IOCON->PIO1_6         |= (0b010<<0/*FUNC=CT32B0_MAT0*/);
	//	LPC_IOCON->PIO1_7         |= (0b010<<0/*FUNC=CT32B0_MAT1*/);
	//	LPC_IOCON->PIO0_1         |= (0b010<<0/*FUNC=CT32B0_MAT2*/);
	//	LPC_IOCON->R_PIO0_11      |= (0b011<<0/*FUNC=CT32B0_MAT3*/);

	// Prescaler
	LPC_TMR32B0->PR  = 4;
	// Capture on CAP0 Rising Edge (to CR0) and Enable Interrupt
	LPC_TMR32B0->CCR =
		(1<<0/*CAP0RE*/) |
		(1<<2/*CAP0I*/);
	LPC_TMR32B0->MCR = 0;
	LPC_TMR32B0->CTCR = (0b00<<0/*Counter/Timer Mode=Timer*/);
	LPC_TMR32B0->TCR = (1<<0/*Counter Enable*/);

	serial.baud(115200);
	for (;;) {
		if (updated) {
			updated = 0;
			serial.printf("[%d] last: %+d\n", error_count, errors[error_index]);

			if (error_count >= 10) {
				int32_t sum = 0;
				for (int i = 0; i < 10; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+ddHz\n", error_count, sum);
			}
			if (error_count >= 100) {
				int32_t sum = 0;
				for (int i = 0; i < 100; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+dcHz\n", error_count, sum);
			}
			if (error_count >= 1000) {
				int32_t sum = 0;
				for (int i = 0; i < 1000; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				// 1000_0000000
				serial.printf("[%d] %+dmHz\n", error_count, sum);
			}
		}
	}
}
  1. トップ
  2. tech
  3. OCXO と GPS 1PPS その2

VFC を PWM によるデジタルコントロールにしてみました。

前回は多回転ボリュームを使って手動で調整をしました。これだと10秒単位ぐらいならともかく、1000秒単位の調整は面倒くさすぎてやっていられません。のでここをMCUで自動化します。

PWM による VFC のコントロール

OCXO から出ている Vref を PWM でスイッチングしてローパスフィルタにかけてコントロール電圧を生成します。DAC デバイスの電源電圧を Vref にするという形でもよさそうですが手元に DAC デバイスがないので PWM でやっています。

PWM の周波数は約30Hzです。これは mbed の PwmOut の period_us() に int の最大値 (1<<15) を設定した値になります。(なんでこのAPIはintなんでしょうかね?)

これにより、int16_t 変数でパルス幅を保持して、pulsewidth_us() が最大の分解能を持つようにしています。

PWMの周波数が30Hzぐらいなので、外部フィルタとしてかなり低い周波数のローパスフィルタを使っています。応答性が高くてもあまり意味がないないし困るので1Hzぐらいのローパスフィルタでも良さそうです。

また、PWM 周波数を変えた直後はグリッチが発生するので、2秒間のGPS1PPS割込みを無視するようなコードを加えました。

制御

PID制御などではなく、まずは適当にインクリメント・デクリメントし続けるような実装にしました。

当然 PID 制御したほうがいいのですが、とりあえず頭を使いたくなかったので…

結果

シリアルに出している直近1000秒ごとの誤差合計を、雑なワンライナーで整形して Google Spreadsheet でグラフ化してみました。主にソフトウェア的要因と思われる過補正を繰替えしている雰囲気を感じられますが、概ね±10mHz(±1ppb)におさまっています。

GPSDO

調整を自動化したので、これはもはやGPSDOと呼べそうです。PID制御にするなど、ソフトウェア的な制御改良の余地はありますが、GPSDOの大枠が実装できました。

どちらかといえばあとはアナログ回路の要素が多くなります。といっても以下らへんを実装すれば実用的になりそうです。

  • 出力が矩形波なので高次のローパスフィルタないしバンドパスフィルタを設計実装する
  • 波形整形するバッファを使っていないのでバッファのしかたを変える
  • GPSのアンテナ入力を外部入力に変える
  • GPS FIx されず、1PPS 信号がきてないときのタイムアウト処理を追加
  • ケースに入れる

アナログまわりが勉強中なので現状の技術力だと難しい面もありますが、製作例もあるのでなんとかはなりそうです。とはいえ、作っても現状だと応用先がスペアナぐらいしかないので、あんまりやる気がありません。

かなり簡単な構成でppbオーダーの高精度なクロックが得られるのは面白いです。もうちょっとアナログ回路の技術力があればさくっと作りあげられそうです。

コード

#include "mbed.h"
#include <stdint.h>
#include <inttypes.h>

void set_system_clock_to_external_10mhz() {
#ifdef DEBUG_CLOCK
	// XXX: set CLKOUT to debug (dp24)
	LPC_IOCON->PIO0_1 = (0b001<<0/*FUNC=CLKOUT*/);
	LPC_SYSCON->CLKOUTCLKSEL = 0b11; /*0b00=IRC, 0b01=SysOsc, 0b10=WDT, 0b11=Main*/
	LPC_SYSCON->CLKOUTDIV = 1;
	LPC_SYSCON->CLKOUTUEN = 0;
	LPC_SYSCON->CLKOUTUEN = 1;
#endif

	// Power down config (irc=on, sysosc=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	// Ensure clock source to internal in temporary
	LPC_SYSCON->MAINCLKSEL = 0b00;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0);

	// Power down config (syspll=off, sysosc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<5/*SYSOSC_PD*/) |
		(1<<7/*SYSPLL_PD*/)
	);

	// Set System OSC = BYPASS (clock fed to XTALIN directly 1.8V / XTALOUT must be floating)
	LPC_SYSCON->SYSOSCCTRL = (1<<0/*BYPASS*/);

	// PLL Clock Source = System OSC
	LPC_SYSCON->SYSPLLCLKSEL = (0b01<<0/*SEL*/);

	// Update PLL Source
	LPC_SYSCON->SYSPLLCLKUEN = 0;
	LPC_SYSCON->SYSPLLCLKUEN = 1;
	while ( (LPC_SYSCON->SYSPLLCLKUEN & 0b1) == 0 ); 

	// Set PLL
	// M = F_clkout / F_clkin
	// FCCO = 2 * P * F_clkout (P = {1, 2, 4, 8}) (FCCO=156-320MHz)
	// F_clkout = 50MHz / M = 5 / P = 2 / FCCO = 200MHz
	LPC_SYSCON->SYSPLLCTRL = ( (5 - 1)<<0/*MSEL*/) | (0b01<<5/*PSEL*/);

	// Power down config (syspll=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<7/*SYSPLL_PD*/)
	);
	// Wait for PLL Lock
	while ( (LPC_SYSCON->SYSPLLSTAT & 0b1) == 0 );

	// Update Main Clock to PLL Output
	LPC_SYSCON->MAINCLKSEL = 0b11;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0); 

	// Power down config (irc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	SystemCoreClock = 50000000;
}

constexpr uint32_t CLOCK = 10000000;
constexpr uint16_t HISTORY = 1000;

int8_t errors[HISTORY];
uint16_t error_index = 0;
uint16_t error_count = 0;

volatile bool updated = 0;
volatile uint8_t skip_second = 0;
Serial serial(USBTX, USBRX);
PwmOut osc_control(dp1);

extern "C" void TIMER32_0_IRQHandler (void) {
	static uint32_t prev = 0;
	LPC_TMR32B0->IR = (1<<4/*CR0 Interrupt*/);

	uint32_t count = LPC_TMR32B0->CR0;
	uint32_t pps_counter;

	if (prev < count) {
		pps_counter = count - prev;
	} else {
		// overflowed
		pps_counter = (0xffffff - prev) + count + 1;
	}
	prev = count;

	if (skip_second > 0) {
		skip_second--;
		return;
	}

	int16_t error = static_cast<int32_t>(pps_counter) - static_cast<int32_t>(CLOCK);
	if (abs(error) < 15) {
		error_index = (error_index + 1) % HISTORY;
		errors[error_index] = error;
		if (error_count < HISTORY) {
			error_count++;
		}
		updated = 1;
	}
}

int16_t pulsewidth = 16500;

void vcf_plus(uint8_t count) {
	pulsewidth -= count;
	osc_control.pulsewidth_us(pulsewidth);
	skip_second = 2;
	serial.printf("osc_control = %d\n", pulsewidth);
}

void vcf_minus(uint8_t count) {
	pulsewidth += count;
	osc_control.pulsewidth_us(pulsewidth);
	skip_second = 2;
	serial.printf("osc_control = %d\n", pulsewidth);
}

int main() {
	set_system_clock_to_external_10mhz();
	NVIC_EnableIRQ(TIMER_32_0_IRQn);

	osc_control.period_us(1<<15);
	osc_control.pulsewidth_us(pulsewidth);

	// enable 32bit counter
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9/*CT32B0 32bit counter clock*/);

	// Capture pin dp14 (for gps 1pps)
	LPC_IOCON->PIO1_5         |= (0b010<<0/*FUNC=CT32B0_CAP0*/);

	// Match output (not used)
	//	LPC_IOCON->PIO1_6         |= (0b010<<0/*FUNC=CT32B0_MAT0*/);
	//	LPC_IOCON->PIO1_7         |= (0b010<<0/*FUNC=CT32B0_MAT1*/);
	//	LPC_IOCON->PIO0_1         |= (0b010<<0/*FUNC=CT32B0_MAT2*/);
	//	LPC_IOCON->R_PIO0_11      |= (0b011<<0/*FUNC=CT32B0_MAT3*/);

	// Prescaler
	LPC_TMR32B0->PR  = 4;
	// Capture on CAP0 Rising Edge (to CR0) and Enable Interrupt
	LPC_TMR32B0->CCR =
		(1<<0/*CAP0RE*/) |
		(1<<2/*CAP0I*/);
	LPC_TMR32B0->MCR = 0;
	LPC_TMR32B0->CTCR = (0b00<<0/*Counter/Timer Mode=Timer*/);
	LPC_TMR32B0->TCR = (1<<0/*Counter Enable*/);

	serial.baud(115200);
	for (;;) {
		if (serial.readable()) {
			char c = serial.getc();
			serial.putc(c);
			serial.printf(" %d\n", c);
			if (c == '+' || c == 'p') {
				vcf_plus(100);
			} else
			if (c == '-' || c == 'm') {
				vcf_minus(100);
			}
		}

		if (updated) {
			updated = 0;
			int8_t last = errors[error_index];
			serial.printf("[%d] last: %+d\n", error_count, last);
			if (last >= 2) {
				vcf_minus(1);
			} else
			if (last <= -2) {
				vcf_plus(1);
			}

			if (error_count >= 10) {
				int32_t sum = 0;
				for (int i = 0; i < 10; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+ddHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}

			}
			if (error_count >= 100) {
				int32_t sum = 0;
				for (int i = 0; i < 100; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+dcHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}
			}
			if (error_count >= 1000) {
				int32_t sum = 0;
				for (int i = 0; i < 1000; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				// 1000_0000000
				serial.printf("[%d] %+dmHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}
			}
		}
	}
}
  1. トップ
  2. tech
  3. OCXO と GPS 1PPS その3