Arduinoの標準関数でデジタルピンのモードをセットするpinMode関数がありますが、この関数と同じ機能を果たし、もっと高速で柔軟な関数がportModeRegisterとして用意されています。この関数はデータディレクションレジスタ(DDR)に直接アクセスするためのマクロ関数であるため、実行速度が速いのですが、低レベルでのアクセスが必要となりpinModeよりは汎用性に欠けるプログラムとなります。ここではportModeRegister関数について少し掘り下げてみます。
portModeRegister() の使い方と関数仕様
ポートのピンモードを設定する際、ポインタにセットする値は、そのポートの全ピンの入出力方向をビットで表現したものです。この値は、ポートに対応するデータディレクションレジスタ(DDR)に書き込まれ、各ビットは特定のピンのモード(入力か出力)を指定します。
仕様
- 説明: 指定されたデジタルピンが属するポートのデータ方向レジスタへのポインタを返します。
- 引数:
port
: ポート識別子。通常はdigitalPinToPort(pin)
関数によって指定されたデジタルピンから取得されます。 - 戻り値:指定されたポートのデータ方向レジスタ(DDR)へのポインタ。
DDR(データディレクションレジスタ)のビット設定
- 0: ピンを入力モードに設定します。
- 1: ピンを出力モードに設定します。
例えば、8ビットのポート(例: PORTD)の場合、DDRは次のようになります。
ピン番号 | DDR7 | DDR6 | DDR5 | DDR4 | DDR3 | DDR2 | DDR1 | DDR0 |
---|---|---|---|---|---|---|---|---|
ピンモード | PD7 | PD6 | PD5 | PD4 | PD3 | PD2 | PD1 | PD0 |
ここで、PD0からPD7はそれぞれポートDのピン0からピン7を表します。DDRの各ビットを1
に設定するとそのピンは出力モード、0
に設定すると入力モードになります。
ATmega328PのポートとDDRについて
DDR(Data Direction Register)の基本
ATmega328Pには複数のI/Oポートがあり、それぞれに対応するDDRが存在します。これらのレジスタを使用して、個々のピンをデジタル入力ピンまたはデジタル出力ピンとして設定できます。
- 0: ピンを入力モードに設定します。この設定では、ピンは外部からの信号を受け取るために使用されます。
- 1: ピンを出力モードに設定します。この設定では、ピンはマイクロコントローラから信号を外部に送出するために使用されます。
ATmega328PのポートとDDR
ATmega328Pは、以下の主要なI/Oポートを持っています:
- PORTB: ピン8から13(Arduino UNOのデジタルピン)
- PORTC: アナログピン0から5(Arduino UNOのアナログピン)
- PORTD: ピン0から7(Arduino UNOのデジタルピン)
それぞれのポートは、対応するデータディレクションレジスタ(DDRB
、DDRC
、DDRD
)を持っています。これらのレジスタを適切に設定することで、ポートに属するピンの動作モードを制御します。
サンプル設定
ポートDのピン2を出力、残りのピンを入力に設定する例を考えます。この場合、DDRを00000100
(2進数)または0x04
(16進数)に設定します。
void setup() {
// PORTDのデータディレクションレジスタを取得
volatile uint8_t* ddrD = portModeRegister(PORTD);
*ddrD = 0x04; // PORTDのピン2を出力に設定、他はすべて入力
}
void loop() {
// メインループ内の処理
}
pinModeとの比較
pinMode()
pinMode()
はArduinoの基本関数の一つで、特定のピンをデジタルの入力または出力に設定するのに使います。この関数は使いやすく、Arduinoの様々なボードで同じ方法で動作するため、初心者にも扱いやすいです。
メリット:
- 使いやすさ:
pinMode()
は非常にシンプルで直感的なAPIを提供し、初心者でも簡単に理解し使用することができます。 - ポータビリティ: さまざまなArduinoボード間でコードの互換性が保たれます。どのボードでも同じ関数呼び出しでピンのモードを設定できます。
- 安全性: 間違ったピン番号やモードを指定しても、内部でエラーハンドリングが行われるため、ハードウェアを誤って損傷するリスクが低くなります。
デメリット:
- 速度: 内部でのエラーチェックと抽象化のため、レジスタ直接操作に比べて実行速度が若干遅くなることがあります。
- 制御の限界: 内部的には一般的な設定のみを提供し、ポートレベルでの細かな設定は行えません。
portModeRegister()
portModeRegister()
は、特定のポートのデータディレクションレジスタ(DDR)へのポインタを返すマクロです。これを使うと、ピンの設定をレジスタレベルで直接行うことができます。
メリット:
- 高速性: レジスタ操作は非常に高速であり、プログラムの実行時間を短縮できます。特に、複数のピン設定を一括で行う場合に効果的です。
- 柔軟性: ポート全体のピンを一度に設定するなど、
pinMode()
よりも柔軟な制御が可能です。 - 効率: 特にリアルタイム性が求められるアプリケーションや、リソースが限られている状況で有効です。
デメリット:
- 複雑性: レジスタ操作にはATmegaなどのマイクロコントローラの内部構造に関する理解が必要です。間違った操作はシステムの不具合や損傷を引き起こす可能性があります。
- ポータビリティの欠如: 特定のマイクロコントローラに依存するコードは、他のタイプのボードに移植した際に再調整が必要になることがあります。
結論
pinMode()
は安全で使いやすく、さまざまなプロジェクトでのピンの設定に適しています。一方、portModeRegister()
は高速で柔軟な制御を求める上級ユーザーや特定の応用に最適です。プロジェクトの要件に応じて、適切な方法を選択することが重要です。
- 説明: 指定されたデジタルピンが属するポートのデータ方向レジスタへのポインタを返します。
- 引数:
port
: ポート識別子。通常はdigitalPinToPort(pin)
関数によって指定されたデジタルピンから取得されます。
- 戻り値:
- 指定されたポートのデータ方向レジスタ(DDR)へのポインタ。
LEDを光らせるサンプルプログラム
以下の例では、portModeRegister()
を使用して Arduino Uno のピン 13 のモードを出力に設定し、LED を制御する方法を示します。
#include <Arduino.h>
void setup() {
// ピン 13 のポートを取得
uint8_t pin = 13;
volatile uint8_t *modeRegister = portModeRegister(digitalPinToPort(pin));
uint8_t bit = digitalPinToBitMask(pin); // ピン 13 を出力に設定
*modeRegister |= bit; // ピン 13 に接続された LED を点灯
volatile uint8_t *outputRegister = portOutputRegister(digitalPinToPort(pin)); *outputRegister |= bit;
}
void loop()
{
// この例では loop 関数では何も行いません
}
参考 DDRを使用したプログラミング
以下は、ATmega328PのPORTD(Arduino UNOのデジタルピン0から7)を使用したDDRの設定例です。この例では、ピン2(PORTDのPD2)を出力モードに設定し、他のピンは入力モードに設定します。
#define F_CPU 16000000UL // クロック速度の定義
#include <avr/io.h>
int main(void) {
// PORTDのピン2を出力に設定
DDRD = (1 << 2);
// 無限ループ
while (1) {
// PORTDのピン2にHIGH信号を出力
PORTD |= (1 << 2);
_delay_ms(500); // 500ミリ秒遅延
// PORTDのピン2にLOW信号を出力
PORTD &= ~(1 << 2);
_delay_ms(500); // 500ミリ秒遅延
}
}
このコードは、ATmega328Pの特定のピンを制御するために直接レジスタを操作しており、Arduino IDEのライブラリを使用せずにAVRプログラミングを行っています。ポートのDDRを操作することで、ピンの入出力設定が可能となり、非常に効率的なハードウェア制御が実現できます。