2020年4月13日月曜日

FrequencyとDuty比の簡易計測器

周波数とDUTY比の簡易計測器を作りたくなりました。
モーターの回転数とかで使いたい所があって
20KHz辺りまである程度正確に測りたいのです。

いつものように右往左往してしまい内容に関しては、
多分にマーティーにしかわからない記録になっております(-_-;)

さて、Arduinoを調べると、pulseIn() 命令で
LOW期間、HIGH期間を計測することができるようです(解説はここ

たぶん、割り込みでタイマーを制御するだろうから
Timer Clockで精度はほぼ決まるのでしょうが...
まあ、調べればいいのですが^^;

PICが頭に浮かんだので、久しぶりに使ってみようかなと^^;
しか~し、もう数年使ってないのです。
少なくともブログ始めてから使ってません(T_T)

早速、当時、随分お世話になった
きむ茶工房ガレージハウス」さんのブログから探します。
Arduinoに出会う前は、随分お世話になりましたm(_ _)m
ブログを移設されて昔と異なるアドレスになってますね~
ソフトウエアについて
 当サイトのソフトウエアは特に記述が無い限りPDS(Public Domain Software)とします。
 作権表示義務無し。商用利用、改造、自由。連絡不要とします。
 役立ててやって下さい。
とあり、誠にありがたいことであります。
活用させていただきますm(_ _)m

サイト内を探すと、やはり、
PICのキャプチャ機能でパルスを計測してみる」ってのが見つかりました。

PIC 16F1829のCCP(Capture/Compare/PWM Modules)モジュールの
Capture機能を使って、周波数とDUTY比を測定するというものです。
ドンピシャであります^^;

CCPモジュールは、全てのPICに装備されているわけではなく、
マーティーの宝箱を探すと、16F1827が1個だけありました!
CCPモジールに関しては、16F1829と同じでした。
マーティー愛用の12F1822は、CCPが1個なので今回は不採用です。
1個しかないので16F1827をポチっておきます。
ここが最安値かな? US$ 13.35/10個(11% OFF)

さて、PICは、5年以上前から使ってないので、すっかり忘れております。
過去に動いたプログラムで、コンパイラや書込の復習が必要です。

先の「PICのキャプチャ機能でパルスを計測してみる」のプログラムでは、
表示の部分が
「ここでご自由に表示処理を行いましょう」になってるのです。
マーティーは、PICの表示には、
秋月電子の「SC1602」というキャラクタLCDを使っています。
半角16文字、2行です。
端子位置が横並びの「SD1602」も持ってます。
というのは、これも「きむ茶工房ガレージハウス」さんの
キャラクタLCDモジュールに表示を行う」を使わせていただいてるからです。
幸い、16F1827でサンプルプログラムが書かれています。

そのサンプルプログラムで昔~し温度計を作ってたので
そのプログラムでコンパイル・ビルド・書込の復習することにします。

書込機は、秋月電子で買った「PIC KIT2」です。
検索するとここにまだ現役でありますね~!
価格も同じ5300円のままで、発売日 2008/10/09 になってます。
PIC KIT2に書き込みの手順は、
こんな事を予期して、マーティーがボードに貼ってました^^;
プログラムソースの日付は、2014年9月6日で6年前でした^^;
ちょっと新規プロジェクトの作成とかで手こずりましたが(-_-;)
コンパイル、ビルドは、成功です。
開発環境は、MPLAB X IDE v2.10、Compilerは、XC8 v1.32 です。
ソフトは、WindowsXPマシンに入れてて
Windows10のマーティーPCには、まだ入れてないのです。
画面はこんな感じです。
昔のプロジェクトを沢山開いたままでした。
PIC KIT2に16F1827を載せて
WindowsXPにUSBで接続して
書き込みソフトは、「PICKit2 v2.61」です。
DEVICE TYPEを設定すると、16F1827が検出されました。
hexファイルを開くのですが、
MPLAB X IDE v2.10のhexファイルは、デフォルトなので深い所にできます。
[Documents and Settings]の下の方なのです。
[Write]!
無事[Programming Successful]がでました!
[Verify]も成功を確認して、
昔~し作って動作してたブレッドボードをお菓子の箱に保存してたので
そのまま、PICを差し替えて、電源ON!
無事動きました!
PIC始めて間もない頃は、とても感動したものです。
左下のTO-92の黒いのが温度センサIC LM35DZです。
10.0 mV/℃の出力電圧をPICのADCで読んでます。
LCDの配線が面倒なので、温度センサーだけ外して
このブレッドボードをそのまま使うことにします。

PICのキャプチャ機能でパルスを計測してみる」から
サンプルプログラムをダウンロードして
「Capture.zip」を解凍するとファイルが3つあります。
周波数計測するプログラムは、この2つです。
CPU Clock:32MHzで
・Capture.c・・・・・クロックソース 1μsec
・HICapture.c・・・クロックソース 125nsec

「Capture.c」でも25KHz辺りまで使えるらしいですが、
1μsecを単純に200倍して±2%と考えると、20KHz
「HICapture.c」の方、125nsecを単純200倍すると、
・クロックソース 1μsec・・・20KHz
・クロックソース 125nsec・・・160KHz
辺りまでは、そこそこ測定できそうと考えていいのかな?

で、「HICapture.c」を使おうと眺めていると
これは、周波数だけでDUTY測定がないですね~
「Capture.c」が、周波数とDUTYを測定していました。
なので、これを元にクロックソースを125nsecに変更した方がラクそうです^^;

さて、16F1829 ⇒ 16F1827に移植しないといけません。
20pin ⇒ 18pin でI/O PIN番号やPORT数も異なるので
定義とかを変更しないといけないのです。
Arduinoの用に簡単にはいかないのです。

まずは、ピンアサインと使用しているモジュールを調べます。
16F1827/16F1829共通
PORT-A、PORT-B、PORT-C、わりと差がありますね~
では、「Capture.c」を眺めていきます。
まず、コンフィグレーション・・・1827と1829で共通なので変更不要です。
と思って進めてたのですが
最初、LCDに何も表示されなくて、気づくまで随分と時間を消費したのですが
先のLCD表示プログラムが32MHzでは、動かなかったのです(T_T)
で、1箇所変更しました。
*********
// コンフィギュレーション2の設定
#pragma config WRT = OFF        // Flashメモリーを保護しない(OFF)
// #pragma config PLLEN = ON       // 動作クロックを32MHzで動作させる(ON)

#pragma config PLLEN = OFF       // 動作クロックを8MHzで動作させる(OFF)
*********

「割り込みの処理」で[Timer1]と[CCP4]を使われてますが
これも16F1827と16F1829で同じです。

CCPのCaptureで使うTimerもTimer1で16F1827/16F1829は、共通なので

この「割り込みの処理」は変更不要です。

ここは、各PORTの設定ですが、
Datasheetを見比べると
16F1827と16F1829でだいぶ異なっています。
CCP4の入力も、1829・・・RC6、1827・・・RA4、と異なっています。
ということで変更します。
コメントだけ変更してる所もあります。
*********
// I/O情報の設定
// OSCCON = 0b01110000 ;    // 内部クロックは8MHzx4(32MHz)とする
    OSCCON = 0b01110000 ;    // 内部クロックは8MHzとする(config PLLENで決まる)

// ANSELA = 0b00000000 ;    // AN0-AN3は使用しない全てデジタルI/Oとする for 1829
    ANSELA = 0b00000000 ;    // AN0-AN4は使用しない全てデジタルI/Oとする for 1827

// ANSELB = 0b00000000 ;    // AN10-AN11は使用しない全てデジタルI/Oとする for 1829
    ANSELB = 0b00000000 ;    // AN5-AN11は使用しない全てデジタルI/Oとする for 1827

// ANSELC = 0b00000000 ;    // AN4-AN9は使用しない全てデジタルI/Oとする 1829 only

// TRISA  = 0b00000000 ;    // ピン(RA)は全て出力に割当てる(RA3は入力専用)
    TRISA  = 0b00110000 ;    // RA4(CCP4)入力ピン(RA)は全て出力に割当てる(RA5は入力専用)

    TRISB  = 0b00000000 ;    // ピン(RB)そのは全て出力に割当てる

// TRISC  = 0b01000000 ;    // RC6(CCP4)入力、そのは全て出力に割当てる 1829 only

// PORTA  = 0b00000000 ;    // RA0-RA5出力ピンの初期化(全てLOWにする) for 1829
    PORTA  = 0b00000000 ;    // RA0-RA7出力ピンの初期化(全てLOWにする) for 1827

// PORTB  = 0b00000000 ;    // RB4-RB7出力ピンの初期化(全てLOWにする) for 1829
    PORTB  = 0b00000000 ;    // RB0-RB7出力ピンの初期化(全てLOWにする) for 1827

// PORTC  = 0b00000000 ;    // RC出力ピンの初期化(全てLOWにする) 1829 only
*********

後は、変更しなくていいハズです。
が、肝心のLCD表示は、各自で考えてね。という事になってるので
表示部分を考えます。
キャラクタLCD「SC1602」は、半角16文字2行なので
ちょっと欲張って少数2位まで、こんな感じにしようと思います。
先の「キャラクタLCDモジュールに表示を行う」の
サンプルプログラムを解凍すると、3つファイルがありますが
skSD1602LCD.c と skSD1602LCD.h を使ってます。
LCD「SC1602」は、4bitパラレルで、6本のI/Oを使います。
I/Oの定義は、「skSD1602LCD.h」に記述されていて
RB2~RB7になっているので、
「Capture.c」で使用するI/Oとカブリはなく
RB2~RB7は、全てデジタル出力として使うので、そのまま使えます^^;
skSD1602LCD.h
ここも、mainで定義されてるので、そのまま使えます。
skSD1602LCD.h
で、16F1827用にした「Capture.c」に表示ルーチンを組み込みます。

main()を変更・追加していきます。(★1が該当箇所)
*****Capture.c*****
#include <xc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "skSD1602LCD.h"

~中略~

     PORTB  = 0b00000000 ;    // RB0-RB7出力ピンの初期化(全てLOWにする) for 1827
//  PORTC  = 0b00000000 ;    // RC出力ピンの初期化(全てLOWにする) 1829 only

     /* LCDの初期化処理を行う */
     lcd_init() ; //★1

     PEIE = 1 ;               // 周辺装置割り込み有効
     GIE  = 1 ;               // 全体の割込み処理を許可する

~中略~

     CaptureSet() ;           // キャプチャ機能の初期設定を行う

     while(1) {
          // 周波数計測の終了を待ちます(MAX25KHz程までは計測出来るでしょう)
          f = PulseCount(1,1) ; // PlusCount(↑,↑)
//★1  fl = 1000000L / f ;       // 周波数の計算[Hz]、1000000LONG÷PlusCount(↑,↑)
          fl = 1000L / f ;       //★1 周波数の計算[KHz]、1000LONG÷PlusCount(↑,↑)
//★1   sprintf(buf,"%ldHz",fl) ; 
        sprintf(buf," Freq %6.2f KHz",fl) ; //★1 " Freq xxx.xx KHz"をbufへ
          // ここでご自由に表示処理を行いましょう。
        lcd_setCursor(0,0) ;    //★1 カーソルを1行目の先頭に移動
        lcd_puts(buf);          //★1 LCD1行目に " Freq xxx.xx KHz"表示

          // 1サイクルのON部分の計測の終了を待ちます
          d = PulseCount(1,0) ; // PlusCount(↑,↓)
          df = ((float)d / (float)f) * 100 ; // デュティ比の計算
//★1  sprintf(buf,"%5.1f%%",df) ; 
        sprintf(buf," Duty %6.2f%%",df) ; //★1 " Duty xxx.xx%"をbufへ
          // ここでご自由に表示処理を行いましょう。
        lcd_setCursor(0,1) ;    //★1 カーソルを2行目の先頭に移動
        lcd_puts(buf);          //★1 LCD2行目に" Duty xxx.xx%"表示

//★1  __delay_ms(1000) ;
          __delay_ms(100) ;   //★1 表示頻度UP
     }
*********

ひとまず、1カウント 1μsecでの周波数とDuty計測のプログラムができました。
いざ、コンパイル!
ありゃ~!ERROR一杯であります(T_T)
「largest unused contiguous range 0x」なるerrorが一杯(T_T)
原因が判明するのにかなりの時間を要しました(-_-;)
「sprintf(buf," Duty %6.2f%%",df) ;」の行が原因でしたが
オリジナルの「sprintf(buf,"%5.1f%%",df) ;」でもダメなのです。
どうやら、sprintf()で浮動小数点変数float(上のdf)を指定すると
オーバーフローを起こすようです。

引数(f)と書式設定の中もint型、だとコンパイルは通ります。
sprintf(buf,"%6.2u",f) ;

きむ茶工房さんは、MPLAB X v4.10、XC8 v1.40ですね~
マーティーのは、MPLAB X v2.10、XC8 v1.32です(-_-;) 
古いからでしょうね~
バージョアップするとお作法が変わるので、したくないしな~

仕方ないので、整数部と小数部を分離して、それぞれ整数として
sprintf()を使うことにします。
で、手っ取り早く整数部と小数部を分離しようと思って
使った関数が、
C = modf(A, &B); 
・A:分離したい浮動小数点変数
・C:Aの整数部を格納
・&B:Aの小数部を格納する変数Bのアドレス
・#include <math.h> とセットです。
なのですが、
これがまたマーティーを悩ませることに(-_-;)

A、B、C ともfloat型で十分だと思ってやってたら、通らない(T_T)
double型にしないとダメなんですね~(-_-;)
これもわかるまで、あれやこれや時間がかかってしまいました~
分かり難いですが、周波数の表示だけでこんなことになってます(-_-;)
*********
char buf[16] ; // 表示用のバッファ
long f ; // 周波数の生値[Hz]
double fl ; // fl:周波数[KHz]
double fl_i , fl_d ; // fl_i:flの整数部、fl_d:flの小数部
int fl_i_int ; // 浮動小数点flの整数部分fl_iを整数として格納
int fl_d_100 ; // 浮動小数点flの小数部分の100倍を整数として格納

f = PulseCount(1,1) ;    // PlusCount(↑,↑)
fl = 1000L / f ;         // 周波数の計算[KHz]
fl = fl + 0.005 ;        // 0.01KHzの位で四捨五入するため
fl_d = modf(fl, &fl_i) ; // 小数部 = modf(引数, 整数部を格納するアドレス)
fl_i_int = (int)fl_i ;   // flの整数部をint型へキャストして小数部を切捨てる
fl_d_100 = fl_d * 100 ;  // flの小数部x100して整数として格納

sprintf(buf," Freq %3d.%02dKHz ",fl_i_int,fl_d_100) ;  //bufに" Freq xxx.xx KHz "を格納
// ここでご自由に表示処理を行いましょう。
lcd_setCursor(0,0) ;   // カーソルを1行目の先頭に移動
lcd_puts(buf);           // LCD1行目に bufの内容を表示
*********

で、やっとコンパイルが通ったわけですが、
実は、最初LCD表示が全くでなかったので、真っ青(T_T)
Clock 32MHz⇒8MHzに落としてやっとLCD表示が出ました。
いつの間にか、Program Space:70.9% になってます。
Compiler XC8 v1.32ですが、Free版なので
生成コードがオプティマイズされないのです。
有償版のPRO modeだと一般に40%削減できると書いてあります。
もうちょっと、処理を追加する予定なのですが
この時点でProgram Space:70.9% じゃ足りなくなりそう(T_T)
sprintf() や modf() を使うのを止めるしかなさそうです^^;

表示レイアウトもちょっと変えます。
まあ、小数点付けるのが面倒だからなのですが(-_-;)

表示の辺りだけ抜粋すると
とても分かり難いですが、バラバラに分解して表示しました。
更にオリジナルの1カウント 1μsecから
Timer Clock=Fosc/4(8MHz Prescale 1:1)にしたので0.5μsecです。
なのでfl=2000000L/f; で周波数をHzで算出してます。
*********

char buf[16] ;
long f , d ;
float fl ; //fl(周波数)は、小数まで表示する、modf()使わないのでfloatに戻す
int fl_int ; //周波数はHz表示で整数のみにする。その格納用
float df ; //df(Duty)は、小数まで表示する、modf()使わないのでfloatに戻す
int df_i_int ; //dfx100(%)の整数部分
int df_d_10 ; //浮動小数点dfx100(%)の小数部分の10倍を整数として格納
char freq[7] , duty_i[4] , duty_d[2] ; // LCD表示用に数値をcharに変換用
int lcd_i = 0 ; //LCD表示クリヤのカウント用  
~中略~
while(1) {
// 周波数計測の終了を待ちます
   f = PulseCount(1,1) ;    // PlusCount(↑,↑)
   fl = 2000000L / f ;      //周波数の計算[Hz]、2000000LONG÷PlusCount(↑,↑) 1count 500nsec
   fl = fl + 0.5 ;          //0.1Hzの位で四捨五入するため
   fl_int = (int)fl ;     //fl(Hz)の整数部をint型へキャストして小数部を切捨てる

   lcd_setCursor(0,0) ;    //カーソルを1行目の先頭に移動
   lcd_i = ++lcd_i ;
   lcd_puts(" Freq ") ;
   itoa(freq,fl_int,10) ; //整数"fl_int"を10進のchar"freq"に変換
   lcd_puts(freq) ;
   lcd_puts(" Hz") ;

// 1サイクルのON部分の計測の終了を待ちます
   d = PulseCount(1,0) ; // PlusCount(↑,↓)
   df = ((float)d / (float)f) * 100 ; // デュティ比の計算[%]
   df = df + 0.05 ;           //0.1%の位で四捨五入するため
   df_i_int = (int)df  ;           //df(%)をintにキャストして整数部をduty_iに格納
   df_d_10 = (df - (float)df_i_int) * 10 ; 
          //df(%)の小数部にx10して、小数部の第1位だけを整数として duty_dに格納
   lcd_setCursor(0,1) ;    //カーソルを2行目の先頭に移動
   lcd_puts(" Duty ") ;
   itoa(duty_i,df_i_int,10) ; //整数"df_i_int"を10進のchar"duty_i"に変換
   lcd_puts(duty_i) ;
   lcd_puts(".") ;
   itoa(duty_d,df_d_10,10) ;  //整数"df_i_int"を10進のchar"duty_i"に変換
   lcd_puts(duty_d) ;
   lcd_puts(" %") ;

__delay_ms(100) ;   //表示頻度UP
}
*********

Program Space:59.0% になりました!
まあこれくらいだったらよさそうです。
後日、printf()の書式設定は大飯食らいってことを知ったのであります(-_-;)
動いたショットは、これ!
リビングのテーブルにシリコンマット敷いた臨時作業台であります^^;
中央下のは、ATtiny85の方形波発振器です。
DUTYが小さい時。
1KHzですが、DUTYも正しく表示されています。

もしかして、プログラム一式「16F1827_FreqDuty_v6.zip」を見たい方は、
こちらからどうぞ(^_^)

Timer1のClockを1カウント0.5μsecにしたので
最初と同じ様に単純に200倍して±2%と考えると
・クロックソース 0.5μsec・・・40KHz
辺りまで測定できそうです。

上手く動き出したので、更にClockを16MHzに上げて
周波数をどこまで計測できるかやろうとして、
一旦外して書き込んだら、16F1827が死にました_| ̄|○
書き込みソフトでは、WriteもVerifyも「Successful」になってるので、
出力回路が死んでると思われます。

AliExpressでポチってた10個が到着するのを待つしかありません(T_T)
死んでる出力を別PORTに変更すれば、まだ使えそうですが...

それにしてもよく考えると、というか途中で気づいてはいたのですが(-_-;)
Duty100%、つまりDCの時は、
エッジがないので周波数もDUTY比も計測できません。
対策を考えるか別の方法にしないといけないですね~

まあ、これはこれで簡易周波数カウンタとして活躍できるかな^^;
この程度ならArduino+I2C I/FのLCDで作り直した方がいいんでしょうね~

マーティーのプログラミング能力が劣化してるな~(-_-;)

2 件のコメント:

昔青年 さんのコメント...

最近は、Arduino IDEばかりで、PICもHEXファイルの提供でなくコンパイルが必要だったりすると、再勉強に時間がかかります。(勉強にはなりますが)
コロナ騒ぎでAlieからの商品到着がえらく時間がかかっていますね。

マーティーの工房日誌 さんのコメント...

Arduinoを始める前にやってたので、できたんだと思います。
勉強する順序が逆だったらやってない可能性大ですね~(-_-;)
Aliからは手のひらサイズの封筒の小物は通常っぽいですが
ちょっと大きくなると遅いですね~
それよりも送料が高騰してきてる気がします(T_T)