誰かが、タイマーのセットアップに使用されるこの奇妙なコードを説明できますか?


10

他の人が書いたスケッチを見ていると、次のようなコードに出会うことがあります。

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

私が知っているのは、それがタイミング/タイマーと関係があることです(私はそう思います)。このようにコードを解読(および作成)するにはどうすればよいですか?どのようなものがありTCCR1ATCCR1BTCNT1CS12TIMSK1、とTOIE1


答えるのに十分な知識はありませんが、electronics.stackexchange.com / questions / 92350 /…、forum.arduino.cc/index.php?topic=134602.0、およびstackoverflow.com/questions/9475482/…です。これらをすでに見たかどうかはわかりません。
匿名のペンギン

1
Atmelウェブサイトからデバイスの「完全」データシートをダウンロードし、タイマーに関する章を読んでください。私の意見では、データシートは驚くほど読みやすいです。
ジッピー2014

回答:


15

これは奇妙に見えません。これは、通常のMCUコードが実際にどのように見えるかです。

ここにあるのは、メモリマップされた周辺機器の概念の例です。基本的に、MCUハードウェアは、それに割り当てられたMCUのSRAMアドレス空間に特別な場所を持っています。これらのアドレスに書き込む場合、アドレスnに書き込まれたバイトのビットがペリフェラルmの動作を制御します。

基本的に、メモリの特定のバンクには、文字通り、SRAMセルからハードウェアへの配線がほとんどありません。そのバイトのこのビットに「1」を書き込むと、このSRAMセルが論理Highに設定され、ハードウェアの一部がオンになります。

MCUのヘッダーを調べると、キーワード<->アドレスマッピングの大きな表が多数あります。これは、TCCR1Betc ...などがコンパイル時に解決される方法です。

このメモリマッピングメカニズムは、MCUで非常に広く使用されています。arduinoのATmega MCUは、PIC、ARM、MSP430、STM32、およびSTM8 MCUシリーズと同様に、私がすぐに馴染みのない多くのMCUを使用しています。


Arduinoコードは奇妙なもので、MCU制御レジスタに間接的にアクセスする関数があります。これはやや "見栄えがよい"ように見えますが、速度もはるかに遅く、プログラム領域をより多く使用します。

不可思議な定数はすべてATmega328Pデータシートに詳細に記載されています。Arduinoのピンをときどき切り替えるだけでなく、何か他のことに興味がある場合は、このデータシートを必ずお読みください。

上記のデータシートから抜粋を選択してください:

ここに画像の説明を入力してください ここに画像の説明を入力してください ここに画像の説明を入力してください

したがって、たとえば、TIMSK1 |= (1 << TOIE1);ビットをTOIE1に設定しますTIMSK1。これは、バイナリ1(0b00000001)をTOIE1ビット単位で左にシフトTOIE1し、ヘッダーファイルで0として定義することで実現されます。これは、現在の値にビット単位でOR演算され、TIMSK1この1ビットを効果的に高く設定します。

のビット0のドキュメントを見ると、次のTIMSK1ように記述されていることがわかります。

このビットが1に書き込まれ、ステータスレジスタのIフラグが設定されると(割り込みはグローバルに有効になります)、タイマー/カウンター1オーバーフロー割り込みが有効になります。TIFR1にあるTOV1フラグが設定されると、対応する割り込みベクター(57ページの「割り込み」を参照)が実行されます。

他のすべての行は同じ方法で解釈する必要があります。


いくつかのメモ:

のようなものも表示される場合がありますTIMSK1 |= _BV(TOIE1);_BV()一般的に使用されるマクロから元々AVR libcの実装_BV(TOIE1)は機能的にはと同じですが(1 << TOIE1)、読みやすくなっています。

また、次のような行が表示される場合もあります:TIMSK1 &= ~(1 << TOIE1);またはTIMSK1 &= ~_BV(TOIE1);。これは、のビットの設定を解除するTIMSK1 |= _BV(TOIE1);という点で、の反対の機能を持っています。これは、によって生成されたビットマスクを取得し、ビット単位のNOT演算を実行して()、このNOTed値(0b11111110)でAND をとることによって実現されます。TOIE1TIMSK1_BV(TOIE1)~TIMSK1

これらすべてのケースで、(1 << TOIE1)またはの値はコンパイル時に_BV(TOIE1)完全に解決されるため、機能的には単純な定数になり、実行時に計算するための実行時間がかかりません。


