9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Arduino(ATMega328)で直接ポートを操作する

Last updated at Posted at 2022-01-13

##はじめに
Arduinoで直接ポートを操作する為の覚書です。ポートを直接使うというのはどういう事か? 通常、ArduinoでGPIOを使って入出力を行う場合には、

pinMode(11, INPUT);
a = digitalRead(11);
pinMode(12, OUTPUT);
digitalWrite(12, HIGH);

などとして、指定したピンに入力、もしくはHIGH/LOWのステートを出力します。Arduinoの流儀に従えば、これは各ピン、1ピンずつこの設定を行うことになります。一般的にArduinoでプログラムする場合にはこの流儀に従ってください。(プログラムの見やすさや、互換性などを考えるとArduinoの流儀に従う方がメリットが大きいです)
しかし、__このプログラミングはスピードを犠牲にしています。どうしてもスピードが必要だったり、複数のI/Oを同時に変化させたい場合には直接ポートを操作__して、入出力を行う方法があります。

##公開動画
以下の動画をあげていますのでご興味のある方はご覧ください。

注)私の勘違いにより動画内のPORTCのアサインが違っています。
PC6にはA6ではなくRESETがPC7にはA7ではなく、何もアサインされていません。
下の方にピンアサインが載せてあるのでそれを参照してください。

##PORTに付けられた名前
PORTを直接操作するにはポートが割り当てられたレジスタ・アドレスから直接読み出し、書き込みを行います。しかしアドレスの即値をプログラマーが知る必要はありません。名前が定義されているので、あたかも変数に値を代入するかの様に使えます。使える名前は以下のものになります。

DDRB :ポートBの方向レジスタ。入出力方向(INPUT/OUTPUTを設定する)
DDRC :ポートCの方向レジスタ。入出力方向(INPUT/OUTPUTを設定する)
DDRD :ポートDの方向レジスタ。入出力方向(INPUT/OUTPUTを設定する)
PORTB :ポートBの出力レジスタ。出力するデータを書き込む
PORTC :ポートCの出力レジスタ。出力するデータを書き込む
PORTD :ポートDの出力レジスタ。出力するデータを書き込む
PINB :ポートBの入力レジスタ、入力したデータが保存されている
PINC :ポートCの入力レジスタ、入力したデータが保存されている
PIND :ポートDの入力レジスタ、入力したデータが保存されている

各ポートのレジスタには1つで最大8個のGPIOがアサインされています。

##ポートにどの様に設定するか
上記で示しているレジスタは8ビットのレジスタです。従って、8bitをまとめて設定することになります。これはArduinoが8bit CPUであることに由来します。8bit CPUは「8bitのデータをまとめて取り扱える」という能力を有したCPUです。1bitづつのデータを取り扱う事はできますが、それなりの処理が必要になります。

なので8bitまとめて設定する場合は簡単です。

PORTB = 0b10101010;

ポートBにアサインされている8個のI/Oにまとめて出力するステートを設定します。このやり方の場合、8bitまとめて設定されます。これで各bitにアサインされているGPIOのステートをまとめて変更できます。特定のbitだけを変更したい場合は後述します。

入出力方向はDDR(Data Direction Register)で設定します。

DDRB = 0b10101010;

これで0に設定したbitはINPUT、1に設定したbitはOUTPUTに設定されます。
0:INPUT
1:OUTPUT

です。

入力はPINレジスタを読み出す事で実現できます。

uint8_t input_data = PINB;

でPORTBにアサインされているGPIO全てが読み出されて、8bitのデータinput_dataに代入されます。

##ポートにはどの様にGPIOがアサインされているか?
ArduinoNanoで見ると各ポートは以下の様に対応します。
image.png
また、各ポートに対してbitのアサインは以下の様になります。
ただしPC6をI/Oピンとして使用する場合にはFuseを書き換える必要があるようです。
image.png
例えばD13であれば、PORTBのbit5が対応します。
#プログラミング
D13-11をOUTPUTにD10-D8をINPUTに設定し、D13=High, D12=Low, D11=Highに設定する場合。

DDRB  = 0b111000;
PORTB = 0b101000;

となります。
1bitづつ設定したいのであれば、bitシフトと、bit演算を用いて

PORTB = (PORTB & 0b011111) | (0b1 << 5); // bit5だけHighにする

とします。
入力は

uint8_t d10_8 = PINB & 0x000111;

でD10-8のデータがbit2-0に入ります。
1bitづつ入力したいのであれば出力の時と同様、bitシフトと、bit演算で

uint8_t d10 = (PINB >> 2) & 0b1; // bit2だけを読み出す
uint8_t  d9 = (PINB >> 1) & 0b1; // bit1だけを読み出す
uint8_t  d8 = (PINB >> 0) & 0b1; // bit0だけを読み出す

とします。

