組み込み系

Arduino処理速度とメモリー使用改善の策

arduino LED前述しました様に、限られたメモリーと限られたCPU資源による組込みプログラムは製品化には必須条件です。
特に、Arduinoは便利な標準関数が多く用意されていますが、じつはこの便利関数はオーバーヘッドをがかなり大きくて処理時間やメモリー使用量に影響したりします。
その中で最初に簡単にできる対策として、ポートの入出力関数[digitalWrite]]digitalRead][analogRead]の置き換えが有効手段としてあります。
これについてまとめてみます。

digitalWrite/digitalRead のスリム化と高速化

IOポートを簡単に制御するために用意されているこの2つの関数は、実は別の方法によって速度は70%ほど向上します。
digitalWriteを使った、単純なプログラムで、8番ピンから13番ピンまでにHIGHを出力するプログラムは下記のようになります。

digitalWriteによるプログラム

int port[]={8,9,10,11,12,13};
//ポートの初期設定
void setup() {
  int i;
  for(i=0;i<6;i++){
    pinMode(port[i],OUTPUT);
  }
}
//動作
void loop() {
  //forでまわさずにあえて1行ずつ書いてみました
  digitalWrite(port[0],HIGH);
  digitalWrite(port[1],HIGH);
  digitalWrite(port[2],HIGH);
  digitalWrite(port[3],HIGH);
  digitalWrite(port[4],HIGH);
  digitalWrite(port[5],HIGH);
}

ポート直接制御のプログラム

//書き方1 マクロを使わない
void setup() {
 DDRB = DDRB | B11100000; //1で出力 0 で入力
 DDRD = DDRD | B01000000;
 DDRC = DDRC | B10000000;
}
void loop() {
 PORTB = B11100000; //出力に指定したポートに1を出力するとHIGH 0でLOW
 PORTD = B01000000;
 PORTC = B10000000;
}
//書き方2 マクロを使う
void setup() {
 DDRB = DDRB | B11100000; //1で出力 0 で入力
 DDRD = DDRD | B01000000;
 DDRC = DDRC | B10000000;
}
void loop() {
   PORTB |= _BV(7)|BV(6)|BV(5);
   PORTD |= _BV(6);
   PORTC |= _BV(7)|;  
}

//ちなみにLOWを出力する場合はこのようにも書きます

void setup() {
 DDRB = DDRB | B11100000; //1で出力 0 で入力
 DDRD = DDRD | B01000000;
 DDRC = DDRC | B10000000;
}
void loop() {
 PORTB &= ~(_BV(7)|~_BV(6)|~_BV(5));
 PORTD &= ~_BV(6);
 PORTC &= ~_BV(7); 
}

上記2つは同じ処理をしています。
書き方2ではマクロを使っていますので人によってはかき方1のほうがわかりやすかったりします。
PIC系の方は書き方1が慣れ親しんでいるのではないでしょうか?

 速度のと処理の違い

digitalWriteを使用した場合1行で1ポートの制御しかできませんが、ポート直接制御を利用した場合は、1行で複数ポートの制御が可能です。
また、digitalWriteは1行で44サイクル、約4~5μ秒かかりますが、ポート直接制御では1行で3サイクルしかかかりません。
これだけでも相当な処理速度の改善が図れます。
本格的に組込みプログラミングを行う場合にはまちがえなく、上記のポート直接制御を使うべきです。

処理命令のまとめ

上記に示しました digitalWriteのほかに、入力としてのdigitalReadも同じように置き換えることで高速化スリム化ができます。
また、IOポート利用時はあらかじめ pinModeでピンの入出力も設定しておく必要がありますが、これもポート直接命令では記述がかわります。
下記に整理しておきます。

pinModeの置き換え公式

公式 DDR(N) = B********  / DDR(N) = DDR(N) | B********
DDRB = DDRB | B11100000
説明 (N)…対象となる出力ピンが存在するポートのアルファベット文字
(*)…1で出力 0で入力を指定

