はじめに
Arduino で割と有名な RC 回路の時定数を使ったコンデンサの静電容量測定は、時定数が短いと結構な動作速度が求められるので、いろんなコマンドのサイクル数を測ってみた。
使う開発環境は DxCore。
ちなみにこのやり方ではコンデンサの正極側で analogRead() をしつづけるんだけど、それを AVR64DD28 でやると、基準抵抗を 10kΩくらいまで下げないと、電圧がかなり下がってしまって、このやりかたが成り立たなくなったけど、それはまた別の記事にするかもです。
測定の仕組み
ある程度時間の掛る部分なら micros() だけど micros() 自体が時間が掛かるので、今回はタイマーのレジスタを直接調べて測定する。
DxCore のデフォルト設定では micros() には TCB2 のタイマーが使われている。
TCB2.CTRLA=0b11 となっており、これはタイマーの基準クロック CLK_PER を 1/2 する、つまり基準クロック 2回で、タイマーが 1進む。
では CLK_PER だけど、今度は CLKCTRL.MCLKCTRLB=0 となっていて、これはマイコンのクロックを分割せずにそのまま使うということになる。
つまり、結果としてマイコンが 2サイクルするとタイマーが 1進む形になっている。
TCB2.CTRLA の設定を変えて 2分割しないようにもできるけど、どの関数がタイマーに依存してるか知らないので、ここは変えない。変えずに、(ほぼ)同じコマンドを 2回実行すれば、それがそのコマンドのサイクル数となる。
結果
内部水晶 24MHz で駆動してサイクル数を測定。時間はサイクル数/24Mhz から計算しただけで、観測はしていない。
| 命令 | サイクル数 | 時間[us] | コメント |
|---|---|---|---|
| micros() | 119 | 4.958 | |
| digitalWrite(Pin_x,HIGH) | 102 | 4.250 | |
| digitalWrite(Pin_x,LOW) | 100 | 4.167 | |
| PORTx.OUT=PORTx.OUT | 0bxxxxxxxx | 9 | 0.375 | digitalWrite(Pin_x,HIGH) の代用 |
| PORTx.OUT=PORTx.OUT & 0bxxxxxxxx | 9 | 0.375 | digitalWrite(Pin_x,LOW) の代用 |
| PORTx.OUT=0bxxxxxxxx | 5 | 0.208 | |
| digitalRead() | 6 | 0.250 | |
| (PORTx.OUT >> n) & 1 | 6 | 0.250 | digitalRead() の代用 |
| analogRead() | 212 | 8.833 | 12bit,最速設定 *1) |
*1) 次の設定をしてある。ADCクロックは 2MHz, サンプリング遅延無し
analogReadResolution(12);
analogClockSpeed(20000);
analogSampleDuration(0);
ソース
最適化 (主に LTO?) でプロセスが省かれる (i=0;i++;i++; みたいなことをすると、いきなり i=2 とする。i=2 みたいに変数を使っても Serial.print() とかで使わないと、省いてしまう。同じレジスタ操作を省く?それは無い?) のを恐れて、多分無駄が多いと思うし、もしかしたらこれでも省かれてしまっていてちゃんとサイクル数を計測できていないとかもあるかも。
実行すると、こんな感じでシリアル出力する。
CLKCTRL= 0
TCB2.CTRLA= 11
t1-t0= 5, micros(): 119
digitalWrite(PIN_xxx,HIGH): 102
digitalWrite(PIN_xxx,LOW): 100
PORTx.OUT=PORTx.OUT | 0bxxxxxxxx: 9
PORTx.OUT=PORTx.OUT & 0bxxxxxxxx: 9
PORTA.OUT=0
PORTC.OUT=0
PORTx.OUT=0bxxxxxxxx: 5
digitalRead(): 6
b0= 1, b1= 0, (PORTx.OUT >> n)&1: 6
an0= 1965, an1= 2099, analogRead(): 212
ソースです。
---- 展開 ----
// 速度が必要な用途のために、いろいろ計測してみる
// define
/* {{{ */
#define DEBUG 1 // デバグ出力不要なら 0
#if DEBUG
#define D_Begin(...) Serial.begin(__VA_ARGS__);
#define D_print(...) Serial.print(__VA_ARGS__)
#define D_write(...) Serial.write(__VA_ARGS__)
#define D_println(...) Serial.println(__VA_ARGS__)
#else
#define D_Begin(...)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#endif
/* }}} */
void setup() {
//definition
//汎用
unsigned int cnt0; // 16ビット整数用
unsigned int cnt1; // 16ビット整数用
unsigned int an0; // 16ビット整数用
unsigned int an1; // 16ビット整数用
unsigned long t0; // 32ビット整数用
unsigned long t1; // 32ビット整数用
boolean b0;
boolean b1;
//結果格納用
Serial.begin(9600);
// Timer TCB2 に関する設定を調査
/* {{{ */
D_print("CLKCTRL= ");
D_print(CLKCTRL.MCLKCTRLB,BIN);
D_println();
//CLKCTRL=0 なので CLK_PER = クロックそのもので、それがタイマー TCB2 にも向かう。
D_print("TCB2.CTRLA= ");
D_print(TCB2.CTRLA,BIN);
D_println();
//CTRLA=0b11 なので CLSEL=001 なので CLK_PER/2 でタイマーがカウント。つまり 2サイクルに
//一回カウントが進む。
//CTRLA=0b01 とすると 1サイクル 1カウントになるけど、どの関数がどこで使っているか知らな
//いので触らない。
/* }}} */
// micros() 測定
/* {{{ */
TCB2.CNT=0; //桁溢れに対応する実力がないのでタイマーをリセット
cnt0=TCB2.CNT;
t0=micros();
t1=micros(); //最適化で勝手に縮められたくないので別の変数にしておく。
cnt1=TCB2.CNT;
Serial.print("t1-t0= ");
Serial.print(t1-t0); //使わないと warning が出るし、最適化ですっ飛ばされても嫌だ。
Serial.print(", micros(): ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// digitalWrite の High を計測
/* {{{ */
pinMode(PIN_PA7,OUTPUT);
pinMode(PIN_PC0,OUTPUT); //同じピンだと最適化で纏められたら恐いので別のピンにしておく
// もしかしたらピン毎に速度が違うとか、High -> High と Low -> High で違うとかあるかもだ
// けど、無視
TCB2.CNT=0;
cnt0=TCB2.CNT;
digitalWrite(PIN_PA7,HIGH);
digitalWrite(PIN_PC0,HIGH);
cnt1=TCB2.CNT;
Serial.print("digitalWrite(PIN_xxx,HIGH): ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// digitalWrite の Low を計測
/* {{{ */
TCB2.CNT=0;
cnt0=TCB2.CNT;
digitalWrite(PIN_PA7,LOW);
digitalWrite(PIN_PC0,LOW);
cnt1=TCB2.CNT;
Serial.print("digitalWrite(PIN_xxx,LOW): ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// PORTx.OUT = PORTx.OUT | 0bxxxxxxxx を計測
/* {{{ */
// PA7, PC0 を High へ切り替え
TCB2.CNT=0;
cnt0=TCB2.CNT;
PORTA.OUT=PORTA.OUT | 0b10000000;
PORTC.OUT=PORTC.OUT | 0b00000001;
cnt1=TCB2.CNT;
Serial.print("PORTx.OUT=PORTx.OUT | 0bxxxxxxxx: ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// PORTx.OUT = PORTx.OUT & 0bxxxxxxxx を計測
/* {{{ */
// PA7, PC0 を Low へ切り替え
TCB2.CNT=0;
cnt0=TCB2.CNT;
PORTA.OUT=PORTA.OUT & 0b01111111;
PORTC.OUT=PORTC.OUT & 0b11111110;
cnt1=TCB2.CNT;
Serial.print("PORTx.OUT=PORTx.OUT & 0bxxxxxxxx: ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// PORTx.OUT = 0bxxxxxxxx を計測
/* {{{ */
D_print("PORTA.OUT=");
D_print(PORTA.OUT,BIN);
D_println();
D_print("PORTC.OUT=");
D_print(PORTC.OUT,BIN);
D_println();
// ここまでの時点でどちらも 0
// digitalWrite の High も Low も大差なかったから、High 側だけでいいや。
TCB2.CNT=0;
cnt0=TCB2.CNT;
PORTA.OUT=0b10000000;
PORTC.OUT=0b00000001;
cnt1=TCB2.CNT;
Serial.print("PORTx.OUT=0bxxxxxxxx: ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// digitalRead() を計測
/* {{{ */
pinMode(PIN_PA7,INPUT);
pinMode(PIN_PC0,INPUT);
// もしかしたらピン毎に速度が違うとか、High -> High と Low -> High で違うとかあるかもだ
// けど、無視
TCB2.CNT=0;
cnt0=TCB2.CNT;
digitalRead(PIN_PA7);
digitalRead(PIN_PC0);
cnt1=TCB2.CNT;
Serial.print("digitalRead(): ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// (PORTA.OUT >> 7) & 1, (PORTC.OUT >> 1) & 1 を計測
/* {{{ */
pinMode(PIN_PA7,INPUT);
pinMode(PIN_PC1,INPUT); //PC0 だとビットシフトが不要だから PC1 にする
TCB2.CNT=0;
cnt0=TCB2.CNT;
b0=(PORTA.OUT>>7) &1; //何かに使わないと省かれるかもだから
b1=(PORTC.OUT>>1) &1; //何かに使わないと省かれるかもだから
cnt1=TCB2.CNT;
Serial.print("b0= ");
Serial.print(b0);
Serial.print(", b1= ");
Serial.print(b1);
Serial.print(", (PORTx.OUT >> n)&1: ");
Serial.print(cnt1-cnt0);
Serial.println();
/* }}} */
// analogRead() の計測 (12ビット最速)
analogReadResolution(12);
analogClockSpeed(20000);
analogSampleDuration(0);
TCB2.CNT=0;
cnt0=TCB2.CNT;
an0=analogRead(PIN_PD7);
an1=analogRead(PIN_PC0);
cnt1=TCB2.CNT;
Serial.print("an0= "); //最適化で消されない対策
Serial.print(an0);
Serial.print(", an1= ");
Serial.print(an1);
Serial.print(", analogRead(): ");
Serial.print(cnt1-cnt0);
Serial.println();
}
void loop() {
}