SSブログ

PSoC 3 で作る周波数カウンタ [PSoC]このエントリーを含むはてなブックマーク#

カウンタ部

サイクルタイムを測定するために、これまで同一チップのカウンタを利用してきました。 これでも測定は出来るのですが、外部で周期または周波数を測定して処理時間を測定しようと考えました。 測定のためには測定器が必要ですが、ここでは自分で作ってみます。 材料は、最近めっきり使わなくなってしまった評価ボード CY8CKIT-030 です。

コンセプト

周波数カウンタを作るにあたって、ふたつの測定方法が考えられます。

ひとつは、被測定パルスをカウンタで数えさせておき、カウンタの値を周期的に取得する方法です。 この方法では、たとえば1秒周期でカウンタの値を読み込むと 1Hz 単位で周波数を知ることが出来ます。 被測定パルスの周波数が高いとカウンタの動作周波数を上回ってしまう場合があります。 こんな場合には、プリスケーラと呼ばれる分周器に被測定パルスを与えて、分周された出力の周波数を測定し、分周比率を掛けて被測定パルスの周波数とします。

もう一つは、被測定パルス1周期分の時間を測定する方法です。 本当に1周期分の時間を測定すると測定側のクロック周波数を非常に高くしなくてはなりません。 そのため、被測定パルスをプリスケーラで分周して、分周された信号の周期を測定します。 今回は、こちらの方法をとります。

プリスケーラ

プリスケーラ

プリスケーラは、百分周から十億分周を行うことが出来る多段分周器で構成されています。 最後の百分周は、入りきらなかったので別のページに配置しています。

Pin_Probe 入力をクロックとして使って、分周を行います。 そのため、 Pin_Probe コンポーネントにはクロック同期機能を付けていません。

PSoC Creator には、 Frequency Divider と呼ばれるコンポーネントがあります。 このコンポーネントを多段に接続する事で、簡単に分周比の大きな分周器が出来そうですが、そうはいきませんでした。 これは、 Frequency Divider の div 出力のパルス幅が1クロック分ではなくもっと長くなってしまうのが原因でした。 そこで、パルス幅を1クロックパルスに制限して分周器のカスケード接続が簡単に行えるように新たに CascadeDivider コンポーネントを作成しました。


カスケード可能分周器

div 出力のパルス幅を制限するために en 入力と AND をとって簡単に次段に接続できるようにしました。 出力部に D Flip Flop が付いていますが、このコンポーネントが無くても分周自体は行えます。

D Flip Flop が無い状態で分周器を多段に接続すると、初段の div 出力から各段の AND 回路によって、 div 出力がどんどん遅れます。 すると、後段の en 入力のセットアップ時間が削られて、対応可能な周波数が下がります。 そこで、 div 出力に D Flip Flop を追加して出力を遅延させて、次段の en 入力でセットアップ時間をかせぎます。 この回路の目的は分周をする事なので、遅延が増えても問題になりません。 D Flip Flop を入れた効果により、最大周波数は 31MHz から 35MHz に上がりました。


分周比レジスタ

分周比は、 Control Register を使用して設定します。 Control Register は、 BUS_CLK で駆動されており、 Pin_Probe で駆動されるプリスケーラとはクロックが異なっています。 使用するクロックによって分割された範囲の事をクロックドメインと呼んでいます。 通常、クロックドメインを越えて信号をやり取りすると、タイミング上の問題が発生するため、間に受け側のクロックドメインに合わせるための仕掛けが必要です。

Control Register の出力に追加された Sync コンポーネントを使用すると、 BUS_CLK に同期した信号を Pin_Probe に同期した信号に変換します。 これで、クロックドメインを越えることができるので、安心して、プリスケーラの設定を CPU から行うことが出来ます。


キー入力回路

プリスケーラの分周比を変更するために評価ボード上のタクトスイッチを使用します。 実際の処理は、ソフトウェアで行います。 ここでは、スイッチの状態を正しく伝えるための回路が追加されています。