digitalWriteの置き換え公式

公式 PORT(N) = B*********   / PORT(N) = PORT(N) | B*******
PORT(N) = _BV($)           / PORT(N) = PORT(N) | _BV($)
PORT(N) = ~_BV($)         / PORT(N) = PORT(N) | ~_BV($)
PORTB = B1100000;
PORTB = PORTB | B1100000
PORTC = _BV(7) | _BV(6) ;
PORTC = PORTC | _BV(7) | _BV(6) ;
PORTB &= ~(_BV(7)|~_BV(6)|~_BV(5));
説明 (N)…対象となる出力ピンが存在するポートのアルファベット文字
(*)…1でHIGH 0でLOW
_BV($)…指定した$番にHIGHを出力
~_BV($)…指定した$番にLOWを出力

digitalReadの置き換え公式

公式 val = PIN(N) & _BV($)
int val = PINB & _BV(7)
説明 (N)…対象となる出力ピンが存在するポートのアルファベット文字
_BV($)…指定した$番のピン状態を読み込む

ポート直接制御のためにはピン配置とポートの関係を理解する

ポート直接制御のためにはポートとピンの関係を理解しなくてはいけません。
下記にその関係を整理しておきます。

 

Type:UNO,Pro(5V),Pro(3V),ProMini,Nano,LilyPadMain,LilyPadSimple,LilyPadSimpleSnap

CPU & port:328P/168
Atmega168PinMap2
 

Arduinoのピン番号からみたポート割付(D=Digital,A=AnlogInput)

Arduinoのボードにシルク印刷されているピン番号を元にどのポートにわりあてられているかを見る表です。

Pin番号 D0 D1 D2 D3 D4 D5 D6
ポート  PD0 PD1 PD2 PD3 PD4 PD5 PD6
Pin番号 D7 D8  D9  D10  D11  D12  D13 
ポート PD7 PB0 PB1 PB2 PB3 PB4 PB5
Pin番号 A0 A1 A2 A3 A4 A5
ポート PC0 PC1 PC2 PC3 PC4 PC5

ポートからみたArduinoのピン番号

Arduinoのポート側からみてどの出力ピンに対応するかを見る逆表です。

ポート(B) PB0 PB1 PB2 PB3 PB4 PB5 PB6  PB7
Pin番号 D8 D9 D10 D11 D12 D13
ポート(C) PC0 PC1 PC2 PC3 PC4 PC5 PC6 PC7 
Pin番号 A0 A1 A2 A3 A4 A5
ポート(D) PD0 PD1 PD2 PD3 PD4 PD5  PD6 PD7 
Pin番号 D0 D1 D2 D3 D4 D5 D6 D7
Type:MEGA
CPU & port:2560PinMap2560sma_
 

Arduinoのピン番号からみたポート割付(D=Digital,A=AnlogInput)

Arduinoのボードにシルク印刷されているピン番号を元にどのポートにわりあてられているかを見る表です。

Pin番号 D0 D1 D2 D3 D4 D5 D6
ポート PE0 PE1 PE4 PE5 PG5 PE3 PH3
Pin番号 D7 D8 D9 D10 D11 D12 D13
ポート PH4 PH5 PH6 PB4 PB5 PB6 PB7
Pin番号 D14 D15 D16 D17 D18 D19 D20
ポート PJ1 PJ0 PH1 PH0 PD3 PD2 PD1
Pin番号 D21 D22 D23 D24 D25 D26 D27
ポート PD0 PA0 PA1 PA2 PA3 PA4 PA5
Pin番号 D28 D29 D30 D31 D32 D33 D34
ポート PA6 PA7 PC7 PC6 PC5 PC4 PC3
Pin番号 D35 D36 D37 D38 D39 D40 D41
ポート PC2 PC1 PC0 PD7 PG2 PG1 PG0
Pin番号 D42 D43 D44 D45 D46 D47 D48
ポート PL7 PL6 PL5 PL4 PL3 PL2 PL1
Pin番号 D49 D50 D51 D52 D53
ポート PL0 PB3 PB2 PB1 PB0
Pin番号 A0 A1 A2 A3 A4 A5 A6
ポート PF0 PF1 PF2 PF3 PF4 PF5 PF6
Pin番号 A7 A8 A9 A10 A11 A12 A13
ポート PF7 PK0 PK1 PK2 PK3 PK4 PK5
Pin番号 A14 A15
ポート PK6 PK7

