38
39

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 5 years have passed since last update.

Arduinoプログラムの高速化

Last updated at Posted at 2017-12-27

※本記事はATMega328Pを搭載するArduino Unoを想定して書かれています。

#はじめに
Arduinoの統合開発環境Arduino IDEはアナログ入出力などを短く明快な記述で実現するモジュールを提供する一方、kHz~MHzオーダーの高速な処理を犠牲にしています。
そこで本記事ではArduinoがその頭脳部としてAtmel社のマイコン、AVRシリーズを搭載している(Arduino UnoではATMega328P)ことに注目し、AVR風の命令を直接記述することで、より高速な動作を狙います。

※本記事の記述ではポートの競合等を考慮しておらず、複雑なプログラムにおいては予期しない不具合を引き起こす可能性があります。

#AVR(ATMega328P)プログラミングの基本
AVRへの命令は8bitひとまとまりのレジスタを読み書きする形で行います。
8bitの2進数はB00101100のように「B~」と表現されます。
レジスタはそれぞれ大文字アルファベットと数字による名前(レジスタ変数)を持ち、一般的な代入の形で読み書きできます。

uint8_t a = ADCL; // 読みの例
TCCR0A = B10000110; // 書きの例

また、レジスタ8bitのうち特定のビットだけHIGHにしたい場合にはOR|を、LOWにしたい場合にはAND&とNOT~をそれぞれ用いて下記のように表現できます。

DDRB = B00001111;    // B00001111
DDRB |= B00100000;   // B00101111
// レジスタ'DDRB'の3ビット目をHIGHにして他は変えない

PORTD = B00001111;   // B00001111
PORTD &= ~B00000010; // B00001101
// レジスタ'PORTD'の7ビット目をLOWにして他は変えない

##基本的なピンの入出力
ATMega328Pは28本のピンを持ち(図1、図2)、そのうち入出力に用いる23本のピンはB0~B7,C0~C6,D0~D7という3つのグループに分かれた名前を持っています。

