MCUを使用した0〜1MHz(0.25Hz分解能)方形波の測定


8

0から1MHzの範囲で変化し、0.25Hzの分解能を持つ方形波の周波数を測定する必要があります。

私はまだどのコントローラーを決定していませんが、それはおそらく20ピンAttinyの1つでしょう。

通常、より低い周波数の信号を測定する方法は、タイマーキャプチャモードで設定された2つのタイマーを使用して、外部信号の立ち上がりエッジで割り込みを行うように設定し、別のタイマーを毎秒割り込みするように設定することにより、前のタイマーは1秒後のレジスタ値をカウントします信号の周波数に等しくなります。

ただし、この方法は明らかに0.25Hzの解像度で0〜1MHzの範囲の信号をキャプチャするには機能しません。これには22ビットカウンターが必要です(AFAIK 8ビットマイクロには8/16ビットカウンターしかない)。

私が持っていたアイデアの1つは、信号をマイクロに適用する前に分割することでしたが、信号を61で除算する必要があるため、これは実際的ではなく、周波数は61秒ごとにしか更新できず、数秒ごとにしたい場合があります。

頻度を4秒ごとに更新できる別の方法はありますか?


更新:

最も簡単な解決策は、外部割り込みまたはタイマーキャプチャを使用して、信号の立ち上がりエッジで割り込みを行いisr、変数の型をインクリメントすることですlong int。4秒ごとに変数を読み取ります(0.25Hzまでの周波数を測定できるようにするため)。


アップデート2:

JustJeffが指摘したように、8ビットMCUは1MHz信号に追いつくことができないため、すべての立ち上がりエッジで割り込みを中断し、long int...

私はtimororrが提案した方法を選びました。実装に取り​​掛かったら、投稿して結果を共有します。ご提案いただきありがとうございます。


経過報告:

ここで紹介するアイデアのいくつかをテストし始めました。まず、vicatcuのコードを試しました。頻度の計算後にTCNT1がクリアされないという明らかな問題がありました-大したことではありません...

次に、コードをデバッグするときに、約2〜7回の頻度で計算されたタイマー1(外部イベントをカウントするように構成されたタイマー)のオーバーフローカウントが2だけ短いことに気付きました。これをタイマー0 ISRのレイテンシに置き、ifステートメントブロックをISRからメイン(以下のスニペットを参照)に移動して、ISRにフラグを設定することにしました。いくつかのデバッグは、最初の測定は問題ないが、その後の読み取りごとに、タイマー1のオーバーフローカウントが2を超えることを示しました。これは説明できません。

int main()
{
    while(1)
    {
        if(global_task_timer_ms > 0 && (T0_overflow == 1))
        {
            global_task_timer_ms--;
            T0_overflow = 0;
        }

        .....
    }
}

次に私はティムロスの提案を実装しようとすることを決めました。Atmega16の唯一の16ビットタイマーが外部信号の立ち上がりエッジをキャプチャするために使用されているため、必要な間隔(各timer_isr割り込みの間に約15ms)を生成するには、2つの8ビットタイマーをカスケードする必要があります。

オーバーヘッドのほとんどがタイマーにシフトされ、CPUが処理する短い残りが1つしかないため、このソリューションが機能し、はるかに効率的だと思いました。しかし、私が期待していたほど正確ではなかったため、測定値は約70Hzだけ前後にシフトしましたが、高周波数では問題にはなりませんが、低周波数では許容できません。私は問題の分析に多くの時間を費やしませんでしたが、2つの16ビットタイマーを備えた非常に遅い8051コントローラーにtimrorrsの提案と同様の構成を実装しており、結果は非常に正確だったため、タイマーのカスケード構成はそれほど正確ではないようです。

これでvicatcuの提案に戻りましたが、周波数計算をTimer 0 isr (下のスニペットを参照)に移動しました。このコードにより、一貫して適度に正確な測定が行われました。少しの校正で、精度は約+/- 10Hzになります。

ISR(TIMER0_OVF_vect)
{            

    TCNT0 = TIMER0_PRELOAD;         //Reload timer for 1KHz overflow rate

    if(task_timer_ms > 0)
    {
        task_timer_ms--;
    }
    else
    {     
        frequency_hz = 1.0 * TCNT1;
        TCNT1 = 0;
        frequency_hz += global_num_overflows * 65536.0;
        global_num_overflows  = 0;
        frequency_hz /= (TASK_PERIOD_MS / 1000.0);
        task_timer_ms = TASK_PERIOD_MS;
    }                                                 
}       

