Arduino M0のanalogRead関数を高速化する(1)

このページをスマートフォンなどでご覧になる場合は、画面を横長にする方が読みやすくなる事があります。
目次へ  前のページへ (1) (2) 次のページへ
2016年04月29日 公開。
2016年05月01日 AdcBooster関数を一部修正。

この記事では、arduino.orgArduino M0analogRead関数(A/D変換をして結果を返す関数)を高速動作させる方法について解説します。

手元に実機がないために未確認ですが、同様の手法がおそらくArduino M0 Proにおいても有効です。(Arduino M0とArduino M0 Proは、A/Dコンバータ関係は同一のライブラリを使用している) さらに、arduino.ccArduino/Genuino Zeroでも有効かも知れません。(これも実機がないので未確認)

目次

1. いきなり結論 … 1ページ
2. Arduino M0は意外とanalogRead関数が遅い … 1ページ
3. AdcBooster関数の効果 … 1ページ
4. コアライブラリでのA/D変換器の初期化について … 2ページ
5. AdcBooster関数の内部処理 … 2ページ
5-1. A/D変換器の有効化と無効化 … 2ページ
5-2. A/D変換器のクロック周波数の変更 … 2ページ
5-3. 加算平均処理の無効化 … 2ページ
5-4. ウェイトの無効化 … 2ページ
6. 加算平均処理を行った事による、変換誤差の増加の度合いの評価 … 2ページ
7. 最後に … 2ページ

1.いきなり結論

私の書く記事は、前置きが長くて、結論がなかなか出てこない傾向にありますが、今回はずばり結論から書きます。

「Arduino M0のanalogRead関数がなんか遅いな」と感じている人は、リスト1に示すAdcBooster関数をスケッチ内に置き、それをsetup関数内で呼んでから、analogRead関数を使う様にしてください。あなたの感じている問題は、きっと解決するか、あるいは大幅に緩和されます。

リスト1、AdcBooster関数COPY
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関数も同様です。

2.Arduino M0は意外とanalogRead関数が遅い

Arduino M0のanalogRead関数は、実行に結構時間がかかります。

リスト2のスケッチでanalogRead関数の実行時間が計測できますので、実際に何種類かのArduinoで実行時間を計測してみましょう。

リスト2、analogRead関数の実行時間を計測するスケッチCOPY
#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を使います。

図1、Arduino M0でリスト2を実行した結果
図1、Arduino M0でリスト2を実行した結果

リスト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 UnoArduino Dueの3機種でリスト2のスケッチを実行した結果を表1に示します。

表1、各種Arduinoでのリスト2の実行結果
機種 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変換が速いのです。

広告

3.AdcBooster関数の効果

リスト2を書き換えて、setup関数の先頭でリスト1のAdcBooster関数を呼び出すようにしたのが、リスト3になります。

リスト3、setup関数の先頭でAdcBooster関数を呼び出すようにしたanalogRead関数の実行時間計測用スケッチCOPY
#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参照)

表2、Arduino M0において計測したAdcBooster関数の効果
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関数が何をやっているかについて説明します。

目次へ  前のページへ (1) (2) 次のページへ

このページで使われている用語の解説

関連ページ

関連製品

I/Oピン一つで読める4X5キーパッドキット 商品名 I/Oピン一つで読める4X5キーパッドキット
税抜き小売価格 2400円
販売店 スイッチサイエンス
サポートページ
I/Oピン一つで読める4X4キーパッドキット 商品名 I/Oピン一つで読める4X4キーパッドキット
税抜き小売価格 900円
販売店 スイッチサイエンス
サポートページ
I/Oピン一つで読める4X4キーパッド(完成品) 商品名 I/Oピン一つで読める4X4キーパッド(完成品)
税抜き小売価格 1380円
販売店 スイッチサイエンス
サポートページ
PCBgogoのバナー
Arduino 電子工作
このサイトの記事が本になりました
ISBN:978-4-7775-1941-5
工学社の書籍の内容の紹介ページ
本のカバーの写真か書名をクリックすると、Amazonの書籍購入ページに移動します。
電子工作で学ぶ論理回路入門
このサイトの中の人が書いた本です。
ISBN:978-4-7775-2280-4
工学社の書籍の内容の紹介ページ
この本の紹介記事
本のカバーの写真か書名をクリックすると、Amazonの書籍購入ページに移動します。