1bitづつデータを取り出す場合には少し不便ですね。
8bitのデータまとめて取り出す場合には、例えばPORTDのD0-D7を8bitのデータとして扱いたい場合Arduinoの流儀に従うと、条件演算子を使ってうまく書いたとしても

#define LED_OUTPUT_PIN_0  0
#define LED_OUTPUT_PIN_1  1
#define LED_OUTPUT_PIN_2  2
#define LED_OUTPUT_PIN_3  3
#define LED_OUTPUT_PIN_4  4
#define LED_OUTPUT_PIN_5  5
#define LED_OUTPUT_PIN_6  6
#define LED_OUTPUT_PIN_7  7

void setup() {
  pinMode(LED_OUTPUT_PIN_0, OUTPUT);
  pinMode(LED_OUTPUT_PIN_1, OUTPUT);
  pinMode(LED_OUTPUT_PIN_2, OUTPUT);
  pinMode(LED_OUTPUT_PIN_3, OUTPUT);
  pinMode(LED_OUTPUT_PIN_4, OUTPUT);
  pinMode(LED_OUTPUT_PIN_5, OUTPUT);
  pinMode(LED_OUTPUT_PIN_6, OUTPUT);
  pinMode(LED_OUTPUT_PIN_7, OUTPUT);
}

void binary_counter2(uint8_t number){
  digitalWrite(LED_OUTPUT_PIN_0, ((number & 0b00000001)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_1, ((number & 0b00000010)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_2, ((number & 0b00000100)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_3, ((number & 0b00001000)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_4, ((number & 0b00010000)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_5, ((number & 0b00100000)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_6, ((number & 0b01000000)==0) ? LOW : HIGH);
  digitalWrite(LED_OUTPUT_PIN_7, ((number & 0b10000000)==0) ? LOW : HIGH);
}

この様な長いコードになり、かなり面倒です。しかしPORTを使うと

void setup() {
  DDRB  = 0b11111111;
}

void binary_counter2(uint8_t number){
  PORTB = number;
}

これだけで済みます。

##shiftOut関数をポートを直接操作した場合の速度向上
Arduino標準のshiftOut関数を用いた場合、転送にかかる時間は105us程度、
(オシロの波形は、黄色HC595 A、シアンHC595 Latch CLK、マゼンタHC595 Shift CLK)
osc1.jpg
ポートを直接操作すると転送にかかる時間は12.2usと約8.6倍まで速度が向上します。
osc2.jpg

波形を確認した時のプログラム

// 高速版ShiftOut出力波形確認用

// ShiftOutで使うピン
#define HC595_A              2  // シリアル入力
#define HC595_LATCH_CLOCK    4  // ラッチ・シグナル
#define HC595_SHIFT_CLOCK    5  // シフト・レジスタ・クロック

void shiftOutFast(uint8_t byte_data);
void latch_toggle(void);

/*
 * 初期化
 */
void setup() {
  DDRD  = 0b00110100;  // D2,D4,D5(ShiftOutで使うピン) = OUTPUT
  PORTD = 0b00000000;
}

/*
 * ループ
 */
void loop() {
  shiftOutFast(0x55);
  latch_toggle();
  delay(10);
}

/*
 * 高速ShiftOutルーチン
 */
void shiftOutFast(uint8_t byte_data){
  static uint8_t data_a_bit;
  static uint8_t no_effective_bit = PORTD & 0b11001011;
  static uint8_t bit_shift[8] = {1,2,4,8,16,32,64,128};

  data_a_bit = no_effective_bit;
  for(uint8_t i=0; i<8; i++){
    if((byte_data & bit_shift[i]) == 0){
      PORTD = data_a_bit;                                 // HC595_SHIFT_CLOCKをLowにする
      PORTD = data_a_bit = 0b00000000 | no_effective_bit; // HC595_Aを0にする
      PORTD              = 0b00100000 | no_effective_bit; // HC595_SHIFT_CLOCKをHighにする
    }
    else{
      PORTD = data_a_bit;                                 // HC595_SHIFT_CLOCKをLowにする
      PORTD = data_a_bit = 0b00000100 | no_effective_bit; // HC595_Aを1にする
      PORTD              = 0b00100100 | no_effective_bit; // HC595_SHIFT_CLOCKをHighにする
    }
  }

  PORTD = no_effective_bit;
}

/*
 * Latch Clock Toggle
 */
void latch_toggle(void){
  static uint8_t no_effective_bit = PORTD & 0b11001011;
  volatile uint8_t a;

  PORTD = 0b00010000 | no_effective_bit;
  a=0;
  PORTD = no_effective_bit;
}

##履歴
2022.1.27 プログラムに色つけました。
2022.1.13 Initial release
2022.4.12 PORTのアサイン・テーブルを変更しました。PC6:A6→RESET, PC7:A7→NC

9
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?