※本記事は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つのグループに分かれた名前を持っています。
図1. ATMega328Pのピン配置 [ATMega328Pデータシート
http://www.atmel.com/ja/jp/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf]
図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にセットします。
void setup(){
DDRD |= _BV(PD5); // DDRD |= B00100000;
// pinMode(5, OUTPUT);
DDRB &= ~_BV(PB0); // DDRB &= ~B01000000;
// pinMode(8, INPUT);
}
##digitalWrite(pin, value)
PORTxレジスタの該当ビットへHIGH,LOWを直接代入します。
void loop(){
PORTD |= _BV(PD5); // PORTD |= B00100000;
// digitalWrite(5, HIGH);
}
##digitalRead(pin)
該当ピンを含むPORTxレジスタと_BV()
の論理和は、
- 該当ピンに対応するビットがHIGHの状態なら、そのビットのみHIGH
- 該当ピンに対応するビットがLOWの状態なら
B00000000
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データシートより]
表3. TCCRnBレジスタ [ATMega328Pデータシートより]
表5. WGMnビット [ATMega328Pデータシートより]
WGMnビットは波形生成の方式を決定します。
011
と設定してFast PWMモードを使用しましょう。
表4. COMnxビット [ATMega328Pデータシートより]
COMnxビットはPWM波生成の方法を決定します。
基本的に10
と設定すれば問題ありません。
表6. CSnビット [ATMega328Pデータシートより]
CSnビットはクロックの分周比(prescaler)を決定します。
fast PWMモードではクロック源の周波数をf_source
、分周比をp
とすると、PWMキャリア波の周波数f_pwm
はf_pwm = f_source / (256 * p)
であり、分周比を小さくするほどPWMのキャリア波は高周波になります。
// 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/]