GPS周波数標準器 完成!
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は未接続にします。接続した場合、ループが形成されて動作不良が発生します。