ハードウェア編に続いて、ソフトウェア編です。
その前にLCDの表示について。
1段目は、先頭がGateTime表示「*」です。よく見るとパカパカと表示が変わっているので、動いているのがわかります。これは、GateTime表示用のLEDが裏の基板についているので、LCDでわかるようにしました。
続いて周波数。2段目の「M」「K」「Hz」の位置がその位置です。上記であれば、39M999K990Hzです。
1段目の右端がGateTimeです。現在は、0.1secです。
2段目の左端は、Scalerで、現在は1/1です。
2段めの右端の空いているところは、-455kHzが表示されます(私は多分使わないでしょうけれど、、)
ソフトウェアはSourceファイルの中に沢山コメントを入れてあるので、見ていただければわかるのではと思います。多分オリジナルとは随分変わっていると思います。私自身がオリジナルを見てわからなかった部分を調べてコメントに加えてあります。
コンパイルした環境は、MPLAB X v5.35 + XC8 2.20 です。C99に修正してあります。
オリジナルは、2020/7月時点で既にリンクが切れていて追えませんが、当時コピーしてあった、このカウンタの作成にあたっての考え方?がありましたので、Sourceファイルの中に入れておきました。
計時カウンタに用いている設定数値ですが、複数のXtalを使っての合わせ込みをしてあります。計算値と多少違いますが、動作用に使っているXtalの精度もわからないので、こんなもんかな、と思っています。実際に設定したときには、40MHzで2ppm位まであっていました。また、実際には、ハードのカウンタ以外にも、割り込みで動くソフトの部分のOverheadがありますが、アセンブラではないので、どのくらいのOverheadがあるのかわかりません。それらを全部含めての合わせ込みですので、まぁ、そんなものかと思います。
多分、今どきのPICを使うと、カウンタのbit数もでかくなっていると思うので、もっと簡単に作成できるのだろうな、と思いつつ。
以下、ベタ貼りをすると、インデントが飛んでしまうので、見ずらいですが、ご容赦ください。(うまくプログラムを貼れる方法があったら教えて下さい。)
→2020/8/5追加:htmlで、<pre>...</pre>で囲むと原本のままの表示なります。修正しました。
/* * File: main.c * Author: tomo * * 2020/7/18 v6 * V5でgatetimeが1secと0.1secで、別々にgatetime用カウンタを制御しているので、 * 0.1->1secに変更すると数値が大きく変わるのが気持ち悪い * 0.1secをベースにして、0.1secを10回加算する(平均化する)方法に変更 * この方法によると、微調整するにしても一か所で済む * @1sec(10times of gatetime 0.1s * 40,000,000 39,999,920 -80 -2.000ppm * 6,000,000 5,999,993 -7 -1.167ppm */ #define INITIAL_CREDIT1 "Freq Counter v6" #define INITIAL_CREDIT2 "20200718a" /* * この周波数カウンタのもとは以下のInHisTimeのV6。 * 当初(2018年頃)Tryしたときには、開発環境が変更->MPLABXされていて、 * Compilerも->XC8に変更されていたので頑張ってPortingした。 * しばらく放置していたが、2020/6頃から再開。 * 再度環境が変わっていた(C90->C99)ので再度調整 * * 2020/6/26 MPLAB X v5.35 + XC8 2.20 でCompileできた。 * ・Interrupt の書き方の変更 * ・asmの書き方の変更 * ・ultoaのbufのunsignedを削除 */ /* オリジナルは以下のホームページ(InHisTime)。 閉鎖されてしまっているので、もう、追えない。 オリジナルとの変更点: 1)TCXO→Xtalへ 2)Compilerの変更XC8 with MPLABX 3)GateTime 0.1sec 1secでの処理の一本化(1sec=0.1x10回) 4)LCDの表示(M,Kを表示、その他表示場所の変更) 5)LCDへのGate中表示(Gate中はカウンタ値の先頭に「*」を表示) 他 ---- http://www8.plala.or.jp/InHisTime/page078.html 【PIC16F88】 ■周波数カウンタV7 ★概要 以前に、周波数カウンタV5(集大成版)を作成し、ほぼ終了と思っていましたが、更に検討を進めるうちに、 ちょっとした工夫で、思わぬ効果を得ることが出来ました。 ◎プリスケーラ無(1/1)で、40MHzまで測定可能(実測値) ◎プリスケーラ有(1/8)で、80MHzまで測定可能(実測値) ★動作原理 PICを使用した周波数カウンタでは、一般的には、TIMER0とTIMER1を組み合わせて使います。 ◆信号をカウントするために、TIMER0(8ビット)を使用(外部クロック入力同期ON固定) ◆ゲートタイム(1秒、0.1秒)を得るために、TIMER1(16ビット)を使用 しかし、TIMER0は、TIMERのON/OFF制御が出来ないため、入力ピンを強制的に出力モードにして“0”を出力 する等の工夫(本来は余分な事)が必要となります。 そこで、TIMERでON/OFFの出来る、TIMER1とTIMER2を組み合わせて使いました。 ◆信号をカウントするために、TIMER1(16ビット)を使用(外部クロック入力同期制御ON/OFF可能) ◆ゲートタイム(1秒、0.1秒)を得るために、TIMER2(8ビット)を使用→計時カウンタ 特に、外部クロック入力同期制御をOFFにすることが肝心で、もしもこれをONにすると、20MHzの信号でも 1/8のプリスケーラに設定しないと測定が出来ません。多分、TIMER0方式でのカウントが高い周波数まで 伸びないのは、外部クロック入力同期制御がON固定に原因があるのかもしれません。 ■Timer2の計時カウンタ割り込みで行い、Timer1の信号カウンタはソフトウェアでカウント */ #include #include #include #include "LCDlib2.h" //-------------------------------------- // コンフィグレーションビットの指定 //-------------------------------------- // CONFIG1 #pragma config FOSC = HS // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config MCLRE = ON // RA5/MCLR/VPP Pin Function Select bit (MCLR enabled) #pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled) #pragma config LVP = OFF // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EE Memory Code Protection bit (Code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off) #pragma config CCPMX = RB3 // CCP1 Pin Selection bit (CCP1 function on RB3) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) // CONFIG2 #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled) #pragma config IESO = OFF // Internal External Switchover bit (Internal External Switchover mode disabled) ///////////delay関数を使うために発振周波数を定義 1Mhz//////////// #define _XTAL_FREQ 20000000 //********************************************************************** /* <周波数カウンター> ■機能 sw1:プリスケーラの切り替え ・sw1=1 1/1 ・sw1=0 1/8 sw2:ゲートタイムの切り替え ・sw2=1 1秒 ・sw2=0 0.1秒 sw3:-455kHzの有無切り替え ・sw3=1 -0kHz ・sw3=0 -455kHz sw4:表示レンジの切り替え ・sw4=1 Hz表示 ・sw4=0 kHz表示 ■コンフィグ設定 LVP_OFF MCLR_OFF WDT_OFF EXTCLK ■ピンアサイン (16F88) Pin-01 RA2/LCD:D5 Pin-02 RA3/LCD:D4 Pin-03 LED Pin-04 /MCLR to VDD(+5V) via 10kOhm Pin-05 Vss to GND Pin-06 RB0/プリスケーラの切替SW Pin-07 RB1/ゲートタイムの切替SW Pin-08 RB2/455kHzの切替SW Pin-09 RB3/表示レンジの切替SW Pin-10 RB4/LCD:RS Pin-11 RB5/LCD:WR Pin-12 RB6/Signal Pin-13 RB7/LCD:Enable Pin-14 Vdd to +5V Pin-15 Xtal @ 20MHz Pin-16 Xtal @ 20MHz Pin-17 RA0/LCD:D7 Pin-18 RA1/LCD:D6 */ //********************************************************************** #define sw1 RB0 #define sw2 RB1 #define sw3 RB2 #define sw4 RB3 #define LED RA4 #define GATETIME_100MSEC 10 #define GATETIME_1SEC 1 //********************************************************************** static unsigned int MeasurementCnt; //16 bits void __interrupt () myint (void) //TMR2用の計時割り込み処理 一回走行した後に来る割り込み //MeasurementCntが0になるまで待つ { PIR1bits.TMR2IF = 0; //計時用割り込みフラグクリア // MeasurementCnt--; //計時用カウンタdecrement if (MeasurementCnt == 0) { //計時用カウンタがゼロなら T1CONbits.TMR1ON = 0; // 信号用ゲート停止 T2CONbits.TMR2ON = 0; // TIMER2を停止する。 } } //********************************************************************** unsigned long FreqMeasurement(unsigned char gateTime) { static unsigned long freq; // unsigned long → 32bit = 4,294,967,295(4G) // TIMER1の設定 →信号カウンタ PIR1bits.TMR1IF = 0; //信号割り込みフラグクリア→割り込み禁止 TMR1L = 0; //下位16bit TMR1H = 0; //上位16bit // TIMER2の設定 →計時カウンタ PIR1bits.TMR2IF = 0; //計時カウンタ割り込みフラグクリア→割り込み禁止 MeasurementCnt = 123; TMR2 = 0xED; // 2020/7/18調整 // TMR2 = 0xEE; // 31250=0.1/((1/20000000) * 4 * 16) // 0xEE=256-(31250-(256*122)) // freq = 0; // 割り込みを許可する。 INTCONbits.PEIE = 1; INTCONbits.GIE = 1; // 開始 T2CONbits.TMR2ON = 1; //計時タイマを開始する。 T1CONbits.TMR1ON = 1; //信号カウンタゲートを開ける。 // 測定 while (T2CONbits.TMR2ON != 0) { //計時カウンタが未Over if (PIR1bits.TMR1IF == 1) { //1=信号カウンタがOverflowed PIR1bits.TMR1IF = 0; //Clearしてあげる freq++; //信号カウンタをカウントアップ } } //T1 timerを閉じる処理が抜けていたか@2020/7/9追加 T1CONbits.TMR1ON = 0; //信号カウンタゲートを閉じる。 //計時カウンタがOver(1secないしは100msec経ったら)したらここに来る //再度最後にTMR1のOverflowをチェック if (PIR1bits.TMR1IF == 1) { //信号カウンタ割り込み PIR1bits.TMR1IF = 0; //信号カウンタ割り込みクリア freq++; } //換算 16bits=65536 //freqはTMR1のOverflowを数えているので、16bitをかけて、残りの端数を加える freq = freq * 65536; freq = freq + ((unsigned)TMR1H * 256) + (unsigned)TMR1L; //実周波数としてfreqを返す return (freq); } //********************************************************************** void main() { static char* msg; static unsigned long freq, temp;// 32bit 0...4294967295 static char buf[11], fmtbuf[11], prescaler, gateTime; int i; // アナログの設定 ANSEL = 0b00000000; // 使用しない。 // ポートの設定 1=input TRISA = 0b11100000; //p16=RA7=OSC1=in, p15=RA6=OSC2=in, p4=RA5=unuse=out TRISB = 0b01001111; OPTION_REGbits.nRBPU = 0; //Enable pullup resistors on portB // TIMER2の設定 → 計時ゲート PIE1bits.TMR2IE = 1; PIR1bits.TMR2IF = 0; T2CONbits.TOUTPS0 = 0; T2CONbits.TOUTPS1 = 0; T2CONbits.TOUTPS2 = 0; T2CONbits.TOUTPS3 = 0; T2CONbits.TMR2ON = 0; T2CONbits.T2CKPS0 = 1; T2CONbits.T2CKPS1 = 1; //Timer 2 pre-scaler = *16 TMR2 = 0; // TIMER1の設定 → 信号カウンタ PIE1bits.TMR1IE = 0; PIR1bits.TMR1IF = 0; T1CONbits.T1RUN = 0; T1CONbits.T1CKPS0 = 0; T1CONbits.T1CKPS1 = 0; T1CONbits.T1OSCEN = 0; T1CONbits.nT1SYNC = 1; T1CONbits.TMR1CS = 1; T1CONbits.TMR1ON = 0; TMR1L = 0; TMR1H = 0; // 変数の初期化 prescaler = 1; gateTime = GATETIME_1SEC; // LCD初期化 lcd_init(); // LCD画面クリア lcd_cls(); // LCDに文字列出力 lcd_locate( 0, 0 ); lcd_puts( INITIAL_CREDIT1 ); lcd_locate( 1, 0 ); lcd_puts( INITIAL_CREDIT2 ); __delay_ms(2000); lcd_cls(); while (1) { //loop forever // 周波数の測定 msg = "*"; lcd_locate( 0, 0 ); lcd_puts( msg ); LED = 1; //count中表示の点灯 if (gateTime == GATETIME_1SEC){ //gateTime 1sec = 10 times of 0.1s freq = 0; for (i = 1; i < 11; ++i) { temp = FreqMeasurement(GATETIME_100MSEC); freq = freq + temp; } }else{ freq = FreqMeasurement(GATETIME_100MSEC); //count指示 } LED = 0; //count後表示の消灯 msg = " "; lcd_locate( 0, 0 ); lcd_puts( msg ); //換算 freq = freq * prescaler * gateTime; //LCD1602の表示 //0123456789012345(16桁) //---------------- //*123456789 0.1s (0 line) //1/8M K Hz-455K(1 line) // プリスケーラの切り替え if (sw1 == 1) { T1CONbits.T1CKPS0 = 0; T1CONbits.T1CKPS1 = 0; prescaler = 1; msg = "1/1M K Hz"; } else { T1CONbits.T1CKPS0 = 1; T1CONbits.T1CKPS1 = 1; prescaler = 8; msg = "1/8M K Hz"; } lcd_locate( 1, 0 ); lcd_puts( msg ); // ゲートタイムの切り替え if (sw2 == 1) { gateTime = GATETIME_1SEC; msg = "1sec "; } else { gateTime = GATETIME_100MSEC; msg = "0.1s "; } lcd_locate( 0, 11 ); // 1,4 -> 0,11 2020/7/9 lcd_puts( msg ); // ?455kHzの有無 if (sw3 == 0) { freq -= 455000; msg = "-455k"; } else { msg = " "; } lcd_locate( 1, 11 ); lcd_puts( msg ); // 表示レンジの切り替え if (sw4 == 1) { ultoa( buf, freq, 10); // msg = "Hz "; } else { temp = freq / 1000; if ((freq - (temp * 1000)) > 500) { temp++; } freq = temp * 1000; // M/Kを2列目に表示することにしたので // kHzのときも、000を加えて10桁で表示 // 2020/7/9 ultoa( buf, freq, 10 ); } // 周波数の表示 lcd_locate( 0, 0 ); ultoa(buf, freq, 10); sprintf(fmtbuf, "%10s", buf ); lcd_puts( fmtbuf ); __delay_ms(10); } } //**********************************************************************