【Arduino】超音波センサーで距離計をつくってみた!【URM37】

以前、URM37で超音波距離センサを動かしてみましたが(→超音波センサー URM37をArduinoで動かしてみる)、パソコン上で距離を見るだけでは実用性がないので距離計として使える機能を追加してみます。

IMG_6284

今回は超音波センサURM37LCDキーパッドシールドも組み合わせて測定開始ボタンをつけてみたり、バックライトの調光機能を実装してみます。

 

自分だけの距離計にどんな機能を足すか

基本的には距離計というものは距離を測定するだけなので、URM37使えば大抵の機能はソフトウェアの制御でどうにかなると思います。

市販品の距離計はほとんどレーザーでの距離測定になってしまいましたが、距離を測るという基本的なところは超音波と変わらないはずです。そこで市販品のカタログを参考にどんな機能を実装できるか考えてみます。(→BOSCH レーザー距離系GLM50型)

LCDディスプレイに距離を表示させる

あたりまえですが市販の距離計は液晶ディスプレイに測定した距離を表示されるようになっています。

URM37のサンプルスケッチだと測定した距離をシリアル通信でパソコン上の画面に表示させるようになっているので、これをLCDで表示できるようスケッチを変更します。

バックライト機能

最近の距離計はバックライトが付いていて暗い屋内や夜間でも使えるようになっているそうです。

丁度、LCDキーパッドシールドはD10ピンを操作することでバックライトのON/OFFができるようなことが書いてあったはずです。そこで、今回はPWMを使った調光機能を追加します。

測定モードを選べるように

距離計というものは直線距離を測るためだけのものと思っていましたが、最近の距離計は面積や体積まで測定してくれるようです。

しかし、URM37は測定可能な距離がそんなに長くないので今回はこの機能を入れないことにします。今回実装する機能は、直線距離を測る「Distance mode(距離測定モード)」と常に距離を測定する「Real time mode(連続測定モード)」の二機能を実装します。