適切に記述されたコードには、通常、コードにインラインでコメントがあり、レジスターに割り当てられている内容を詳しく説明しています。ここに私が最近書いたかなり単純なソフトSPIルーチンがあります:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCPORTCATmega328P 内の出力ピンの値を制御するthe ジスタです。PINC入力PORTCが利用可能なレジスタです。基本的に、このようなことは、digitalWriteまたはdigitalRead関数を使用したときに内部で行われることです。ただし、arduinoの「ピン番号」を実際のハードウェアピン番号に変換するルックアップ操作があり、これは50クロックサイクルの領域のどこかで行われます。おそらくご想像のとおり、高速化を図ろうとしている場合、1つだけを必要とするはずの操作で50クロックサイクルを浪費することは、とんでもないことです。

上記の関数は、8ビットを転送するために、おそらく100〜200クロックサイクルの領域のどこかを使用します。これには、24回のピン書き込みと8回の読み取りが必要です。これは、digital{stuff}関数を使用するよりもはるかに高速です。


このコードはATmega328Pよりも多くのタイマーを含んでいるため、(Leonardoで使用されている)Atmega32u4でも機能することに注意してください。
jfpoilpret 2014

1
@Ricardo-おそらく、小規模なMCU埋め込みコードの90%以上が直接レジスタ操作を使用しています。間接的なユーティリティ関数で物事を行うことは、IO /周辺機器を操作する一般的なモードではありません。ハードウェア制御を抽象化するためのいくつかのツールキット(たとえば、Atmel ASF)がありますが、それは通常、可能な限りコンパイルしてランタイムオーバーヘッドを削減するように記述されており、ほとんどの場合、データシートを読んで周辺機器を実際に理解する必要があります。
コナーウルフ

1
基本的に、arduinoのことは、「Xを実行する関数はここにあります」と言っても、実際のドキュメントを参照したり、ハードウェアが実行する方法を実際に参照したりする必要はありませんが、ほとんど正常ではありません。導入ツールとしての価値は理解していますが、迅速なプロトタイピングを除いて、実際の専門的な環境では実際には行われていません。
コナーウルフ

1
明確に言うと、組み込みMCUファームウェアでarduinoコードを異常なものにするのは、arduinoコードに固有のものではなく、全体的なアプローチの関数です。基本的に、実際の MCUを適切に理解すれば、適切な処理(ハードウェアレジスタを直接使用するなど)にほとんど時間はかかりません。したがって、実際のMCU開発を学びたい場合は、他の誰か抽象化に頼るのではなく、座ってMCUが実際に何をしているのかを理解する方がはるかに優れています。
コナーウルフ

1
ここで私は少し皮肉かもしれませんが、arduinoコミュニティで私が目にする行動の多くはアンチパターンのプログラミングです。多くの「コピーアンドペースト」プログラミング、ライブラリをブラックボックスとして扱うこと、そしてコミュニティ全体での一般的な貧弱な設計慣行だけを見ています。もちろん、私はEE.stackexchangeにかなり積極的に取り組んでいるので、モデレーターツールをいくつか持っているので、いくぶん斜めのビューがあるかもしれません。私がそこで見たarduinoの質問には、「なぜこれが機能しないのか」ではなく、「C&Pに何を修正すべきかを教えてください」という偏りがあります。
コナーウルフ

3

TCCR1A タイマー/カウンター1制御レジスターAです。

TCCR1B タイマー/カウンター1制御レジスターBです。

TCNT1 タイマー/カウンター1のカウンター値

CS12 タイマー/カウンターの3番目のクロック選択ビットです1

TIMSK1 タイマー/カウンター1の割り込みマスクレジスタです。

TOIE1 タイマー/カウンター1のオーバーフロー割り込みの有効化

したがって、コードは62.5 kHzでタイマー/カウンター1を有効にし、値を34286に設定します。次に、オーバーフロー割り込みを有効にし、65535に達すると、割り込み関数をトリガーします。 ISR(timer0_overflow_vect)


1

CS12はTCCR1Bレジスタのビット2を表すため、値は2です。

(1 << CS12)は値1(0b00000001)を取り、左に2回シフトして(0b00000100)を取得します。操作の順序は、()のものが最初に発生することを指示するため、これは「| =」が評価される前に行われます。

(1 << CS10)は値1(0b00000001)を取り、左に0回シフトして(0b00000001)を取得します。操作の順序は、()のものが最初に発生することを指示するため、これは「| =」が評価される前に行われます。

したがって、TCCR1B | = 0b00000101を取得します。これは、TCCR1B = TCCR1B |と同じです。0b00000101。

「|」以来 「OR」の場合、TCCR1BのCS12以外のすべてのビットは影響を受けません。

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