こんにちは、ENGかぴです。
Arduinoのと超音波距離センサーを組み合わせことで非接触で距離が測定できます。ArduinoのDOでパルス生成することで超音波距離センサーから音波を出力し対象物からの反射波をDIで受けることで対象物までの距離を測定することができます。
秋月電子の「超音波距離センサーモジュール:US-015」を使って距離を測定する方法を説明しています。US-015は2cm~4mの距離が測定できます。応用編として距離測定モジュールを製作して動作確認した結果を下記リンクにまとめています。
Arduinoとトワイライトを使って距離データをモニターする
Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
超音波距離センサーUS-015を使う
秋月電子にはUS-015のデータシートが公開されておりデータシートにスケッチ例があるためそのまま流用することで距離を測定することができます。スケッチ例のポイントとなる箇所についてまとめました。
スケッチ例をそのまま使用する
スケッチ例では、ArduinoのDOを使って模擬パルスを生成して超音波距離センサーに入力することで音波が出力し対象物からの反射波の応答時間から距離を計算しています。
digitalWrite(TrigPin, HIGH); //模擬パルス発生
delayMicroseconds(50); //50μs遅延
digitalWrite(TrigPin, LOW); //LOWに戻す
Time_Echo_us = pulseIn(EchoPin, HIGH); //LOWからHIGHになったとき測定開始
//pulseIn()の書式
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout));
ポイントとなるのはパルスの時間を返す関数であるpulseIn()になります。第一引数はDI入力ピンの番号、第二引数は計算を開始するパルスのレベル、第三引数はタイムアウトまでの時間を指定します。
スケッチ例では第二引数がHIGHを指定しているので第一引数のピンがLOWからHIGHになったときに時間計測を開始し、ピンがLOWになるまでの時間を計測します。
第三引数を指定している場合は指定した時間以内(デフォルトは1秒)にパルスの変化が検出できない場合は0を戻り値として返すようになっています。
簡易的な測定結果
スケッチ例を使って距離を測定してみると気づいたことがありました。下記に気づくまで数mm程度の誤差が出ると感じていました。
モジュールの先端円筒形の末端からの距離ではなく円筒の中にあるシンバルのような形の部分(振動子)からの距離が測定されます。(音波なので振動で受けるためにそのようになっていると思います。)
円筒トップから内部の音波を受ける箇所までの距離は目視ですが約5mmほどなのでその分距離が長くなっていることが分かりました。円筒のトップ2つを水平に配置し円筒のトップを測定の基準にして測定したい場合は計測結果に5mm分補正を入れる方が良いでしょう。
データのばらつきを平均化する
マイコンでアナログデータを処理するとき処理に影響が出ない程度に移動平均などでアナログ値のばらつきを平均化してデータ値として採用することが多くあります。
US-015で距離を測定する場合センサーを確実に固定できていないと値にばらつきが出てしまいます。センサーの角度が少し変わるだけでも数mm単位で値が変化してしまうため、複数回取得したデータを平均化してばらつき抑える対策を行います。
#define FLT_MAX 4
/* US-015のデータを平均化 function add */
bool Us015Filter(){
bool ret = true;
uint8_t i;
uint32_t sum = 0;
Len_mm_X100 = (Time_Echo_us*34)/2;
us015.buf[us015.wp] = Len_mm_X100;
for(i=0; i < FLT_MAX; i++){
if( us015.buf[i] == 0xFFFFFFFF ){ //初期化時の値が残っていれば計算しないようにする
ret = false;
break;
}
sum += us015.buf[i]; //合計を算出
}
if( ret ){
us015.dat = sum >> 2; //バッファ数の4の平均をとるため右に2回シフト
us015.dat -=500; //100倍値なので5mm減らすため-500(基準を円筒のトップにする)
}
if( ++us015.wp >= FLT_MAX ){
us015.wp = 0;
}
return ret;
}
1000ms毎にpluseIn()で取得した距離のデータを換算して一時保存のバッファに格納しています。4回分の距離データを獲得した後は合計値を算出して4で割ると平均値になります。
データ数を2の倍数にしておくと平均値を計算をする時に右にビットシフトするだけで計算できるため演算処理の負担を減らすことができ処理時間の短縮につながります。
US-015の固定環境が大きく影響するため平均化してもばらつきが出る可能性がありますが、ばらつきの大きさが抑えられることが期待できます。
広告
動作確認
データシートのスケッチ例では1000ms毎に測定して表示していましたが、1000ms毎に測定を行い1000ms毎に平均値を表示するようにします。測定方法はスケッチ例で示した条件と同じ環境で行います。
円筒のトップを基準点にするため距離の計算値に対して5mm分補正したところUS-015のトップから対象物の50mmにしたところシリアルモニタの表示も50mmになっていることから補正できていることが分かります。
平均化の効果については値のばらつきが少ないように感じますが、US-015の設置環境によってばらつきがでるため効果については判断が難しいですが1mm以内に収まっているので一定の効果が得られていると言えます。
平均化しているため応答速度が少し遅くなりますが、距離センサーは基本的に固定して距離を測るものなので設置して安定するまでの間(4秒程度)の時間は問題にならないと思います。気になる場合は平均化する時間を早くするとよいのですが、早くしすぎると音波が出力される周期も早くなることから精度が落ちる可能性があるので判断が難しいところです。
US-015は角度が少しずれるだけでも値がばらつくため動かないように固定することが正確なデータを取得するために必要になります。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <MsTimer2.h>
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define TIM_US015 100
#define TIM_US015_DISP 100
#define FLT_MAX 4
struct DIS_MEAN{
uint8_t wp;
unsigned long buf[FLT_MAX];
unsigned long dat;
};
unsigned int EchoPin = 2;
unsigned int TrigPin = 3;
unsigned long Time_Echo_us = 0;
//Len_mm_X100 = length*100
unsigned long Len_mm_X100 = 0;
unsigned long Len_Integer = 0;
unsigned int Len_Fraction = 0;
DIS_MEAN us015;
int16_t timus015 = TIME_OFF;
int16_t timus015disp = TIME_OFF;
int16_t cnt10ms;
/*** Local function prototypes */
void TimerCnt();
void Us015Meas();
bool Us015Filter();
void setup() {
Serial.begin(115200);
pinMode(EchoPin, INPUT);
pinMode(TrigPin, OUTPUT);
MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
MsTimer2::start();
memset(&us015.buf, 0xFFFFFFFF, sizeof(us015.buf));
timus015 = TIM_US015;
timus015disp = TIM_US015_DISP;
}
void loop(){
Us015Meas();
mainTimer();
}
/* US-015のデータを取得 function add */
void Us015Meas(){
if( timus015 == TIME_UP ){
timus015 = TIM_US015;
digitalWrite(TrigPin, HIGH);
delayMicroseconds(50);
digitalWrite(TrigPin, LOW);
Time_Echo_us = pulseIn(EchoPin, HIGH);
if((Time_Echo_us < 60000) && (Time_Echo_us > 1)){
if( Us015Filter()){
if( timus015disp == TIME_UP ){
timus015disp = TIM_US015_DISP;
Len_Integer = us015.dat/100;
Len_Fraction = us015.dat%100;
Serial.print("Present Length is: ");
Serial.print(Len_Integer, DEC);
Serial.print(".");
if(Len_Fraction < 10){
Serial.print("0");
}
Serial.print(Len_Fraction, DEC);
Serial.println("mm");
}
}
}
}
}
/* US-015のデータを平均化 function add */
bool Us015Filter(){
bool ret = true;
uint8_t i;
uint32_t sum = 0;
Len_mm_X100 = (Time_Echo_us*34)/2;
us015.buf[us015.wp] = Len_mm_X100;
for(i=0; i < FLT_MAX; i++){
if( us015.buf[i] == 0xFFFFFFFF ){ //初期化時の値が残っていれば計算しないようにする
ret = false;
break;
}
sum += us015.buf[i];
}
if( ret ){
us015.dat = sum >> 2; //バッファ数の4の平均をとるため右に2回シフト
us015.dat -=500; //100倍値なので5mm減らすため-500(基準を円筒のトップにする)
}
if( ++us015.wp >= FLT_MAX ){
us015.wp = 0;
}
return ret;
}
/* callback function add */
void TimerCnt(){
++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){
if( cnt10ms >= BASE_CNT ){ //10msごとにここに遷移する
cnt10ms -=BASE_CNT;
if( timus015 > TIME_UP ){
timus015--;
}
if( timus015disp > TIME_UP ){
timus015disp--;
}
}
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:アクセンチュアの転職なら【コンサルアクシスコンサルティング】
最後まで、読んでいただきありがとうございました。
大体の距離が分かればいいので5mmから10mm程度であれば誤差が出るものと割り切れる部分もありますが、工夫して誤差を可能な限り小さくしてみたくなります。