ロボット・ハードウェア

超音波距離センサUS-100を割り込みタイマーで自動化!(Arduino Leonardo, Pi Pico版)

記事内に商品プロモーションを含む場合があります

トピックス

この記事では割り込みタイマーを使った超音波距離センサUS-100の使いやすいソフトウェアモジュールとサンプル、及び使い方を解説しています。

超音波距離センサについてご存知の方はこちらから

ソースコードと使い方はこちらから

US-100超音波距離センサ

超音波センサとは

ロボットや電子工作において、障害物との距離を測りたい場合があります。そんなときに役立つのが距離センサです。距離センサにはレーザーを利用する光学式や超音波を利用する超音波式などがあります。それぞれにメリット・デメリットがありますが、今回は超音波式の距離センサを使ってみましょう。

US-100とHC-SR04

今回利用する超音波距離センサはUS-100です(図1)。似たものにHC-SR04があります。

図1:超音波距離センサUS-100

販売サイトはこのあたりですね。adafruitスイッチサイエンスマルツパーツにもあります。

HC-SR04は元々かなり有名な超音波距離センサですが、US-100はそこからさらに便利に扱えるものになっています。

UARTモードとUART

US-100には2つのモードがあります。シリアルUARTモードとHC-SR04モードです。背面のジャンパを外すとHC-SR04モードになりますが、HC-SR04モードはその名の通りHC-SR04と同様の動作になります。しかし今回はより便利なシリアルUARTモードで使います。

図2:US-100の背面のジャンパ。今回はシリアルUARTモードで利用するので外さない。

UARTというのはUniversal Asynchronous Receiver-Transmitterの略で、要するに非同期式のシリアル通信です。Arduinoなどでよく使われる通信方式ですね。センサによっては複雑なデータのやり取りを行う際にI2CやSPIといった同期式シリアル通信を利用しますが、US-100とはシンプルなUARTでの通信を行います。

よくある使い方とその問題点

HC-SR04を用いる場合は直接接続ピンの電圧をデジタル計測し、タイミングを測る必要があります。超音波距離センサではまず超音波を発生させ、その音波が障害物に反射し、反射した音波がマイクに入力されるまでにどのくらいの時間がかかったかで距離を測定します。このあたりの処理をHC-SR04ではきっちり管理してやる必要があります。

その点US-100のシリアルUARTモードではそうした処理が自動で行われ、計算された距離の数値データを直接取り出すことが可能です。

図3:HC-SR04とUS-100(シリアルUARTモード)の使い方の違い
US-100の場合、測定部分を実装する必要がなく、シリアル通信のみで距離の測定が完了する。

今回はArduino IDEを用いてArduino Leonardo及びRaspberry Pi Pico上で実行することを想定します。

まずはHC-SR04を利用する場合の一般的なやり方を見てみましょう。

1.pulseIn()を使う

最も簡単なのはpulseIn()を使う方法です。pulseIn()は指定したピンの電圧の変化を検知し、変化するまでの時間を測る関数です。これにより、図3上の図のように超音波発出から受信までの時間を測定します。あとは音速の値から距離計算をしてやれば障害物までの距離がわかります。しかしpulseIn()は測定の間、他の処理を止めてしまう「ブロッキング」が起こります。そのため他の処理をしながら使う事ができず、非常に不便です。

2. 割り込み処理を使う

次によく用いられる方法が割り込み処理を使う方法です。組み込みシステムにおいて「割り込み」処理は欠かせません。なにか状況の変化に応じて、現在の作業を中断し文字通り「割り込んで」別の処理を行うことを割り込みと言います。

こちらで紹介されているように超音波の測定を割り込み処理にしてしまうことで、他の処理を止めずに距離を測定することができます。この方法は他の処理を止めないで実行でき、かつ正確に測定できると考えられますが、測定のタイミングや受信後の再測定は自分で管理しなくてはいけません。

US-100のUARTモードではUS-100本体とのシリアル通信ですべて完結するのでこのあたりのことは意識しなくても使うことができます。Adafruitでは利用に当たってPingSerialが紹介されています。このライブラリではUARTモードでの利用がサポートされています。

しかし、これを利用する場合でも測定タイミングはこちらで管理しなくてはいけません。このタイミング管理がメインのloop()にある場合、他の処理と合わせると非常に煩雑です。

そこで今回は一定間隔で定期的に測定を行ってくれ、好きなタイミングでそれを読み出す事ができるように、測定を自動化したいと思います。この場合、測定距離は常に一定間隔でアップデートされるため、メインのloop()では任意のタイミングで読み出すだけで利用できるようにします。

タイマー割り込みを使う

Arduinoなどのボードに搭載されたハードウェアタイマーを利用することで一定間隔で割り込みを発生させられる「タイマー割り込み」というものがあります。