誰かが他に提案している場合は、私にオープンにしますが、範囲を使用する必要はありません... 0.25%の解像度を取得することを意図しなくなったため、現在の精度レベルにはあま​​り意味がありません。 。


これを行うには、PICのキャプチャ割り込みと非常に高速で動作するタイマー1を使用して比較的簡単な方法があります。それでも他の方法に興味がある場合はお知らせください。回答の概要を説明します。
Kortuk、

これについてはまだ作業を始めていないので、はい、まだ興味があります。
2010年

何らかの理由で、あなたが私のコメントにコメントしたことを私に知らせなかった。
Kortuk

@Kortuk:ソフトウェアは、回答または質問の1つにコメントを残した場合にのみ通知します。また、@ Kortukを前に置いているため、このコメントが通知される場合もあります。しかし、それはStackOverflowソフトウェアの変更であり、StackExchangeコードベースにだまされたかどうかはわかりません。
Robert Harvey

いいえ、@ kortukを使用していても、あなたが応答したことを知らせませんでした。心配ない。答えが見つかったようです。
Kortuk、2013

回答:


4

可能であれば、タイマー入力を使用してカウンター操作をサポートするマイクロコントローラーを選択することをお勧めします。ISR内のカウンターを手動でインクリメントするのではなく(高周波数では、マイクロコントローラーのアクティビティがすぐに飽和してしまう)、ハードウェアがカウントを処理できるようにします。この時点で、コードは単に定期的な割り込みを待ってから頻度を計算するだけの問題になります。

範囲を拡張して周波数カウンターをより一般化する(MCUの作業を少し犠牲にして複数の範囲の必要性を取り除く)には、次の手法を使用できます。

最高の入力周波数での測定精度を可能にする周期的な割り込みレートを選択します。これは、カウンターのサイズを考慮に入れる必要があります(タイマーカウンターが最大入力周波数でオーバーフローしないようにタイマー期間を選択する必要があります)。この例では、入力カウンター値を変数「timer_input_ctr」から読み取ることができると想定します。

定期的な割り込みをカウントする変数を含めます(起動時に0に初期化する必要があります)。この例では、この変数を「isr_count」と呼びます。割り込み期間は、定数「isr_period」に含まれています。

定期的な割り込みは(C擬似コード)として実装する必要があります。

void timer_isr()
{
  isr_count++;
  if (timer_input_ctr > 0)
  {
    frequency = timer_input_ctr / (isr_count * isr_period).
    timer_input_ctr = 0;
    isr_count = 0;
  }
}

明らかに、この大まかな例は、ローエンドのマイクロコントローラーと互換性がない可能性があるいくつかの浮動小数点演算に依存しています。これを克服する手法はありますが、これらはこの回答の範囲外です。


1
優れたtimororr、それは私が常に望んでいる追加のICのコストなしで私が望んでいることを正確に行います、私はソフトウェアで問題を解決する可能性を却下するには早すぎたと思います。ありがとう
2010年

@timrorr、あなたがそれを読みたいと思うなら、私は以下の私の答えについてのあなたの考えに興味があります
vicatcu

7

2つ(またはそれ以上)の範囲を使用することを検討してください。非常に低い周波数のキャプチャに関する問題は、高い周波数に関する問題とは多少異なります。すでに述べたように、範囲の上限では、カウンターオーバーフローの問題があります。

ただし、範囲の下限を考慮すると、レジスターに十分な数がないと精度が低下します。0.25Hzと0.5Hzを本当に区別したいかどうかはわかりませんが、そうした場合、実際にそれを行うには4秒間数える必要があります。

また、厳密に解釈されるフラットな0.25Hz解像度を指定すると、500,000.25Hzから500,000.00Hzを識別できるようになります。これは、かなり高い精度です。

これらの理由により、異なる範囲を設計することで、カウンターサイズの問題を軽減できます。例のためにランダムに数値を引きます。たとえば、ローエンドの場合は0〜100Hzとし、10秒間隔でカウントすると、0.1Hzの解像度が得られ、カウンターは10ビットでなくても1000まで増やす必要があります。次に、100Hzから10kHzまで、1秒間隔でカウントします。1Hzの解像度しか得られませんが、カウンターは最大で10,000まで実行する必要がありますが、それでも16ビットより小さくなります。10kHzから1MHzの上限範囲はわずか0.01秒でカウントでき、最大カウントは10,000のみであり、解像度は100Hzですが、これは妥当な精度です。