Debouncer コンポーネントは、機械的スイッチに特有のチャタリング(英語で Bounce)を除去(Debounce)するために使用されます。 タクトスイッチは、押すと "0" になる負論理で構成されているので、 Debouncer の出力にインバータを追加しています。 そして、 Status Register に導入して CPU からキーの状態を読み取ります。 キーによる分周比の設定変更は、ソフトウェアで行います。

タイマ

タイマの回路

タイマ部分では、プリスケーラの最終段の100分周で出力された信号を Sync コンポーネントを介して Timer コンポーネントの Capture 入力に導きます。 そして、分周された信号の立ち上がりエッジごとにタイマカウンタの値を記録していきます。 ふたつの立ち上がりエッジの時間差から信号の周期を知ることができます。

記録されたカウンタの値は、 Timer コンポーネントの FIFO に入ります。 FIFO の値を割り込み int_Capture の発生ごとにソフトウェアで読み取って、差分を計算します。 計算した結果は、ソフトウェアで LCD モジュールに表示させます。

このシステムでは、32ビットの Timer コンポーネントが律速となり、28MHzが最大駆動周波数となりました。

ソフトウェア

ソフトウェアは、かなり長くなりました。

#include <project.h>

#define     KEY_UP      (0x01)
#define     KEY_DOWN    (0x02)
#define     MAX_RANGE   (7)
#define     MAX_POWER   (10)
#define     CPU_FREQ    (48e6)

KEY_UP と KEY_DOWN で、 Status Register でのキーの配置を示しています。 MAX_RANGE は、分周器の設定の最大インデックスを示します。 インデックスは、0から始まります。 MAX_POWER は、 LCD に表示可能な十進数の最大桁数を示します。 CPU_FREQ は、 LCD に表示されるサイクル数の想定周波数を示します。

// Interrupt handler
CYBIT int_Capture_flag = 0;

CY_ISR(int_Capture_isr) {
	int_Capture_flag = 1;
}

割り込み処理は、フラグを立てる操作のみを行い、実際の処理はメインループで行います。

// Decimal number generation
CYCODE const uint32 power10[MAX_POWER+1] = {
1UL,
1UL,
10UL,
100UL,
1000UL,
10000UL,
100000UL,
1000000UL,
10000000UL,
100000000UL,
1000000000UL,
};

void LCD_PrintDecUint32(uint32 d, uint8 digits) {
	uint8  m;
	uint8  v;
    static char numbuf[32];
    
    for (m = MAX_POWER; m > 0; m--) {
	    for (v = 0; d >= power10[m]; ) {
	  	  d -= power10[m];
		  v++;
	    }
	    if (m <= digits) {
            numbuf[digits-m] = '0' + v;
	    }
	}
    numbuf[digits] = 0;
    LCD_PrintString(numbuf);
}

LCD に十進数で整数を表示します。 基になる値は、 uint32 の32ビット整数です。 8051 のために、極力乗除算を行わない方式としました。

// Parameters for frequency range
CYCODE const struct {
    double  divisor;
    uint8   mux;
} params[MAX_RANGE+1] = {
    {1e2,  0x0},    // x100 prescaler
    {1e3,  0x1},    // x1k prescaler
    {1e4,  0x2},    // x10k prescaler
    {1e5,  0x3},    // x100k prescaler
    {1e6,  0x4},    // x1M prescaler
    {1e7,  0x5},    // x10M prescaler
    {1e8,  0x6},    // x100M prescaler
    {1e9,  0x7},    // x1G prescaler
};

プリスケーラの設定は、この構造体で定義される8種類です。 ふたつのフィールドは、分周比(divisor)と Control Register に設定する値(mux)を示しています。

// Frequency range control
uint8   range;
double  resolution;
double  cpks;  // cycles per kilo-second
uint8   required;