これは一定間隔で割り込みを発生させるもので、ソフトウェア的に時間管理をするより正確です。このタイマー割り込みを使って、一定間隔でUS-100の測定を行えるようにすれば、定期的に情報が更新される距離センサーとして扱えるはずです。

このタイマーですが多くのボードにとってハードウェアタイマーは貴重な資源であるため、無闇矢鱈に浪費することは避けたいです。そこでこちらのライブラリを使いたいと思います。

TimerInterrupt_Generic

このライブラリではハードウェアタイマー1つに対して複数のタイマー割り込みを発生させることができ、また直接ハードウェアタイマーを扱う際の煩雑さを軽減してくれます。

タイマー割り込みを使ったUS-100用モジュール(Github公開中!)

早速ですが、タイマー割り込みを利用したUS-100用モジュールを作成&公開しています。こちらからダウンロードできます!→Githubページ

Arduino LeonardoRaspberry Pi Picoでの利用を想定していますが、その他のボードでも必要なピンが接続されていてその対応が取れていれば、基本的には利用可能だと思います。

それでは利用方法を以下で見ていきます。Raspberry Pi Pico版はこちら

Arduino Leonardo版

接続図

図4:Arduino LeonardoとUS-100の接続

まずはUS-100とArduino Leonardoを図4のように接続します。

Arduino LeonardoとUS-100の接続ピン
  1. Arduino Leonardoの3.3Vまたは5V と US-100のVCC
  2. Arduino LeonardoのGND と US-100のGND
  3. Arduino LeonardoのDIGITAL 0 と US-100のEcho/RX
  4. Arduino LeonardoのDIGITAL 1 と US-100のTrig/TX

US-100は入力電圧3V~5Vまで対応していますので、VCCは3.3Vでも5Vでもどちらでも構いません。GNDを接続し、UART用のRX, TXをそれぞれの対応ピンに接続すれば完了です。Arduino LeonardoのDIGITAL0, 1にもそれぞれ小さくRX1, TX1と書かれています。

次にスケッチファイルTimerDrivenUS-100.inoをArduino IDEで開きます。同一ディレクトリにUS100.hとUS100.cppというファイルがあることも確認してください。

ここでライブラリTimerInterrupt_Genericをインストールしておきましょう。Library Managerの検索窓に”TimerInterrupt_Generic”と入れると、図5のように表示されますのでインストールされていない場合はインストールしておきます。記事執筆時点(2023/10/2)でのバージョンは1.13.0です。

図5:TimerInterrupt_Genericのインストール画面。図はすでにインストール済みだったため、INSTALLEDと表示されている。

 あとはこのスケッチを書き込むだけです。起動後はシリアルモニタを起動してみましょう。すると、正しく実行できていれば図6のように表示されるはずです。US-100の前方に障害物を配置するとこの距離が変化するのがわかると思います。

図6:実行中のシリアルモニタの表示

では実際にTimerDrivenUS-100.inoのソースコードを見ていきましょう。

まずはsetup()の手前までです。

#include "US100.h"

///Arduino Leonardoを想定

///タイマーを利用する際に必要
#define USE_TIMER_3 true

#include "TimerInterrupt_Generic.h"
#include "ISR_Timer_Generic.h"

ISR_Timer ISR_timer;///ISRタイマー

static const unsigned long HARDWARE_TIMER_INTERVAL_MS = 5UL;

US100 us100(20,4500);///US-100。引数は最小距離と最大距離(指定しない場合デフォルト値が入る)

///ISRタイマーを利用するためのハードウェアタイマー用コールバック関数
void timerHandler(){
  ISR_timer.run();
}

ここまではタイマーの利用に必要な設定です。ピンとこない場合は「おまじない」として書いておくだけでも問題なしです。詳しくはTimerInterrupt_Genericのサンプルコードを見るとわかりやすいですが、注目すべき点はハードウェアタイマー用のコールバック関数timerHandler()を用意している点です。US100はUS100.h, US100.cppで用意しているUS-100管理用クラスのインスタンスを格納します。

ここで設定可能なのは、US100の測定距離の最小値と最大値です。設定しない場合はデフォルト値の最小距離100(mm)と最大距離2500(mm)がセットされますが、今回は20mm, 4500mmで設定しています。Adafruitによると

They work at about 2cm to 450cm away, but we think 10cm-250cm will get you the best results.

https://www.adafruit.com/product/4019

とあり、20mmから4500mmの間で動くはずだが、100mmから2500mmの間で利用するとより効果的であるとのことです。今回は20mmから4500mmで使います。この範囲外の値が観測されたときは値が不正であるとして、”0″を返すようにしています。

ではsetup()をみてみましょう。

