Arduinoプログラムの高速化

※本記事は**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](https://qiita-image-store.s3.amazonaws.com/0/217079/1050ccea-90a1-40a5-91fa-c97ac3d57c9b.png)
図1. ATMega328Pのピン配置 [ATMega328Pデータシート
 http://www.atmel.com/ja/jp/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf]

![uno-1.png](https://qiita-image-store.s3.amazonaws.com/0/217079/257893c9-4d21-41ed-ca9b-fccb389171db.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](https://qiita-image-store.s3.amazonaws.com/0/217079/7269dc6f-5846-9ef8-dcfb-d5586793bce0.png)

表3. TCCRnBレジスタ [ATMega328Pデータシートより]
![ATMega328P_TCCR0B.PNG](https://qiita-image-store.s3.amazonaws.com/0/217079/69257aa3-3f77-7119-ddfc-958f10de7b83.png)

---

表5. WGMnビット [ATMega328Pデータシートより]
![ATMega328P_TCCR0A_WGM0.PNG](https://qiita-image-store.s3.amazonaws.com/0/217079/8a0411af-cd10-016f-be63-d290b172aaf1.png)
**WGMn**ビットは波形生成の方式を決定します。
`011`と設定してFast PWMモードを使用しましょう。

---

表4. COMnxビット [ATMega328Pデータシートより]
![ATMega328P_TCCR0A_COM0A.PNG](https://qiita-image-store.s3.amazonaws.com/0/217079/7dfc1a0b-0326-ab2f-7c11-08648d160d55.png)
**COMnx**ビットはPWM波生成の方法を決定します。
基本的に`10`と設定すれば問題ありません。

---

表6. CSnビット [ATMega328Pデータシートより]
![ATMega328P_TCCR0A_CS0.PNG](https://qiita-image-store.s3.amazonaws.com/0/217079/0a8f9d36-cff3-ffdd-2d87-557fe14233ec.png)
**CSn**ビットはクロックの分周比(prescaler)を決定します。
fast PWMモードではクロック源の周波数を`f_source`、分周比を`p`とすると、PWMキャリア波の周波数`f_pwm`は`f_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/]