はい、以前の質問の更新で4秒までカウントする必要があると述べました。そして、はい、500,000.00Hzと500,000.25Hzを区別できるようにたいと思います。さまざまな範囲を使用することを考えていましたが、信号に6つの選択可能な範囲があるため、これをハードウェアの他の部分と簡単に結び付けることができます。私は4秒更新時間と相まって、ハードウェアカウンタを使用する場合に必要になります確かならば、これは、スペクトルの両端に問題の世話をする必要があります
volting

5

ISRでハードウェアカウンターのオーバーフローをカウントすることにより、ハードウェアカウンターとソフトウェアカウンターを混在させることができます。

ISRで信号のすべてのエッジをカウントすることは、1 MHz信号に対して遅すぎます。そうすれば50kHzくらいまでできると思います。


はい、おそらく正しい-1MHzには遅すぎるでしょうが、20MIPS RISCプロセッサは50KHzよりも優れていると思います。とにかく、信号で8ビットバイナリカウンターをクロックし、カウンターのキャリーアウトをMCUの外部割り込みピンに接続し、キャリービット割り込みとo / pカウントの合計として信号の周波数を読み取ることも検討していました。 n秒ごとのカウンターの値。ハードウェアカウンターとソフトウェアカウンターの組み合わせを言ったときに得ていたものだと思います。
2010年

OPは組み込みのハードウェアカウンターを参照していたと思います。これらはすべて、カウント範囲を改善するために使用できるオーバーフロー割り込みを備えています。
jpc

@starblue、あなたがあなたの答えであなたが考えていたものの下に私が書いたコードはありますか?
vicatcu

2

1秒カウンターを実行する代わりに、0.1秒カウンターにして、カウントに10を掛けますか?

カウンタ番号を格納するだけの問題であれば、追加のコードを使用して、カウンタがオーバーフローするタイミングを追跡し、別のメモリ位置に書き込んで、集計を維持することはできませんか?


2
脳がフリーズしたに違いないと思います。最も単純な解決策は、立ち上がりエッジが検出されるたびにlong int型の変数をインクリメントすることです。1秒ごとにその値を読み取ってから、ゼロにリセットします。
2010年

2
実際には、0.25Hzまで測定するには、4秒ごとに値を読み取る必要があります
2010年

2

16ビットタイマーの入力キャプチャとオーバーフロー割り込み(および変数)を使用して測定を行うことはできませんか?AVR-GCCでATTiny24Aを使用してこれを行う方法を次に示します(テストされておらず、バグがある可能性があります)。

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define TIMER1_BITS           16    // 16 bit timer
#define TIMER1_HZ             8.0e6 // 8MHz crystal
#define TIMER1_OVF_PERIOD_SEC (1.0 * (1 << TIMER1_BITS) / TIMER1_HZ)
#define TIMER1_SEC_PER_TICK   (1.0 / TIMER1_HZ)

//global variables for time keeping
double total_period_sec = 0.0;
uint16_t  num_overflows = 0;

void setup_timer1_capture(){
   // set the ICP (input caputure pin) to a floating input
   DDRA  &= ~_BV(7); // it's A7 on the ATTiny24A...
   PORTA &= ~_BV(7);

   TIMSK1 =   _BV(ICIE1)  // enable input pin capture interrupt
            | _BV(TOIE1); // enable overflow interrupt

   TCCR1B =   _BV(ICNC1)  // activate the input noise canceller
            | _BV(ICES1)  // capture on rising edge of ICP
            | _BV(CS10);  // run the timer at full speed

}

ISR(TIM1_CAPT_vect, ISR_NOBLOCK){ //pin capture interrupt
  uint16_t capture_value_ticks = ICR1; // grab the captured value
  // do some floating point math
  total_period_sec =   1.0 * num_overflows * TIMER1_OVF_PERIOD_SEC
                     + 1.0 * capture_value_ticks / TIMER1_SEC_PER_TICK; 

  num_overflows = 0; // clear helper variable to be ready for next time
}

ISR(TIM1_OVF_vect){   //timer overflow interrupt
    num_overflows++;
}

int main(int argc, char *argv[]){
   setup_timer1_capture();

   sei(); // enable interrupts!

   for(;;){ //forever
      // do whatever you want...
      // the most recently calculated period is available in the 
      // total_period_sec variable 
      // (obviously 1.0 / total_period_sec is the frequency in Hz)
   }

   return 0;
} 