ポートからみたArduinoのピン番号

Arduinoのポート側からみてどの出力ピンに対応するかを見る逆表です。

ポート(A) PA0 PA1 PA2 PA3 PA4 PA5 PA6 PA7
Pin番号 D22 D23 D24 D25 D26 D27 D28 D29
ポート(B) PB0 PB1 PB2 PB3 PB4 PB5 PB6 PB7
Pin番号 D53 D52 D51 D50 D10 D11 D12 D13
ポート(C) PC0 PC1 PC2 PC3 PC4 PC5 PC6 PC7
Pin番号 D37 D36 D35 D34 D33 D32 D31 D30
ポート(D) PD0 PD1 PD2 PD3 PD4 PD5 PD6 PD7
Pin番号 D21 D20 D19 D18 D38
ポート(E) PE0 PE1 PE2 PE3 PE4 PE5 PE6 PE7
Pin番号 D0 D1 D5 D2 D3
ポート(F) PF0 PF1 PF2 PF3 PF4 PF5 PF6 PF7
Pin番号 A0 A1 A2 A3 A4 A5 A6 A7
ポート(G) PG0 PG1 PG2 PG3 PG4 PG5 PG6 PG7
Pin番号 D41 D40 D39 D4
ポート(H) PH0 PH1 PH2 PH3 PH4 PH5 PH6 PH7
Pin番号 D17 D16 D6 D7 D8 D9
ポート(J) PJ0 PJ1 PJ2 PJ3 PJ4 PJ5 PJ6 PJ7
Pin番号 D15 D14
ポート(K) PK0 PK1 PK2 PK3 PK4 PK5 PK6 PK7
Pin番号 A8 A9 A10 A11 A12 A13 A14 A15
ポート(C) PL0 PL1 PL2  PL3  PL4  PL5  PL6  PL7
Pin番号 D49 D48 D47 D46 D45 D44 D43 D42

 

最後に

このように、付属する便利関数はとても便利なのですが、やはりハードウエアに近い部分で制御するほうが、絶対的に速度があがり組込みには必要なことがわかります。
参考までに、pinModeとdigitalWriteがどれだけの処理で構成されているか、Arduinoの関数の中を下記に公開します。
これをみるだけでも十分な高価は理解できると思われます。

void pinMode(uint8_t pin, uint8_t mode)
{
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *reg, *out;

	if (port == NOT_A_PIN) return;

	// JWS: can I let the optimizer do this?
	reg = portModeRegister(port);
	out = portOutputRegister(port);

	if (mode == INPUT) { 
		uint8_t oldSREG = SREG;
                cli();
		*reg &= ~bit;
		*out &= ~bit;
		SREG = oldSREG;
	} else if (mode == INPUT_PULLUP) {
		uint8_t oldSREG = SREG;
                cli();
		*reg &= ~bit;
		*out |= bit;
		SREG = oldSREG;
	} else {
		uint8_t oldSREG = SREG;
                cli();
		*reg |= bit;
		SREG = oldSREG;
	}
}

void digitalWrite(uint8_t pin, uint8_t val)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *out;

	if (port == NOT_A_PIN) return;

	// If the pin that support PWM output, we need to turn it off
	// before doing a digital write.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

	out = portOutputRegister(port);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;
}

2016.1.28 吉川

タイトルとURLをコピーしました