void setRange(uint8 p_range) {
    range = p_range;
    int_Capture_Disable();
    CR1_Write(params[range].mux);
    resolution = 1e0 / (BCLK__BUS_CLK__HZ * params[range].divisor);
    cpks = CPU_FREQ * 1e3;
    required = 2;
    LCD_ClearDisplay();
    LCD_Position(0,15);
    LCD_PutChar('0' + range);
    LCD_Position(0,10);
    LCD_PrintString("Hz");
    LCD_Position(1,10);
    LCD_PrintString("mc");
    Timer_ClearFIFO();
    int_Capture_ClearPending();
    int_Capture_Enable();
}

プリスケーラの設定を変更する時には、 newRange() 関数を呼び出します。 この関数では、プリスケーラの分周比設定と周波数を計算する際に使用される係数、そして LCD の表示の初期化を行っています。

// Frequency and Period calculation
uint32 capture_last = 0;
uint32 capture_now;
uint32 capture_period;
double period;
uint32 freq;
uint32 cycles;

int main()
{
    uint8  key;
    
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    int_Capture_StartEx(int_Capture_isr);
    LCD_Init();
    Timer_Start();
    setRange(0);

    for(;;) {
        /* Place your application code here. */
        if (int_Capture_flag) {
            int_Capture_flag = 0;
            capture_now = Timer_ReadCapture();
            capture_period = capture_last - capture_now;
            capture_last = capture_now;
            if (required == 0) {
                period = (double)capture_period * resolution;
                freq = (uint32)(1e0 / period);
                cycles = (uint32)(period * cpks);
                LCD_Position(0,0);
                LCD_PrintDecUint32(freq, 10);
                LCD_Position(1,0);
                LCD_PrintDecUint32(cycles, 10);
            } else if (required == 1) {
                LCD_Position(1,15);
                LCD_PutChar('*');
                required = 0;
            } else {
                LCD_Position(1,14);
                LCD_PutChar('*');
                required = 1;
            }
        }
        key = SR1_Read();
        if (key == KEY_UP) {
            if (range < MAX_RANGE) {
                setRange(range + 1);
            }
            while (SR1_Read());
        }
        if (key == KEY_DOWN) {
            if (range > 0) {
                setRange(range - 1);
            }
            while (SR1_Read());
        }
    }
}

main() 関数のメインループでは、フラグを監視して周波数を計算・表示する機能とキー入力を検出してプリスケーラの設定を変更する機能が入っています。

プリスケーラの設定変更直後は、タイマから取得した値に正しい値が入っていないため、二回ほど計算と表示を見送っています。 LCD には、入力信号の周波数と CPU_FREQ のクロックで駆動したと仮定した場合のサイクル数が表示されます。

LCD への表示

LCD への表示

できたので、さっそく、 PSoC 4 M-Series のループ周期を測定してみます。 ほぼ、何も行っていない状態ですが、速度が 2.086MHz で、23サイクルを要している事がわかります。 下の段の表示の単位はミリサイクルとなっています。

右上は、プリスケーラの設定インデックスを示しています。 この場合、 "4" と表示されているので、プリスケーラの分周比は x1M である事がわかります。 また、表示間隔が約0.5秒になっており、この時間を利用して数値計算と LCD への表示を行っています。

数値計算と LCD への表示にかなり時間を取られていますので、あまり分周比を低くすると正しい周波数が表示されなくなります。 この約 2MHz の信号の場合には、プリスケーラの分周比を x10k よりも低くすると、上記の処理中に次の割り込みが発生してしまい、正しい値が得られませんでした。

逆にプリスケーラの分周比を x1M よりも大きくすると、表示間隔が数秒以上に伸びてしまい、使い勝手が悪くなってしまいます。 適切な分周比の設定を選ぶ必要があります。

プロジェクトアーカイブ

この記事で作成したプロジェクトは、このファイルの拡張子を "zip" に変更すると再現できるようになります。


nice!(0)  コメント(0)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。