二点を測定してピタゴラスの定理から残りの一点を測定できる「辺測定モード」も面白そうですが、これはmath.hを使うときのネタのために今回は温存します(^^;

 

スケッチ

これが距離計のスケッチになります。

基本的には以前使用したLCD KeyPad ShieldのスケッチURM37のスケッチを組み合わせています、足りないところはさらにプログラムを書き加えて対応しています。ちなみにURM37のピンアサインはLCDキーパッドシールド上のD12とD13につなげました。

#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define LCD_LIGHT 10

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
#define ERRORR    6

#define Real    100
#define Dist    101

int LCD_LIGH_Value = 250; //LCDのバックライト光度
int URPWM = 13;           // PWM Output 0-25000US,Every 50US represent 1cm
int URTRIG = 12;          // PWM trigger pin
int mode = Dist;

uint8_t EnPwmCmd[4] = {0x44, 0x02, 0xbb, 0x01}; // distance measure command


/**************************************************************/
/*関数名:read_LCD_buttons*/
/*動作;A0ポートの電圧値から押されたボタンを判別*/
/**************************************************************/
int read_LCD_buttons(int adc_key_in)
{
  if (adc_key_in > 1000) return btnNONE;   //1023, 戻り値5, 5.11V
  if (adc_key_in < 50)   return btnRIGHT;  //0   , 戻り値0, 0V
  if (adc_key_in < 250)  return btnUP;     //144 , 戻り値1, 0.71V
  if (adc_key_in < 450)  return btnDOWN;   //329 , 戻り値2, 1.65V
  if (adc_key_in < 650)  return btnLEFT;   //504 , 戻り値3, 2.53V
  if (adc_key_in < 850)  return btnSELECT; //741 , 戻り値4, 3.71V

  /* 全てのifが失敗(通常はこれを返さない)*/
  return ERRORR;
}

/**************************************************************/
/*関数名:LCD_light_up*/
/*動作;LCDのバックライト光度を上げる*/
/**************************************************************/
void LCD_light_up() {
  if (LCD_LIGH_Value < 250)
    LCD_LIGH_Value = LCD_LIGH_Value + 50;
  analogWrite(LCD_LIGHT, LCD_LIGH_Value);
  delay(100);
}

/**************************************************************/
/*関数名:LCD_light_down*/
/*動作;LCDのバックライト光度を下げる*/
/**************************************************************/
void LCD_light_down() {
  if (LCD_LIGH_Value > 0)
    LCD_LIGH_Value = LCD_LIGH_Value - 50;
  analogWrite(LCD_LIGHT, LCD_LIGH_Value);
  delay(100);
}

/**************************************************************/
/*関数名:out_analog_state*/
/*動作;押されたボタンごとに処理を割り振る*/
/**************************************************************/
void out_analog_state(int lcd_key)
{
  lcd.setCursor(0, 1);
  switch (lcd_key)
  {
    case btnRIGHT:
      {
        break;
      }
    case btnLEFT:         //モードの切り替えボタン
      {
        mode_change();
        mode_change_LCD();
        delay(200);
        break;
      }
    case btnUP:           //バックライト光度上げる
      {
        LCD_light_up();
        break;
      }
    case btnDOWN:         //バックライト光度下げる
      {
        LCD_light_down();
        break;
      }
    case btnSELECT:       //距離測定
      {
        if (mode == Dist)
        {
          out_Distance_LCD(Distan_Mode());
          delay(500);
        }
        else {
          while (1) {
            delay(200);
            if (btnSELECT == read_LCD_buttons(analogRead(0))) {
              delay(1000);
              break;
            }
            out_Distance_LCD(PWM_Mode());
          }
        }
        break;
      }
    case btnNONE:
      {
        break;
      }
    case ERRORR:
      {
        break;
      }
  }
}

/**************************************************************/
/*関数名:Distan_Mode*/
/*動作;3回測定して平均値を返す*/
/**************************************************************/
int Distan_Mode() {
  int a;
  int Dist_add = 0;
  for (a = 0; a <= 2; a++) {
    Dist_add = Dist_add + PWM_Mode();
    delay(50);
  }
  return Dist_add / 3;
}

/**************************************************************/
/*関数名:PWM_Mode_Setup*/
/*動作;URM37の初期化*/
/**************************************************************/
void PWM_Mode_Setup()
{
  pinMode(URTRIG, OUTPUT);                    // A low pull on pin COMP/TRIG
  digitalWrite(URTRIG, HIGH);                 // Set to HIGH

  pinMode(URPWM, INPUT);                      // Sending Enable PWM mode command

  for (int i = 0; i < 4; i++)
  {
    Serial.write(EnPwmCmd[i]);
  }
}

/**************************************************************/
/*関数名:PWM_Mode*/
/*動作;URM37で距離測定*/
/**************************************************************/
int PWM_Mode()
{ // a low pull on pin COMP/TRIG  triggering a sensor reading
  unsigned int Distance = 0;
  digitalWrite(URTRIG, LOW);
  digitalWrite(URTRIG, HIGH);               // reading Pin PWM will output pulses

  unsigned long DistanceMeasured = pulseIn(URPWM, LOW);

  if (DistanceMeasured >= 10200)
  { // the reading is invalid.
    Serial.println("Invalid");
  }
  else
  {
    Distance = DistanceMeasured / 50;       // every 50us low level stands for 1cm
    Serial.print("Distance=");
    Serial.print(Distance);
    Serial.println("cm");
    return Distance;
  }
}

/**************************************************************/
/*関数名:out_Distance_LCD*/
/*動作:渡された引数をLCDに表す*/
/**************************************************************/
void out_Distance_LCD(int Distance) {
  mode_change_LCD();
  lcd.setCursor(0, 1);
  lcd.print(Distance);
  lcd.setCursor(5, 1);
  lcd.print("cm");
}

/**************************************************************/
/*関数名:mode_change*/
/*動作:動作モードを切り替える*/
/**************************************************************/
void mode_change() {
  if (mode == Dist)
    mode = Real;
  else
    mode = Dist;
}

/**************************************************************/
/*関数名:mode_change_LCD*/
/*動作:動作モード状態をLCDに表示させる*/
/**************************************************************/
void mode_change_LCD() {
  lcd.clear() ;
  lcd.setCursor(12, 0);
  lcd.print("mode");
  if (mode == Dist) {
    lcd.setCursor(0, 0);
    lcd.print("Distance");
  } else {
    lcd.setCursor(0, 0);
    lcd.print("Real time");
  }
}

/**************************************************************/
/*関数名:setup*/
/*動作;LCDとURM37の初期化*/
/**************************************************************/
void setup()
{
  Serial.begin(9600);
  analogWrite(LCD_LIGHT, LCD_LIGH_Value);
  PWM_Mode_Setup();
  pinMode(LCD_LIGHT, OUTPUT);
  lcd.begin(16, 2);
  mode_change_LCD();
}

/**************************************************************/
/*関数名:loop*/
/*動作;ボタンを読み込んで処理を分岐*/
/**************************************************************/
void loop()
{
  out_analog_state(read_LCD_buttons(analogRead(0)));
}

 

基本動作の説明

電源を入れるとDistance mode(距離測定モード)で待機状態になります、この状態ではSelectボタンを押すごとにURM37が動き、測定した距離がLCDディスプレイに表示されます。Leftボタンを押すとReal time mode(連続測定モード)に切り替わります、Selectボタンを押すと測定が始まり、再度Selectが押されるまで測定を続けます。

UP, DOWNボタンを押すとバックライトの光度が調整できます。

IMG_6287

Distance mode(測定モード)ではSelectボタンを押すごとに測定が行われる。3回距離測定を行って、その平均値をLCDディスプレイに表示させる仕様にした。

IMG_6288

Real time mode(連続測定モード)ではSelectを押すと測定が開始される、再度Selectボタンが押されるまで測定した距離をリアルタイムに表示する。

 

PWM制御

LCDキーパッドシールドの光度を調整しているところ、UP/DOWNボタンを押すごとにPWM出力が50づつ上下する。PWM250と200の明るさの違いがあまりないので、この辺は配列を使ってPWMを直接指定したほうが良かったかもしれません。

動画

 

スケッチの説明

大まかな流れ

この大まかな流れとしては、setup関数(初期化)→loop関数(ボタン状態の取得)→out_analog_state関数(各動作の実行)→loop関数(ボタン状態の取得)→out_analog_state関数(各動作の実行)→…の繰り返しです。

loop関数ではA0アナログピンの電圧を取得して各関数にその結果を渡しています。取得したA0ピンの電圧はread_LCD_buttons関数でボタン判別され、結果をout_analog_state関数に渡すことでArduinoに何をさせるかを決定します。

 

測定モードの判別と切り替え

測定モードの判別にはグローバル変数のint mode変数を参照します。初期状態は測定モード(Distance mode)となっています。

Leftキーを押すとmode_change関数が実行されます、実行される毎にmode変数がDistとRealで書き換わります。

 

バックライト光度の調整

バックライトを調光するには LCD_light_up関数と LCD_light_down関数を実行します。現在の明るさの状態の取得にはグローバル変数のLCD_LIGH_Valueを参照します、初期状態では光度250としています。

D10デジタルピンをPWM制御で操作することで調光機能を実現しています(→analogWrite関数

 

距離の測定(Distance mode)

Selectボタンが押されると、if文で現在のmode状態を取得します。mode変数の内容がDistであればDistan_Mode関数が実行されます。

Distan_Mode()関数ではfor文内で3回だけ距離測定(PWM_Mode関数の実行)されます、その平均値が戻り値として返されます。

 

距離の測定(Real time mode)

Selectボタンが押されると、if文で現在のmode状態を取得します。mode変数の内容がDistでなければ直下のwhile文が実行されます。

一度Selectボタンが押されてwhile文が実行されると、再度Selectボタンを押すまで距離測定(PWM_Mode関数の実行)が行われます。

 

測定した距離をLCDディスプレイで表示

PWM_Mode関数で取得した距離はint型の戻り値として返されます。これをout_Distance_LCD関数に渡すことでLCDディスプレイに測定した距離が表示されます。

距離を再表示する前に一度lcd.clear関数ですべての表示を消してしまうので、mode_change_LCDを再実行してから距離を表示するようにしています。

 

まとめ

ディスプレイとボタンでArduino単体で動かせるようになると、できることが増えますしモノづくり感があっていいですよね。

Distance modeでは特に深い意味もなく3回測定して平均値を出す仕様にしていますが、30cmくらいの近距離の測定ではなぜか測定される距離が大きく外れてしまうんです。このトラブルについてはDistan_Mode関数にDelay(50)を入れることで解決しましたが、超音波測定の世界は奥が深いです、市販品がすべてレーザーになったのも頷けます(^^;

今回作った距離計はURM37を動作させるタイミングを操作したり、取得した距離を計算して表示させるだけなのでそこまで複雑なスケッチではないと思います(綺麗なスケッチではないですが…)。ただ、割り込み処理を使わなかったためReal time modeのボタン操作周りの動作をかなりdelayのタイミングでちょっと無茶させてる感じがあるので、次回の改善点としたいと思います。

更に距離計としての機能を拡張するなら、math.hを使って辺測定機能をいれたり、温度センサを追加してもっと測定精度の向上、Bluetoothを載せてスマホと連携されるなど様々な機能が考えられます、思いついたアイディアをすぐに実行できるのがArduinoのいいところですね。

 

今回の作業環境

  • ハードウェア
    • Arduino UNO R3
    • LCDキーパッドシールド (SainSmart製)
    • URM37 v3.2
    • ブレッドボード・ジャンパーコード(オス-オス)
    • USBケーブル A-Bコネクタ
  • ソフトウェア
    • Arduno 1.6.7