これは奇妙に見えません。これは、通常のMCUコードが実際にどのように見えるかです。
ここにあるのは、メモリマップされた周辺機器の概念の例です。基本的に、MCUハードウェアは、それに割り当てられたMCUのSRAMアドレス空間に特別な場所を持っています。これらのアドレスに書き込む場合、アドレスnに書き込まれたバイトのビットがペリフェラルmの動作を制御します。
基本的に、メモリの特定のバンクには、文字通り、SRAMセルからハードウェアへの配線がほとんどありません。そのバイトのこのビットに「1」を書き込むと、このSRAMセルが論理Highに設定され、ハードウェアの一部がオンになります。
MCUのヘッダーを調べると、キーワード<->アドレスマッピングの大きな表が多数あります。これは、TCCR1B
etc ...などがコンパイル時に解決される方法です。
このメモリマッピングメカニズムは、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 をとることによって実現されます。TOIE1
TIMSK1
_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;
}
PORTC
PORTC
ATmega328P 内の出力ピンの値を制御するthe ジスタです。PINC
の入力値PORTC
が利用可能なレジスタです。基本的に、このようなことは、digitalWrite
またはdigitalRead
関数を使用したときに内部で行われることです。ただし、arduinoの「ピン番号」を実際のハードウェアピン番号に変換するルックアップ操作があり、これは50クロックサイクルの領域のどこかで行われます。おそらくご想像のとおり、高速化を図ろうとしている場合、1つだけを必要とするはずの操作で50クロックサイクルを浪費することは、とんでもないことです。
上記の関数は、8ビットを転送するために、おそらく100〜200クロックサイクルの領域のどこかを使用します。これには、24回のピン書き込みと8回の読み取りが必要です。これは、digital{stuff}
関数を使用するよりもはるかに高速です。