void setup() {

  Serial.begin(9600);///シリアルモニターに表示するためのシリアル通信開始

  us100.setup();///セットアップ開始

  ITimer3.init();///ハードウェアタイマー初期化

  // ISRタイマー実行のためのハードウェアタイマーの設定(TimerInterrupt_Genericのサンプルコード参照)
  if (ITimer3.attachInterruptInterval(HARDWARE_TIMER_INTERVAL_MS, timerHandler))
  {
    Serial.print(F("Starting  ITimer3, millis() = ")); 
    Serial.println(millis());
  }
  else{
    Serial.println(F("Can't set ITimer3. Select another frequency or a timer"));
  }

  ///ISRタイマーの実行間隔とコールバック関数を設定
  ISR_timer.setInterval(US100::TIMER_INTERVAL_MS, US100::ISR_timerHandler, &us100);

}

ここでは表示用のシリアル通信を開始しています。次にUS100をsetup()し、こちらも別途シリアル通信が開始されています。またISRタイマーの呼び出し間隔、つまりUS-100での測定間隔は100(ms)にしています。

ここまでできたら(わからない場合はこのまま記述するだけで動くと思います。)、あとはloop()で利用するだけです。

void loop() {

  int dist = us100.getDistance();
        
  Serial.print("Distance: ");
  Serial.print(dist);
  Serial.println("mm");

  delay(100);

}

今回のサンプルではgetDistance()で100ms毎にUS-100から距離情報を受け取り、表示するようにしています。このように、タイミング管理について全く気にせず利用することができます。またタイマー割り込みを利用しているため、loop()中にdelay()のように他の動作を止めてしまう処理を入れ込んでも影響を受けずに、比較的正確に距離計測を行うことができます。

Raspberry Pi Pico版

接続図

図7:Rasbperry Pi PicoとUS-100の接続

今度はRaspberry Pi Picoでの利用についてみていきましょう。図7のように接続します。

Rasapberry Pi PicoとUS-100の接続ピン
  1. Pi Picoの3.3V(3V3(OUT)) と US-100のVCC
  2. Pi PicoのGND と US-100のGND
  3. Pi PicoのGP 1(UART0 RX) と US-100のEcho/RX
  4. Pi PicoのGP 0(UART0 TX) と US-100のTrig/TX

VCCは3.3Vに接続します。GNDを接続し、UART用のRX, TXをそれぞれの対応ピンに接続すれば完了です。

次にスケッチファイルTimerDrivenUS-100ForPiPico.inoをArduino IDEで開きます。同一ディレクトリにUS100.hとUS100.cppというファイルがあることも確認してください。

Arduino Leonardoのときと同様に、ライブラリTimerInterrupt_Genericもインストールしておきましょう。

あとはこのスケッチをアップロードすれば完了です。

それではソースコードのArduino Leonardo版からの主な変更点だけみていきましょう。

#include "US100.h"

#include "TimerInterrupt_Generic.h"
#include "ISR_Timer_Generic.h"


MBED_RPI_PICO_Timer ITimer(0);///for pico
ISR_Timer ISR_timer;

static const unsigned long HARDWARE_TIMER_INTERVAL_US = 10000UL;///for pico

まずタイマーの設定に関する部分がArduino Leonardo版と異なっています。ハードウェアタイマーのインターバルもミリ秒からマイクロ秒になっています。

タイマーハンドラの書き方も若干異なります。

void timerHandler(uint alarm_num){

  TIMER_ISR_START(alarm_num);///for pico

  ISR_timer.run();

  TIMER_ISR_END(alarm_num);///for pico
}

以降はほんのちょっとだけsetup()が異なるだけです。

void setup() {

  Serial.begin(9600);

  us100.setup();

  if (ITimer.attachInterruptInterval(HARDWARE_TIMER_INTERVAL_US, timerHandler))///for pico
  {
    Serial.print(F("Starting  ITimer, millis() = ")); 
    Serial.println(millis());
  }
  else{
    Serial.println(F("Can't set ITimer. Select another frequency or a timer"));
  }

  ISR_timer.setInterval(US100::TIMER_INTERVAL_MS, US100::ISR_timerHandler, &us100);

}


void loop() {

  int dist = us100.getDistance();
  Serial.print("Distance: ");
  Serial.print(dist);
  Serial.println("mm");

  delay(100);

}

メモ

・US100.hのTIMER_INTERVAL_MSを変更することで、測定間隔を100msから変更することが可能です。

・US-100は温度を測定することも可能ですが、今回のコードでも温度測定ができます。us100.requestTemperature()を事前に実行しておいて、us100.getTemperature()とすると温度が取得できるはずです。

・US-100はデータシートにあまり詳細が書かれていなくて仕様がわからない部分があるのですが、いくつかはPingSerialの説明に書かれています。それによると、測定間隔を短くしすぎた場合、測定が終わる前に再度距離情報の取得をリクエストしても、そこは無視してくれるようです。また測定できない場合は91ms程度でタイムアウトしてくれるようです。その辺も鑑み、デフォルトでは測定間隔を100msにしています。