...とにかく、それはコンパイルされます:)


編集私のコードから出力されたlssファイルを調べたところ、生成されたコードには命令が多すぎて、8MHzクロックで1MHzでトリップしない... TIM1_OVF_vectの1行の単純な増分でさえ、19命令を生成します!したがって、1MHzイベントを処理するには、確実に最適化し、おそらくいくつかのもの(おそらくnum_overflowsとcapture_value_ticks)を登録割り当て、インラインアセンブラー(重要なものをlssファイルから盗みます)を使用し、処理を割り込みからメインに移動する必要があります。可能な限りループします。


周期を使用して周波数を測定することは、遅い波形(内部タイマーが外部信号よりもはるかに速いことに依存している)で非常にうまく機能しますが、入力信号の周波数が高くなるとすぐに限界に達します。基本的には、ご存じのとおり、タイマーキャプチャ割り込み内で費やされた時間が支配的になります。コードの他の部分を実行する時間はありません。私はATTinyについてあまり詳しくありませんが、データシートをざっと見てみると、timer / counter1が外部イベントのカウントをサポートしているため、ハードウェアでカウントを処理できます。
timrorr 2010年

@timrorr、すごいはい、それはある方法でそれを行う方法賢く:)私は別のポストに更新さAVR-GCCのコードを掲載しました。よく見て、あなたの考えを見てみませんか?
vicatcu

2

以前の投稿に対する@timrorrの提案に従って、このコードを代替として投稿します。これはc99言語標準を使用してATTiny24A用にコンパイルされますが、それを超える方法で実際にテストしたことはありません。

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#define TIMER0_PRELOAD   0x83 // for 8MHz crystal and overflow @ 1kHz
#define TIMER0_PRESCALE 0x03  // divide by 64
#define TASK_PERIOD_MS 4000   // execute task every 4 seconds

//global variables for time keeping
volatile uint16_t  global_num_overflows = 0;
volatile uint16_t  global_task_timer_ms = TASK_PERIOD_MS;

void setup_timers(){
    // set the T1 pin (PA.4) to a floating input (external event)
    DDRA  &= ~_BV(4);
    PORTA &= ~_BV(4);

    // set Timer1 to count external events
    TIMSK1 = _BV(TOIE1);      // enable overflow interrupt
    TCCR1B =   _BV(CS10)      // clock on external positive edge of T1 pin
        | _BV(CS11)
        | _BV(CS12);

    // set Timer0 for task timing (overflow once per ms)
    TCCR0B = TIMER0_PRESCALE;
    TCNT0  = TIMER0_PRELOAD;  // setup appropriate timeout
    TIMSK0 = _BV(TOIE0);      // enable timer0 overflow interrupt
}


ISR(TIM1_OVF_vect){   //timer1 overflow interrupt
    global_num_overflows++;
}

ISR(TIM0_OVF_vect){            //timer0 overflow interrupt @ 1kHz
    TCNT0 = TIMER0_PRELOAD;   // preload timer for 1kHz overflow rate
    if(global_task_timer_ms > 0){
        global_task_timer_ms--;
    }
}

int main(int argc, char *argv[]){
    double frequency_hz = 0;
    uint16_t num_overflows = 0;
    uint16_t num_positive_edges  = 0;
    setup_timers();
    sei(); // enable interrupts!
    for(;;){ //forever
        if(global_task_timer_ms == 0){ // wait for task to be scheduled
            ATOMIC_BLOCK(ATOMIC_FORCEON){
                num_overflows        = global_num_overflows; // copy the volatile variable into a local variable
                global_num_overflows = 0;                    // clear it for next time
                num_positive_edges   = TCNT1;                // copy num positive edge events to local variable
            }

            // calculate the 'average' frequency during this task period
            frequency_hz  = 1.0 * num_positive_edges;  // num edges since last overflow
            frequency_hz += num_overflows * 65536.0;   // edges per overflow of 16 bit timer
            frequency_hz /= (TASK_PERIOD_MS / 1000.0); // over the task interval in seconds

            global_task_timer_ms = TASK_PERIOD_MS;     // reschedule task
        }

        // use frequency_hz for whatever other processing you want to do
    }
    return 0;
}

これは、Timer1のハードウェア機能を少し活用したものであり、元の投稿と比較して、大量の処理サイクルを解放します。


2
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.