本記事は「Arduinoの裏側」シリーズの第3回です。 今回は、Arduinoで最も頻繁に使用される機能である「GPIO」と、その背後で動作する「タイマー」に焦点を当てます。
digitalWrite や analogWrite は非常に便利な関数ですが、ハードウェアを抽象化する過程で無視できない処理コストや、機能的な制約が発生しています。本稿では、これらの関数が内部で行っている処理を解剖し、AVRマイコン本来のレジスタ操作との比較を行います。
前回はこちら
cratech.hatenablog.jp
第一回はこちら
cratech.hatenablog.jp


1. digitalWriteの内部処理とオーバーヘッド
Arduinoにおいて、特定のピンをHIGHにするコードは以下のように記述されます。
digitalWrite(13, HIGH);
一見、単一の命令に見えますが、コンパイル後のアセンブラレベルでは数十行の命令に展開されます。Arduino Coreライブラリ(wiring_digital.c)の実装を確認すると、主に以下の処理が実行されていることが分かります。
ピン番号の検証: 指定されたピンが存在するかどうかのチェック。
PWMの停止: 対象のピンでPWM(analogWrite)が動作している場合、タイマー接続を解除する処理(turnOffPWM)。
ポートとビットマスクの変換: Arduinoのピン番号(例: 13)を、AVRのポート(例: PORTB)とビット位置(例: 第5ビット)に変換するためのテーブル参照。
割り込みの考慮: レジスタ書き換え時の原子性(Atomicity)を保証するためのステータスレジスタ操作(SREG)。
これらの「安全装置」と「抽象化」により、digitalWrite の実行には約50〜60クロックサイクル(16MHz動作時で約3〜4マイクロ秒)を要します。これは、マイクロ秒単位の厳密なタイミング制御には不向きな遅延です。
2. レジスタ構造の理解(DDR, PORT, PIN)
AVRマイコン(ATmega328P等)のGPIOは、主に3つの8ビットレジスタによって管理されています。
DDRx (Data Direction Register): 入出力の方向を決定します(0=入力, 1=出力)。
PORTx (Data Register): 出力時のH/L状態、または入力時のプルアップ抵抗の有無を制御します。
PINx (Input Pins Address): ピンの現在の論理レベルを読み取ります。
例えば、Arduino Unoの「13番ピン」は、AVRマイコン上の「ポートBの第5ビット(PB5)」に物理的に接続されています。
3. レジスタ直接操作による高速化
前述の digitalWrite(13, HIGH); と同等の操作を、レジスタ直接操作(Direct Port Manipulation)で記述すると以下のようになります。
// PORTBの第5ビットを1にする(HIGH出力)
PORTB |= (1 << 5);
// PORTBの第5ビットを0にする(LOW出力)
PORTB &= ~(1 << 5);
この記述であれば、コンパイル後は sbi (Set Bit in I/O Register) や cbi (Clear Bit in I/O Register) といった、わずか2クロックサイクル(125ナノ秒)で完了するアセンブリ命令に変換されます。
単純計算で、digitalWrite と比較して20倍から30倍の高速化が実現可能です。
4. タイマーとPWMの深層
GPIOと同様に、analogWrite もまた、複雑なタイマーレジスタの設定を隠蔽しています。
ATmega328Pには、Timer0(8bit)、Timer1(16bit)、Timer2(8bit)の3つのハードウェアタイマーが存在します。Arduinoの標準環境では、これらは以下のように割り当てられています。
Timer0: 時間管理(millis(), delay())および、5, 6番ピンのPWM。
Timer1: 9, 10番ピンのPWM、およびServoライブラリ。
Timer2: 3, 11番ピンのPWM、およびTone関数。
標準機能の限界とレジスタ操作
analogWrite を使用した場合、PWM周波数は約490Hz(一部のピンは約980Hz)に固定されます。しかし、モータードライバの制御や高速なスイッチングが必要な場面では、この周波数では不適切な場合があります。
ここで、タイマー関連のレジスタ(TCCR: Timer/Counter Control Register)を直接操作する必要が生じます。
例えば、Timer2のプリスケーラ(分周比)設定を変更することで、PWM周波数を数kHz〜数十kHzに変更したり、比較一致レジスタ(OCR: Output Compare Register)を操作して、ハードウェアレベルで正確な波形を生成することが可能になります。
ただし、Timer0のレジスタ設定を変更する際は注意が必要です。ここを変更すると、依存している delay() や millis() のカウント速度まで変化してしまい、プログラム全体の時制が狂うリスクがあります。
5. 抽象化と直接操作の使い分け
レジスタ直接操作は強力ですが、以下のデメリットも伴います。
可読性の低下: どのピンやタイマーを操作しているかが直感的に分かりにくい。
移植性の喪失: マイコンが変わればレジスタ構成も変わるため、互換性が失われる。
副作用のリスク: 特にタイマー操作は、システム時間関数など他のライブラリに影響を与える可能性がある。
したがって、以下のような基準で使い分けることが推奨されます。
通常の用途: 標準関数を使用する(可読性と安全性を優先)。
高速制御・特殊な周波数が必要な場合: 高速SPI通信や、可聴域外のPWM生成などにおいてのみ、レジスタ直接操作を使用する。
6. 結論
Arduinoの利便性は、ハードウェアの複雑さを隠蔽することで成り立っています。しかし、エンジニアとしては、その隠蔽された「皮」を剥ぎ取り、必要に応じてシリコン本来の性能を引き出す知識を持っておくべきです。
GPIOとタイマーレジスタの理解は、そのための第一歩となります。