2016年04月29日 | 公開。 |
2016年05月01日 | AdcBooster関数を一部修正。 |
この記事では、arduino.orgのArduino M0でanalogRead関数(A/D変換をして結果を返す関数)を高速動作させる方法について解説します。
手元に実機がないために未確認ですが、同様の手法がおそらくArduino M0 Proにおいても有効です。(Arduino M0とArduino M0 Proは、A/Dコンバータ関係は同一のライブラリを使用している) さらに、arduino.ccのArduino/Genuino Zeroでも有効かも知れません。(これも実機がないので未確認)
私の書く記事は、前置きが長くて、結論がなかなか出てこない傾向にありますが、今回はずばり結論から書きます。
「Arduino M0のanalogRead関数がなんか遅いな」と感じている人は、リスト1に示すAdcBooster関数をスケッチ内に置き、それをsetup関数内で呼んでから、analogRead関数を使う様にしてください。あなたの感じている問題は、きっと解決するか、あるいは大幅に緩和されます。
void AdcBooster()
{
ADC->CTRLA.bit.ENABLE = 0; // Disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV64 | // Divide Clock by 64.
ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // 1 sample
ADC_AVGCTRL_ADJRES(0x00ul); // Adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // Sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // Enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
} // AdcBooster
2016年05月01日追記:AdcBooster関数にバス同期のコード(Waito for synchronizationとコメントに書いた2行)が抜けていたので、追加しました。リスト3の中のAdcBooster関数も同様です。
Arduino M0のanalogRead関数は、実行に結構時間がかかります。
リスト2のスケッチでanalogRead関数の実行時間が計測できますので、実際に何種類かのArduinoで実行時間を計測してみましょう。
#ifdef ARDUINO_ARCH_SAMD
#define ser SerialUSB // when Arduino M0/M0 pro
#else
#define ser Serial // when another Arduino
#endif
void setup() {
while(!ser);
ser.begin(9600);
delay(5000); // wait for starting serial monitor
ser.println("START SPEED TEST");
uint32_t StartTime=millis();
for(int i=0; i<10000; i++) { // execute A/D conversion for 100,000 times
volatile uint16_t result;
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
} // for i
uint32_t EndTime=millis();
ser.println(EndTime-StartTime);
}
void loop() {
}
Arduino IDE 1.7.10でリスト2をコンパイルし、Arduino M0で実行して、シリアルモニタで結果を観察すると、図1の様になります。
注:以後この記事では、Arduino IDE 1.7.10を使います。
リスト2は、analogRead関数を10万回実行し、その実行時間をms単位で表示するスケッチです。10万回の実行に20,812ms(20.812秒)かかるのですから、1回の実行には208.12μsかかる事になります。さらにその逆数からanalogRead関数の繰り返し周波数(1秒あたりの実行回数、すなわち、連続変換時のサンプリング周波数)を計算すると、4.805kHzになります。
サンプリング周波数が5kHzを切るのは、結構遅いと思いませんか?実は「Arduio M0は安くて高性能だし、簡単なシールドをつけてオシロースコープになったら面白そう」と考えて、analogRead関数の実行速度を調べ始めたのですが、この結果にがっかりしました。
5kHz未満のサンプリング周波数がどれほど残念なのかは、他の種類のArduinoと比較すると、より明瞭になります。Arduino M0、Arduino Uno、Arduino Dueの3機種でリスト2のスケッチを実行した結果を表1に示します。
機種 | analogRead関数を 10万回実行するのに かかる時間 [ms] |
alalogRead関数の 1回あたりの 実行時間 [μs] |
analogRead関数の 繰り返し周波数 (サンプリング周波数) [kHz] |
Arduio Unoを 基準とした サンプリング周波数の 倍率 |
---|---|---|---|---|
Arduino M0 | 20812 | 208.12 | 4.805 | 0.538 |
Arduino Uno | 11199 | 111.99 | 8.929 | 1.00 |
Arduino Due | 329 | 3.29 | 304.0 | 34.0 |
この表を見ると分かるように、Arduino M0では、Arduino Unoの53.8%のサンプリング周波数しか得られていません。Arduino M0が48MHz動作の32ビットマイコンで、Arduino Unoが16MHz動作の8ビットマイコンである事を考えると、ちょっと信じられない遅さです。
一方でArduino Dueは、Arduino M0の63.3倍、Arduino Unoの34.0倍のサンプリング周波数を実現しており、さすが上位機種だけの事はあります。
参考:Arduio Unoに搭載されているATmega328Pのデータシートによると、A/D変換の最小時間は13μs(サンプリング周波数に換算すると76.9kHz)になっています。それに対して、Arduino M0に搭載されているSAMD21のデータシートによると、サンプリング周波数の上限は300kHzとなっています。内蔵マイコンのスペック上は、Arduino M0の方が、Arduino UnoよりもA/D変換が速いのです。
リスト2を書き換えて、setup関数の先頭でリスト1のAdcBooster関数を呼び出すようにしたのが、リスト3になります。
#ifdef ARDUINO_ARCH_SAMD
#define ser SerialUSB // when Arduino M0/M0 pro
#else
#define ser Serial // when another Arduino
#endif
void AdcBooster()
{
ADC->CTRLA.bit.ENABLE = 0; // Disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV64 | // Divide Clock by 64.
ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // 1 sample
ADC_AVGCTRL_ADJRES(0x00ul); // Adjusting result by 0
ADC->SAMPCTRL.reg = 0x00; // Sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // Enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // Wait for synchronization
} // AdcBooster
void setup() {
AdcBooster();
while(!ser);
ser.begin(9600);
delay(5000); // wait for starting serial monitor
ser.println("START SPEED TEST");
uint32_t StartTime=millis();
for(int i=0; i<10000; i++) { // execute A/D conversion for 100,000 times
volatile uint16_t result;
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
result=analogRead(A0);
} // for i
uint32_t EndTime=millis();
ser.println(EndTime-StartTime);
}
void loop() {
}
注:AdcBooster関数はSAMDマイコンを搭載したArduino(Arduino M0など)でしか実行できませんので、リスト3は、Arduino UnoやArduino Dueでは実行できません。
リスト3をArduino M0で実行すると、1602msとなります。(表2参照)
AdcBooster関数の 呼び出しの有無 |
analogRead関数を 10万回実行するのに かかる時間 [ms] |
alalogRead関数の 1回あたりの 実行時間 [μs] |
analogRead関数の 繰り返し周波数 (サンプリング周波数) [kHz] |
Arduio Unoを 基準とした サンプリング周波数の 倍率 |
---|---|---|---|---|
なし(リスト2) | 20812 | 208.12 | 4.805 | 0.538 |
あり(リスト3) | 1602 | 16.02 | 64.42 | 7.21 |
サンプリング周波数は64.42kHzにまで上がりました。これなら、音声帯域をカバーできます。
Arduino Unoと比較すると、サンプリング周波数が7.21倍となっており、Arduino Unoより十分速いサンプリングが実現していることが分かります。
また、Arduino M0でAdcBooster関数を使わなかった場合と比較すると、サンプリング周波数が13.4倍に向上しています。
この様に、AdcBooster関数を呼び出すと、かなりの速度向上が実現します。
次のページでは、AdcBooster関数が何をやっているかについて説明します。
商品名 | I/Oピン一つで読める4X5キーパッドキット | |
税抜き小売価格 | 2400円 | |
販売店 | スイッチサイエンス | |
サポートページ | I/Oピン一つで読める4X5キーパッドキットサポートページ |
商品名 | I/Oピン一つで読める4X4キーパッドキット | |
税抜き小売価格 | 900円 | |
販売店 | スイッチサイエンス | |
サポートページ | I/Oピン一つで読める4X4キーパッドキットサポートページ |
商品名 | I/Oピン一つで読める4X4キーパッド(完成品) | |
税抜き小売価格 | 1380円 | |
販売店 | スイッチサイエンス | |
サポートページ | I/Oピン一つで読める4X4キーパッド(完成品)サポートページ |