ATMega328P_PinMapping.PNG
図1. ATMega328Pのピン配置 [ATMega328Pデータシート
http://www.atmel.com/ja/jp/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf]

uno-1.png
図2. Arduino Unoのピン対応表 (ATMega328Pのピン名は薄茶色囲みの"Port Pin") [Pighixxx http://www.pighixxx.com/test/portfolio-items/uno/?portfolioID=314]

###方向設定レジスタDDRx
ピンの入出力を指定します。
8ビットの左から1ビットずつがx7,x6,~x0ピンに対応しており、HIGHのピンはOUTPUT、LOWのピンはINPUTとなります。

void setup(){
  DDRD = B10001000;
  // D7ピンとD3ピンをOUTPUTに,D6,5,4,2,1,0ピンをINPUTに設定
}

###データ出力レジスタPORTx
対応するピンからの出力を指定します。
同じく8ビットの左から1ビットずつがx7,x6,~x0ピンに対応しています。

void setup(){
  DDRB = B11111111;
  // B0~7ピンをOUTPUTに設定
}

void loop(){
  PORTB = B11000101;
  // B7,6,2,0ピンからHIGHを出力,B5,4,3,1ピンからLOWを出力
}

##便利なマクロ

###_BV(pin)
指定したピンがHIGHになるような8ビットを返すマクロです。
特定のピン1つの状態を指定したいときに利用します。

PORTB = B00110011;         // B00110011

PORTB = _BV(PB3);          // B00001000
// PB3ピンに対応するビットをHIGHにする

PORTB |= _BV(PB7);         // B10001000
// PB7ピンに対応するビットだけを変更しHIGHにする

PORTB &= ~_BV(PB3);        // B10000000
// PB3ピンに対応するビットだけを変更しLOWにする

PORTB = _BV(PB3)|_BV(PB2); // B00001100
// PB3ピンとPB2ピンに対応するビットを変更しHIGHにする(ORでつなげることができる)

#基本的なArduino命令の書き換え
##pinMode(pin, mode)
対象ピンに対応するビットを方向レジスタDDRxへの入力で1=HIGH=OUTPUTまたは0=LOW=INPUTにセットします。

test.ino
void setup(){
  DDRD |= _BV(PD5); // DDRD |= B00100000;
  // pinMode(5, OUTPUT);

  DDRB &= ~_BV(PB0); // DDRB &= ~B01000000;
  // pinMode(8, INPUT);
}

##digitalWrite(pin, value)
PORTxレジスタの該当ビットへHIGH,LOWを直接代入します。

test.ino
void loop(){
  PORTD |= _BV(PD5); // PORTD |= B00100000;
  // digitalWrite(5, HIGH);
}

##digitalRead(pin)
該当ピンを含むPORTxレジスタと_BV()の論理和は、

  1. 該当ピンに対応するビットがHIGHの状態なら、そのビットのみHIGH
  2. 該当ピンに対応するビットがLOWの状態ならB00000000
test.ino
void loop(){
  unsigned char a;

  if(PORTD & _BV(PD5)) {
    a = HIGH;
  } else {
    a = LOW;
  }
  // digitalRead(5);
}

##analogWrite(pin, value)
AVRのPWM波形出力を利用します。
出力したいピンに応じてTIMER/COUNTERnの設定を行います(表)。
表1. TIMER/COUNTERn

TIMER/COUNTERn ATMega32Pのポート Arduinoのポート クロック
Timer/Counter0(注1) PD6(OC0A) D6 8MHz
Timer/Counter0(注1) PD5(OC0B) D5 8MHz
Timer/Counter1 PB1(OC1A) D9 16MHz
Timer/Counter1 PB2(OC1B) D10 16MHz
Timer/Counter2 PB3(OC2A) D11 8MHz
Timer/Counter2 PD3(OC2B) D3 8MHz
注1)Arduinoの関数micros()はTimer/Counter0のカウントを利用して経過時間を返すため、Timer/Counter0のクロックに関する設定を変更すると、micros()及びそれを内部で利用しているdelay()delayMicroseconds()等の実行結果が変化します。[参考:https://garretlab.web.fc2.com/arduino/inside/arduino/wiring.c/delay.html]

設定はTCCRnAレジスタ及びTCCRnBレジスタの状態で決定されます(表2~6)。

表2. TCCRnAレジスタ [ATMega328Pデータシートより]
ATMega328P_TCCR0A.PNG

表3. TCCRnBレジスタ [ATMega328Pデータシートより]
ATMega328P_TCCR0B.PNG


表5. WGMnビット [ATMega328Pデータシートより]
ATMega328P_TCCR0A_WGM0.PNG
WGMnビットは波形生成の方式を決定します。
011と設定してFast PWMモードを使用しましょう。


表4. COMnxビット [ATMega328Pデータシートより]
ATMega328P_TCCR0A_COM0A.PNG
COMnxビットはPWM波生成の方法を決定します。
基本的に10と設定すれば問題ありません。


表6. CSnビット [ATMega328Pデータシートより]
ATMega328P_TCCR0A_CS0.PNG
CSnビットはクロックの分周比(prescaler)を決定します。
fast PWMモードではクロック源の周波数をf_source、分周比をpとすると、PWMキャリア波の周波数f_pwmf_pwm = f_source / (256 * p)であり、分周比を小さくするほどPWMのキャリア波は高周波になります。

test.ino
// analogWrite(6, 127);

// TIMER0はPD5(ArduinoのD5),PD6(ArduinoのD6)に対応
// OC0BがPD5,OC0AがPD6

void setup(){
  TCCR0A = _BV(COM0A1)|_BV(WGM01)|_BV(WGM00); // TCCR0A = B10000011;
  TCCR0B = _BV(CS00); // TCCR0B = B00000001;
  // fast PWM mode
  // 動作クロックは分周なしの8MHz
  // PWMキャリア波の周波数は8MHz/256=31.25kHz
}

void loop(){
  OCR0A = 127;
  // OC0AのCompare Match値を127にセット
  // PD6ピンからvalue=127に対応するアナログ電圧(Unoでは5*127/255=2.49V)が出力される
}

##analogRead(pin)
未確認です。
以下を参考に、ATMega328PのAD変換機能を利用してください。

  • AnalogRead()の内部構造 [https://garretlab.web.fc2.com/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_analog.c/analogRead.html]
  • AVR A/D変換 [http://ziqoo.com/wiki/index.php?AVR%20A%2FD%CA%D1%B4%B9]

#参考

  • Arduino処理速度とメモリー使用改善の策 [http://cammy.co.jp/technical/2016/01/17/arduino_003/]
  • Arduinoソフトウェアの内部構造 [https://garretlab.web.fc2.com/arduino/inside/index.html]
  • (※PDF, 442ページ)Atmel ATMega328Pデータシート [http://www.atmel.com/ja/jp/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf]
  • How to modify the PWM frequency on the arduino-part1(fast PWM and Timer 0)
    [http://www.eprojectszone.com/2016/08/07/how-to-modify-the-pwm-frequency-on-the-arduino-part1/]
38
39
0

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
38
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?